GP-3754: post-review review

GP-3754: last pass
GP-3754: most review issues address (ymm0/testSave not); tests pass
GP-3754: review - eval/exec
GP-3754: moved registers to thread
GP-3754: review pass 7 - register w/o banks
GP-3754: review pass 6 - basic clean-up items
GP-3754: pass 5 - tests all pass (minus commented-out ones)
GP-3754: review pass 4(?)
GP-3754: review pass 3
GP-3754: review pass 2
GP-3754: review pass 1
GP-3754: ref Pybag
GP-3754: clean-up
GP-3754: testing post-changes in hooks
GP-3754: fix for env
GP-3754: convenience method for get_debugger
GP-3754: tests all running successfully
GP-3754: hook tests running
GP-3754: hook tests: memory changed not wokring, bpt modified does nothing
GP-3754: lot of work to get testExited to pass
GP-3754: start on hooks tests; mods to methods not-runnable while running
GP-3754: methods tests working
GP-3754: bulk of bpt tests running
GP-3754: whittling down the list
GP-3754: more passing method tests
GP-3754: at least a few method tests working (don't run batch)
GP-3754: cmd tests pass w/o closing stdin
GP-3754: command test basically running
GP-3754: 3 cmd tests failing; 3 commented out
GP-3754: ghidra_trace_set_values uses broken except for in testGetValues
GP-3754: whittling down the command tests
GP-3754: tests esp. SetValue
GP-3754: testMinimal works but cannot 'execute'
GP-3754: JUnits still don't run
GP-3754: breakpoints in the list
GP-3754: continued work on hooks; bpts not registering as bpts
GP-3754: templates for hooks
GP-3754: tests, first viable hook (module_load)'
GP-3754: first pass at methods
GP-3754: memory/regs working
GP-3754: most of the puts done
GP-3754: modules/regions working
GP-3754: process/threads working
GP-3754: added to manifest
GP-3754: minimal shell: arch faked out / thru activate w/o push
This commit is contained in:
d-millar 2023-09-12 11:46:10 -04:00
parent f64c38ef7f
commit abbc18f927
20 changed files with 6266 additions and 2 deletions

View file

@ -0,0 +1,424 @@
/* ###
* 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.dbgeng.rmi;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.Before;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteAsyncResult;
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiAcceptor;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.app.services.TraceRmiService;
import ghidra.dbg.testutil.DummyProc;
import ghidra.framework.Application;
import ghidra.framework.OperatingSystem;
import ghidra.framework.TestApplicationUtils;
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.PluginDescription;
import ghidra.framework.plugintool.util.PluginException;
import ghidra.framework.plugintool.util.PluginPackage;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDebuggerGUITest {
/**
* Some features have to be disabled to avoid permissions issues in the test container. Namely,
* don't try to disable ASLR.
*/
public static final String PREAMBLE = """
from ghidradbg.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 = 300;
protected static final int QUIT_TIMEOUT_MS = 1000;
protected TraceRmiService traceRmi;
private Path pythonPath;
private Path outFile;
private Path errFile;
//@BeforeClass
public static void setupPython() throws Throwable {
new ProcessBuilder("gradle", "Debugger-agent-dbgeng:assemblePyPackage")
.directory(TestApplicationUtils.getInstallationDirectory())
.inheritIO()
.start()
.waitFor();
}
protected void setPythonPath(ProcessBuilder pb) throws IOException {
String sep =
OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS ? ";" : ":";
String rmiPyPkg = Application.getModuleSubDirectory("Debugger-rmi-trace",
"build/pypkg/src").getAbsolutePath();
String gdbPyPkg = Application.getModuleSubDirectory("Debugger-agent-dbgeng",
"build/pypkg/src").getAbsolutePath();
String add = rmiPyPkg + sep + gdbPyPkg;
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("python3"));
}
catch (RuntimeException e) {
pythonPath = Paths.get(DummyProc.which("python"));
}
outFile = Files.createTempFile("pydbgout", null);
errFile = Files.createTempFile("pydbgerr", 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("Error") || (0 != exitCode && 1 != exitCode && 143 != exitCode)) {
throw new PythonError(exitCode, stdout, stderr);
}
return stdout;
}
}
protected record ExecInPython(Process python, CompletableFuture<PythonResult> future) {
}
@SuppressWarnings("resource") // Do not close stdin
protected ExecInPython execInPython(String script) throws IOException {
ProcessBuilder pb = new ProcessBuilder(pythonPath.toString(), "-i");
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();
OutputStream stdin = pyproc.getOutputStream();
stdin.write(script.getBytes());
stdin.flush();
//stdin.close();
return new ExecInPython(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 = execInPython(script).future;
return result.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
}
protected record PythonAndHandler(ExecInPython exec, TraceRmiHandler handler)
implements AutoCloseable {
protected RemoteMethod getMethod(String name) {
return Objects.requireNonNull(handler.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 expr) {
RemoteMethod execute = getMethod("evaluate");
return (String) execute.invoke(Map.of("expr", expr));
}
@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(handler.isClosed()));
}
finally {
exec.python.destroyForcibly();
}
}
}
protected PythonAndHandler startAndConnectPython(Function<String, String> scriptSupplier)
throws Exception {
TraceRmiAcceptor acceptor = traceRmi.acceptOne(null);
ExecInPython exec =
execInPython(scriptSupplier.apply(sockToStringForPython(acceptor.getAddress())));
acceptor.setTimeout(CONNECT_TIMEOUT_MS);
try {
TraceRmiHandler handler = acceptor.accept();
return new PythonAndHandler(exec, handler);
}
catch (SocketTimeoutException e) {
exec.python.destroyForcibly();
exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
throw e;
}
}
protected PythonAndHandler startAndConnectPython() throws Exception {
return startAndConnectPython(addr -> """
%s
ghidra_trace_connect('%s')
""".formatted(PREAMBLE, addr));
}
@SuppressWarnings("resource")
protected String runThrowError(Function<String, String> scriptSupplier)
throws Exception {
PythonAndHandler conn = startAndConnectPython(scriptSupplier);
PythonResult r = conn.exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
String stdout = r.handle();
waitForPass(() -> assertTrue(conn.handler.isClosed()));
return stdout;
}
protected void waitStopped() {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
waitForPass(() -> assertEquals("STOPPED", tb.objValue(proc, 0, "_state")));
waitTxDone();
}
protected void waitRunning() {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
waitForPass(() -> assertEquals("RUNNING", tb.objValue(proc, 0, "_state")));
waitTxDone();
}
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();
}
record MemDump(long address, byte[] data) {
}
protected MemDump parseHexDump(String dump) throws IOException {
// First, get the address. Assume contiguous, so only need top line.
List<String> lines = List.of(dump.split("\n"));
List<String> toksLine0 = List.of(lines.get(0).split("\\s+"));
String addrstr = toksLine0.get(0);
if (addrstr.contains(":")) {
addrstr = addrstr.substring(0, addrstr.indexOf(":"));
}
long address = Long.parseLong(addrstr, 16);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
for (String l : lines) {
List<String> parts = List.of(l.split(":"));
assertEquals(2, parts.size());
String hex = parts.get(1).substring(0, 48);
byte[] lineData = NumericUtilities.convertStringToBytes(hex);
assertNotNull("Converted to null: " + hex, parts.get(1));
buf.write(lineData);
}
return new MemDump(address, buf.toByteArray());
}
record RegDump() {
}
protected RegDump parseRegDump(String dump) {
return new RegDump();
}
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 void assertBreakLoc(TraceObjectValue locVal, String key, Address addr, int len,
Set<TraceBreakpointKind> kinds, String expression) throws Exception {
assertEquals(key, locVal.getEntryKey());
TraceObject loc = locVal.getChild();
TraceObject spec = loc;
assertEquals(new AddressRangeImpl(addr, len), loc.getValue(0, "_range").getValue());
assertEquals(TraceBreakpointKindSet.encode(kinds), spec.getValue(0, "_kinds").getValue());
assertTrue(spec.getValue(0, "_expression").getValue().toString().contains(expression));
}
protected void assertWatchLoc(TraceObjectValue locVal, String key, Address addr, int len,
Set<TraceBreakpointKind> kinds, String expression) throws Exception {
assertEquals(key, locVal.getEntryKey());
TraceObject loc = locVal.getChild();
assertEquals(new AddressRangeImpl(addr, len), loc.getValue(0, "_range").getValue());
assertEquals(TraceBreakpointKindSet.encode(kinds), loc.getValue(0, "_kinds").getValue());
}
protected void waitTxDone() {
waitFor(() -> tb.trace.getCurrentTransactionInfo() == null);
}
public static void waitForPass(Runnable runnable, long timeoutMs, long retryDelayMs) {
long start = System.currentTimeMillis();
AssertionError lastError = null;
while (System.currentTimeMillis() - start < timeoutMs) {
try {
runnable.run();
return;
}
catch (AssertionError e) {
lastError = e;
}
try {
Thread.sleep(retryDelayMs);
}
catch (InterruptedException e) {
// Retry sooner, I guess.
}
}
if (lastError == null) {
throw new AssertionError("Timed out before first try?");
}
throw lastError;
}
}

View file

@ -0,0 +1,380 @@
/* ###
* 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.dbgeng.rmi;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Objects;
import org.junit.Ignore;
import org.junit.Test;
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.dbg.testutil.DummyProc;
import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathPredicates;
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.target.TraceObject;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
private static final long RUN_TIMEOUT_MS = 20000;
private static final long RETRY_MS = 500;
record PythonAndTrace(PythonAndHandler conn, ManagedDomainObject mdo) implements AutoCloseable {
public void execute(String cmd) {
conn.execute(cmd);
}
public String executeCapture(String cmd) {
return conn.executeCapture(cmd);
}
@Override
public void close() throws Exception {
conn.close();
mdo.close();
}
}
@SuppressWarnings("resource")
protected PythonAndTrace startAndSyncPython(String exec) throws Exception {
PythonAndHandler conn = startAndConnectPython();
try {
ManagedDomainObject mdo;
conn.execute("from ghidradbg.commands import *");
conn.execute(
"util.set_convenience_variable('ghidra-language', 'x86:LE:64:default')");
if (exec != null) {
start(conn, exec);
mdo = waitDomainObject("/New Traces/pydbg/"+exec);
}
else {
conn.execute("ghidra_trace_start()");
mdo = waitDomainObject("/New Traces/pydbg/noname");
}
tb = new ToyDBTraceBuilder((Trace) mdo.get());
return new PythonAndTrace(conn, mdo);
}
catch (Exception e) {
conn.close();
throw e;
}
}
protected long lastSnap(PythonAndTrace conn) {
return conn.conn.handler().getLastSnapshot(tb.trace);
}
@Test // The 10s wait makes this a pretty expensive test
public void testOnNewThread() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
conn.execute("from ghidradbg.commands import *");
txPut(conn, "processes");
waitForPass(() -> {
TraceObject proc = tb.objAny("Processes[]");
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()),
RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("dbg().go(10)");
waitForPass(() -> assertTrue(tb.objValues(lastSnap(conn), "Processes[].Threads[]").size() > 4),
RUN_TIMEOUT_MS, RETRY_MS);
}
}
@Test
public void testOnThreadSelected() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
txPut(conn, "processes");
waitForPass(() -> {
TraceObject inf = tb.objAny("Processes[]");
assertNotNull(inf);
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
}, RUN_TIMEOUT_MS, RETRY_MS);
txPut(conn, "threads");
waitForPass(() -> assertEquals(4,
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
RUN_TIMEOUT_MS, RETRY_MS);
// Now the real test
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));
}, RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("util.select_thread(2)");
waitForPass(() -> {
String tnum = conn.executeCapture("util.selected_thread()");
assertTrue(tnum.contains("2"));
String threadIndex = threadIndex(traceManager.getCurrentObject());
assertTrue(tnum.contains(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 threadIndex = threadIndex(traceManager.getCurrentObject());
assertTrue(tnum.contains(threadIndex));
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
protected String getIndex(TraceObject object, String pattern, int n) {
if (object == null) {
return null;
}
PathPattern pat = PathPredicates.parse(pattern).getSingletonPattern();
// if (pat.countWildcards() != 1) {
// throw new IllegalArgumentException("Exactly one wildcard required");
// }
List<String> path = object.getCanonicalPath().getKeyList();
if (path.size() < pat.asPath().size()) {
return null;
}
List<String> matched = pat.matchKeys(path.subList(0, pat.asPath().size()));
if (matched == null) {
return null;
}
if (matched.size() <= n) {
return null;
}
return matched.get(n);
}
protected String threadIndex(TraceObject object) {
return getIndex(object, "Processes[].Threads[]", 1);
}
protected String frameIndex(TraceObject object) {
return getIndex(object, "Processes[].Threads[].Stack[]", 2);
}
@Test
@Ignore
public void testOnSyscallMemory() throws Exception {
// TODO: Need a specimen
// 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
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')");
waitForPass(() -> {
ByteBuffer buf = ByteBuffer.allocate(10);
tb.trace.getMemoryManager().getBytes(lastSnap(conn), tb.addr(address), buf);
assertEquals(0x7f, buf.get(0));
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
@Test
public void testOnRegisterChanged() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
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()");
String path = "Processes[].Threads[].Registers";
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
AddressSpace space = tb.trace.getBaseAddressFactory()
.getAddressSpace(registers.getCanonicalPath().toString());
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(space, false);
waitForPass(() -> assertEquals("1234",
regs.getValue(lastSnap(conn), tb.reg("RAX")).getUnsignedValue().toString(16)));
}
}
@Test
public void testOnCont() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
txPut(conn, "processes");
conn.execute("util.get_debugger()._control.SetExecutionStatus(DbgEng.DEBUG_STATUS_GO)");
waitRunning();
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
waitForPass(() -> {
assertEquals("RUNNING", tb.objValue(proc, lastSnap(conn), "_state"));
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
@Test
public void testOnStop() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
txPut(conn, "processes");
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
waitForPass(() -> {
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
@Test
public void testOnExited() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("netstat.exe")) {
txPut(conn, "processes");
waitStopped();
conn.execute("util.get_debugger().go()");
waitForPass(() -> {
TraceSnapshot snapshot =
tb.trace.getTimeManager().getSnapshot(lastSnap(conn), false);
assertNotNull(snapshot);
assertEquals("Exited with code 0", snapshot.getDescription());
TraceObject proc = tb.objAny("Processes[]");
assertNotNull(proc);
Object val = tb.objValue(proc, lastSnap(conn), "_exit_code");
assertThat(val, instanceOf(Number.class));
assertEquals(0, ((Number) val).longValue());
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
@Test
public void testOnBreakpointCreated() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
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("dbg.stepi()");
waitForPass(() -> {
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
assertEquals(1, brks.size());
return (TraceObject) brks.get(0);
});
}
}
@Test
public void testOnBreakpointModified() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
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("dbg.stepi()");
TraceObject brk = waitForPass(() -> {
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
assertEquals(1, brks.size());
return (TraceObject) brks.get(0);
});
assertEquals(true, tb.objValue(brk, lastSnap(conn), "Enabled"));
conn.execute("dbg.bd(0)");
conn.execute("dbg.stepi()");
assertEquals(false, tb.objValue(brk, lastSnap(conn), "Enabled"));
/* Not currently enabled
assertEquals("", tb.objValue(brk, lastSnap(conn), "Command"));
conn.execute("dbg.bp(expr=pc, windbgcmd='bl')");
conn.execute("dbg.stepi()");
assertEquals("bl", tb.objValue(brk, lastSnap(conn), "Command"));
*/
}
}
@Test
public void testOnBreakpointDeleted() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
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("dbg.stepi()");
TraceObject brk = waitForPass(() -> {
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
assertEquals(1, brks.size());
return (TraceObject) brks.get(0);
});
conn.execute("dbg.cmd('bc %s')".formatted(brk.getCanonicalPath().index()));
conn.execute("dbg.stepi()");
waitForPass(
() -> assertEquals(0,
tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()));
}
}
private void start(PythonAndHandler conn, String obj) {
conn.execute("from ghidradbg.commands import *");
if (obj != null)
conn.execute("ghidra_trace_create('"+obj+"')");
else
conn.execute("ghidra_trace_create()");
conn.execute("ghidra_trace_sync_enable()");
}
private void txPut(PythonAndTrace conn, String obj) {
conn.execute("ghidra_trace_txstart('Tx" + obj + "')");
conn.execute("ghidra_trace_put_" + obj +"()");
conn.execute("ghidra_trace_txcommit()");
}
private long getAddressAtOffset(PythonAndTrace conn, int offset) {
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+"+offset+")";
String ret = conn.executeCapture(inst);
String[] split = ret.split("\\s+"); // get target
return Long.decode(split[1]);
}
}

View file

@ -0,0 +1,959 @@
/* ###
* 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.dbgeng.rmi;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.*;
import java.util.*;
import org.junit.Test;
import generic.Unique;
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod;
import ghidra.app.plugin.core.debug.service.rmi.trace.ValueDecoder;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.dbg.testutil.DummyProc;
import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathPredicates;
import ghidra.program.model.address.*;
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.breakpoint.TraceBreakpointKind;
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;
public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
@Test
public void testEvaluate() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
RemoteMethod evaluate = conn.getMethod("evaluate");
assertEquals("11",
evaluate.invoke(Map.of("expr", "3+4*2")));
}
}
@Test
public void testExecuteCapture() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
RemoteMethod execute = conn.getMethod("execute");
assertEquals(false,
execute.parameters().get("to_string").defaultValue().get(ValueDecoder.DEFAULT));
assertEquals("11\n",
execute.invoke(Map.of("cmd", "print(3+4*2)", "to_string", true)));
}
}
@Test
public void testExecute() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
conn.execute("ghidra_trace_kill()");
}
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
// Just confirm it's present
}
}
@Test
public void testRefreshAvailable() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, null);
txCreate(conn, "Available");
RemoteMethod refreshAvailable = conn.getMethod("refresh_available");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject available = Objects.requireNonNull(tb.objAny("Available"));
refreshAvailable.invoke(Map.of("node", available));
// Would be nice to control / validate the specifics
List<TraceObject> list = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0), PathPredicates.parse("Available[]"))
.map(p -> p.getDestination(null))
.toList();
assertThat(list.size(), greaterThan(2));
}
}
}
@Test
public void testRefreshBreakpoints() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod refreshBreakpoints = conn.getMethod("refresh_breakpoints");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
conn.execute("dbg = util.get_debugger()");
conn.execute("pc = dbg.reg.get_pc()");
conn.execute("dbg.bp(expr=pc)");
conn.execute("dbg.ba(expr=pc+4)");
txPut(conn, "breakpoints");
TraceObject breakpoints = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
refreshBreakpoints.invoke(Map.of("node", breakpoints));
List<TraceObjectValue> procBreakLocVals = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0),
PathPredicates.parse("Processes[].Breakpoints[]"))
.map(p -> p.getLastEntry())
.toList();
assertEquals(2, procBreakLocVals.size());
AddressRange rangeMain =
procBreakLocVals.get(0).getChild().getValue(0, "_range").castValue();
Address main = rangeMain.getMinAddress();
assertBreakLoc(procBreakLocVals.get(0), "[0]", main, 1,
Set.of(TraceBreakpointKind.SW_EXECUTE),
"ntdll!LdrInit");
assertBreakLoc(procBreakLocVals.get(1), "[1]", main.add(4), 1,
Set.of(TraceBreakpointKind.HW_EXECUTE),
"ntdll!LdrInit");
}
}
}
@Test
public void testRefreshBreakpoints2() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "all");
RemoteMethod refreshProcWatchpoints = conn.getMethod("refresh_breakpoints");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
conn.execute("dbg = util.get_debugger()");
conn.execute("pc = dbg.reg.get_pc()");
conn.execute("dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)");
conn.execute("dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)");
conn.execute("dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)");
TraceObject locations =
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
refreshProcWatchpoints.invoke(Map.of("node", locations));
List<TraceObjectValue> procBreakVals = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0),
PathPredicates.parse("Processes[].Breakpoints[]"))
.map(p -> p.getLastEntry())
.toList();
assertEquals(3, procBreakVals.size());
AddressRange rangeMain0 =
procBreakVals.get(0).getChild().getValue(0, "_range").castValue();
Address main0 = rangeMain0.getMinAddress();
AddressRange rangeMain1 =
procBreakVals.get(1).getChild().getValue(0, "_range").castValue();
Address main1 = rangeMain1.getMinAddress();
AddressRange rangeMain2 =
procBreakVals.get(2).getChild().getValue(0, "_range").castValue();
Address main2 = rangeMain2.getMinAddress();
assertWatchLoc(procBreakVals.get(0), "[0]", main0, (int) rangeMain0.getLength(),
Set.of(TraceBreakpointKind.HW_EXECUTE),
"main");
assertWatchLoc(procBreakVals.get(1), "[1]", main1, (int) rangeMain1.getLength(),
Set.of(TraceBreakpointKind.WRITE),
"main+4");
assertWatchLoc(procBreakVals.get(2), "[2]", main2, (int) rangeMain1.getLength(),
Set.of(TraceBreakpointKind.READ),
"main+8");
}
}
}
@Test
public void testRefreshProcesses() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, null);
txCreate(conn, "Processes");
txCreate(conn, "Processes[1]");
RemoteMethod refreshProcesses = conn.getMethod("refresh_processes");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject processes = Objects.requireNonNull(tb.objAny("Processes"));
refreshProcesses.invoke(Map.of("node", processes));
// Would be nice to control / validate the specifics
List<TraceObject> list = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0), PathPredicates.parse("Processes[]"))
.map(p -> p.getDestination(null))
.toList();
assertEquals(1, list.size());
}
}
}
@Test
public void testRefreshEnvironment() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
String path = "Processes[].Environment";
start(conn, "notepad.exe");
txPut(conn, "all");
RemoteMethod refreshEnvironment = conn.getMethod("refresh_environment");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject env = Objects.requireNonNull(tb.objAny(path));
refreshEnvironment.invoke(Map.of("node", env));
// Assumes pydbg on Windows amd64
assertEquals("pydbg", env.getValue(0, "_debugger").getValue());
assertEquals("x86_64", env.getValue(0, "_arch").getValue());
assertEquals("windows", env.getValue(0, "_os").getValue());
assertEquals("little", env.getValue(0, "_endian").getValue());
}
}
}
@Test
public void testRefreshThreads() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
String path = "Processes[].Threads";
start(conn, "notepad.exe");
txCreate(conn, path);
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject threads = Objects.requireNonNull(tb.objAny(path));
refreshThreads.invoke(Map.of("node", threads));
// Would be nice to control / validate the specifics
int listSize = tb.trace.getThreadManager().getAllThreads().size();
assertEquals(4, listSize);
}
}
}
@Test
public void testRefreshStack() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
String path = "Processes[].Threads[].Stack";
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod refreshStack = conn.getMethod("refresh_stack");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
txPut(conn, "frames");
TraceObject stack = Objects.requireNonNull(tb.objAny(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),
PathPredicates.parse("Processes[].Threads[].Stack[]"))
.map(p -> p.getDestination(null))
.toList();
assertTrue(list.size() > 1);
}
}
}
@Test
public void testRefreshRegisters() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
String path = "Processes[].Threads[].Registers";
start(conn, "notepad.exe");
conn.execute("ghidra_trace_txstart('Tx')");
conn.execute("ghidra_trace_putreg()");
conn.execute("ghidra_trace_txcommit()");
RemoteMethod refreshRegisters = conn.getMethod("refresh_registers");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
conn.execute("regs = util.get_debugger().reg");
conn.execute("regs._set_register('rax', int(0xdeadbeef))");
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 rax = regs.getValue(snap, tb.reg("rax"));
assertEquals("deadbeef", rax.getUnsignedValue().toString(16));
}
}
}
@Test
public void testRefreshMappings() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
String path = "Processes[].Memory";
start(conn, "notepad.exe");
txCreate(conn, path);
RemoteMethod refreshMappings = conn.getMethod("refresh_mappings");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject memory = Objects.requireNonNull(tb.objAny(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 (PythonAndHandler conn = startAndConnectPython()) {
String path = "Processes[].Modules";
start(conn, "notepad.exe");
txCreate(conn, path);
RemoteMethod refreshModules = conn.getMethod("refresh_modules");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject modules = Objects.requireNonNull(tb.objAny(path));
refreshModules.invoke(Map.of("node", modules));
// 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("notepad.exe")));
assertNotEquals(tb.addr(0), Objects.requireNonNull(modBash.getBase()));
}
}
}
@Test
public void testActivateThread() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod activateThread = conn.getMethod("activate_thread");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
txPut(conn, "threads");
PathPattern pattern =
PathPredicates.parse("Processes[].Threads[]").getSingletonPattern();
List<TraceObject> list = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0), pattern)
.map(p -> p.getDestination(null))
.toList();
assertEquals(4, list.size());
for (TraceObject t : list) {
activateThread.invoke(Map.of("thread", t));
String out = conn.executeCapture("util.get_debugger().get_thread()");
List<String> indices = pattern.matchKeys(t.getCanonicalPath().getKeyList());
assertEquals(out, "%s".formatted(indices.get(1)));
}
}
}
}
@Test
public void testRemoveProcess() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "netstat.exe");
txPut(conn, "processes");
RemoteMethod removeProcess = conn.getMethod("remove_process");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/netstat.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]"));
removeProcess.invoke(Map.of("process", proc2));
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("[]"));
}
}
}
@Test
public void testAttachObj() throws Exception {
try (DummyProc dproc = DummyProc.run("notepad.exe")) {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, null);
txPut(conn, "available");
RemoteMethod attachObj = conn.getMethod("attach_obj");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject target =
Objects.requireNonNull(tb.obj("Available[%d]".formatted(dproc.pid)));
attachObj.invoke(Map.of("target", target));
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("%d".formatted(dproc.pid)));
}
}
}
}
@Test
public void testAttachPid() throws Exception {
try (DummyProc dproc = DummyProc.run("notepad.exe")) {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, null);
txPut(conn, "available");
RemoteMethod attachPid = conn.getMethod("attach_pid");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
Objects.requireNonNull(tb.objAny("Available["+dproc.pid+"]", Lifespan.at(0)));
attachPid.invoke(Map.of("pid", dproc.pid));
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("%d".formatted(dproc.pid)));
}
}
}
}
@Test
public void testDetach() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "netstat.exe");
txPut(conn, "processes");
RemoteMethod detach = conn.getMethod("detach");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/netstat.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
detach.invoke(Map.of("process", proc));
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("[]"));
}
}
}
@Test
public void testLaunchEntry() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, null);
txPut(conn, "processes");
RemoteMethod launch = conn.getMethod("launch_loader");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
launch.invoke(Map.ofEntries(
Map.entry("file", "notepad.exe")));
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("notepad.exe"));
}
}
}
@Test //Can't do this test because create(xxx, initial_break=False) doesn't return
public void testLaunch() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, null);
txPut(conn, "processes");
RemoteMethod launch = conn.getMethod("launch");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
launch.invoke(Map.ofEntries(
Map.entry("timeout", 1L),
Map.entry("file", "notepad.exe")));
txPut(conn, "processes");
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("notepad.exe"));
}
}
}
@Test
public void testKill() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod kill = conn.getMethod("kill");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
kill.invoke(Map.of("process", proc));
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("[]"));
}
}
}
@Test
public void testStepInto() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod step_into = conn.getMethod("step_into");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!getInst(conn).contains("call")) {
step_into.invoke(Map.of("thread", thread));
}
String disCall = getInst(conn);
// lab0:
// -> addr0
//
// lab1:
// addr1
String[] split = disCall.split("\\s+"); // get target
long pcCallee = Long.decode(split[split.length-1]);
step_into.invoke(Map.of("thread", thread));
long pc = getAddressAtOffset(conn, 0);
assertEquals(pcCallee, pc);
}
}
}
@Test
public void testStepOver() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod step_over = conn.getMethod("step_over");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!getInst(conn).contains("call")) {
step_over.invoke(Map.of("thread", thread));
}
String disCall = getInst(conn);
String[] split = disCall.split("\\s+"); // get target
long pcCallee = Long.decode(split[split.length-1]);
step_over.invoke(Map.of("thread", thread));
long pc = getAddressAtOffset(conn, 0);
assertNotEquals(pcCallee, pc);
}
}
}
@Test
public void testStepTo() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
RemoteMethod step_into = conn.getMethod("step_into");
RemoteMethod step_to = conn.getMethod("step_to");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!getInst(conn).contains("call")) {
step_into.invoke(Map.of("thread", thread));
}
step_into.invoke(Map.of("thread", thread));
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
for (int i = 0; i < 4; i++) {
sz += Integer.parseInt(getInstSizeAtOffset(conn, sz));
}
long pcNext = getAddressAtOffset(conn, sz);
boolean success = (boolean) step_to.invoke(Map.of("thread", thread, "address", tb.addr(pcNext), "max", 10));
assertTrue(success);
long pc = getAddressAtOffset(conn, 0);
assertEquals(pcNext, pc);
}
}
}
@Test
public void testStepOut() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod step_into = conn.getMethod("step_into");
RemoteMethod step_out = conn.getMethod("step_out");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!getInst(conn).contains("call")) {
step_into.invoke(Map.of("thread", thread));
}
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
long pcNext = getAddressAtOffset(conn, sz);
step_into.invoke(Map.of("thread", thread));
step_out.invoke(Map.of("thread", thread));
long pc = getAddressAtOffset(conn, 0);
assertEquals(pcNext, pc);
}
}
}
@Test
public void testBreakAddress() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakAddress = conn.getMethod("break_address");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0);
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString(Long.toHexString(address)));
}
}
}
@Test
public void testBreakExpression() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
breakExpression.invoke(Map.of("expression", "entry"));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("entry"));
}
}
}
@Test
public void testBreakHardwareAddress() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakAddress = conn.getMethod("break_hw_address");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0);
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString(Long.toHexString(address)));
}
}
}
@Test
public void testBreakHardwareExpression() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_hw_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
breakExpression.invoke(Map.of("expression", "entry"));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("entry"));
}
}
}
@Test
public void testBreakReadRange() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakRange = conn.getMethod("break_read_range");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0);
AddressRange range = tb.range(address, address + 3); // length 4
breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("%x".formatted(address)));
assertThat(out, containsString("sz=4"));
assertThat(out, containsString("type=r"));
}
}
}
@Test
public void testBreakReadExpression() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_read_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
long address = getAddressAtOffset(conn, 0);
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString(Long.toHexString(address>>24)));
assertThat(out, containsString("sz=1"));
assertThat(out, containsString("type=r"));
}
}
}
@Test
public void testBreakWriteRange() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakRange = conn.getMethod("break_write_range");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0);
AddressRange range = tb.range(address, address + 3); // length 4
breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("%x".formatted(address)));
assertThat(out, containsString("sz=4"));
assertThat(out, containsString("type=w"));
}
}
}
@Test
public void testBreakWriteExpression() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_write_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
long address = getAddressAtOffset(conn, 0);
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString(Long.toHexString(address>>24)));
assertThat(out, containsString("sz=1"));
assertThat(out, containsString("type=w"));
}
}
}
@Test
public void testBreakAccessRange() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakRange = conn.getMethod("break_access_range");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0);
AddressRange range = tb.range(address, address + 3); // length 4
breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("%x".formatted(address)));
assertThat(out, containsString("sz=4"));
assertThat(out, containsString("type=rw"));
}
}
}
@Test
public void testBreakAccessExpression() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_access_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
long address = getAddressAtOffset(conn, 0);
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString(Long.toHexString(address>>24)));
assertThat(out, containsString("sz=1"));
assertThat(out, containsString("type=rw"));
}
}
}
@Test
public void testToggleBreakpoint() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakAddress = conn.getMethod("break_address");
RemoteMethod toggleBreakpoint = conn.getMethod("toggle_breakpoint");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
long address = getAddressAtOffset(conn, 0);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
txPut(conn, "breakpoints");
TraceObject bpt = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints[]"));
toggleBreakpoint.invoke(Map.of("breakpoint", bpt, "enabled", false));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("disabled"));
}
}
}
@Test
public void testDeleteBreakpoint() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakAddress = conn.getMethod("break_address");
RemoteMethod deleteBreakpoint = conn.getMethod("delete_breakpoint");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
long address = getAddressAtOffset(conn, 0);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
txPut(conn, "breakpoints");
TraceObject bpt = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints[]"));
deleteBreakpoint.invoke(Map.of("breakpoint", bpt));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("[]"));
}
}
}
private void start(PythonAndHandler conn, String obj) {
conn.execute("from ghidradbg.commands import *");
if (obj != null)
conn.execute("ghidra_trace_create('"+obj+"')");
else
conn.execute("ghidra_trace_create()"); }
private void txPut(PythonAndHandler conn, String obj) {
conn.execute("ghidra_trace_txstart('Tx')");
conn.execute("ghidra_trace_put_" + obj + "()");
conn.execute("ghidra_trace_txcommit()");
}
private void txCreate(PythonAndHandler conn, String path) {
conn.execute("ghidra_trace_txstart('Fake')");
conn.execute("ghidra_trace_create_obj('%s')".formatted(path));
conn.execute("ghidra_trace_txcommit()");
}
private String getInst(PythonAndHandler conn) {
return getInstAtOffset(conn, 0);
}
private String getInstAtOffset(PythonAndHandler conn, int offset) {
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+"+offset+")";
String ret = conn.executeCapture(inst);
return ret.substring(1, ret.length()-1); // remove <>
}
private String getInstSizeAtOffset(PythonAndHandler conn, int offset) {
String instSize = "util.get_inst_sz(util.get_debugger().reg.get_pc()+"+offset+")";
return conn.executeCapture(instSize);
}
private long getAddressAtOffset(PythonAndHandler conn, int offset) {
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+"+offset+")";
String ret = conn.executeCapture(inst);
String[] split = ret.split("\\s+"); // get target
return Long.decode(split[1]);
}
}

View file

@ -134,7 +134,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
ghidra_trace_connect %s
file bash
script ghidralldb.util.set_convenience_variable('ghidra-language','Toy:BE:64:default')
script ghidralldb.util.set_convenience_varaible('ghidra-compiler','default')
script ghidralldb.util.set_convenience_variable('ghidra-compiler','default')
ghidra_trace_start myToy
quit
"""