diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html
index a8f8beeaaa..6ec4618341 100644
--- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html
+++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html
@@ -38,13 +38,21 @@
Time - the "time" coordinate for the snapshot. This is the same as the Snap column for
most snapshots. For scratch snapshots, this is the same as the Schedule column.
- Timestamp - the "wall-clock" time of the event. If the debugger doesn't give an event
- time, or the snapshot does not correspond to an event, then it is the snapshot creation
- time.
-
Event Thread - the thread that caused the event, if applicable. In the case of thread
creation, this should probably be the spawned thread, not the parent.
+ PC - the address of the instruction to execute next. Different debuggers may have
+ different subtleties in how the report PC.
+
+ Module - the name of the module containing the PC.
+
+ Function - the name of the function containing the PC, if Ghidra has the corresponding
+ module image imported, analyzed, and mapped.
+
+ Timestamp - the "wall-clock" time of the event. If the debugger doesn't give an event
+ time, or the snapshot does not correspond to an event, then it is the snapshot creation time.
+ (hidden by default)
+
Schedule - if applicable, a source snap and the stepping schedule which produces this
snapshot. This always applies to scratch snapshots produced by emulation, but may
(rarely) apply to recorded events if the stepping schedule between them is somehow known. See
diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png
index 65ade2bd18..3b584931fa 100644
Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png differ
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/RegisterLocationTrackingSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/RegisterLocationTrackingSpec.java
index f04cba17f0..f41afad328 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/RegisterLocationTrackingSpec.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/RegisterLocationTrackingSpec.java
@@ -84,8 +84,12 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec, Loca
if (value == null) {
return null;
}
- // TODO: Action to select the address space
- // Could use code unit, but that can't specify space, yet, either....
+ /**
+ * NOTE: I don't think the user needs a way to select the address space. For PC and SP, the
+ * tracker provides the best default, i.e., the default (code) space and the compiler's
+ * physical stack space. For watches, I believe the sleigh syntax allows the user to pick,
+ * but I can't recall testing that.
+ */
return platform.mapGuestToHost(computeDefaultAddressSpace(coordinates)
.getAddress(value.getUnsignedValue().longValue(), true));
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/SPLocationTrackingSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/SPLocationTrackingSpec.java
index 7e7722bdfe..e9da700410 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/SPLocationTrackingSpec.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/SPLocationTrackingSpec.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -59,7 +59,7 @@ public enum SPLocationTrackingSpec implements RegisterLocationTrackingSpec {
@Override
public AddressSpace computeDefaultAddressSpace(DebuggerCoordinates coordinates) {
- return coordinates.getTrace().getBaseLanguage().getDefaultDataSpace();
+ return coordinates.getPlatform().getCompilerSpec().getStackBaseSpace();
}
@Override
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java
index bf51354785..e9cd96baa8 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java
@@ -31,6 +31,7 @@ import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.docking.settings.Settings;
import ghidra.framework.model.DomainObjectEvent;
import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.Address;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.target.TraceObjectValue;
@@ -50,8 +51,11 @@ public class DebuggerSnapshotTablePanel extends JPanel {
implements EnumeratedTableColumn {
SNAP("Snap", Long.class, SnapshotRow::getSnap, false),
TIME("Time", TraceSchedule.class, SnapshotRow::getTime, true),
- TIMESTAMP("Timestamp", Date.class, SnapshotRow::getTimeStamp, true),
EVENT_THREAD("Event Thread", String.class, SnapshotRow::getEventThreadName, true),
+ PC("PC", Address.class, SnapshotRow::getProgramCounter, true),
+ MODULE("Module", String.class, SnapshotRow::getModuleName, true),
+ FUNCTION("Function", ghidra.program.model.listing.Function.class, SnapshotRow::getFunction, true),
+ TIMESTAMP("Timestamp", Date.class, SnapshotRow::getTimeStamp, false),
SCHEDULE("Schedule", TraceSchedule.class, SnapshotRow::getSchedule, false),
DESCRIPTION("Description", String.class, SnapshotRow::getDescription, //
SnapshotRow::setDescription, true);
@@ -140,7 +144,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
- SnapshotRow row = new SnapshotRow(snapshot);
+ SnapshotRow row = new SnapshotRow(snapshot, tool);
snapshotTableModel.add(row);
}
@@ -175,7 +179,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
@Override
protected String formatNumber(Number value, Settings settings) {
return switch (value) {
- case null -> "";
+ case null -> "";
// SNAP is the only column with Long type
case Long snap -> getTimeRadix().format(snap);
default -> super.formatNumber(value, settings);
@@ -185,7 +189,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
@Override
protected String getText(Object value) {
return switch (value) {
- case null -> "";
+ case null -> "";
case Date date -> DateUtils.formatDateTimestamp(date);
case TraceSchedule schedule -> schedule.toString(getTimeRadix());
default -> value.toString();
@@ -195,7 +199,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
@Override
public String getFilterString(Object t, Settings settings) {
return switch (t) {
- case null -> "";
+ case null -> "";
// SNAP is the only column with Long type
case Long snap -> getTimeRadix().format(snap);
case Number n -> formatNumber(n, settings);
@@ -221,6 +225,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
}
};
+ protected final PluginTool tool;
protected final SnapshotTableModel snapshotTableModel;
protected final GTable snapshotTable;
protected final GhidraTableFilterPanel snapshotFilterPanel;
@@ -233,6 +238,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
public DebuggerSnapshotTablePanel(PluginTool tool) {
super(new BorderLayout());
+ this.tool = tool;
snapshotTableModel = new SnapshotTableModel(tool);
snapshotTable = new GTable(snapshotTableModel);
snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@@ -243,22 +249,31 @@ public class DebuggerSnapshotTablePanel extends JPanel {
TableColumnModel columnModel = snapshotTable.getColumnModel();
TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal());
- snapCol.setPreferredWidth(40);
+ snapCol.setPreferredWidth(20);
snapCol.setCellRenderer(styleCurrentRenderer);
TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIME.ordinal());
- timeCol.setPreferredWidth(40);
+ timeCol.setPreferredWidth(20);
timeCol.setCellRenderer(styleCurrentRenderer);
+ TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
+ etCol.setPreferredWidth(20);
+ etCol.setCellRenderer(styleCurrentRenderer);
+ TableColumn pcCol = columnModel.getColumn(SnapshotTableColumns.PC.ordinal());
+ pcCol.setPreferredWidth(40);
+ pcCol.setCellRenderer(styleCurrentRenderer);
+ TableColumn moduleCol = columnModel.getColumn(SnapshotTableColumns.MODULE.ordinal());
+ moduleCol.setPreferredWidth(40);
+ moduleCol.setCellRenderer(styleCurrentRenderer);
+ TableColumn functionCol = columnModel.getColumn(SnapshotTableColumns.FUNCTION.ordinal());
+ functionCol.setPreferredWidth(40);
+ functionCol.setCellRenderer(styleCurrentRenderer);
TableColumn timeStampCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal());
timeStampCol.setPreferredWidth(200);
timeStampCol.setCellRenderer(styleCurrentRenderer);
- TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
- etCol.setPreferredWidth(40);
- etCol.setCellRenderer(styleCurrentRenderer);
TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal());
schdCol.setPreferredWidth(60);
schdCol.setCellRenderer(styleCurrentRenderer);
TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal());
- descCol.setPreferredWidth(200);
+ descCol.setPreferredWidth(20);
descCol.setCellRenderer(styleCurrentRenderer);
}
@@ -319,7 +334,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
for (TraceSnapshot snapshot : hideScratch
? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
: manager.getAllSnapshots()) {
- SnapshotRow row = new SnapshotRow(snapshot);
+ SnapshotRow row = new SnapshotRow(snapshot, tool);
toAdd.add(row);
if (current != DebuggerCoordinates.NOWHERE &&
snapshot.getKey() == current.getViewSnap()) {
@@ -340,7 +355,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
Collection extends TraceSnapshot> sratch =
manager.getSnapshots(Long.MIN_VALUE, true, 0, false);
snapshotTableModel.addAll(sratch.stream()
- .map(s -> new SnapshotRow(s))
+ .map(s -> new SnapshotRow(s, tool))
.collect(Collectors.toList()));
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/SnapshotRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/SnapshotRow.java
index aac8473150..87c389db13 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/SnapshotRow.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/SnapshotRow.java
@@ -18,17 +18,32 @@ package ghidra.app.plugin.core.debug.gui.time;
import java.util.Date;
import db.Transaction;
+import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
+import ghidra.framework.plugintool.ServiceProvider;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressSpace;
+import ghidra.program.model.lang.Register;
+import ghidra.program.model.lang.RegisterValue;
+import ghidra.program.model.listing.Function;
import ghidra.trace.model.Trace;
+import ghidra.trace.model.guest.TraceGuestPlatform;
+import ghidra.trace.model.guest.TracePlatform;
+import ghidra.trace.model.memory.*;
+import ghidra.trace.model.stack.TraceStack;
+import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
public class SnapshotRow {
private final TraceSnapshot snapshot;
+ private final ServiceProvider serviceProvider;
+
private final Trace trace;
- public SnapshotRow(TraceSnapshot snapshot) {
+ public SnapshotRow(TraceSnapshot snapshot, ServiceProvider serviceProvider) {
this.snapshot = snapshot;
+ this.serviceProvider = serviceProvider;
this.trace = snapshot.getTrace();
}
@@ -52,9 +67,127 @@ public class SnapshotRow {
return new Date(snapshot.getRealTime());
}
- public String getEventThreadName() {
+ private Address getProgramCounterByStack() {
TraceThread thread = snapshot.getEventThread();
- return thread == null ? "" : thread.getName(snapshot.getKey());
+ if (thread == null) {
+ return null;
+ }
+ long snap = getTime().getSnap();
+ TraceStack stack;
+ try {
+ stack = trace.getStackManager().getLatestStack(thread, snap);
+ }
+ catch (IllegalStateException e) {
+ // Schema does not specify a stack
+ return null;
+ }
+ if (stack == null) {
+ return null;
+ }
+ TraceStackFrame frame = stack.getFrame(snap, 0, false);
+ if (frame == null) {
+ return null;
+ }
+ return frame.getProgramCounter(snap);
+ }
+
+ private Address getProgramCounterByRegister() {
+ TraceThread thread = getEventThread();
+ if (thread == null) {
+ return null;
+ }
+ long viewSnap = snapshot.getKey();
+ long snap = getTime().getSnap();
+ /**
+ * LATER: Some notion of an event platform? Or perhaps the thread has some attribute to
+ * indicate which platform is active?
+ *
+ * I could use the tool's "current" platform, but that may produce odd behavior when
+ * changing platforms. Each would have the most recent PC for the selected platform, which
+ * is totally irrelevant. For now, seek out the platform with the most recent update to its
+ * PC for the event thread. While this should be perfectly accurate, it's a bit expensive.
+ */
+ record MostRecentValue(TracePlatform platform, long snap, RegisterValue value) {
+ static MostRecentValue choose(MostRecentValue a, MostRecentValue b) {
+ if (a == null) {
+ return b;
+ }
+ if (b == null) {
+ return a;
+ }
+ // Prefer negative ("view") snaps to positive. Of that, pick most recent.
+ if (Long.compareUnsigned(a.snap, b.snap) > 0) {
+ return a;
+ }
+ return b;
+ }
+
+ static MostRecentValue get(TracePlatform platform, TraceThread thread, long viewSnap,
+ long snap) {
+ Register reg = platform.getLanguage().getProgramCounter();
+ TraceMemoryManager mm = thread.getTrace().getMemoryManager();
+ TraceMemorySpace regs = reg.getAddressSpace().isRegisterSpace()
+ ? mm.getMemoryRegisterSpace(thread, false)
+ : mm.getMemorySpace(reg.getAddressSpace(), false);
+ if (regs == null) {
+ return null;
+ }
+ if (regs.getState(platform, viewSnap, reg) == TraceMemoryState.KNOWN) {
+ RegisterValue value = regs.getValue(platform, viewSnap, reg);
+ return value == null ? null : new MostRecentValue(platform, viewSnap, value);
+ }
+ RegisterValue value = regs.getValue(platform, snap, reg);
+ return value == null ? null : new MostRecentValue(platform, snap, value);
+ }
+
+ Address mapToHost() {
+ AddressSpace codeSpace = platform.getAddressFactory().getDefaultAddressSpace();
+ return platform.mapGuestToHost(
+ codeSpace.getAddress(value.getUnsignedValue().longValue(), true));
+ }
+ }
+ MostRecentValue choice = MostRecentValue.get(trace.getPlatformManager().getHostPlatform(),
+ thread, viewSnap, snap);
+ for (TraceGuestPlatform guest : trace.getPlatformManager().getGuestPlatforms()) {
+ choice =
+ MostRecentValue.choose(choice, MostRecentValue.get(guest, thread, viewSnap, snap));
+ }
+ return choice == null ? null : choice.mapToHost();
+ }
+
+ public Address getProgramCounter() {
+ Address byStack = getProgramCounterByStack();
+ return byStack != null ? byStack : getProgramCounterByRegister();
+ }
+
+ public Function getFunction() {
+ Address pc = getProgramCounter();
+ if (pc == null) {
+ return null;
+ }
+ return DebuggerStaticMappingUtils.getFunction(pc, trace, getTime().getSnap(),
+ serviceProvider);
+ }
+
+ public String getModuleName() {
+ Address pc = getProgramCounter();
+ if (pc == null) {
+ return null;
+ }
+ return DebuggerStaticMappingUtils.getModuleName(pc, trace, getTime().getSnap());
+ }
+
+ private TraceThread getEventThread() {
+ TraceThread thread = snapshot.getEventThread();
+ if (thread != null) {
+ return thread;
+ }
+ return getTime().getEventThread(trace);
+ }
+
+ public String getEventThreadName() {
+ TraceThread thread = getEventThread();
+ return thread == null ? "" : thread.getName(getTime().getSnap());
}
public TraceSchedule getSchedule() {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java
index 8cbf630e09..c69a2a72bf 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java
@@ -316,6 +316,11 @@ public enum DebuggerStaticMappingUtils {
public static Function getFunction(Address pc, DebuggerCoordinates coordinates,
ServiceProvider serviceProvider) {
+ return getFunction(pc, coordinates.getTrace(), coordinates.getSnap(), serviceProvider);
+ }
+
+ public static Function getFunction(Address pc, Trace trace, long snap,
+ ServiceProvider serviceProvider) {
if (pc == null) {
return null;
}
@@ -324,8 +329,7 @@ public enum DebuggerStaticMappingUtils {
if (mappingService == null) {
return null;
}
- TraceLocation dloc = new DefaultTraceLocation(coordinates.getTrace(),
- null, Lifespan.at(coordinates.getSnap()), pc);
+ TraceLocation dloc = new DefaultTraceLocation(trace, null, Lifespan.at(snap), pc);
ProgramLocation sloc = mappingService.getOpenMappedLocation(dloc);
if (sloc == null) {
return null;
@@ -346,14 +350,13 @@ public enum DebuggerStaticMappingUtils {
}
public static String getModuleName(Address pc, DebuggerCoordinates coordinates) {
- if (pc == null) {
+ return getModuleName(pc, coordinates.getTrace(), coordinates.getSnap());
+ }
+
+ public static String getModuleName(Address pc, Trace trace, long snap) {
+ if (pc == null || trace == null) {
return null;
}
- Trace trace = coordinates.getTrace();
- if (trace == null) {
- return null;
- }
- long snap = coordinates.getSnap();
for (TraceModule module : trace.getModuleManager().getModulesAt(snap, pc)) {
// Just take the first
return computeModuleShortName(module.getName(snap));
diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimePluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimePluginScreenShots.java
index 77372ae33e..a54e799021 100644
--- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimePluginScreenShots.java
+++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimePluginScreenShots.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,28 +15,84 @@
*/
package ghidra.app.plugin.core.debug.gui.time;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.concurrent.TimeUnit;
+
import org.junit.*;
import db.Transaction;
+import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
+import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
-import ghidra.app.services.DebuggerTraceManagerService;
+import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
+import ghidra.app.services.*;
+import ghidra.framework.model.DomainFolder;
+import ghidra.framework.model.DomainObject;
+import ghidra.program.model.lang.Register;
+import ghidra.program.model.lang.RegisterValue;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.symbol.SourceType;
+import ghidra.program.util.ProgramLocation;
import ghidra.test.ToyProgramBuilder;
import ghidra.trace.database.ToyDBTraceBuilder;
+import ghidra.trace.model.DefaultTraceLocation;
+import ghidra.trace.model.Lifespan;
+import ghidra.trace.model.memory.TraceMemorySpace;
+import ghidra.trace.model.target.TraceObject.ConflictResolution;
+import ghidra.trace.model.target.path.KeyPath;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
+import ghidra.util.InvalidNameException;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.ConsoleTaskMonitor;
+import ghidra.util.task.TaskMonitor;
import help.screenshot.GhidraScreenShotGenerator;
public class DebuggerTimePluginScreenShots extends GhidraScreenShotGenerator {
+ private static final TaskMonitor MONITOR = new ConsoleTaskMonitor();
+
+ ProgramManager programManager;
DebuggerTraceManagerService traceManager;
+ DebuggerStaticMappingService mappingService;
+
DebuggerTimePlugin timePlugin;
DebuggerTimeProvider timeProvider;
+
ToyDBTraceBuilder tb;
+ Program progHw;
+ Program progLibc;
+
+ protected void intoProject(DomainObject obj) {
+ waitForDomainObject(obj);
+ DomainFolder rootFolder = tool.getProject().getProjectData().getRootFolder();
+ waitForCondition(() -> {
+ try {
+ rootFolder.createFile(obj.getName(), obj, MONITOR);
+ return true;
+ }
+ catch (InvalidNameException | CancelledException e) {
+ throw new AssertionError(e);
+ }
+ catch (IOException e) {
+ // Usually "object is busy". Try again.
+ return false;
+ }
+ });
+ }
+
+ public static void waitForDomainObject(DomainObject object) {
+ object.flushEvents();
+ waitForSwing();
+ }
@Before
public void setUpMine() throws Throwable {
+ programManager = addPlugin(tool, ProgramManagerPlugin.class);
traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
+ mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class);
timePlugin = addPlugin(tool, DebuggerTimePlugin.class);
timeProvider = waitForComponentProvider(DebuggerTimeProvider.class);
@@ -46,42 +102,105 @@ public class DebuggerTimePluginScreenShots extends GhidraScreenShotGenerator {
@After
public void tearDownMine() {
tb.close();
+
+ if (progHw != null) {
+ progHw.release(this);
+ progHw = null;
+ }
+ if (progLibc != null) {
+ progLibc.release(this);
+ progLibc = null;
+ }
}
@Test
public void testCaptureDebuggerTimePlugin() throws Throwable {
+ progHw = createDefaultProgram("helloworld", ToyProgramBuilder._X64, this);
+ progLibc = createDefaultProgram("libc", ToyProgramBuilder._X64, this);
+
long fakeClock = (long) Integer.MAX_VALUE * 1000;
TraceSnapshot snap;
+
+ try (Transaction tx = progHw.openTransaction("Populate main")) {
+ progHw.getMemory()
+ .createInitializedBlock(".text", tb.addr(0x00400000), 0x2000, (byte) 0, MONITOR,
+ false);
+ progHw.getFunctionManager()
+ .createFunction("main", tb.addr(0x00401234),
+ tb.set(tb.range(0x00401234, 0x00401300)), SourceType.IMPORTED);
+ }
+ try (Transaction tx = progLibc.openTransaction("Populate puts")) {
+ progLibc.getMemory()
+ .createInitializedBlock(".text", tb.addr(0x00400000), 0x2000, (byte) 0, MONITOR,
+ false);
+ progLibc.getFunctionManager()
+ .createFunction("puts", tb.addr(0x00400110),
+ tb.set(tb.range(0x00400110, 0x00400120)), SourceType.IMPORTED);
+ }
+
+ intoProject(progHw);
+ intoProject(progLibc);
+ intoProject(tb.trace);
+ programManager.openProgram(progLibc);
+ programManager.openProgram(progHw);
+ traceManager.openTrace(tb.trace);
+ mappingService.changesSettled().get(1, TimeUnit.SECONDS);
+
try (Transaction tx = tb.startTransaction()) {
- snap = tb.trace.getTimeManager().createSnapshot("Trace started");
- snap.setRealTime(fakeClock);
+ tb.trace.getObjectManager().createRootObject(ProgramEmulationUtils.EMU_SESSION_SCHEMA);
- TraceThread thread = tb.getOrAddThread("[1]", snap.getKey());
+ tb.trace.getModuleManager()
+ .addLoadedModule("Modules[helloword]", "helloworld",
+ tb.range(0x00400000, 0x00402000), 0);
+ tb.trace.getModuleManager()
+ .addLoadedModule("Modules[libc]", "libc",
+ tb.range(0x7fff0000, 0x7fff2000), 0);
- snap = tb.trace.getTimeManager().createSnapshot("Thread STOPPED");
+ mappingService.addMapping(
+ new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), tb.addr(0x00400000)),
+ new ProgramLocation(progHw, tb.addr(0x00400000)), 0x2000, false);
+ mappingService.addMapping(
+ new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), tb.addr(0x7fff0000)),
+ new ProgramLocation(progLibc, tb.addr(0x00400000)), 0x2000, false);
+
+ TraceThread thread = tb.getOrAddThread("Threads[1]", 0);
+ tb.trace.getObjectManager()
+ .createObject(KeyPath.parse("Threads[1].Registers"))
+ .insert(Lifespan.nowOn(0), ConflictResolution.DENY);
+ thread.setName(0, "1 main");
+ TraceMemorySpace regs =
+ tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
+ Register pc = tb.host.getLanguage().getProgramCounter();
+
+ snap = tb.trace.getTimeManager().createSnapshot("STOP");
snap.setEventThread(thread);
snap.setRealTime(fakeClock);
fakeClock += 1000;
+ regs.setValue(snap.getKey(), new RegisterValue(pc, BigInteger.valueOf(0x00401234)));
- snap = tb.trace.getTimeManager().createSnapshot("Thread BREAKPOINT_HIT");
+ snap = tb.trace.getTimeManager().createSnapshot("BREAK");
snap.setEventThread(thread);
snap.setRealTime(fakeClock);
fakeClock += 2300;
+ regs.setValue(snap.getKey(), new RegisterValue(pc, BigInteger.valueOf(0x7fff0110)));
- snap = tb.trace.getTimeManager().createSnapshot("Thread STEP_COMPLETED");
+ snap = tb.trace.getTimeManager().createSnapshot("STEP");
snap.setEventThread(thread);
snap.setRealTime(fakeClock);
snap.setSchedule(TraceSchedule.parse(snap.getKey() - 1 + ":1"));
fakeClock += 444;
+ regs.setValue(snap.getKey(), new RegisterValue(pc, BigInteger.valueOf(0x7fff0113)));
- snap = tb.trace.getTimeManager().createSnapshot("Thread STEP_COMPLETED");
+ snap = tb.trace.getTimeManager().createSnapshot("STEP");
snap.setEventThread(thread);
snap.setRealTime(fakeClock);
snap.setSchedule(TraceSchedule.parse(snap.getKey() - 1 + ":1"));
fakeClock += 100;
+ regs.setValue(snap.getKey(), new RegisterValue(pc, BigInteger.valueOf(0x7fff0115)));
}
- traceManager.openTrace(tb.trace);
+ mappingService.changesSettled().get(1, TimeUnit.SECONDS);
+
traceManager.activateTrace(tb.trace);
traceManager.activateSnap(snap.getKey());
diff --git a/GhidraDocs/GhidraClass/Debugger/A5-Navigation.html b/GhidraDocs/GhidraClass/Debugger/A5-Navigation.html
index ec03eacf85..9a149b9e11 100644
--- a/GhidraDocs/GhidraClass/Debugger/A5-Navigation.html
+++ b/GhidraDocs/GhidraClass/Debugger/A5-Navigation.html
@@ -234,11 +234,16 @@ snapshot is at the bottom. The columns are:
windows that indicate life spans refer to these numbers. If emulating
(covered later in this course), this column may display the
schedule.
-The Timestamp column gives the time when the
-snapshot was created, i.e., the time when the event occurred.
The Event Thread column indicates which thread
caused the target to break. This only applies to snapshots that were
created because of an event, which is most.
+The PC column gives the address of the next
+instruction.
+The Function column gives the name of the function
+containing the PC mapped to its static program database, if
+available.
+The Module column gives the name of the module
+containing the PC.
The Description column describes the event that
generated the snapshot. This can be edited in the table, or by pressing
CTRL-SHIFT-N to
diff --git a/GhidraDocs/GhidraClass/Debugger/A5-Navigation.md b/GhidraDocs/GhidraClass/Debugger/A5-Navigation.md
index 96b75851ca..b842ddb110 100644
--- a/GhidraDocs/GhidraClass/Debugger/A5-Navigation.md
+++ b/GhidraDocs/GhidraClass/Debugger/A5-Navigation.md
@@ -125,9 +125,11 @@ The columns are:
* The **Time** column numbers each snapshot.
Other windows that indicate life spans refer to these numbers.
If emulating (covered later in this course), this column may display the schedule.
-* The **Timestamp** column gives the time when the snapshot was created, i.e., the time when the event occurred.
* The **Event Thread** column indicates which thread caused the target to break.
This only applies to snapshots that were created because of an event, which is most.
+* The **PC** column gives the address of the next instruction.
+* The **Function** column gives the name of the function containing the PC mapped to its static program database, if available.
+* The **Module** column gives the name of the module containing the PC.
* The **Description** column describes the event that generated the snapshot.
This can be edited in the table, or by pressing **`CTRL`-`SHIFT`-`N`** to mark interesting snapshots.
diff --git a/GhidraDocs/GhidraClass/Debugger/images/Navigation_DialogCompareTimes.png b/GhidraDocs/GhidraClass/Debugger/images/Navigation_DialogCompareTimes.png
index 05d09d046e..1fa3931b45 100644
Binary files a/GhidraDocs/GhidraClass/Debugger/images/Navigation_DialogCompareTimes.png and b/GhidraDocs/GhidraClass/Debugger/images/Navigation_DialogCompareTimes.png differ
diff --git a/GhidraDocs/GhidraClass/Debugger/images/Navigation_TimeAfterCallSRandCallRand.png b/GhidraDocs/GhidraClass/Debugger/images/Navigation_TimeAfterCallSRandCallRand.png
index 5d325a40ce..a7a56241d1 100644
Binary files a/GhidraDocs/GhidraClass/Debugger/images/Navigation_TimeAfterCallSRandCallRand.png and b/GhidraDocs/GhidraClass/Debugger/images/Navigation_TimeAfterCallSRandCallRand.png differ