Merge remote-tracking branch 'origin/GP-326_d-millar_how_to_add_a_debugger--SQUASHED'

This commit is contained in:
Ryan Kurtz 2025-01-08 14:56:18 -05:00
commit 52a1550eff
28 changed files with 4816 additions and 1 deletions

View file

@ -0,0 +1,379 @@
/* ###
* IP: GHIDRA
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.drgn.rmi;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import java.io.FileWriter;
import java.io.IOException;
import java.net.*;
import java.nio.file.*;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.*;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.Before;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.app.services.TraceRmiService;
import ghidra.debug.api.tracermi.*;
import ghidra.framework.*;
import ghidra.framework.main.ApplicationLevelOnlyPlugin;
import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginsConfiguration;
import ghidra.framework.plugintool.util.*;
import ghidra.pty.testutil.DummyProc;
import ghidra.util.Msg;
import junit.framework.AssertionFailedError;
public abstract class AbstractDrgnTraceRmiTest extends AbstractGhidraHeadedDebuggerTest {
protected static String CORE = "core.12137";
protected static String MDO = "/New Traces/" + CORE;
public static String PREAMBLE = """
import os
import drgn
import drgn.cli
os.environ['OPT_TARGET_KIND'] = 'coredump'
os.environ['OPT_TARGET_IMG'] = '$CORE'
from ghidradrgn.commands import *
""";
// Connecting should be the first thing the script does, so use a tight timeout.
protected static final int CONNECT_TIMEOUT_MS = 3000;
protected static final int TIMEOUT_SECONDS = 30000;
protected static final int QUIT_TIMEOUT_MS = 1000;
protected static boolean didSetupPython = false;
protected TraceRmiService traceRmi;
private Path pythonPath;
private Path outFile;
private Path errFile;
@Before
public void assertOS() {
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
}
//@BeforeClass
public static void setupPython() throws Throwable {
if (didSetupPython) {
// Only do this once when running the full suite.
return;
}
String gradle = DummyProc.which("gradle");
new ProcessBuilder(gradle, "Debugger-agent-drgn:assemblePyPackage")
.directory(TestApplicationUtils.getInstallationDirectory())
.inheritIO()
.start()
.waitFor();
didSetupPython = true;
}
protected void setPythonPath(ProcessBuilder pb) throws IOException {
String sep =
OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX ? ";" : ":";
String rmiPyPkg = Application.getModuleSubDirectory("Debugger-rmi-trace",
"build/pypkg/src").getAbsolutePath();
String drgnPyPkg = Application.getModuleSubDirectory("Debugger-agent-drgn",
"build/pypkg/src").getAbsolutePath();
String add = rmiPyPkg + sep + drgnPyPkg;
pb.environment().compute("PYTHONPATH", (k, v) -> v == null ? add : (v + sep + add));
}
@Before
public void setupTraceRmi() throws Throwable {
traceRmi = addPlugin(tool, TraceRmiPlugin.class);
try {
pythonPath = Paths.get(DummyProc.which("drgn"));
}
catch (RuntimeException e) {
Msg.error(this, e);
}
outFile = Files.createTempFile("drgnout", null);
errFile = Files.createTempFile("drgnerr", null);
}
protected void addAllDebuggerPlugins() throws PluginException {
PluginsConfiguration plugConf = new PluginsConfiguration() {
@Override
protected boolean accepts(Class<? extends Plugin> pluginClass) {
return !ApplicationLevelOnlyPlugin.class.isAssignableFrom(pluginClass);
}
};
for (PluginDescription pd : plugConf
.getPluginDescriptions(PluginPackage.getPluginPackage("Debugger"))) {
addPlugin(tool, pd.getPluginClass());
}
}
protected static String addrToStringForPython(InetAddress address) {
if (address.isAnyLocalAddress()) {
return "127.0.0.1"; // Can't connect to 0.0.0.0 as such. Choose localhost.
}
return address.getHostAddress();
}
protected static String sockToStringForPython(SocketAddress address) {
if (address instanceof InetSocketAddress tcp) {
return addrToStringForPython(tcp.getAddress()) + ":" + tcp.getPort();
}
throw new AssertionError("Unhandled address type " + address);
}
protected record PythonResult(boolean timedOut, int exitCode, String stdout, String stderr) {
protected String handle() {
if (stderr.contains("RuntimeError") || 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;
}
}
protected record ExecInDrgn(Process python, CompletableFuture<PythonResult> future) {
}
@SuppressWarnings("resource") // Do not close stdin
protected ExecInDrgn execInDrgn(String script) throws IOException {
ResourceFile rf = Application.getModuleDataFile("TestResources", CORE);
script = script.replace("$CORE", rf.getAbsolutePath());
Path fp = Files.createTempFile("test", ".py");
FileWriter fw = new FileWriter(fp.toFile());
fw.write(script);
fw.close();
ProcessBuilder pb = new ProcessBuilder(pythonPath.toString(), "-c",
rf.getAbsolutePath(), fp.toFile().getAbsolutePath());
setPythonPath(pb);
// If commands come from file, Python will quit after EOF.
Msg.info(this, "outFile: " + outFile);
Msg.info(this, "errFile: " + errFile);
//pb.inheritIO();
pb.redirectInput(ProcessBuilder.Redirect.PIPE);
pb.redirectOutput(outFile.toFile());
pb.redirectError(errFile.toFile());
Process pyproc = pb.start();
return new ExecInDrgn(pyproc, CompletableFuture.supplyAsync(() -> {
try {
if (!pyproc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
Msg.error(this, "Timed out waiting for Python");
pyproc.destroyForcibly();
pyproc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS);
return new PythonResult(true, -1, Files.readString(outFile),
Files.readString(errFile));
}
Msg.info(this, "Python exited with code " + pyproc.exitValue());
return new PythonResult(false, pyproc.exitValue(), Files.readString(outFile),
Files.readString(errFile));
}
catch (Exception e) {
return ExceptionUtils.rethrow(e);
}
finally {
pyproc.destroyForcibly();
}
}));
}
public static class PythonError extends RuntimeException {
public final int exitCode;
public final String stdout;
public final String stderr;
public PythonError(int exitCode, String stdout, String stderr) {
super("""
exitCode=%d:
----stdout----
%s
----stderr----
%s
""".formatted(exitCode, stdout, stderr));
this.exitCode = exitCode;
this.stdout = stdout;
this.stderr = stderr;
}
}
protected String runThrowError(String script) throws Exception {
CompletableFuture<PythonResult> result = execInDrgn(script).future;
return result.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
}
protected record PythonAndConnection(ExecInDrgn exec, TraceRmiConnection connection)
implements AutoCloseable {
protected RemoteMethod getMethod(String name) {
return Objects.requireNonNull(connection.getMethods().get(name));
}
public void execute(String cmd) {
RemoteMethod execute = getMethod("execute");
execute.invoke(Map.of("cmd", cmd));
}
public RemoteAsyncResult executeAsync(String cmd) {
RemoteMethod execute = getMethod("execute");
return execute.invokeAsync(Map.of("cmd", cmd));
}
public String executeCapture(String cmd) {
RemoteMethod execute = getMethod("execute");
return (String) execute.invoke(Map.of("cmd", cmd, "to_string", true));
}
@Override
public void close() throws Exception {
Msg.info(this, "Cleaning up python");
exec.python().destroy();
try {
PythonResult r = exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
r.handle();
waitForPass(() -> assertTrue(connection.isClosed()));
}
finally {
exec.python.destroyForcibly();
}
}
}
protected PythonAndConnection startAndConnectDrgn(Function<String, String> scriptSupplier)
throws Exception {
TraceRmiAcceptor acceptor = traceRmi.acceptOne(null);
ExecInDrgn exec =
execInDrgn(scriptSupplier.apply(sockToStringForPython(acceptor.getAddress())));
acceptor.setTimeout(CONNECT_TIMEOUT_MS);
try {
TraceRmiConnection connection = acceptor.accept();
return new PythonAndConnection(exec, connection);
}
catch (SocketTimeoutException e) {
exec.python.destroyForcibly();
exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
throw e;
}
}
protected PythonAndConnection startAndConnectDrgn() throws Exception {
return startAndConnectDrgn(addr -> """
%s
ghidra_trace_connect('%s')
drgn.cli.run_interactive(prog)
""".formatted(PREAMBLE, addr));
}
@SuppressWarnings("resource")
protected String runThrowError(Function<String, String> scriptSupplier)
throws Exception {
PythonAndConnection conn = startAndConnectDrgn(scriptSupplier);
PythonResult r = conn.exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
String stdout = r.handle();
//waitForPass(() -> assertTrue(conn.connection.isClosed()));
return stdout;
}
protected String extractOutSection(String out, String head) {
String[] split = out.split("\n");
String xout = "";
for (String s : split) {
if (!s.startsWith("(python)") && !s.equals("")) {
xout += s + "\n";
}
}
return xout.split(head)[1].split("---")[0].replace("(python)", "").trim();
}
protected ManagedDomainObject openDomainObject(String path) throws Exception {
DomainFile df = env.getProject().getProjectData().getFile(path);
assertNotNull(df);
return new ManagedDomainObject(df, false, false, monitor);
}
protected ManagedDomainObject waitDomainObject(String path) throws Exception {
DomainFile df;
long start = System.currentTimeMillis();
while (true) {
df = env.getProject().getProjectData().getFile(path);
if (df != null) {
return new ManagedDomainObject(df, false, false, monitor);
}
Thread.sleep(1000);
if (System.currentTimeMillis() - start > 30000) {
throw new TimeoutException("30 seconds expired waiting for domain file");
}
}
}
protected long getMaxSnap() {
Long maxSnap = tb.trace.getTimeManager().getMaxSnap();
return maxSnap == null ? 0 : maxSnap;
}
protected void waitTxDone() {
waitFor(() -> tb.trace.getCurrentTransactionInfo() == null);
}
public static void waitForPass(Runnable runnable) {
AtomicReference<AssertionError> lastError = new AtomicReference<>();
waitForCondition(() -> {
try {
runnable.run();
return true;
}
catch (AssertionError e) {
lastError.set(e);
return false;
}
}, () -> lastError.get().getMessage());
}
public static void waitForCondition(BooleanSupplier condition,
Supplier<String> failureMessageSupplier) throws AssertionFailedError {
int totalTime = 0;
while (totalTime <= DEFAULT_WAIT_TIMEOUT * 10) {
if (condition.getAsBoolean()) {
return; // success
}
totalTime += sleep(DEFAULT_WAIT_DELAY * 10);
}
String failureMessage = "Timed-out waiting for condition";
if (failureMessageSupplier != null) {
failureMessage = failureMessageSupplier.get();
}
throw new AssertionFailedError(failureMessage);
}
}

View file

@ -0,0 +1,909 @@
/* ###
* IP: GHIDRA
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.drgn.rmi;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
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.debug.api.tracermi.TraceRmiAcceptor;
import ghidra.debug.api.tracermi.TraceRmiConnection;
import ghidra.framework.Application;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
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.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.listing.TraceCodeSpace;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue;
import ghidra.trace.model.target.path.KeyPath;
import ghidra.trace.model.target.path.PathFilter;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.Msg;
public class DrgnCommandsTest extends AbstractDrgnTraceRmiTest {
//@Test
public void testManual() throws Exception {
TraceRmiAcceptor acceptor = traceRmi.acceptOne(null);
Msg.info(this,
"Use: ghidra_trace_connect(" + sockToStringForPython(acceptor.getAddress()) + ")");
TraceRmiConnection connection = acceptor.accept();
Msg.info(this, "Connected: " + sockToStringForPython(connection.getRemoteAddress()));
connection.waitClosed();
Msg.info(this, "Closed");
}
@Test
public void testConnectErrorNoArg() throws Exception {
try {
runThrowError("""
from ghidradrgn.commands import *
ghidra_trace_connect()
quit()
""");
fail();
}
catch (PythonError e) {
assertThat(e.stderr, containsString("'ghidra_trace_connect'"));
assertThat(e.stderr, containsString("'address'"));
}
}
@Test
public void testConnect() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
quit()
""".formatted(PREAMBLE, addr));
}
@Test
public void testDisconnect() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_disconnect()
quit()
""".formatted(PREAMBLE, addr));
}
@Test
public void testStartTraceDefaults() throws Exception {
// Default name and lcsp
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
assertEquals("x86:LE:64:default",
tb.trace.getBaseLanguage().getLanguageID().getIdAsString());
assertEquals("gcc",
tb.trace.getBaseCompilerSpec().getCompilerSpecID().getIdAsString());
}
}
@Test
public void testStartTraceDefaultNoFile() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_start()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject("/New Traces/drgn/noname")) {
assertThat(mdo.get(), instanceOf(Trace.class));
}
}
@Test
public void testStartTraceCustomize() throws Exception {
runThrowError(
addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create(start_trace=False)
util.set_convenience_variable('ghidra-language','Toy:BE:64:default')
util.set_convenience_variable('ghidra-compiler','default')
ghidra_trace_start('myToy')
quit()
"""
.formatted(PREAMBLE, addr));
DomainFile df = env.getProject().getProjectData().getFile("/New Traces/myToy");
assertNotNull(df);
try (ManagedDomainObject mdo = new ManagedDomainObject(df, false, false, monitor)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
assertEquals("Toy:BE:64:default",
tb.trace.getBaseLanguage().getLanguageID().getIdAsString());
assertEquals("default",
tb.trace.getBaseCompilerSpec().getCompilerSpecID().getIdAsString());
}
}
@Test
public void testStopTrace() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_stop()
quit()
""".formatted(PREAMBLE, addr));
DomainFile df =
env.getProject().getProjectData().getFile(MDO);
assertNotNull(df);
}
@Test
public void testInfo() throws Exception {
AtomicReference<String> refAddr = new AtomicReference<>();
String out = runThrowError(addr -> {
refAddr.set(addr);
return """
%s
print('---Import---')
ghidra_trace_info()
print('---BeforeConnect---')
ghidra_trace_connect('%s')
print('---Connect---')
ghidra_trace_info()
print('---Create---')
ghidra_trace_create()
print('---Start---')
ghidra_trace_info()
ghidra_trace_stop()
print('---Stop---')
ghidra_trace_info()
ghidra_trace_disconnect()
print('---Disconnect---')
ghidra_trace_info()
quit()
""".formatted(PREAMBLE, addr);
});
assertEquals("""
Not connected to Ghidra""",
extractOutSection(out, "---Import---"));
assertEquals("""
Connected to %s %s at %s
No trace""".formatted(
Application.getName(), Application.getApplicationVersion(), refAddr.get()),
extractOutSection(out, "---Connect---").replaceAll("\r", ""));
assertEquals("""
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""",
extractOutSection(out, "---Disconnect---"));
}
@Test
public void testLcsp() throws Exception {
String out = runThrowError(addr ->
"""
%s
ghidra_trace_connect('%s')
print('---Import---')
ghidra_trace_info_lcsp()
print('---Create---')
ghidra_trace_create()
print('---File---')
ghidra_trace_info_lcsp()
util.set_convenience_variable('ghidra-language','DATA:BE:64:default')
print('---Language---')
ghidra_trace_info_lcsp()
util.set_convenience_variable('ghidra-compiler','posStack')
print('---Compiler---')
ghidra_trace_info_lcsp()
quit()
""".formatted(PREAMBLE, addr));
assertEquals("""
Selected Ghidra language: x86:LE:64:default
Selected Ghidra compiler: gcc""",
extractOutSection(out, "---File---").replaceAll("\r", ""));
assertEquals("""
Using the DATA64 compiler map
Selected Ghidra language: DATA:BE:64:default
Selected Ghidra compiler: pointer64""",
extractOutSection(out, "---Language---").replaceAll("\r", ""));
assertEquals("""
Selected Ghidra language: DATA:BE:64:default
Selected Ghidra compiler: posStack""",
extractOutSection(out, "---Compiler---").replaceAll("\r", ""));
}
@Test
public void testSnapshot() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot')
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceSnapshot snapshot = Unique.assertOne(tb.trace.getTimeManager().getAllSnapshots());
assertEquals(0, snapshot.getKey());
assertEquals("Scripted snapshot", snapshot.getDescription());
}
}
@Test
public void testPutreg() throws Exception {
String count = IntStream.iterate(0, i -> i < 32, i -> i + 1)
.mapToObj(Integer::toString)
.collect(Collectors.joining(",", "{", "}"));
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot')
ghidra_trace_putreg()
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr, count));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
long snap = Unique.assertOne(tb.trace.getTimeManager().getAllSnapshots()).getKey();
List<TraceObjectValue> regVals = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0),
PathFilter.parse("Processes[].Threads[].Stack[].Registers"))
.map(p -> p.getLastEntry())
.toList();
TraceObjectValue tobj = regVals.get(0);
AddressSpace t1f0 = tb.trace.getBaseAddressFactory()
.getAddressSpace(tobj.getCanonicalPath().toString());
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(t1f0, false);
RegisterValue rip = regs.getValue(snap, tb.reg("rip"));
assertEquals("3a40cdf7ff7f0000", rip.getUnsignedValue().toString(16));
try (Transaction tx = tb.trace.openTransaction("Float80 unit")) {
TraceCodeSpace code = tb.trace.getCodeManager().getCodeSpace(t1f0, true);
code.definedData()
.create(Lifespan.nowOn(0), tb.reg("st0"), Float10DataType.dataType);
}
}
}
@Test
public void testDelreg() throws Exception {
String count = IntStream.iterate(0, i -> i < 32, i -> i + 1)
.mapToObj(Integer::toString)
.collect(Collectors.joining(",", "{", "}"));
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot')
ghidra_trace_putreg()
ghidra_trace_delreg()
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr, count));
// The spaces will be left over, but the values should be zeroed
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
long snap = Unique.assertOne(tb.trace.getTimeManager().getAllSnapshots()).getKey();
List<TraceObjectValue> regVals = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0),
PathFilter.parse("Processes[].Threads[].Stack[].Registers"))
.map(p -> p.getLastEntry())
.toList();
TraceObjectValue tobj = regVals.get(0);
AddressSpace t1f0 = tb.trace.getBaseAddressFactory()
.getAddressSpace(tobj.getCanonicalPath().toString());
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(t1f0, false);
RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
assertEquals("0", rax.getUnsignedValue().toString(16));
}
}
@Test
public void testCreateObj() throws Exception {
String out = runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_start()
ghidra_trace_txstart('Create Object')
print('---Id---')
ghidra_trace_create_obj('Test.Objects[1]')
print('---')
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject("/New Traces/drgn/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject object = tb.trace.getObjectManager()
.getObjectByCanonicalPath(KeyPath.parse("Test.Objects[1]"));
assertNotNull(object);
String created = extractOutSection(out, "---Id---");
long id = Long.parseLong(created.split("id=")[1].split(",")[0]);
assertEquals(object.getKey(), id);
}
}
@Test
public void testInsertObj() throws Exception {
String out = runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_start()
ghidra_trace_txstart('Create Object')
ghidra_trace_create_obj('Test.Objects[1]')
print('---Lifespan---')
ghidra_trace_insert_obj('Test.Objects[1]')
print('---')
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject("/New Traces/drgn/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject object = tb.trace.getObjectManager()
.getObjectByCanonicalPath(KeyPath.parse("Test.Objects[1]"));
assertNotNull(object);
Lifespan life = Unique.assertOne(object.getLife().spans());
assertEquals(Lifespan.nowOn(0), life);
assertEquals("Inserted object: lifespan=[0,+inf)",
extractOutSection(out, "---Lifespan---"));
}
}
@Test
public void testRemoveObj() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Create Object')
ghidra_trace_create_obj('Test.Objects[1]')
ghidra_trace_insert_obj('Test.Objects[1]')
ghidra_trace_set_snap(1)
ghidra_trace_remove_obj('Test.Objects[1]')
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject object = tb.trace.getObjectManager()
.getObjectByCanonicalPath(KeyPath.parse("Test.Objects[1]"));
assertNotNull(object);
Lifespan life = Unique.assertOne(object.getLife().spans());
assertEquals(Lifespan.at(0), life);
}
}
@SuppressWarnings("unchecked")
protected <T> T runTestSetValue(String extra, String drgnExpr, String gtype)
throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Create Object')
ghidra_trace_create_obj('Test.Objects[1]')
ghidra_trace_insert_obj('Test.Objects[1]')
%s
ghidra_trace_set_value('Test.Objects[1]', 'test', %s, '%s')
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr, extra, drgnExpr, gtype));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject object = tb.trace.getObjectManager()
.getObjectByCanonicalPath(KeyPath.parse("Test.Objects[1]"));
assertNotNull(object);
TraceObjectValue value = object.getValue(0, "test");
return value == null ? null : (T) value.getValue();
}
}
@Test
public void testSetValueNull() throws Exception {
assertNull(runTestSetValue("", "None", "VOID"));
}
@Test
public void testSetValueBool() throws Exception {
assertEquals(Boolean.TRUE, runTestSetValue("", "True", "BOOL"));
}
@Test
public void testSetValueByte() throws Exception {
assertEquals(Byte.valueOf((byte) 1), runTestSetValue("", "'1'", "BYTE"));
}
@Test
public void testSetValueChar() throws Exception {
assertEquals(Character.valueOf('A'), runTestSetValue("", "'A'", "CHAR"));
}
@Test
public void testSetValueShort() throws Exception {
assertEquals(Short.valueOf((short) 1), runTestSetValue("", "'1'", "SHORT"));
}
@Test
public void testSetValueInt() throws Exception {
assertEquals(Integer.valueOf(1), runTestSetValue("", "'1'", "INT"));
}
@Test
public void testSetValueLong() throws Exception {
assertEquals(Long.valueOf(1), runTestSetValue("", "'1'", "LONG"));
}
@Test
public void testSetValueString() throws Exception {
assertEquals("HelloWorld!", runTestSetValue("", "\'HelloWorld!\'", "STRING"));
}
@Test //- how do we input long strings in python
public void testSetValueStringWide() throws Exception {
assertEquals("HelloWorld!", runTestSetValue("", "u\'HelloWorld!\'", "STRING"));
}
@Test
public void testSetValueBoolArr() throws Exception {
assertArrayEquals(new boolean[] { true, false },
runTestSetValue("", "[True,False]", "BOOL_ARR"));
}
@Test
public void testSetValueByteArrUsingString() throws Exception {
assertArrayEquals(new byte[] { 'H', 1, 'W' },
runTestSetValue("", "'H\\1W'", "BYTE_ARR"));
}
@Test
public void testSetValueByteArrUsingArray() throws Exception {
assertArrayEquals(new byte[] { 'H', 0, 'W' },
runTestSetValue("", "['H',0,'W']", "BYTE_ARR"));
}
@Test
public void testSetValueCharArrUsingString() throws Exception {
assertArrayEquals(new char[] { 'H', 1, 'W' },
runTestSetValue("", "'H\\1W'", "CHAR_ARR"));
}
@Test
public void testSetValueCharArrUsingArray() throws Exception {
assertArrayEquals(new char[] { 'H', 0, 'W' },
runTestSetValue("", "['H',0,'W']", "CHAR_ARR"));
}
@Test
public void testSetValueShortArrUsingString() throws Exception {
assertArrayEquals(new short[] { 'H', 1, 'W' },
runTestSetValue("", "'H\\1W'", "SHORT_ARR"));
}
@Test
public void testSetValueShortArrUsingArray() throws Exception {
assertArrayEquals(new short[] { 'H', 0, 'W' },
runTestSetValue("", "['H',0,'W']", "SHORT_ARR"));
}
@Test
public void testSetValueIntArrayUsingMixedArray() throws Exception {
// Because explicit array type is chosen, we get null terminator
assertArrayEquals(new int[] { 'H', 0, 'W' },
runTestSetValue("", "['H',0,'W']", "INT_ARR"));
}
@Test
public void testSetValueIntArrUsingArray() throws Exception {
assertArrayEquals(new int[] { 1, 2, 3, 4 },
runTestSetValue("", "[1,2,3,4]", "INT_ARR"));
}
@Test
public void testSetValueLongArr() throws Exception {
assertArrayEquals(new long[] { 1, 2, 3, 4 },
runTestSetValue("", "[1,2,3,4]", "LONG_ARR"));
}
@Test
public void testSetValueStringArr() throws Exception {
assertArrayEquals(new String[] { "1", "A", "dead", "beef" },
runTestSetValue("", "['1','A','dead','beef']", "STRING_ARR"));
}
@Test
public void testSetValueAddress() throws Exception {
Address address = runTestSetValue("", "0xdeadbeef", "ADDRESS");
// Don't have the address factory to create expected address
assertEquals(0xdeadbeefL, address.getOffset());
assertEquals("ram", address.getAddressSpace().getName());
}
@Test
public void testSetValueObject() throws Exception {
TraceObject object = runTestSetValue("", "'Test.Objects[1]'", "OBJECT");
assertEquals("Test.Objects[1]", object.getCanonicalPath().toString());
}
@Test
public void testRetainValues() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Create Object')
ghidra_trace_create_obj('Test.Objects[1]')
ghidra_trace_insert_obj('Test.Objects[1]')
ghidra_trace_set_value('Test.Objects[1]', '[1]', '"A"', 'STRING')
ghidra_trace_set_value('Test.Objects[1]', '[2]', '"B"', 'STRING')
ghidra_trace_set_value('Test.Objects[1]', '[3]', '"C"', 'STRING')
ghidra_trace_set_snap(10)
ghidra_trace_retain_values('Test.Objects[1]', '[1] [3]')
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject object = tb.trace.getObjectManager()
.getObjectByCanonicalPath(KeyPath.parse("Test.Objects[1]"));
assertNotNull(object);
assertEquals(Map.ofEntries(
Map.entry("[1]", Lifespan.nowOn(0)),
Map.entry("[2]", Lifespan.span(0, 9)),
Map.entry("[3]", Lifespan.nowOn(0))),
object.getValues(Lifespan.ALL)
.stream()
.collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan())));
}
}
@Test
public void testGetObj() throws Exception {
String out = runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_start()
ghidra_trace_txstart('Create Object')
print('---Id---')
ghidra_trace_create_obj('Test.Objects[1]')
print('---')
ghidra_trace_txcommit()
print('---GetObject---')
ghidra_trace_get_obj('Test.Objects[1]')
print('---')
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject("/New Traces/drgn/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject object = tb.trace.getObjectManager()
.getObjectByCanonicalPath(KeyPath.parse("Test.Objects[1]"));
assertNotNull(object);
assertEquals("1\tTest.Objects[1]", extractOutSection(out, "---GetObject---"));
}
}
@Test
public void testGetValues() throws Exception {
String out = runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Create Object')
ghidra_trace_create_obj('Test.Objects[1]')
ghidra_trace_insert_obj('Test.Objects[1]')
ghidra_trace_set_value('Test.Objects[1]', 'vnull', None, 'VOID')
ghidra_trace_set_value('Test.Objects[1]', 'vbool', True, 'BOOL')
ghidra_trace_set_value('Test.Objects[1]', 'vbyte', '1', 'BYTE')
ghidra_trace_set_value('Test.Objects[1]', 'vchar', 'A', 'CHAR')
ghidra_trace_set_value('Test.Objects[1]', 'vshort', '2', 'SHORT')
ghidra_trace_set_value('Test.Objects[1]', 'vint', '3', 'INT')
ghidra_trace_set_value('Test.Objects[1]', 'vlong', '4', 'LONG')
ghidra_trace_set_value('Test.Objects[1]', 'vstring', 'Hello', 'STRING')
vboolarr = [True, False]
ghidra_trace_set_value('Test.Objects[1]', 'vboolarr', vboolarr, 'BOOL_ARR')
vbytearr = [1, 2, 3]
ghidra_trace_set_value('Test.Objects[1]', 'vbytearr', vbytearr, 'BYTE_ARR')
vchararr = 'Hello'
ghidra_trace_set_value('Test.Objects[1]', 'vchararr', vchararr, 'CHAR_ARR')
vshortarr = [1, 2, 3]
ghidra_trace_set_value('Test.Objects[1]', 'vshortarr', vshortarr, 'SHORT_ARR')
vintarr = [1, 2, 3]
ghidra_trace_set_value('Test.Objects[1]', 'vintarr', vintarr, 'INT_ARR')
vlongarr = [1, 2, 3]
ghidra_trace_set_value('Test.Objects[1]', 'vlongarr', vlongarr, 'LONG_ARR')
ghidra_trace_set_value('Test.Objects[1]', 'vaddr', 0xdeadbeef, 'ADDRESS')
ghidra_trace_set_value('Test.Objects[1]', 'vobj', 'Test.Objects[1]', 'OBJECT')
ghidra_trace_txcommit()
print('---GetValues---')
ghidra_trace_get_values('Test.Objects[1].')
print('---')
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
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
Test.Objects[1] vbytearr [0,+inf) b'\\x01\\x02\\x03' BYTE_ARR
Test.Objects[1] vchar [0,+inf) 'A' CHAR
Test.Objects[1] vchararr [0,+inf) 'Hello' CHAR_ARR
Test.Objects[1] vint [0,+inf) 3 INT
Test.Objects[1] vintarr [0,+inf) [1, 2, 3] INT_ARR
Test.Objects[1] vlong [0,+inf) 4 LONG
Test.Objects[1] vlongarr [0,+inf) [1, 2, 3] LONG_ARR
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""",
extractOutSection(out, "---GetValues---").replaceAll("\r", ""));
}
}
@Test
public void testGetValuesRng() throws Exception {
String out = runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Create Object')
ghidra_trace_create_obj('Test.Objects[1]')
ghidra_trace_insert_obj('Test.Objects[1]')
ghidra_trace_set_value('Test.Objects[1]', 'vaddr', 0xdeadbeef, 'ADDRESS')
ghidra_trace_txcommit()
print('---GetValues---')
ghidra_trace_get_values_rng(0xdeadbeef, 10)
print('---')
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
assertEquals("""
Parent Key Span Value Type
Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS""",
extractOutSection(out, "---GetValues---").replaceAll("\r", ""));
}
}
@Test
public void testActivateObject() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
#set language c++
ghidra_trace_txstart('Create Object')
ghidra_trace_create_obj('Test.Objects[1]')
ghidra_trace_insert_obj('Test.Objects[1]')
ghidra_trace_txcommit()
ghidra_trace_activate('Test.Objects[1]')
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
assertSame(mdo.get(), traceManager.getCurrentTrace());
assertEquals("Test.Objects[1]",
traceManager.getCurrentObject().getCanonicalPath().toString());
}
}
@Test
public void testDisassemble() throws Exception {
String out = runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Tx')
pc = get_pc()
ghidra_trace_putmem(pc, 16)
print('---Disassemble---')
ghidra_trace_disassemble(pc)
print('---')
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
// Not concerned about specifics, so long as disassembly occurs
long total = 0;
for (CodeUnit cu : tb.trace.getCodeManager().definedUnits().get(0, true)) {
total += cu.getLength();
}
String extract = extractOutSection(out, "---Disassemble---");
String[] split = extract.split("\r\n");
// NB: core.12137 has no memory
//assertEquals("Disassembled %d bytes".formatted(total),
// split[0]);
assertEquals(0, total);
assertEquals("", split[0]);
}
}
@Test
public void testPutProcesses() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_start()
ghidra_trace_txstart('Tx')
ghidra_trace_put_processes()
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject("/New Traces/drgn/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
// Would be nice to control / validate the specifics
Collection<TraceObject> processes = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0), PathFilter.parse("Processes[]"))
.map(p -> p.getDestination(null))
.toList();
assertEquals(0, processes.size());
}
}
@Test
public void testPutEnvironment() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Tx')
ghidra_trace_put_environment()
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
// Assumes LLDB on Linux amd64
TraceObject envobj =
Objects.requireNonNull(tb.objAny("Processes[].Environment", Lifespan.at(0)));
assertEquals("drgn", envobj.getValue(0, "_debugger").getValue());
assertEquals("X86_64", envobj.getValue(0, "_arch").getValue());
assertEquals("Language.C", envobj.getValue(0, "_os").getValue());
assertEquals("little", envobj.getValue(0, "_endian").getValue());
}
}
@Test
public void testPutRegions() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Tx')
ghidra_trace_put_regions()
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
// Would be nice to control / validate the specifics
Collection<? extends TraceMemoryRegion> all =
tb.trace.getMemoryManager().getAllRegions();
assertThat(all.size(), greaterThan(2));
}
}
@Test
public void testPutModules() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Tx')
ghidra_trace_put_modules()
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
// Would be nice to control / validate the specifics
Collection<? extends TraceModule> all = tb.trace.getModuleManager().getAllModules();
TraceModule modBash =
Unique.assertOne(all.stream().filter(m -> m.getName().contains("helloWorld")));
assertNotEquals(tb.addr(0), Objects.requireNonNull(modBash.getBase()));
}
}
@Test
public void testPutThreads() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Tx')
ghidra_trace_put_threads()
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
// Would be nice to control / validate the specifics
Collection<? extends TraceThread> threads = tb.trace.getThreadManager().getAllThreads();
assertEquals(1, threads.size());
}
}
@Test
public void testPutFrames() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
ghidra_trace_create()
ghidra_trace_txstart('Tx')
ghidra_trace_put_frames()
ghidra_trace_txcommit()
quit()
""".formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
// Would be nice to control / validate the specifics
List<TraceObject> stack = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0),
PathFilter.parse("Processes[0].Threads[].Stack[]"))
.map(p -> p.getDestination(null))
.toList();
assertEquals(7, stack.size());
}
}
@Test
public void testMinimal() throws Exception {
runThrowError(addr -> """
%s
ghidra_trace_connect('%s')
print('FINISHED')
quit()
""".formatted(PREAMBLE, addr));
}
}

View file

@ -0,0 +1,286 @@
/* ###
* IP: GHIDRA
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.drgn.rmi;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.*;
import org.junit.Test;
import generic.Unique;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.debug.api.tracermi.RemoteMethod;
import ghidra.framework.Application;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.path.PathFilter;
import ghidra.trace.model.target.path.PathPattern;
public class DrgnMethodsTest extends AbstractDrgnTraceRmiTest {
@Test
public void testExecuteCapture() throws Exception {
try (PythonAndConnection conn = startAndConnectDrgn()) {
RemoteMethod execute = conn.getMethod("execute");
assertEquals(false, execute.parameters().get("to_string").getDefaultValue());
assertEquals("11\n",
execute.invoke(Map.of(
"cmd", "print(3+4*2)",
"to_string", true)));
}
}
@Test
public void testExecute() throws Exception {
try (PythonAndConnection conn = startAndConnectDrgn()) {
start(conn, null);
}
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
// Just confirm it's present
}
}
@Test
public void testRefreshProcesses() throws Exception {
try (PythonAndConnection conn = startAndConnectDrgn()) {
start(conn, null);
txCreate(conn, "Processes");
RemoteMethod attachCore = conn.getMethod("attach_core");
RemoteMethod refreshProcesses = conn.getMethod("refresh_processes");
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject processes = Objects.requireNonNull(tb.objAny0("Processes"));
refreshProcesses.invoke(Map.of("node", processes));
List<TraceObject> list = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(getMaxSnap()), PathFilter.parse("Processes[]"))
.map(p -> p.getDestination(null))
.toList();
assertEquals(1, list.size());
ResourceFile rf = Application.getModuleDataFile("TestResources", CORE);
attachCore.invoke(Map.of("processes", processes, "core", rf.getAbsolutePath()));
refreshProcesses.invoke(Map.of("node", processes));
list = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(getMaxSnap()), PathFilter.parse("Processes[]"))
.map(p -> p.getDestination(null))
.toList();
assertEquals(2, list.size());
}
}
}
@Test
public void testRefreshEnvironment() throws Exception {
try (PythonAndConnection conn = startAndConnectDrgn()) {
String path = "Processes[].Environment";
start(conn, null);
txPut(conn, "all");
RemoteMethod refreshEnvironment = conn.getMethod("refresh_environment");
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject envobj = Objects.requireNonNull(tb.objAny0(path));
refreshEnvironment.invoke(Map.of("node", envobj));
assertEquals("drgn", envobj.getValue(0, "_debugger").getValue());
assertEquals("X86_64", envobj.getValue(0, "_arch").getValue());
assertEquals("Language.C", envobj.getValue(0, "_os").getValue());
assertEquals("little", envobj.getValue(0, "_endian").getValue());
}
}
}
@Test
public void testRefreshThreads() throws Exception {
try (PythonAndConnection conn = startAndConnectDrgn()) {
String path = "Processes[].Threads";
start(conn, null);
txCreate(conn, path);
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject threads = Objects.requireNonNull(tb.objAny0(path));
refreshThreads.invoke(Map.of("node", threads));
int listSize = tb.trace.getThreadManager().getAllThreads().size();
assertEquals(1, listSize);
}
}
}
@Test
public void testRefreshStack() throws Exception {
try (PythonAndConnection conn = startAndConnectDrgn()) {
String path = "Processes[].Threads[].Stack";
start(conn, null);
txPut(conn, "processes");
RemoteMethod refreshStack = conn.getMethod("refresh_stack");
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
txPut(conn, "frames");
TraceObject stack = Objects.requireNonNull(tb.objAny0(path));
refreshStack.invoke(Map.of("node", stack));
// Would be nice to control / validate the specifics
List<TraceObject> list = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0),
PathFilter.parse("Processes[].Threads[].Stack[]"))
.map(p -> p.getDestination(null))
.toList();
assertEquals(7, list.size());
}
}
}
@Test
public void testRefreshRegisters() throws Exception {
try (PythonAndConnection conn = startAndConnectDrgn()) {
String path = "Processes[].Threads[].Stack[].Registers";
start(conn, null);
conn.execute("ghidra_trace_txstart('Tx')");
conn.execute("ghidra_trace_putreg()");
conn.execute("ghidra_trace_delreg()");
conn.execute("ghidra_trace_txcommit()");
RemoteMethod refreshRegisters = conn.getMethod("refresh_registers");
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
refreshRegisters.invoke(Map.of("node", registers));
long snap = 0;
AddressSpace t1f0 = tb.trace.getBaseAddressFactory()
.getAddressSpace(registers.getCanonicalPath().toString());
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(t1f0, false);
RegisterValue rip = regs.getValue(snap, tb.reg("rip"));
assertEquals("3a40cdf7ff7f0000", rip.getUnsignedValue().toString(16));
}
}
}
@Test
public void testRefreshMappings() throws Exception {
try (PythonAndConnection conn = startAndConnectDrgn()) {
String path = "Processes[].Memory";
start(conn, null);
txCreate(conn, path);
RemoteMethod refreshMappings = conn.getMethod("refresh_mappings");
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject memory = Objects.requireNonNull(tb.objAny0(path));
refreshMappings.invoke(Map.of("node", memory));
// Would be nice to control / validate the specifics
Collection<? extends TraceMemoryRegion> all =
tb.trace.getMemoryManager().getAllRegions();
assertThat(all.size(), greaterThan(2));
}
}
}
@Test
public void testRefreshModules() throws Exception {
try (PythonAndConnection conn = startAndConnectDrgn()) {
String path = "Processes[].Modules";
start(conn, null);
txCreate(conn, path);
RemoteMethod refreshModules = conn.getMethod("refresh_modules");
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject modules = Objects.requireNonNull(tb.objAny0(path));
refreshModules.invoke(Map.of("node", modules));
// Would be nice to control / validate the specifics
Collection<? extends TraceModule> all = tb.trace.getModuleManager().getAllModules();
TraceModule mod =
Unique.assertOne(all.stream().filter(m -> m.getName().contains("helloWorld")));
assertNotEquals(tb.addr(0), Objects.requireNonNull(mod.getBase()));
}
}
}
@Test
public void testActivateThread() throws Exception {
try (PythonAndConnection conn = startAndConnectDrgn()) {
start(conn, null);
txPut(conn, "processes");
RemoteMethod activateThread = conn.getMethod("activate_thread");
try (ManagedDomainObject mdo = openDomainObject(MDO)) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
txPut(conn, "threads");
PathPattern pattern =
PathFilter.parse("Processes[].Threads[]").getSingletonPattern();
List<TraceObject> list = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0), pattern)
.map(p -> p.getDestination(null))
.toList();
assertEquals(1, list.size());
for (TraceObject t : list) {
activateThread.invoke(Map.of("thread", t));
String out = conn.executeCapture("print(util.selected_thread())").strip();
List<String> indices = pattern.matchKeys(t.getCanonicalPath(), true);
assertEquals("%s".formatted(indices.get(1)), out);
}
}
}
}
private void start(PythonAndConnection conn, String obj) {
conn.execute("from ghidradrgn.commands import *");
conn.execute("ghidra_trace_create()");
}
private void txPut(PythonAndConnection conn, String obj) {
conn.execute("ghidra_trace_txstart('Tx')");
conn.execute("ghidra_trace_put_" + obj + "()");
conn.execute("ghidra_trace_txcommit()");
}
private void txCreate(PythonAndConnection conn, String path) {
conn.execute("ghidra_trace_txstart('Fake')");
conn.execute("ghidra_trace_create_obj('%s')".formatted(path));
conn.execute("ghidra_trace_txcommit()");
}
}