{
+ public static final String NAME = "Disassemble";
+
+ public record Reqs(DebuggerPlatformMapper mapper, TraceThread thread, TraceObject object,
+ TraceProgramView view) {
+
+ public static Reqs fromView(PluginTool tool, TraceProgramView view) {
+ DebuggerTraceManagerService traceManager =
+ tool.getService(DebuggerTraceManagerService.class);
+ DebuggerPlatformService platformService =
+ tool.getService(DebuggerPlatformService.class);
+ if (platformService == null) {
+ return null;
+ }
+ Trace trace = view.getTrace();
+ DebuggerCoordinates current = traceManager == null
+ ? DebuggerCoordinates.NOWHERE
+ : traceManager.getCurrentFor(trace);
+ TraceThread thread = current.getThread();
+ TraceObject object = current.getObject();
+ DebuggerPlatformMapper mapper =
+ platformService.getMapper(trace, object, view.getSnap());
+ if (mapper == null) {
+ return null;
+ }
+ return new Reqs(mapper, thread, object, view);
+ }
+
+ public static Reqs fromContext(PluginTool tool, ActionContext context) {
+ if (!(context instanceof DebuggerListingActionContext lac)) {
+ return null;
+ }
+ return fromView(tool, lac.getProgram());
+ }
+ }
+
+ private final PluginTool tool;
+ private final AddressSetView set;
+ private final Reqs reqs;
+ private final Address address;
+
+ public CurrentPlatformTraceDisassembleCommand(PluginTool tool, AddressSetView set,
+ Reqs reqs, Address address) {
+ super(NAME, true, true, false);
+ this.tool = tool;
+ this.set = set;
+ this.reqs = reqs;
+ this.address = address;
+ }
+
+ @Override
+ public boolean applyToTyped(TraceProgramView view, TaskMonitor monitor) {
+ DisassemblyResult result = reqs.mapper.disassemble(
+ reqs.thread, reqs.object, address, set, view.getSnap(), monitor);
+ if (!result.isSuccess()) {
+ tool.setStatusInfo(result.getErrorMessage(), true);
+ }
+ return true;
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblerPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblerPlugin.java
index 5c3674931b..93f09ed9eb 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblerPlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblerPlugin.java
@@ -27,10 +27,7 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext;
import ghidra.app.services.DebuggerPlatformService;
import ghidra.app.services.DebuggerTraceManagerService;
-import ghidra.debug.api.platform.DebuggerPlatformMapper;
import ghidra.framework.plugintool.*;
-import ghidra.framework.plugintool.AutoService.Wiring;
-import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*;
@@ -40,8 +37,6 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TraceGuestPlatform;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.program.TraceProgramView;
-import ghidra.trace.model.target.TraceObject;
-import ghidra.trace.model.thread.TraceThread;
@PluginInfo(
shortDescription = "Disassemble trace bytes in the debugger",
@@ -61,21 +56,6 @@ import ghidra.trace.model.thread.TraceThread;
})
public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionProvider {
- protected static class Reqs {
- final DebuggerPlatformMapper mapper;
- final TraceThread thread;
- final TraceObject object;
- final TraceProgramView view;
-
- public Reqs(DebuggerPlatformMapper mapper, TraceThread thread, TraceObject object,
- TraceProgramView view) {
- this.mapper = mapper;
- this.thread = thread;
- this.object = object;
- this.view = view;
- }
- }
-
public static RegisterValue deriveAlternativeDefaultContext(Language language,
LanguageID alternative, Address address) {
LanguageService langServ = DefaultLanguageService.getLanguageService();
@@ -100,19 +80,11 @@ public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionPro
return result;
}
- @AutoServiceConsumed
- DebuggerTraceManagerService traceManager;
- @AutoServiceConsumed
- DebuggerPlatformService platformService;
- @SuppressWarnings("unused")
- private final Wiring autoServiceWiring;
-
CurrentPlatformTraceDisassembleAction actionDisassemble;
CurrentPlatformTracePatchInstructionAction actionPatchInstruction;
public DebuggerDisassemblerPlugin(PluginTool tool) {
super(tool);
- this.autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
}
@Override
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java
index 9b71cf3db5..8fe7b167fd 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java
@@ -33,6 +33,7 @@ import ghidra.debug.api.target.Target;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.model.DomainObject;
+import ghidra.framework.model.DomainObjectChangeRecord;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
@@ -100,10 +101,16 @@ public abstract class DebuggerReadsMemoryTrait {
protected class ForReadsTraceListener extends TraceDomainObjectListener {
public ForReadsTraceListener() {
+ listenForUntyped(DomainObject.DO_OBJECT_RESTORED, this::objectRestored);
listenFor(TraceSnapshotChangeType.ADDED, this::snapshotAdded);
listenFor(TraceMemoryStateChangeType.CHANGED, this::memStateChanged);
}
+ private void objectRestored(DomainObjectChangeRecord rec) {
+ actionRefreshSelected.updateEnabled(null);
+ doAutoRead();
+ }
+
private void snapshotAdded(TraceSnapshot snapshot) {
actionRefreshSelected.updateEnabled(null);
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java
index 9b1b1d951a..3cde8958e3 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java
@@ -56,7 +56,7 @@ public class VisibleAutoReadMemorySpec implements AutoReadMemorySpec {
Target target = coordinates.getTarget();
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible,
- s -> s == TraceMemoryState.KNOWN);
+ s -> s == TraceMemoryState.KNOWN || s == TraceMemoryState.ERROR);
AddressSet toRead = visible.subtract(alreadyKnown);
if (toRead.isEmpty()) {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java
index 6a6279f4fc..922fccd948 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java
@@ -57,7 +57,7 @@ public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec {
Target target = coordinates.getTarget();
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible,
- s -> s == TraceMemoryState.KNOWN);
+ s -> s == TraceMemoryState.KNOWN || s == TraceMemoryState.ERROR);
AddressSet toRead = visible.subtract(alreadyKnown);
if (toRead.isEmpty()) {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java
index d5b14e6a51..0d9ce2a3c9 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java
@@ -48,7 +48,8 @@ import ghidra.app.nav.ListingPanelContainer;
import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
-import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand;
+import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand;
+import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand.Reqs;
import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
@@ -180,10 +181,10 @@ public class DebuggerListingProvider extends CodeViewerProvider {
if (codeViewer.isEmpty()) {
return;
}
- Swing.runIfSwingOrRunLater(
- () -> codeViewer.get()
- .getListingPanel()
- .scrollTo(new ProgramLocation(program, selection.getMinAddress())));
+ ListingPanel listingPanel = codeViewer.get().getListingPanel();
+ Swing.runIfSwingOrRunLater(() -> {
+ listingPanel.scrollTo(new ProgramLocation(program, selection.getMinAddress()));
+ });
}
@Override
@@ -1203,8 +1204,10 @@ public class DebuggerListingProvider extends CodeViewerProvider {
}
AddressSpace space = start.getAddressSpace();
AddressSet set = new AddressSet(space.getMinAddress(), space.getMaxAddress());
- TraceDisassembleCommand dis =
- new TraceDisassembleCommand(current.getPlatform(), start, set);
+
+ Reqs reqs = Reqs.fromView(tool, view);
+ CurrentPlatformTraceDisassembleCommand dis =
+ new CurrentPlatformTraceDisassembleCommand(tool, set, reqs, start);
dis.run(tool, view);
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTreeModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTreeModel.java
index 0203f48c97..add9835c6e 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTreeModel.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTreeModel.java
@@ -210,7 +210,7 @@ public class ObjectTreeModel implements DisplaysModified {
/**
* Our nodes are re-usable. They're cached so that as an item comes and goes, its
* corresponding node can also come and go without being re-instantiated each time.
- * Furthermore, it's like to have all the same children as before, too. For now, we'll
+ * Furthermore, it's likely to have all the same children as before, too. For now, we'll
* just ignore dispose. If there's too many unexpected behaviors resulting from this,
* then perhaps we should just have dispose also remove itself from the node cache.
*/
@@ -219,12 +219,25 @@ public class ObjectTreeModel implements DisplaysModified {
@Override
public int compareTo(GTreeNode node) {
- return TargetObjectKeyComparator.CHILD.compare(this.getName(), node.getName());
+ if (!(node instanceof AbstractNode that)) {
+ return -1;
+ }
+ int c;
+ c = TargetObjectKeyComparator.CHILD.compare(this.getValue().getEntryKey(),
+ that.getValue().getEntryKey());
+ if (c != 0) {
+ return c;
+ }
+ c = Lifespan.DOMAIN.compare(this.getValue().getMinSnap(), that.getValue().getMinSnap());
+ if (c != 0) {
+ return c;
+ }
+ return 0;
}
@Override
public String getName() {
- return getValue().getEntryKey() + "@" + getValue().getMinSnap();
+ return getValue().getEntryKey() + "@" + System.identityHashCode(getValue());
}
@Override
@@ -340,14 +353,14 @@ public class ObjectTreeModel implements DisplaysModified {
@Override
public String getDisplayText() {
if (trace == null) {
- return "No trace is active";
+ return "No trace is active";
}
TraceObject root = trace.getObjectManager().getRootObject();
if (root == null) {
- return "Trace has no model";
+ return "Trace has no model";
}
- return "" +
- HTMLUtilities.escapeHTML(display.getObjectDisplay(root.getCanonicalParent(0)));
+ return "" + HTMLUtilities
+ .escapeHTML(display.getObjectDisplay(root.getCanonicalParent(0)), true);
}
@Override
@@ -424,7 +437,8 @@ public class ObjectTreeModel implements DisplaysModified {
@Override
public String getDisplayText() {
String html = HTMLUtilities.escapeHTML(
- value.getEntryKey() + ": " + display.getPrimitiveValueDisplay(value.getValue()));
+ value.getEntryKey() + ": " + display.getPrimitiveValueDisplay(value.getValue()),
+ true);
return "" + html;
}
@@ -471,8 +485,8 @@ public class ObjectTreeModel implements DisplaysModified {
@Override
public String getDisplayText() {
- return "" + HTMLUtilities.escapeHTML(value.getEntryKey()) + ": " +
- HTMLUtilities.escapeHTML(display.getObjectLinkDisplay(value)) + "";
+ return "" + HTMLUtilities.escapeHTML(value.getEntryKey(), true) + ": " +
+ HTMLUtilities.escapeHTML(display.getObjectLinkDisplay(value), true) + "";
}
@Override
@@ -513,7 +527,7 @@ public class ObjectTreeModel implements DisplaysModified {
@Override
public String getDisplayText() {
- return "" + HTMLUtilities.escapeHTML(display.getObjectDisplay(value));
+ return "" + HTMLUtilities.escapeHTML(display.getObjectDisplay(value), true);
}
@Override
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectsTreePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectsTreePanel.java
index 5a1be08cc9..97f5adf083 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectsTreePanel.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectsTreePanel.java
@@ -262,6 +262,20 @@ public class ObjectsTreePanel extends JPanel {
}
//tree.filterChanged();
// Repaint for bold current path is already going to happen
+
+ // Repaint is not enough, as node sizes may change
+ for (TraceObjectKeyPath path = current.getPath(); path != null; path = path.parent()) {
+ AbstractNode node = treeModel.getNode(path);
+ if (node != null) {
+ node.fireNodeChanged();
+ }
+ }
+ for (TraceObjectKeyPath path = previous.getPath(); path != null; path = path.parent()) {
+ AbstractNode node = treeModel.getNode(path);
+ if (node != null) {
+ node.fireNodeChanged();
+ }
+ }
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/TerminalTextFieldElement.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/TerminalTextFieldElement.java
index 2e2b0ae4b6..81dada432d 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/TerminalTextFieldElement.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/TerminalTextFieldElement.java
@@ -218,8 +218,41 @@ public class TerminalTextFieldElement implements FieldElement {
@Override
public void paint(JComponent c, Graphics g, int x, int y) {
- line.forEachRun(
- (attrs, start, end) -> paintChars(c, g, x, y, attrs, start, end));
+ if (!(g instanceof Graphics2D g2)) {
+ line.forEachRun(
+ (attrs, start, end) -> paintChars(c, g, x, y, attrs, start, end));
+ return;
+ }
+ Object aaHint = c.getClientProperty(RenderingHints.KEY_TEXT_ANTIALIASING);
+ Object lcdHint = c.getClientProperty(RenderingHints.KEY_TEXT_LCD_CONTRAST);
+ Object aaOld =
+ aaHint == null ? null : g2.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
+ Object lcdOld =
+ lcdHint == null ? null : g2.getRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST);
+ if (aaOld == aaHint) {
+ aaHint = null;
+ }
+ if (lcdOld == lcdHint) {
+ lcdHint = null;
+ }
+ try {
+ if (aaHint != null) {
+ g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, aaHint);
+ }
+ if (lcdHint != null) {
+ g2.setRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST, lcdHint);
+ }
+ line.forEachRun(
+ (attrs, start, end) -> paintChars(c, g, x, y, attrs, start, end));
+ }
+ finally {
+ if (aaHint != null) {
+ g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, aaOld);
+ }
+ if (lcdHint != null) {
+ g2.setRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST, lcdOld);
+ }
+ }
}
@Override
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java
index d93690e3ba..aaa9952172 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java
@@ -609,10 +609,16 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
/**
* Scroll the view of the listing to the given location.
+ *
+ *
+ * If the given location is not displayed, this has no effect.
* @param location the location
*/
public void scrollTo(ProgramLocation location) {
FieldLocation fieldLocation = getFieldLocation(location);
+ if (fieldLocation == null) {
+ return;
+ }
fieldPanel.scrollTo(fieldLocation);
}
diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/AbstractDbgEngTraceRmiTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/AbstractDbgEngTraceRmiTest.java
index 08113a1eee..d6241d4359 100644
--- a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/AbstractDbgEngTraceRmiTest.java
+++ b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/AbstractDbgEngTraceRmiTest.java
@@ -16,7 +16,7 @@
package agent.dbgeng.rmi;
import static org.junit.Assert.*;
-import static org.junit.Assume.*;
+import static org.junit.Assume.assumeTrue;
import java.io.*;
import java.net.*;
@@ -27,6 +27,7 @@ import java.util.function.Function;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.Before;
+import org.junit.BeforeClass;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
@@ -63,6 +64,40 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
protected static final int TIMEOUT_SECONDS = 300;
protected static final int QUIT_TIMEOUT_MS = 1000;
+ protected static boolean didSetupPython = false;
+
+ public static final String INSTRUMENT_STATE = """
+ import sys
+ from comtypes.hresult import S_OK
+ from ghidradbg import commands, hooks
+ print("Instrumenting")
+ @hooks.log_errors
+ def on_state_changed(*args):
+ if args[0] != DbgEng.DEBUG_CES_EXECUTION_STATUS:
+ return S_OK
+ print("State changed: {:x}".format(args[1]))
+ sys.stdout.flush()
+ proc = util.selected_process()
+ trace = commands.STATE.trace
+ with commands.STATE.client.batch():
+ with trace.open_tx("State changed proc {}".format(proc)):
+ commands.put_state(proc)
+ return S_OK
+
+ # Without this, the engine seems to GO after the interrupt
+ @hooks.log_errors
+ def on_exception(*args):
+ return DbgEng.DEBUG_STATUS_BREAK
+
+ @util.dbg.eng_thread
+ def install_hooks():
+ print("Installing")
+ util.dbg._base.events.engine_state(handler=on_state_changed)
+ util.dbg._base.events.exception(handler=on_exception)
+
+ install_hooks()
+ """;
+
protected TraceRmiService traceRmi;
private Path pythonPath;
private Path outFile;
@@ -75,11 +110,17 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
//@BeforeClass
public static void setupPython() throws Throwable {
- new ProcessBuilder("gradle", "Debugger-agent-dbgeng:assemblePyPackage")
+ if (didSetupPython) {
+ // Only do this once when running the full suite.
+ return;
+ }
+ String gradle = DummyProc.which("gradle.bat");
+ new ProcessBuilder(gradle, "Debugger-agent-dbgeng:assemblePyPackage")
.directory(TestApplicationUtils.getInstallationDirectory())
.inheritIO()
.start()
.waitFor();
+ didSetupPython = true;
}
protected void setPythonPath(ProcessBuilder pb) throws IOException {
@@ -140,6 +181,10 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
if (stderr.contains("Error") || (0 != exitCode && 1 != exitCode && 143 != exitCode)) {
throw new PythonError(exitCode, stdout, stderr);
}
+ System.out.println("--stdout--");
+ System.out.println(stdout);
+ System.out.println("--stderr--");
+ System.out.println(stderr);
return stdout;
}
}
@@ -227,9 +272,9 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
return execute.invokeAsync(Map.of("cmd", cmd));
}
- public String executeCapture(String expr) {
- RemoteMethod execute = getMethod("evaluate");
- return (String) execute.invoke(Map.of("expr", expr));
+ public String executeCapture(String cmd) {
+ RemoteMethod execute = getMethod("execute");
+ return (String) execute.invoke(Map.of("cmd", cmd, "to_string", true));
}
@Override
@@ -281,15 +326,15 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
return stdout;
}
- protected void waitStopped() {
+ protected void waitStopped(String message) {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
- waitForPass(() -> assertEquals("STOPPED", tb.objValue(proc, 0, "_state")));
+ waitForPass(() -> assertEquals(message, "STOPPED", tb.objValue(proc, 0, "_state")));
waitTxDone();
}
- protected void waitRunning() {
+ protected void waitRunning(String message) {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
- waitForPass(() -> assertEquals("RUNNING", tb.objValue(proc, 0, "_state")));
+ waitForPass(() -> assertEquals(message, "RUNNING", tb.objValue(proc, 0, "_state")));
waitTxDone();
}
diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/DbgEngCommandsTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/DbgEngCommandsTest.java
index 1fa3e2419f..8506d42ec5 100644
--- a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/DbgEngCommandsTest.java
+++ b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/DbgEngCommandsTest.java
@@ -28,18 +28,23 @@ import java.util.stream.IntStream;
import org.junit.Test;
+import db.Transaction;
import generic.Unique;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.dbg.util.PathPredicates;
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
import ghidra.debug.api.tracermi.TraceRmiConnection;
+import ghidra.framework.Application;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.address.*;
+import ghidra.program.model.data.Float10DataType;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.CodeUnit;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.*;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
+import ghidra.trace.model.listing.TraceCodeSpace;
+import ghidra.trace.model.listing.TraceData;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.target.*;
@@ -176,9 +181,11 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
%s
print('---Import---')
ghidra_trace_info()
+ print('---BeforeConnect---')
ghidra_trace_connect('%s')
print('---Connect---')
ghidra_trace_info()
+ print('---Create---')
ghidra_trace_create('notepad.exe')
print('---Start---')
ghidra_trace_info()
@@ -196,20 +203,19 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
Not connected to Ghidra""",
extractOutSection(out, "---Import---"));
assertEquals("""
- Connected to Ghidra at %s
-
- No trace""".formatted(refAddr.get()),
- extractOutSection(out, "---Connect---").replaceAll("\r", "").substring(0, 48));
- String expected = """
- Connected to Ghidra at %s
-
- Trace active""".formatted(refAddr.get());
- String actual = extractOutSection(out, "---Start---").replaceAll("\r", "");
- assertEquals(expected, actual.substring(0, expected.length()));
+ Connected to %s %s at %s
+ No trace""".formatted(
+ Application.getName(), Application.getApplicationVersion(), refAddr.get()),
+ extractOutSection(out, "---Connect---").replaceAll("\r", ""));
assertEquals("""
- Connected to Ghidra at %s
-
- No trace""".formatted(refAddr.get()),
+ Connected to %s %s at %s
+ Trace active""".formatted(
+ Application.getName(), Application.getApplicationVersion(), refAddr.get()),
+ extractOutSection(out, "---Start---").replaceAll("\r", ""));
+ assertEquals("""
+ Connected to %s %s at %s
+ No trace""".formatted(
+ Application.getName(), Application.getApplicationVersion(), refAddr.get()),
extractOutSection(out, "---Stop---").replaceAll("\r", ""));
assertEquals("""
Not connected to Ghidra""",
@@ -224,7 +230,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
%s
print('---Import---')
ghidra_trace_info_lcsp()
- print('---')
+ print('---Create---')
ghidra_trace_create('notepad.exe', start_trace=False)
print('---File---')
ghidra_trace_info_lcsp()
@@ -237,21 +243,16 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
quit()
""".formatted(PREAMBLE));
- assertTrue(
- extractOutSection(out, "---File---").replaceAll("\r", "")
- .contains(
- """
- Selected Ghidra language: x86:LE:64:default
-
- Selected Ghidra compiler: windows"""));
+ assertEquals("""
+ Selected Ghidra language: x86:LE:64:default
+ Selected Ghidra compiler: windows""",
+ extractOutSection(out, "---File---").replaceAll("\r", ""));
assertEquals("""
Selected Ghidra language: Toy:BE:64:default
-
Selected Ghidra compiler: default""",
extractOutSection(out, "---Language---").replaceAll("\r", ""));
assertEquals("""
Selected Ghidra language: Toy:BE:64:default
-
Selected Ghidra compiler: posStack""",
extractOutSection(out, "---Compiler---").replaceAll("\r", ""));
}
@@ -319,11 +320,11 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
ghidra_trace_create('notepad.exe')
ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot')
- ghidra_trace_putmem('$pc 16')
+ pc = util.get_pc()
+ ghidra_trace_putmem(pc, 16)
ghidra_trace_txcommit()
print('---Dump---')
- pc = util.get_debugger().reg.get_pc()
- util.get_debugger().dd(pc, count=1)
+ util.dbg.dd(pc, count=1)
print('---')
ghidra_trace_kill()
quit()
@@ -348,11 +349,11 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
ghidra_trace_create('notepad.exe')
ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot')
- ghidra_trace_putmem_state('$pc 16 error')
+ pc = util.get_pc()
+ ghidra_trace_putmem_state(pc, 16, 'error', pages=False)
ghidra_trace_txcommit()
print('---Start---')
- pc = util.get_debugger().reg.get_pc()
- util.get_debugger().dd(pc, count=1)
+ util.dbg.dd(pc, count=1)
print('---')
ghidra_trace_kill()
quit()
@@ -380,12 +381,12 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
ghidra_trace_create('notepad.exe')
ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot')
- ghidra_trace_putmem('$pc 16')
- ghidra_trace_delmem('$pc 8')
+ pc = util.get_pc()
+ ghidra_trace_putmem(pc, 16)
+ ghidra_trace_delmem(pc, 8)
ghidra_trace_txcommit()
print('---Dump---')
- pc = util.get_debugger().reg.get_pc()
- util.get_debugger().dd(pc, count=1)
+ util.dbg.dd(pc, count=1)
print('---')
ghidra_trace_kill()
quit()
@@ -412,9 +413,8 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
%s
ghidra_trace_connect('%s')
ghidra_trace_create('notepad.exe')
- regs = util.get_debugger().reg
- regs._set_register("rax", int(0xdeadbeef))
- regs._set_register("st0", int(1.5))
+ util.dbg.cmd('r rax=0xdeadbeef')
+ util.dbg.cmd('r st0=1.5')
ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot')
ghidra_trace_putreg()
@@ -438,20 +438,15 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
assertEquals("deadbeef", rax.getUnsignedValue().toString(16));
- // TODO: Pybag currently doesn't suppport non-int assignments
- /*
- * // RegisterValue ymm0 = regs.getValue(snap, tb.reg("ymm0")); // // LLDB
- * treats registers in arch's endian // assertEquals(
- * "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100", //
- * ymm0.getUnsignedValue().toString(16));
- *
- * // TraceData st0; // try (Transaction tx =
- * tb.trace.openTransaction("Float80 unit")) { // TraceCodeSpace code =
- * tb.trace.getCodeManager().getCodeSpace(t1f0, true); // st0 =
- * code.definedData() // .create(Lifespan.nowOn(0), tb.reg("st0"),
- * Float10DataType.dataType); // } // assertEquals("1.5",
- * st0.getDefaultValueRepresentation());
- */
+ TraceData st0;
+ try (Transaction tx = tb.trace.openTransaction("Float80 unit")) {
+ TraceCodeSpace code = tb.trace.getCodeManager().getCodeSpace(t1f0, true);
+ st0 = code.definedData()
+ .create(Lifespan.nowOn(0), tb.reg("st0"), Float10DataType.dataType);
+ }
+
+ // TODO: Pybag doesn't support non-integer registers
+ // assertEquals("1.5", st0.getDefaultValueRepresentation());
}
}
@@ -464,8 +459,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
%s
ghidra_trace_connect('%s')
ghidra_trace_create('notepad.exe')
- regs = util.get_debugger().reg
- regs._set_register("st0", int(1.5))
+ util.dbg.cmd('r rax=0xdeadbeef')
ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot')
ghidra_trace_putreg()
@@ -490,19 +484,6 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
assertEquals("0", rax.getUnsignedValue().toString(16));
-
- // TODO: As above, not currently supported by pybag
- /*
- * // RegisterValue ymm0 = regs.getValue(snap, tb.reg("ymm0")); //
- * assertEquals("0", ymm0.getUnsignedValue().toString(16));
- *
- * // TraceData st0; // try (Transaction tx =
- * tb.trace.openTransaction("Float80 unit")) { // TraceCodeSpace code =
- * tb.trace.getCodeManager().getCodeSpace(t1f0, true); // st0 =
- * code.definedData() // .create(Lifespan.nowOn(0), tb.reg("st0"),
- * Float10DataType.dataType); // } // assertEquals("0.0",
- * st0.getDefaultValueRepresentation());
- */
}
}
@@ -551,9 +532,8 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
assertNotNull(object);
Lifespan life = Unique.assertOne(object.getLife().spans());
assertEquals(Lifespan.nowOn(0), life);
- String expected = "Inserted object: lifespan=[0,+inf)";
- String actual = extractOutSection(out, "---Lifespan---");
- assertEquals(expected, actual.substring(0, expected.length()));
+ assertEquals("Inserted object: lifespan=[0,+inf)",
+ extractOutSection(out, "---Lifespan---"));
}
}
@@ -788,9 +768,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
TraceObject object = tb.trace.getObjectManager()
.getObjectByCanonicalPath(TraceObjectKeyPath.parse("Test.Objects[1]"));
assertNotNull(object);
- String expected = "1\tTest.Objects[1]";
- String actual = extractOutSection(out, "---GetObject---");
- assertEquals(expected, actual.substring(0, expected.length()));
+ assertEquals("1\tTest.Objects[1]", extractOutSection(out, "---GetObject---"));
}
}
@@ -834,8 +812,9 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
- String expected = """
+ assertEquals("""
Parent Key Span Value Type
+ Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS
Test.Objects[1] vbool [0,+inf) True BOOL
Test.Objects[1] vboolarr [0,+inf) [True, False] BOOL_ARR
Test.Objects[1] vbyte [0,+inf) 1 BYTE
@@ -849,16 +828,8 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
Test.Objects[1] vobj [0,+inf) Test.Objects[1] OBJECT
Test.Objects[1] vshort [0,+inf) 2 SHORT
Test.Objects[1] vshortarr [0,+inf) [1, 2, 3] SHORT_ARR
- Test.Objects[1] vstring [0,+inf) 'Hello' STRING
- Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS"""
- .replaceAll(" ", "")
- .replaceAll("\n", "");
- String actual = extractOutSection(out, "---GetValues---").replaceAll(" ", "")
- .replaceAll("\r", "")
- .replaceAll("\n", "");
- assertEquals(
- expected,
- actual.substring(0, expected.length()));
+ Test.Objects[1] vstring [0,+inf) 'Hello' STRING""",
+ extractOutSection(out, "---GetValues---").replaceAll("\r", ""));
}
}
@@ -874,19 +845,17 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
ghidra_trace_set_value('Test.Objects[1]', 'vaddr', '(void*)0xdeadbeef', 'ADDRESS')
ghidra_trace_txcommit()
print('---GetValues---')
- ghidra_trace_get_values_rng('(void*)0xdeadbeef 10')
+ ghidra_trace_get_values_rng(0xdeadbeef, 10)
print('---')
ghidra_trace_kill()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
- String expected = """
+ assertEquals("""
Parent Key Span Value Type
-
- Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS""";
- String actual = extractOutSection(out, "---GetValues---").replaceAll("\r", "");
- assertEquals(expected, actual.substring(0, expected.length()));
+ Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS""",
+ extractOutSection(out, "---GetValues---").replaceAll("\r", ""));
}
}
@@ -919,9 +888,10 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
ghidra_trace_connect('%s')
ghidra_trace_create('notepad.exe')
ghidra_trace_txstart('Tx')
- ghidra_trace_putmem('$pc 16')
+ pc = util.get_pc()
+ ghidra_trace_putmem(pc, 16)
print('---Disassemble---')
- ghidra_trace_disassemble('$pc')
+ ghidra_trace_disassemble(pc)
print('---')
ghidra_trace_txcommit()
ghidra_trace_kill()
@@ -991,10 +961,9 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
%s
ghidra_trace_connect('%s')
ghidra_trace_create('notepad.exe')
- dbg = util.get_debugger()
- pc = dbg.reg.get_pc()
- dbg.bp(expr=pc)
- dbg.ba(expr=pc+4)
+ pc = util.get_pc()
+ util.dbg.bp(expr=pc)
+ util.dbg.ba(expr=pc+4)
ghidra_trace_txstart('Tx')
ghidra_trace_put_breakpoints()
ghidra_trace_txcommit()
@@ -1007,6 +976,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
.getValuePaths(Lifespan.at(0),
PathPredicates.parse("Processes[].Breakpoints[]"))
.map(p -> p.getLastEntry())
+ .sorted(Comparator.comparing(TraceObjectValue::getEntryKey))
.toList();
assertEquals(2, procBreakLocVals.size());
AddressRange rangeMain =
@@ -1029,11 +999,10 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
ghidra_trace_connect('%s')
ghidra_trace_create('notepad.exe')
ghidra_trace_txstart('Tx')
- dbg = util.get_debugger()
- pc = dbg.reg.get_pc()
- dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)
- dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)
- dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)
+ pc = util.get_pc()
+ util.dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)
+ util.dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)
+ util.dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)
ghidra_trace_put_breakpoints()
ghidra_trace_txcommit()
ghidra_trace_kill()
@@ -1045,6 +1014,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
.getValuePaths(Lifespan.at(0),
PathPredicates.parse("Processes[].Breakpoints[]"))
.map(p -> p.getLastEntry())
+ .sorted(Comparator.comparing(TraceObjectValue::getEntryKey))
.toList();
assertEquals(3, procBreakVals.size());
AddressRange rangeMain0 =
diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/DbgEngHooksTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/DbgEngHooksTest.java
index 841128a474..0383fbb296 100644
--- a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/DbgEngHooksTest.java
+++ b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/DbgEngHooksTest.java
@@ -15,12 +15,11 @@
*/
package agent.dbgeng.rmi;
+import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.*;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Objects;
+import java.util.*;
import org.junit.Ignore;
import org.junit.Test;
@@ -28,19 +27,22 @@ import org.junit.Test;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathPredicates;
+import ghidra.debug.api.tracermi.RemoteMethod;
import ghidra.program.model.address.AddressSpace;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
+import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.time.TraceSnapshot;
public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
- private static final long RUN_TIMEOUT_MS = 20000;
+ private static final long RUN_TIMEOUT_MS = 5000;
private static final long RETRY_MS = 500;
- record PythonAndTrace(PythonAndConnection conn, ManagedDomainObject mdo) implements AutoCloseable {
+ record PythonAndTrace(PythonAndConnection conn, ManagedDomainObject mdo)
+ implements AutoCloseable {
public void execute(String cmd) {
conn.execute(cmd);
}
@@ -85,8 +87,9 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
return conn.conn.connection().getLastSnapshot(tb.trace);
}
- @Test // The 10s wait makes this a pretty expensive test
+ @Test
public void testOnNewThread() throws Exception {
+ final int INIT_NOTEPAD_THREAD_COUNT = 4; // This could be fragile
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
conn.execute("from ghidradbg.commands import *");
txPut(conn, "processes");
@@ -98,14 +101,18 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
}, RUN_TIMEOUT_MS, RETRY_MS);
txPut(conn, "threads");
- waitForPass(() -> assertEquals(4,
+ waitForPass(() -> assertEquals(INIT_NOTEPAD_THREAD_COUNT,
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
RUN_TIMEOUT_MS, RETRY_MS);
- conn.execute("dbg().go(10)");
+ // Via method, go is asynchronous
+ RemoteMethod go = conn.conn.getMethod("go");
+ TraceObject proc = tb.objAny("Processes[]");
+ go.invoke(Map.of("process", proc));
waitForPass(
- () -> assertTrue(tb.objValues(lastSnap(conn), "Processes[].Threads[]").size() > 4),
+ () -> assertThat(tb.objValues(lastSnap(conn), "Processes[].Threads[]").size(),
+ greaterThan(INIT_NOTEPAD_THREAD_COUNT)),
RUN_TIMEOUT_MS, RETRY_MS);
}
}
@@ -116,39 +123,39 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
txPut(conn, "processes");
waitForPass(() -> {
- TraceObject inf = tb.objAny("Processes[]");
- assertNotNull(inf);
- assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
+ TraceObject proc = tb.obj("Processes[0]");
+ assertNotNull(proc);
+ assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
}, RUN_TIMEOUT_MS, RETRY_MS);
txPut(conn, "threads");
waitForPass(() -> assertEquals(4,
- tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
+ tb.objValues(lastSnap(conn), "Processes[0].Threads[]").size()),
RUN_TIMEOUT_MS, RETRY_MS);
// Now the real test
+ conn.execute("print('Selecting 1')");
conn.execute("util.select_thread(1)");
waitForPass(() -> {
- String tnum = conn.executeCapture("util.selected_thread()");
- assertTrue(tnum.contains("1"));
- String threadIndex = threadIndex(traceManager.getCurrentObject());
- assertTrue(tnum.contains(threadIndex));
+ String tnum = conn.executeCapture("print(util.selected_thread())").strip();
+ assertEquals("1", tnum);
+ assertEquals(tb.obj("Processes[0].Threads[1]"), traceManager.getCurrentObject());
}, RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("util.select_thread(2)");
waitForPass(() -> {
- String tnum = conn.executeCapture("util.selected_thread()");
- assertTrue(tnum.contains("2"));
+ String tnum = conn.executeCapture("print(util.selected_thread())").strip();
+ assertEquals("2", tnum);
String threadIndex = threadIndex(traceManager.getCurrentObject());
- assertTrue(tnum.contains(threadIndex));
+ assertEquals("2", threadIndex);
}, RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("util.select_thread(0)");
waitForPass(() -> {
- String tnum = conn.executeCapture("util.selected_thread()");
- assertTrue(tnum.contains("0"));
+ String tnum = conn.executeCapture("print(util.selected_thread())").strip();
+ assertEquals("0", tnum);
String threadIndex = threadIndex(traceManager.getCurrentObject());
- assertTrue(tnum.contains(threadIndex));
+ assertEquals("0", threadIndex);
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
@@ -190,21 +197,44 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
// FWIW, I've already seen this getting exercised in other tests.
}
- //@Test - dbgeng has limited support via DEBUG_CDS_DATA,
- // but expensive to implement anything here
+ /**
+ * dbgeng has limited support via DEBUG_CDS_DATA. It tells us what space has changed, but not
+ * the address(es). We have some options:
+ *
+ * 1) Ignore it. This puts the onus of refreshing on the user. The upside is that past
+ * observations are preserved. The downside is we can't be certain of their accuracy.
+ *
+ * 2) Invalidate the entire space. This will ensure the UI either updates automatically or
+ * indicates the possible staleness. The downside is that we lose past observations.
+ *
+ * 3) Remember what addresses have been fetched since last BREAK, and refresh them all. This is
+ * better than refreshing the entire space (prohibitive), but we could get right back there if
+ * the user has captured the full space and then modifies a single byte.
+ *
+ * For the moment, we favor option (2), as we'd prefer never to display inaccurate data,
+ * especially as non-stale. The lost observations are a small price to pay, since they're not
+ * particularly important for the interactive use case.
+ */
+ @Test
public void testOnMemoryChanged() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
- conn.execute("ghidra_trace_txstart('Tx')");
- conn.execute("ghidra_trace_putmem('$pc 10')");
- conn.execute("ghidra_trace_txcommit()");
long address = getAddressAtOffset(conn, 0);
- conn.execute("util.get_debugger().write(" + address + ", b'\\x7f')");
+
+ conn.execute("ghidra_trace_txstart('Tx')");
+ conn.execute("ghidra_trace_putmem(%d, 10)".formatted(address));
+ conn.execute("ghidra_trace_txcommit()");
waitForPass(() -> {
- ByteBuffer buf = ByteBuffer.allocate(10);
- tb.trace.getMemoryManager().getBytes(lastSnap(conn), tb.addr(address), buf);
- assertEquals(0x7f, buf.get(0));
+ assertEquals(TraceMemoryState.KNOWN,
+ tb.trace.getMemoryManager().getState(lastSnap(conn), tb.addr(address)));
+ }, RUN_TIMEOUT_MS, RETRY_MS);
+
+ conn.execute("util.dbg.write(%d, b'\\x7f')".formatted(address));
+
+ waitForPass(() -> {
+ assertEquals(TraceMemoryState.UNKNOWN,
+ tb.trace.getMemoryManager().getState(lastSnap(conn), tb.addr(address)));
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
@@ -216,8 +246,7 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
conn.execute("ghidra_trace_txstart('Tx')");
conn.execute("ghidra_trace_putreg()");
conn.execute("ghidra_trace_txcommit()");
- conn.execute("util.get_debugger().reg._set_register('rax', 0x1234)");
- conn.execute("util.get_debugger().stepi()");
+ conn.execute("util.dbg.cmd('r rax=0x1234')");
String path = "Processes[].Threads[].Registers";
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
@@ -234,8 +263,15 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
txPut(conn, "processes");
- conn.execute("util.get_debugger()._control.SetExecutionStatus(DbgEng.DEBUG_STATUS_GO)");
- waitRunning();
+ // WaitForEvents is not required for this test to pass.
+ conn.execute("""
+ @util.dbg.eng_thread
+ def go_no_wait():
+ util.dbg._base._control.SetExecutionStatus(DbgEng.DEBUG_STATUS_GO)
+
+ go_no_wait()
+ """);
+ waitRunning("Missed running after go");
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
waitForPass(() -> {
@@ -260,9 +296,10 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
public void testOnExited() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("netstat.exe")) {
txPut(conn, "processes");
- waitStopped();
+ waitStopped("Missed initial stop");
- conn.execute("util.get_debugger().go()");
+ // Do the synchronous wait here, since netstat should terminate
+ conn.execute("util.dbg.go()");
waitForPass(() -> {
TraceSnapshot snapshot =
@@ -285,14 +322,12 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
txPut(conn, "breakpoints");
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
- conn.execute("dbg = util.get_debugger()");
- conn.execute("pc = dbg.reg.get_pc()");
- conn.execute("dbg.bp(expr=pc)");
+ conn.execute("pc = util.get_pc()");
+ conn.execute("util.dbg.bp(expr=pc)");
waitForPass(() -> {
List