GP-4415: Lots of lldb trace-rmi fixes

Breakpoint Enabled atribute.
Test fixes on macOS and Linux.
Re-work value conversion a bit.
shlexify commands.
Add method display names.
This commit is contained in:
Dan 2024-03-22 08:56:59 -04:00
parent 523f6e4cbe
commit eb5bf458a4
22 changed files with 1828 additions and 1222 deletions

View file

@ -20,7 +20,8 @@ import static org.junit.Assert.*;
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Function;
@ -28,8 +29,11 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.io.output.TeeOutputStream;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.BeforeClass;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
@ -45,6 +49,7 @@ import ghidra.framework.plugintool.PluginsConfiguration;
import ghidra.framework.plugintool.util.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.pty.*;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
@ -54,56 +59,70 @@ import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebuggerTest {
record PlatDep(String name, String endian, String lang, String cSpec, String callMne,
String intReg, String floatReg) {
static final PlatDep ARM64 =
new PlatDep("arm64", "little", "AARCH64:LE:64:v8A", "default", "bl", "x0", "s0");
static final PlatDep X8664 = // Note AT&T callq
new PlatDep("x86_64", "little", "x86:LE:64:default", "gcc", "callq", "rax", "st0");
}
public static final PlatDep PLAT = computePlat();
static PlatDep computePlat() {
return switch (System.getProperty("os.arch")) {
case "aarch64" -> PlatDep.ARM64;
case "x86" -> PlatDep.X8664;
case "amd64" -> PlatDep.X8664;
default -> throw new AssertionError(
"Unrecognized arch: " + System.getProperty("os.arch"));
};
}
static String getSpecimenClone() {
return DummyProc.which("expCloneExit");
}
static String getSpecimenPrint() {
return DummyProc.which("expPrint");
}
static String getSpecimenRead() {
return DummyProc.which("expRead");
}
/**
* Some features have to be disabled to avoid permissions issues in the test container. Namely,
* don't try to disable ASLR.
*
* Color codes mess up the address parsing.
*/
public static final String PREAMBLE = """
script import ghidralldb
settings set use-color false
settings set target.disable-aslr false
""";
// 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;
public static final String INSTRUMENT_STOPPED =
"""
ghidra_trace_txopen "Fake" 'ghidra_trace_create_obj Processes[1]'
define do-set-stopped
ghidra_trace_set_value Processes[1] _state '"STOPPED"'
end
define set-stopped
ghidra_trace_txopen Stopped do-set-stopped
end
#lldb.debugger.HandleCommand('target stop-hook add -P ghidralldb.hooks.StopHook')
#python lldb.events.stop.connect(lambda e: lldb.execute("set-stopped"))""";
public static final String INSTRUMENT_RUNNING =
"""
ghidra_trace_txopen "Fake" 'ghidra_trace_create_obj Processes[1]'
define do-set-running
ghidra_trace_set_value Processes[1] _state '"RUNNING"'
end
define set-running
ghidra_trace_txopen Running do-set-running
end
#lldb.debugger.HandleCommand('target stop-hook add -P ghidralldb.hooks.StopHook')
#python lldb.events.cont.connect(lambda e: lldb.execute("set-running"))""";
protected TraceRmiService traceRmi;
private Path lldbPath;
private Path outFile;
private Path errFile;
// @BeforeClass
public static void setupPython() throws Throwable {
new ProcessBuilder("gradle", "Debugger-agent-lldb:assemblePyPackage")
.directory(TestApplicationUtils.getInstallationDirectory())
.inheritIO()
.start()
.waitFor();
new ProcessBuilder("gradle",
"Debugger-rmi-trace:assemblePyPackage",
"Debugger-agent-lldb:assemblePyPackage")
.directory(TestApplicationUtils.getInstallationDirectory())
.inheritIO()
.start()
.waitFor();
}
protected void setPythonPath(ProcessBuilder pb) throws IOException {
protected void setPythonPath(Map<String, String> env) throws IOException {
String sep =
OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS ? ";" : ":";
String rmiPyPkg = Application.getModuleSubDirectory("Debugger-rmi-trace",
@ -111,7 +130,7 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
String gdbPyPkg = Application.getModuleSubDirectory("Debugger-agent-lldb",
"build/pypkg/src").getAbsolutePath();
String add = rmiPyPkg + sep + gdbPyPkg;
pb.environment().compute("PYTHONPATH", (k, v) -> v == null ? add : (v + sep + add));
env.compute("PYTHONPATH", (k, v) -> v == null ? add : (v + sep + add));
}
@Before
@ -124,8 +143,6 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
catch (RuntimeException e) {
lldbPath = Paths.get(DummyProc.which("lldb"));
}
outFile = Files.createTempFile("lldbout", null);
errFile = Files.createTempFile("lldberr", null);
}
protected void addAllDebuggerPlugins() throws PluginException {
@ -156,71 +173,70 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
throw new AssertionError("Unhandled address type " + address);
}
protected record LldbResult(boolean timedOut, int exitCode, String stdout, String stderr) {
protected record LldbResult(boolean timedOut, int exitCode, String out) {
protected String handle() {
if (!"".equals(stderr) || (0 != exitCode && 143 != exitCode)) {
throw new LldbError(exitCode, stdout, stderr);
if (0 != exitCode && 143 != exitCode) {
throw new LldbError(exitCode, out);
}
return stdout;
return out;
}
}
protected record ExecInLldb(Process lldb, CompletableFuture<LldbResult> future) {
}
protected record ExecInLldb(Pty pty, PtySession lldb, CompletableFuture<LldbResult> future,
Thread pumper) {}
@SuppressWarnings("resource") // Do not close stdin
protected ExecInLldb execInLldb(String script) throws IOException {
ProcessBuilder pb = new ProcessBuilder(lldbPath.toString());
setPythonPath(pb);
Pty pty = PtyFactory.local().openpty();
Map<String, String> env = new HashMap<>(System.getenv());
setPythonPath(env);
env.put("TERM", "xterm-256color");
ByteArrayOutputStream capture = new ByteArrayOutputStream();
OutputStream tee = new TeeOutputStream(System.out, capture);
Thread pumper = new StreamPumper(pty.getParent().getInputStream(), tee);
pumper.start();
PtySession lldbSession = pty.getChild().session(new String[] { lldbPath.toString() }, env);
// If commands come from file, LLDB will quit after EOF.
Msg.info(this, "outFile: " + outFile);
Msg.info(this, "errFile: " + errFile);
pb.redirectInput(ProcessBuilder.Redirect.PIPE);
pb.redirectOutput(outFile.toFile());
pb.redirectError(errFile.toFile());
Process lldbProc = pb.start();
OutputStream stdin = lldbProc.getOutputStream();
OutputStream stdin = pty.getParent().getOutputStream();
stdin.write(script.getBytes());
stdin.flush();
return new ExecInLldb(lldbProc, CompletableFuture.supplyAsync(() -> {
return new ExecInLldb(pty, lldbSession, CompletableFuture.supplyAsync(() -> {
try {
if (!lldbProc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
Msg.error(this, "Timed out waiting for LLDB");
lldbProc.destroyForcibly();
lldbProc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS);
return new LldbResult(true, -1, Files.readString(outFile),
Files.readString(errFile));
}
Msg.info(this, "LLDB exited with code " + lldbProc.exitValue());
return new LldbResult(false, lldbProc.exitValue(), Files.readString(outFile),
Files.readString(errFile));
int exitVal = lldbSession.waitExited(TIMEOUT_SECONDS, TimeUnit.SECONDS);
Msg.info(this, "LLDB exited with code " + exitVal);
return new LldbResult(false, exitVal, capture.toString());
}
catch (TimeoutException e) {
return new LldbResult(true, -1, capture.toString());
}
catch (Exception e) {
return ExceptionUtils.rethrow(e);
}
finally {
lldbProc.destroyForcibly();
try {
pty.close();
}
catch (IOException e) {
Msg.warn(this, "Couldn't close pty: " + e);
}
lldbSession.destroyForcibly();
pumper.interrupt();
}
}));
}), pumper);
}
public static class LldbError extends RuntimeException {
public final int exitCode;
public final String stdout;
public final String stderr;
public final String out;
public LldbError(int exitCode, String stdout, String stderr) {
public LldbError(int exitCode, String out) {
super("""
exitCode=%d:
----stdout----
----out----
%s
----stderr----
%s
""".formatted(exitCode, stdout, stderr));
""".formatted(exitCode, out));
this.exitCode = exitCode;
this.stdout = stdout;
this.stderr = stderr;
this.out = out;
}
}
@ -250,17 +266,32 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
return (String) execute.invoke(Map.of("cmd", cmd, "to_string", true));
}
public Object evaluate(String expr) {
RemoteMethod evaluate = getMethod("evaluate");
return evaluate.invoke(Map.of("expr", expr));
}
public Object pyeval(String expr) {
RemoteMethod pyeval = getMethod("pyeval");
return pyeval.invoke(Map.of("expr", expr));
}
@Override
public void close() throws Exception {
Msg.info(this, "Cleaning up lldb");
exec.lldb().destroy();
execute("settings set auto-confirm true");
exec.pty.getParent().getOutputStream().write("""
quit
""".getBytes());
try {
LldbResult r = exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
r.handle();
waitForPass(() -> assertTrue(connection.isClosed()));
}
finally {
exec.pty.close();
exec.lldb.destroyForcibly();
exec.pumper.interrupt();
}
}
}
@ -276,8 +307,10 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
return new LldbAndConnection(exec, connection);
}
catch (SocketTimeoutException e) {
exec.pty.close();
exec.lldb.destroyForcibly();
exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
exec.pumper.interrupt();
throw e;
}
}
@ -285,7 +318,7 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
protected LldbAndConnection startAndConnectLldb() throws Exception {
return startAndConnectLldb(addr -> """
%s
ghidra_trace_connect %s
ghidra trace connect %s
""".formatted(PREAMBLE, addr));
}
@ -294,36 +327,56 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
throws Exception {
LldbAndConnection conn = startAndConnectLldb(scriptSupplier);
LldbResult r = conn.exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
conn.exec.pty.close();
conn.exec.pumper.interrupt();
String stdout = r.handle();
waitForPass(() -> assertTrue(conn.connection.isClosed()));
return stdout;
}
protected void waitStopped() {
protected void waitState(String state) {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
waitForPass(() -> assertEquals("STOPPED", tb.objValue(proc, 0, "_state")));
for (int i = 0; i < 5; i++) {
try {
waitForPass(() -> {
Long snap = tb.trace.getTimeManager().getMaxSnap();
assertEquals(state, tb.objValue(proc, snap != null ? snap : 0, "_state"));
});
break;
}
catch (AssertionError e) {
if (i == 4) {
throw e;
}
}
}
waitTxDone();
}
protected void waitRunning() {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
waitForPass(() -> assertEquals("RUNNING", tb.objValue(proc, 0, "_state")));
waitTxDone();
protected void waitStopped(LldbAndConnection conn) {
waitForPass(() -> assertEquals(Boolean.TRUE,
conn.pyeval("util.get_debugger().GetTargetAtIndex(0).GetProcess().is_stopped")));
// waitState("STOPPED");
}
protected void waitRunning(LldbAndConnection conn) {
waitForPass(() -> assertEquals(Boolean.TRUE,
conn.pyeval("util.get_debugger().GetTargetAtIndex(0).GetProcess().is_running")));
// waitState("RUNNING");
}
protected String extractOutSection(String out, String head) {
String[] split = out.split("\n");
String[] split = out.replace("\r", "").split("\n");
String xout = "";
for (String s : split) {
if (!s.startsWith("(lldb)") && !s.equals("")) {
if (!s.startsWith("(lldb)") && !s.contains("script print(") && !s.equals("")) {
xout += s + "\n";
}
}
return xout.split(head)[1].split("---")[0].replace("(lldb)", "").trim();
}
record MemDump(long address, byte[] data) {
}
record MemDump(long address, byte[] data) {}
protected MemDump parseHexDump(String dump) throws IOException {
// First, get the address. Assume contiguous, so only need top line.
@ -348,13 +401,6 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
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);
@ -376,6 +422,14 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
}
}
protected void assertLocalOs(String actual) {
assertThat(actual, Matchers.startsWith(switch (OperatingSystem.CURRENT_OPERATING_SYSTEM) {
case LINUX -> "linux";
case MAC_OS_X -> "macos";
default -> throw new AssertionError("What OS?");
}));
}
protected void assertBreakLoc(TraceObjectValue locVal, String key, Address addr, int len,
Set<TraceBreakpointKind> kinds, String expression) throws Exception {
assertEquals(key, locVal.getEntryKey());

View file

@ -15,7 +15,8 @@
*/
package agent.lldb.rmi;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
@ -41,7 +42,7 @@ import ghidra.trace.model.time.TraceSnapshot;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class LldbHooksTest extends AbstractLldbTraceRmiTest {
private static final long RUN_TIMEOUT_MS = 20000;
private static final long RUN_TIMEOUT_MS = 5000;
private static final long RETRY_MS = 500;
record LldbAndTrace(LldbAndConnection conn, ManagedDomainObject mdo) implements AutoCloseable {
@ -64,10 +65,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
protected LldbAndTrace startAndSyncLldb() throws Exception {
LldbAndConnection conn = startAndConnectLldb();
try {
// TODO: Why does using 'set arch' cause a hang at quit?
conn.execute(
"ghidralldb.util.set_convenience_variable('ghidra-language', 'x86:LE:64:default')");
conn.execute("ghidra_trace_start");
conn.execute("ghidra trace start");
ManagedDomainObject mdo = waitDomainObject("/New Traces/lldb/noname");
tb = new ToyDBTraceBuilder((Trace) mdo.get());
return new LldbAndTrace(conn, mdo);
@ -102,7 +100,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("continue");
waitStopped();
waitStopped(conn.conn);
txPut(conn, "threads");
waitForPass(() -> assertEquals(2,
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
@ -131,7 +129,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("continue");
waitStopped();
waitStopped(conn.conn);
waitForPass(() -> {
TraceObject inf = tb.objAny("Processes[]");
assertNotNull(inf);
@ -207,11 +205,11 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
try (LldbAndTrace conn = startAndSyncLldb()) {
traceManager.openTrace(tb.trace);
start(conn, "bash");
conn.execute("breakpoint set -n read");
start(conn, getSpecimenPrint());
conn.execute("breakpoint set -n puts");
conn.execute("cont");
waitStopped();
waitStopped(conn.conn);
waitForPass(() -> assertThat(
tb.objValues(lastSnap(conn), "Processes[].Threads[].Stack[]").size(),
greaterThan(2)),
@ -224,6 +222,8 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
conn.execute("frame select 0");
waitForPass(() -> assertEquals("0", frameIndex(traceManager.getCurrentObject())),
RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("kill");
}
}
@ -234,16 +234,16 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
// FWIW, I've already seen this getting exercised in other tests.
}
@Test
//@Test // LLDB does not provide the necessary events
public void testOnMemoryChanged() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
conn.execute("expr *((char*)(void(*)())main) = 0x7f");
conn.execute("ghidra_trace_txstart 'Tx'");
conn.execute("ghidra_trace_putmem `(void(*)())main` 10");
conn.execute("ghidra_trace_txcommit");
//conn.execute("ghidra trace tx-start 'Tx'");
//conn.execute("ghidra trace putmem '(void(*)())main' 10");
//conn.execute("ghidra trace tx-commit");
waitForPass(() -> {
ByteBuffer buf = ByteBuffer.allocate(10);
@ -253,15 +253,15 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
}
}
@Test
//@Test // LLDB does not provide the necessary events
public void testOnRegisterChanged() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
conn.execute("expr $rax = 0x1234");
conn.execute("ghidra_trace_txstart 'Tx'");
conn.execute("ghidra_trace_putreg");
conn.execute("ghidra_trace_txcommit");
conn.execute("expr $%s = 0x1234".formatted(PLAT.intReg()));
//conn.execute("ghidra trace tx-start 'Tx'");
//conn.execute("ghidra trace putreg");
//conn.execute("ghidra trace tx-commit");
String path = "Processes[].Threads[].Stack[].Registers";
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
@ -269,29 +269,33 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
.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)));
regs.getValue(lastSnap(conn), tb.reg(PLAT.intReg()))
.getUnsignedValue()
.toString(16)));
}
}
@Test
public void testOnCont() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash");
start(conn, getSpecimenRead());
conn.execute("cont");
waitRunning();
waitRunning(conn.conn);
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
waitForPass(() -> {
assertEquals("RUNNING", tb.objValue(proc, lastSnap(conn), "_state"));
}, RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("process interrupt");
}
}
@Test
public void testOnStop() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
TraceObject inf = waitForValue(() -> tb.objAny("Processes[]"));
waitForPass(() -> {
@ -303,25 +307,22 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
@Test
public void testOnExited() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) {
conn.execute("file bash");
conn.execute("ghidra_trace_sync_enable");
conn.execute("process launch --stop-at-entry -- -c 'exit 1'");
txPut(conn, "processes");
start(conn, getSpecimenPrint());
conn.execute("cont");
waitRunning();
waitRunning(conn.conn);
waitForPass(() -> {
TraceSnapshot snapshot =
tb.trace.getTimeManager().getSnapshot(lastSnap(conn), false);
assertNotNull(snapshot);
assertEquals("Exited with code 1", snapshot.getDescription());
assertEquals("Exited with code 72", snapshot.getDescription());
TraceObject proc = tb.objAny("Processes[]");
assertNotNull(proc);
Object val = tb.objValue(proc, lastSnap(conn), "_exit_code");
assertThat(val, instanceOf(Number.class));
assertEquals(1, ((Number) val).longValue());
assertEquals(72, ((Number) val).longValue());
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
@ -329,7 +330,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
@Test
public void testOnBreakpointCreated() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
conn.execute("breakpoint set -n main");
@ -346,9 +347,10 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
@Test
public void testOnBreakpointModified() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
assertEquals(0, tb.objValues(lastSnap(conn), "Breakpoints[]").size());
//conn.execute("script lldb.debugger.SetAsync(False)");
conn.execute("breakpoint set -n main");
conn.execute("stepi");
TraceObject brk = waitForPass(() -> {
@ -357,6 +359,8 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
return (TraceObject) brks.get(0);
});
assertEquals(null, tb.objValue(brk, lastSnap(conn), "Condition"));
waitStopped(conn.conn);
conn.execute("breakpoint modify -c 'x>3'");
conn.execute("stepi");
// NB: Testing "Commands" requires multi-line input - not clear how to do this
@ -372,7 +376,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
@Test
public void testOnBreakpointDeleted() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
conn.execute("breakpoint set -n main");
@ -384,6 +388,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
return (TraceObject) brks.get(0);
});
waitStopped(conn.conn);
conn.execute("breakpoint delete %s".formatted(brk.getCanonicalPath().index()));
conn.execute("stepi");
@ -395,15 +400,15 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
private void start(LldbAndTrace conn, String obj) {
conn.execute("file " + obj);
conn.execute("ghidra_trace_sync_enable");
conn.execute("ghidra trace sync-enable");
conn.execute("process launch --stop-at-entry");
txPut(conn, "processes");
}
private void txPut(LldbAndTrace conn, String obj) {
conn.execute("ghidra_trace_txstart 'Tx" + obj + "'");
conn.execute("ghidra_trace_put_" + obj);
conn.execute("ghidra_trace_txcommit");
conn.execute("ghidra trace tx-start 'Tx" + obj + "'");
conn.execute("ghidra trace put-" + obj);
conn.execute("ghidra trace tx-commit");
}
}

View file

@ -18,9 +18,11 @@ package agent.lldb.rmi;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;
import java.util.*;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@ -31,6 +33,7 @@ import ghidra.dbg.testutil.DummyProc;
import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathPredicates;
import ghidra.debug.api.tracermi.RemoteMethod;
import ghidra.framework.OperatingSystem;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.database.ToyDBTraceBuilder;
@ -59,10 +62,10 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testExecute() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
conn.execute("kill");
}
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
// Just confirm it's present
}
}
@ -70,7 +73,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testRefreshAvailable() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("ghidra_trace_start");
conn.execute("ghidra trace start");
txCreate(conn, "Available");
RemoteMethod refreshAvailable = conn.getMethod("refresh_available");
@ -93,13 +96,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testRefreshBreakpoints() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod refreshBreakpoints = conn.getMethod("refresh_breakpoints");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
//waitStopped();
//waitStopped(conn);
conn.execute("breakpoint set --name main");
conn.execute("breakpoint set -H --name main");
@ -132,14 +135,14 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testRefreshProcBreakpoints() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
txPut(conn, "breakpoints");
RemoteMethod refreshProcBreakpoints = conn.getMethod("refresh_proc_breakpoints");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
TraceObject locations =
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
@ -171,19 +174,20 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testRefreshProcWatchpoints() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "all");
RemoteMethod refreshProcWatchpoints = conn.getMethod("refresh_proc_watchpoints");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
TraceObject locations =
Objects.requireNonNull(tb.objAny("Processes[].Watchpoints"));
conn.execute("watchpoint set expression -- `(void(*)())main`");
conn.execute("watchpoint set expression -w read -- `(void(*)())main`+-0x20");
conn.execute("watchpoint set expression -w read_write -- `(void(*)())main`+0x30");
conn.execute("watchpoint set expression -s 1 -- `(void(*)())main`");
conn.execute("watchpoint set expression -s 1 -w read -- `(void(*)())main`+-0x20");
conn.execute(
"watchpoint set expression -s 1 -w read_write -- `(void(*)())main`+0x30");
refreshProcWatchpoints.invoke(Map.of("node", locations));
List<TraceObjectValue> procWatchLocVals = tb.trace.getObjectManager()
@ -219,7 +223,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testRefreshProcesses() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("ghidra_trace_start");
conn.execute("ghidra trace start");
txCreate(conn, "Processes");
txCreate(conn, "Processes[1]");
@ -244,21 +248,20 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
public void testRefreshEnvironment() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
String path = "Processes[].Environment";
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "all");
RemoteMethod refreshEnvironment = conn.getMethod("refresh_environment");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject env = Objects.requireNonNull(tb.objAny(path));
refreshEnvironment.invoke(Map.of("node", env));
// Assumes LLDB on Linux amd64
assertEquals("lldb", env.getValue(0, "_debugger").getValue());
assertEquals("x86_64", env.getValue(0, "_arch").getValue());
assertEquals("linux", env.getValue(0, "_os").getValue());
assertEquals("little", env.getValue(0, "_endian").getValue());
assertEquals(PLAT.name(), env.getValue(0, "_arch").getValue());
assertLocalOs(env.getValue(0, "_os").castValue());
assertEquals(PLAT.endian(), env.getValue(0, "_endian").getValue());
}
}
}
@ -267,11 +270,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
public void testRefreshThreads() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
String path = "Processes[].Threads";
start(conn, "bash");
start(conn, getSpecimenPrint());
txCreate(conn, path);
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject threads = Objects.requireNonNull(tb.objAny(path));
@ -287,15 +290,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
public void testRefreshStack() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
String path = "Processes[].Threads[].Stack";
conn.execute("file bash");
conn.execute("ghidra_trace_start");
conn.execute("file " + getSpecimenPrint());
conn.execute("ghidra trace start");
txPut(conn, "processes");
breakAt(conn, "read");
breakAt(conn, "puts");
RemoteMethod refreshStack = conn.getMethod("refresh_stack");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
waitTxDone();
txPut(conn, "frames");
TraceObject stack = Objects.requireNonNull(tb.objAny(path));
@ -316,16 +320,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
public void testRefreshRegisters() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
String path = "Processes[].Threads[].Stack[].Registers";
start(conn, "bash");
conn.execute("ghidra_trace_txstart 'Tx'");
conn.execute("ghidra_trace_putreg");
conn.execute("ghidra_trace_txcommit");
start(conn, getSpecimenPrint());
conn.execute("ghidra trace tx-start 'Tx'");
conn.execute("ghidra trace putreg");
conn.execute("ghidra trace tx-commit");
RemoteMethod refreshRegisters = conn.getMethod("refresh_registers");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
conn.execute("expr $rax = 0xdeadbeef");
conn.execute("expr $%s = 0xdeadbeef".formatted(PLAT.intReg()));
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
refreshRegisters.invoke(Map.of("node", registers));
@ -334,9 +338,9 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
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"));
RegisterValue intRegVal = regs.getValue(snap, tb.reg(PLAT.intReg()));
// LLDB treats registers in arch's endian
assertEquals("deadbeef", rax.getUnsignedValue().toString(16));
assertEquals("deadbeef", intRegVal.getUnsignedValue().toString(16));
}
}
}
@ -345,11 +349,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
public void testRefreshMappings() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
String path = "Processes[].Memory";
start(conn, "bash");
start(conn, getSpecimenPrint());
txCreate(conn, path);
RemoteMethod refreshMappings = conn.getMethod("refresh_mappings");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject memory = Objects.requireNonNull(tb.objAny(path));
@ -367,11 +371,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
public void testRefreshModules() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
String path = "Processes[].Modules";
start(conn, "bash");
start(conn, getSpecimenPrint());
txCreate(conn, path);
RemoteMethod refreshModules = conn.getMethod("refresh_modules");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject modules = Objects.requireNonNull(tb.objAny(path));
@ -379,27 +383,30 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
// 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("bash")));
assertNotEquals(tb.addr(0), Objects.requireNonNull(modBash.getBase()));
TraceModule modExpPrint = Unique.assertOne(
all.stream().filter(m -> m.getName().contains("expPrint")));
assertNotEquals(tb.addr(0), Objects.requireNonNull(modExpPrint.getBase()));
}
}
}
@Test
public void testActivateThread() throws Exception {
// This test crashes lldb-1500.0.404.7 on macOS arm64
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
try (LldbAndConnection conn = startAndConnectLldb()) {
// TODO: need to find this file (same issue in LldbHookTests
String dproc = DummyProc.which("expCloneExit");
conn.execute("file " + dproc);
conn.execute("ghidra_trace_start");
conn.execute("ghidra trace start");
txPut(conn, "processes");
breakAt(conn, "work");
RemoteMethod activateThread = conn.getMethod("activate_thread");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expCloneExit")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
waitTxDone();
txPut(conn, "threads");
@ -415,7 +422,10 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
activateThread.invoke(Map.of("thread", t));
String out = conn.executeCapture("thread info");
List<String> indices = pattern.matchKeys(t.getCanonicalPath().getKeyList());
assertThat(out, containsString("tid = %s".formatted(indices.get(1))));
long index = Long.decode(indices.get(1));
assertThat(out, Matchers
.either(containsString("tid = %s".formatted(index)))
.or(containsString("tid = 0x%x".formatted(index))));
}
}
}
@ -424,15 +434,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testActivateFrame() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash");
conn.execute("ghidra_trace_start");
conn.execute("file " + getSpecimenPrint());
conn.execute("ghidra trace start");
txPut(conn, "processes");
breakAt(conn, "read");
breakAt(conn, "puts");
RemoteMethod activateFrame = conn.getMethod("activate_frame");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
waitTxDone();
txPut(conn, "frames");
@ -456,11 +467,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testRemoveProcess() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod removeProcess = conn.getMethod("remove_process");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]"));
@ -474,10 +485,12 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testAttachObj() throws Exception {
// Missing specimen for macOS
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
String sleep = DummyProc.which("expTraceableSleep");
try (DummyProc dproc = DummyProc.run(sleep)) {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("ghidra_trace_start");
conn.execute("ghidra trace start");
txPut(conn, "available");
txPut(conn, "processes");
@ -499,10 +512,12 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testAttachPid() throws Exception {
// Missing specimen for macOS
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
String sleep = DummyProc.which("expTraceableSleep");
try (DummyProc dproc = DummyProc.run(sleep)) {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("ghidra_trace_start");
conn.execute("ghidra trace start");
txPut(conn, "processes");
RemoteMethod attachPid = conn.getMethod("attach_pid");
@ -522,12 +537,12 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testDetach() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
//conn.execute("process attach -p %d".formatted(dproc.pid));
RemoteMethod detach = conn.getMethod("detach");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
@ -543,7 +558,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testLaunchEntry() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("ghidra_trace_start");
conn.execute("ghidra trace start");
txPut(conn, "processes");
RemoteMethod launch = conn.getMethod("launch_loader");
@ -553,11 +568,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
launch.invoke(Map.ofEntries(
Map.entry("process", proc),
Map.entry("file", "bash")));
waitStopped();
Map.entry("file", getSpecimenPrint())));
waitStopped(conn);
String out = conn.executeCapture("target list");
assertThat(out, containsString("bash"));
assertThat(out, containsString(getSpecimenPrint()));
}
}
}
@ -565,7 +580,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test //Not clear how to send interrupt
public void testLaunch() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("ghidra_trace_start");
conn.execute("ghidra trace start");
txPut(conn, "processes");
RemoteMethod launch = conn.getMethod("launch");
@ -575,17 +590,17 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
launch.invoke(Map.ofEntries(
Map.entry("process", proc),
Map.entry("file", "bash")));
Map.entry("file", getSpecimenRead())));
txPut(conn, "processes");
waitRunning();
waitRunning(conn);
Thread.sleep(100); // Give it plenty of time to block on read
conn.execute("process interrupt");
txPut(conn, "processes");
waitStopped();
waitStopped(conn);
String out = conn.executeCapture("bt");
assertThat(out, containsString("read"));
@ -596,13 +611,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testKill() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod kill = conn.getMethod("kill");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
kill.invoke(Map.of("process", proc));
@ -613,95 +628,123 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
}
}
protected void stepToCall(LldbAndConnection conn, RemoteMethod step, TraceObject thread)
throws InterruptedException {
while (true) {
String dis = conn.executeCapture("dis -c1 -s '$pc'");
if (dis.contains(PLAT.callMne())) {
return;
}
step.invoke(Map.of("thread", thread));
}
}
record FoundHex(int idx, long value) {
static FoundHex findHex(List<String> tokens, int start) {
for (int i = start; i < tokens.size(); i++) {
String tok = tokens.get(i);
if (tok.startsWith("0x")) {
return new FoundHex(i, Long.decode(tok));
}
}
throw new AssertionError("Could not find 0x");
}
}
record CallInstr(long next, long target) {
static CallInstr parse(String dis2) {
List<String> tokens = List.of(dis2.split("\\s+"));
int mneIndex = tokens.indexOf(PLAT.callMne());
assertNotEquals("Could not find " + PLAT.callMne(), -1, mneIndex);
FoundHex target = FoundHex.findHex(tokens, mneIndex + 1);
FoundHex next = FoundHex.findHex(tokens, target.idx + 1);
return new CallInstr(next.value, target.value);
}
}
@Test
public void testStepInto() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod step_into = conn.getMethod("step_into");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
waitTxDone();
txPut(conn, "threads");
conn.execute("script lldb.debugger.SetAsync(False)");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!conn.executeCapture("dis -c1 -s '$pc'").contains("call")) {
step_into.invoke(Map.of("thread", thread));
}
stepToCall(conn, step_into, thread);
String dis2 = conn.executeCapture("dis -c2 -s '$pc'");
// lab0:
// -> addr0
//
// lab1:
// addr1
long pcNext = Long.decode(dis2.strip().split("\n")[4].strip().split("\\s+")[0]);
CallInstr instr = CallInstr.parse(dis2);
step_into.invoke(Map.of("thread", thread));
String disAt = conn.executeCapture("dis -c1 -s '$pc'");
long pc = Long.decode(disAt.strip().split("\n")[1].strip().split("\\s+")[1]);
assertNotEquals(pcNext, pc);
FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0);
assertEquals(instr.target, pc.value);
}
}
}
@Test
//@Test // Debug information required (at least on macOS arm64)
public void testStepOver() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash");
conn.execute("ghidra_trace_start");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
breakAt(conn, "read");
RemoteMethod step_over = conn.getMethod("step_over");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
txPut(conn, "threads");
conn.execute("script lldb.debugger.SetAsync(False)");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!conn.executeCapture("dis -c1 -s '$pc'").contains("call")) {
step_over.invoke(Map.of("thread", thread));
}
stepToCall(conn, step_over, thread);
String dis2 = conn.executeCapture("dis -c2 -s '$pc'");
// lab0:
// -> addr0
// addr1
long pcNext = Long.decode(dis2.strip().split("\n")[2].strip().split("\\s+")[0]);
System.err.println(dis2);
CallInstr instr = CallInstr.parse(dis2);
// This winds up a step_into if lldb can't place its breakpoint
step_over.invoke(Map.of("thread", thread));
String disAt = conn.executeCapture("dis -c1 -s '$pc'");
long pc = Long.decode(disAt.strip().split("\n")[1].strip().split("\\s+")[1]);
assertEquals(pcNext, pc);
FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0);
assertEquals(instr.next, pc.value);
}
}
}
//@Test Not obvious "thread until -a" works (and definitely requires debug info")
public void testAdvance() throws Exception {
//@Test // Debug information required
public void testStepAdvance() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod step_ext = conn.getMethod("step_ext");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
RemoteMethod step_advance = conn.getMethod("step_advance");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
//waitStopped();
waitStopped(conn);
txPut(conn, "threads");
conn.execute("script lldb.debugger.SetAsync(False)");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
String dis3 = conn.executeCapture("disassemble -c3 -s '$pc'");
// TODO: Examine for control transfer?
long pcTarget = Long.decode(dis3.strip().split("\n")[2].strip().split("\\s+")[0]);
List<String> lines = List.of(dis3.split("\n"));
String last = lines.get(lines.size() - 1);
FoundHex addr = FoundHex.findHex(List.of(last.split("\\s+")), 0);
step_ext.invoke(Map.of("thread", thread, "address", tb.addr(pcTarget)));
step_advance.invoke(Map.of("thread", thread, "address", tb.addr(addr.value)));
String dis1 = conn.executeCapture("disassemble -c1 -s '$pc'");
long pc = Long.decode(dis1.strip().split("\n")[1].strip().split("\\s+")[1]);
assertEquals(pcTarget, pc);
String disAt = conn.executeCapture("disassemble -c1 -s '$pc'");
FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0);
assertEquals(addr.value, pc);
}
}
}
@ -709,16 +752,18 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testFinish() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash");
conn.execute("ghidra_trace_start");
conn.execute("file " + getSpecimenPrint());
conn.execute("ghidra trace start");
txPut(conn, "processes");
breakAt(conn, "read");
breakAt(conn, "puts");
RemoteMethod activate = conn.getMethod("activate_thread");
RemoteMethod step_out = conn.getMethod("step_out");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
waitTxDone();
txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
@ -735,18 +780,20 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
}
@Test
public void testReturn() throws Exception {
public void testStepReturn() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash");
conn.execute("ghidra_trace_start");
conn.execute("file " + getSpecimenPrint());
conn.execute("ghidra trace start");
txPut(conn, "processes");
breakAt(conn, "read");
breakAt(conn, "puts");
RemoteMethod activate = conn.getMethod("activate_thread");
RemoteMethod ret = conn.getMethod("return");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
RemoteMethod ret = conn.getMethod("step_return");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
waitTxDone();
txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
@ -765,11 +812,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testBreakAddress() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod breakAddress = conn.getMethod("break_address");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
@ -786,13 +833,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testBreakExpression() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
breakExpression.invoke(Map.of("expression", "main"));
@ -806,13 +853,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
// Are hardware breakpoints available on our VMs?
public void testBreakHardwareAddress() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod breakAddress = conn.getMethod("break_hw_address");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
@ -827,13 +874,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
//@Test There appear to be issues with hardware register availability in our virtual environments
public void testBreakHardwareExpression() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_hw_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
breakExpression.invoke(Map.of("expression", "`(void(*)())main`"));
@ -848,22 +895,22 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testBreakReadRange() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod breakRange = conn.getMethod("break_read_range");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
AddressRange range = tb.range(address, address + 3); // length 4
AddressRange range = tb.range(address, address + 0); // length 1
breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("watchpoint list");
assertThat(out, containsString("0x%x".formatted(address)));
assertThat(out, containsString("size = 4"));
assertThat(out, containsString("size = 1"));
assertThat(out, containsString("type = r"));
}
}
@ -872,14 +919,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testBreakReadExpression() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_read_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
breakExpression.invoke(Map.of("expression", "`(void(*)())main`"));
breakExpression.invoke(Map.of(
"expression", "`(void(*)())main`",
"size", 1));
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
String out = conn.executeCapture("watchpoint list");
@ -892,22 +941,22 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testBreakWriteRange() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod breakRange = conn.getMethod("break_write_range");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
AddressRange range = tb.range(address, address + 3); // length 4
AddressRange range = tb.range(address, address + 0); // length 1
breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("watchpoint list");
assertThat(out, containsString("0x%x".formatted(address)));
assertThat(out, containsString("size = 4"));
assertThat(out, containsString("size = 1"));
assertThat(out, containsString("type = w"));
}
}
@ -916,14 +965,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testBreakWriteExpression() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_write_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
breakExpression.invoke(Map.of("expression", "`(void(*)())main`"));
breakExpression.invoke(Map.of(
"expression", "`(void(*)())main`",
"size", 1));
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
String out = conn.executeCapture("watchpoint list");
@ -936,22 +987,22 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testBreakAccessRange() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod breakRange = conn.getMethod("break_access_range");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
AddressRange range = tb.range(address, address + 3); // length 4
AddressRange range = tb.range(address, address + 0); // length 1
breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("watchpoint list");
assertThat(out, containsString("0x%x".formatted(address)));
assertThat(out, containsString("size = 4"));
assertThat(out, containsString("size = 1"));
assertThat(out, containsString("type = rw"));
}
}
@ -960,14 +1011,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testBreakAccessExpression() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_access_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
breakExpression.invoke(Map.of("expression", "`(void(*)())main`"));
breakExpression.invoke(Map.of(
"expression", "`(void(*)())main`",
"size", 1));
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
String out = conn.executeCapture("watchpoint list");
@ -981,11 +1034,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testBreakException() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod breakExc = conn.getMethod("break_exception");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
breakExc.invoke(Map.of("lang", "C++"));
@ -1000,16 +1053,15 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testToggleBreakpoint() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash");
conn.execute("ghidra_trace_start");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
breakAt(conn, "main");
RemoteMethod toggleBreakpoint = conn.getMethod("toggle_breakpoint");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
conn.execute("breakpoint set -n main");
txPut(conn, "breakpoints");
TraceObject bpt = Objects.requireNonNull(tb.objAny("Breakpoints[]"));
@ -1024,19 +1076,17 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testToggleBreakpointLocation() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash");
conn.execute("ghidra_trace_start");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
breakAt(conn, "main");
RemoteMethod toggleBreakpointLocation = conn.getMethod("toggle_breakpoint_location");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
conn.execute("breakpoint set -n main");
txPut(conn, "breakpoints");
// NB. Requires canonical path. Inf[].Brk[] is a link
TraceObject loc = Objects.requireNonNull(tb.objAny("Breakpoints[][]"));
toggleBreakpointLocation.invoke(Map.of("location", loc, "enabled", false));
@ -1050,16 +1100,15 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testDeleteBreakpoint() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash");
conn.execute("ghidra_trace_start");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
breakAt(conn, "main");
RemoteMethod deleteBreakpoint = conn.getMethod("delete_breakpoint");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
waitStopped(conn);
conn.execute("breakpoint set -n main");
txPut(conn, "breakpoints");
TraceObject bpt = Objects.requireNonNull(tb.objAny("Breakpoints[]"));
@ -1074,15 +1123,17 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test
public void testDeleteWatchpoint() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash");
start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_read_expression");
RemoteMethod deleteWatchpoint = conn.getMethod("delete_watchpoint");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
breakExpression.invoke(Map.of("expression", "`(void(*)())main`"));
breakExpression.invoke(Map.of(
"expression", "`(void(*)())main`",
"size", 1));
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
String out = conn.executeCapture("watchpoint list");
@ -1101,25 +1152,26 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
private void start(LldbAndConnection conn, String obj) {
conn.execute("file " + obj);
conn.execute("ghidra_trace_start");
conn.execute("ghidra trace start");
conn.execute("process launch --stop-at-entry");
}
private void txPut(LldbAndConnection conn, String obj) {
conn.execute("ghidra_trace_txstart 'Tx'");
conn.execute("ghidra_trace_put_" + obj);
conn.execute("ghidra_trace_txcommit");
conn.execute("ghidra trace tx-start 'Tx'");
conn.execute("ghidra trace put-" + obj);
conn.execute("ghidra trace tx-commit");
}
private void txCreate(LldbAndConnection conn, String path) {
conn.execute("ghidra_trace_txstart 'Fake'");
conn.execute("ghidra_trace_create_obj %s".formatted(path));
conn.execute("ghidra_trace_txcommit");
conn.execute("ghidra trace tx-start 'Fake'");
conn.execute("ghidra trace create-obj %s".formatted(path));
conn.execute("ghidra trace tx-commit");
}
private void breakAt(LldbAndConnection conn, String fn) {
conn.execute("ghidra_trace_sync_enable");
conn.execute("ghidra trace sync-enable");
conn.execute("breakpoint set -n " + fn);
conn.execute("script lldb.debugger.SetAsync(False)");
conn.execute("run");
}