GP-0: Print LLDB output in case of test failure.

This commit is contained in:
Dan
2025-07-24 14:38:31 +00:00
parent fc4b780025
commit dcbfa2aa79
4 changed files with 86 additions and 10 deletions
@@ -194,7 +194,7 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
} }
protected record ExecInLldb(Pty pty, PtySession lldb, CompletableFuture<LldbResult> future, protected record ExecInLldb(Pty pty, PtySession lldb, CompletableFuture<LldbResult> future,
Thread pumper) {} Thread pumper, ByteArrayOutputStream capture) {}
@SuppressWarnings("resource") // Do not close stdin @SuppressWarnings("resource") // Do not close stdin
protected ExecInLldb execInLldb(String script) throws IOException { protected ExecInLldb execInLldb(String script) throws IOException {
@@ -239,7 +239,7 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
lldbSession.destroyForcibly(); lldbSession.destroyForcibly();
pumper.interrupt(); pumper.interrupt();
} }
}), pumper); }), pumper, capture);
} }
public static class LldbError extends RuntimeException { public static class LldbError extends RuntimeException {
@@ -262,8 +262,20 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
return result.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle(); return result.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
} }
protected record LldbAndConnection(ExecInLldb exec, TraceRmiConnection connection) protected class LldbAndConnection implements AutoCloseable {
implements AutoCloseable { private final ExecInLldb exec;
private final TraceRmiConnection connection;
private boolean success = false;
public LldbAndConnection(ExecInLldb exec, TraceRmiConnection connection) {
this.exec = exec;
this.connection = connection;
}
public TraceRmiConnection connection() {
return connection;
}
protected RemoteMethod getMethod(String name) { protected RemoteMethod getMethod(String name) {
return Objects.requireNonNull(connection.getMethods().get(name)); return Objects.requireNonNull(connection.getMethods().get(name));
} }
@@ -293,6 +305,10 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
return pyeval.invoke(Map.of("expr", expr)); return pyeval.invoke(Map.of("expr", expr));
} }
public void success() {
success = true;
}
@Override @Override
public void close() throws Exception { public void close() throws Exception {
Msg.info(this, "Cleaning up lldb"); Msg.info(this, "Cleaning up lldb");
@@ -306,6 +322,10 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
waitForPass(() -> assertTrue(connection.isClosed())); waitForPass(() -> assertTrue(connection.isClosed()));
} }
finally { finally {
if (!success) {
Msg.info(this, "LLDB output:\n" + exec.capture.toString());
}
exec.pty.close(); exec.pty.close();
exec.lldb.destroyForcibly(); exec.lldb.destroyForcibly();
exec.pumper.interrupt(); exec.pumper.interrupt();
@@ -1238,6 +1238,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
""".formatted(PREAMBLE, addr); """.formatted(PREAMBLE, addr);
try (LldbAndConnection conn = startAndConnectLldb(scriptSupplier)) { try (LldbAndConnection conn = startAndConnectLldb(scriptSupplier)) {
conn.execute("script print('FINISHED')"); conn.execute("script print('FINISHED')");
conn.success();
} }
} }
} }
@@ -15,14 +15,15 @@
*/ */
package agent.lldb.rmi; package agent.lldb.rmi;
import static org.hamcrest.Matchers.*; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.experimental.categories.Category; import org.junit.experimental.categories.Category;
@@ -52,6 +53,10 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
return conn.executeCapture(cmd); return conn.executeCapture(cmd);
} }
public void success() {
conn.success();
}
@Override @Override
public void close() throws Exception { public void close() throws Exception {
conn.close(); conn.close();
@@ -103,6 +108,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
waitForPass(() -> assertEquals(2, waitForPass(() -> assertEquals(2,
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()), tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
RUN_TIMEOUT_MS, RETRY_MS); RUN_TIMEOUT_MS, RETRY_MS);
conn.success();
} }
} }
@@ -165,6 +171,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
String threadIndex = threadIndex(traceManager.getCurrentObject()); String threadIndex = threadIndex(traceManager.getCurrentObject());
assertTrue(ti0.contains(threadIndex)); assertTrue(ti0.contains(threadIndex));
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
conn.success();
} }
} }
@@ -222,13 +229,12 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
RUN_TIMEOUT_MS, RETRY_MS); RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("kill"); conn.execute("kill");
conn.success();
} }
} }
@Test //@Test // Need a specimen
@Ignore
public void testOnSyscallMemory() throws Exception { public void testOnSyscallMemory() throws Exception {
// TODO: Need a specimen
// FWIW, I've already seen this getting exercised in other tests. // FWIW, I've already seen this getting exercised in other tests.
} }
@@ -248,6 +254,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
tb.trace.getMemoryManager().getBytes(lastSnap(conn), tb.addr(address), buf); tb.trace.getMemoryManager().getBytes(lastSnap(conn), tb.addr(address), buf);
assertEquals(0x7f, buf.get(0)); assertEquals(0x7f, buf.get(0));
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
conn.success();
} }
} }
@@ -270,6 +277,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
regs.getValue(lastSnap(conn), tb.reg(PLAT.intReg())) regs.getValue(lastSnap(conn), tb.reg(PLAT.intReg()))
.getUnsignedValue() .getUnsignedValue()
.toString(16))); .toString(16)));
conn.success();
} }
} }
@@ -287,6 +295,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("process interrupt"); conn.execute("process interrupt");
conn.success();
} }
} }
@@ -299,6 +308,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
waitForPass(() -> { waitForPass(() -> {
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state")); assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
conn.success();
} }
} }
@@ -321,6 +331,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
assertThat(val, instanceOf(Number.class)); assertThat(val, instanceOf(Number.class));
assertEquals(72, ((Number) val).longValue()); assertEquals(72, ((Number) val).longValue());
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
conn.success();
} }
} }
@@ -338,6 +349,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
assertEquals(1, brks.size()); assertEquals(1, brks.size());
return (TraceObject) brks.get(0); return (TraceObject) brks.get(0);
}); });
conn.success();
} }
} }
@@ -367,6 +379,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
waitForPass( waitForPass(
() -> assertEquals("x>3", tb.objValue(brk, lastSnap(conn), "Condition"))); () -> assertEquals("x>3", tb.objValue(brk, lastSnap(conn), "Condition")));
conn.success();
} }
} }
@@ -392,6 +405,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
waitForPass( waitForPass(
() -> assertEquals(0, () -> assertEquals(0,
tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size())); tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()));
conn.success();
} }
} }
@@ -407,5 +421,4 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
conn.execute("ghidra trace put-" + obj); conn.execute("ghidra trace put-" + obj);
conn.execute("ghidra trace tx-commit"); conn.execute("ghidra trace tx-commit");
} }
} }
@@ -15,6 +15,7 @@
*/ */
package agent.lldb.rmi; package agent.lldb.rmi;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
@@ -55,6 +56,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
assertEquals(false, execute.parameters().get("to_string").getDefaultValue()); assertEquals(false, execute.parameters().get("to_string").getDefaultValue());
assertEquals("test\n", assertEquals("test\n",
execute.invoke(Map.of("cmd", "script print('test')", "to_string", true))); execute.invoke(Map.of("cmd", "script print('test')", "to_string", true)));
conn.success();
} }
} }
@@ -63,6 +65,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, getSpecimenPrint()); start(conn, getSpecimenPrint());
conn.execute("kill"); conn.execute("kill");
conn.success();
} }
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
// Just confirm it's present // Just confirm it's present
@@ -89,6 +92,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
.toList(); .toList();
assertThat(list.size(), greaterThan(2)); assertThat(list.size(), greaterThan(2));
} }
conn.success();
} }
} }
@@ -128,6 +132,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
Set.of(TraceBreakpointKind.HW_EXECUTE), Set.of(TraceBreakpointKind.HW_EXECUTE),
"main"); "main");
} }
conn.success();
} }
} }
@@ -177,6 +182,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
Set.of(TraceBreakpointKind.READ, TraceBreakpointKind.WRITE), Set.of(TraceBreakpointKind.READ, TraceBreakpointKind.WRITE),
"main+0x30"); "main+0x30");
} }
conn.success();
} }
} }
@@ -201,6 +207,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
.toList(); .toList();
assertEquals(1, list.size()); assertEquals(1, list.size());
} }
conn.success();
} }
} }
@@ -223,6 +230,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
assertLocalOs(env.getValue(0, "_os").castValue()); assertLocalOs(env.getValue(0, "_os").castValue());
assertEquals(PLAT.endian(), env.getValue(0, "_endian").getValue()); assertEquals(PLAT.endian(), env.getValue(0, "_endian").getValue());
} }
conn.success();
} }
} }
@@ -243,6 +251,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
// Would be nice to control / validate the specifics // Would be nice to control / validate the specifics
Unique.assertOne(tb.trace.getThreadManager().getAllThreads()); Unique.assertOne(tb.trace.getThreadManager().getAllThreads());
} }
conn.success();
} }
} }
@@ -273,6 +282,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
.toList(); .toList();
assertTrue(list.size() > 1); assertTrue(list.size() > 1);
} }
conn.success();
} }
} }
@@ -302,6 +312,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
// LLDB treats registers in arch's endian // LLDB treats registers in arch's endian
assertEquals("deadbeef", intRegVal.getUnsignedValue().toString(16)); assertEquals("deadbeef", intRegVal.getUnsignedValue().toString(16));
} }
conn.success();
} }
} }
@@ -324,6 +335,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
tb.trace.getMemoryManager().getAllRegions(); tb.trace.getMemoryManager().getAllRegions();
assertThat(all.size(), greaterThan(2)); assertThat(all.size(), greaterThan(2));
} }
conn.success();
} }
} }
@@ -347,6 +359,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
all.stream().filter(m -> m.getName(SNAP).contains("expPrint"))); all.stream().filter(m -> m.getName(SNAP).contains("expPrint")));
assertNotEquals(tb.addr(0), Objects.requireNonNull(modExpPrint.getBase(SNAP))); assertNotEquals(tb.addr(0), Objects.requireNonNull(modExpPrint.getBase(SNAP)));
} }
conn.success();
} }
} }
@@ -388,6 +401,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
.or(containsString("tid = 0x%x".formatted(index)))); .or(containsString("tid = 0x%x".formatted(index))));
} }
} }
conn.success();
} }
} }
@@ -421,6 +435,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
assertThat(out, containsString("#%s".formatted(level))); assertThat(out, containsString("#%s".formatted(level)));
} }
} }
conn.success();
} }
} }
@@ -440,6 +455,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
String out = conn.executeCapture("target list"); String out = conn.executeCapture("target list");
assertThat(out, containsString("No targets")); assertThat(out, containsString("No targets"));
} }
conn.success();
} }
} }
@@ -466,6 +482,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
String out = conn.executeCapture("target list"); String out = conn.executeCapture("target list");
assertThat(out, containsString("pid=%d".formatted(dproc.pid))); assertThat(out, containsString("pid=%d".formatted(dproc.pid)));
} }
conn.success();
} }
} }
} }
@@ -490,6 +507,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
String out = conn.executeCapture("target list"); String out = conn.executeCapture("target list");
assertThat(out, containsString("pid=%d".formatted(dproc.pid))); assertThat(out, containsString("pid=%d".formatted(dproc.pid)));
} }
conn.success();
} }
} }
} }
@@ -512,6 +530,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
//assertThat(out, containsString("pid=%d".formatted(dproc.pid))); //assertThat(out, containsString("pid=%d".formatted(dproc.pid)));
assertThat(out, containsString("detached")); assertThat(out, containsString("detached"));
} }
conn.success();
} }
} }
@@ -534,6 +553,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
String out = conn.executeCapture("target list"); String out = conn.executeCapture("target list");
assertThat(out, containsString(getSpecimenPrint())); assertThat(out, containsString(getSpecimenPrint()));
} }
conn.success();
} }
} }
@@ -565,6 +585,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
String out = conn.executeCapture("bt"); String out = conn.executeCapture("bt");
assertThat(out, containsString("read")); assertThat(out, containsString("read"));
} }
conn.success();
} }
} }
@@ -585,6 +606,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
String out = conn.executeCapture("target list"); String out = conn.executeCapture("target list");
assertThat(out, containsString("exited")); assertThat(out, containsString("exited"));
} }
conn.success();
} }
} }
@@ -648,6 +670,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0); FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0);
assertEquals(instr.target, pc.value); assertEquals(instr.target, pc.value);
} }
conn.success();
} }
} }
@@ -677,6 +700,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0); FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0);
assertEquals(instr.next, pc.value); assertEquals(instr.next, pc.value);
} }
conn.success();
} }
} }
@@ -706,6 +730,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0); FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0);
assertEquals(addr.value, pc); assertEquals(addr.value, pc);
} }
conn.success();
} }
} }
@@ -736,6 +761,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
int finalDepth = getDepth(conn); int finalDepth = getDepth(conn);
assertEquals(initDepth - 1, finalDepth); assertEquals(initDepth - 1, finalDepth);
} }
conn.success();
} }
} }
@@ -766,6 +792,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
int finalDepth = getDepth(conn); int finalDepth = getDepth(conn);
assertEquals(initDepth - 1, finalDepth); assertEquals(initDepth - 1, finalDepth);
} }
conn.success();
} }
} }
@@ -787,6 +814,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
assertThat(out, containsString("main")); assertThat(out, containsString("main"));
assertThat(out, containsString(Long.toHexString(address))); assertThat(out, containsString(Long.toHexString(address)));
} }
conn.success();
} }
} }
@@ -806,6 +834,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
String out = conn.executeCapture("breakpoint list"); String out = conn.executeCapture("breakpoint list");
assertThat(out, containsString("main")); assertThat(out, containsString("main"));
} }
conn.success();
} }
} }
@@ -828,6 +857,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
String out = conn.executeCapture("breakpoint list"); String out = conn.executeCapture("breakpoint list");
assertThat(out, containsString(Long.toHexString(address))); assertThat(out, containsString(Long.toHexString(address)));
} }
conn.success();
} }
} }
@@ -849,6 +879,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
//NB: a little odd that this isn't in hex //NB: a little odd that this isn't in hex
assertThat(out, containsString(Long.toString(address))); assertThat(out, containsString(Long.toString(address)));
} }
conn.success();
} }
} }
@@ -875,6 +906,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
assertThat(out, containsString("size = 1")); assertThat(out, containsString("size = 1"));
assertThat(out, containsString("type = r")); assertThat(out, containsString("type = r"));
} }
conn.success();
} }
} }
@@ -897,6 +929,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
assertThat(out, containsString(Long.toHexString(address))); assertThat(out, containsString(Long.toHexString(address)));
assertThat(out, containsString("type = r")); assertThat(out, containsString("type = r"));
} }
conn.success();
} }
} }
@@ -925,6 +958,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
containsString("type = w"), containsString("type = w"),
containsString("type = m"))); containsString("type = m")));
} }
conn.success();
} }
} }
@@ -949,6 +983,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
containsString("type = w"), containsString("type = w"),
containsString("type = m"))); containsString("type = m")));
} }
conn.success();
} }
} }
@@ -975,6 +1010,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
assertThat(out, containsString("size = 1")); assertThat(out, containsString("size = 1"));
assertThat(out, containsString("type = rw")); assertThat(out, containsString("type = rw"));
} }
conn.success();
} }
} }
@@ -997,6 +1033,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
assertThat(out, containsString(Long.toHexString(address))); assertThat(out, containsString(Long.toHexString(address)));
assertThat(out, containsString("type = rw")); assertThat(out, containsString("type = rw"));
} }
conn.success();
} }
} }
@@ -1017,6 +1054,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
assertThat(out, containsString("Exception")); assertThat(out, containsString("Exception"));
assertThat(out, containsString("__cxa_throw")); assertThat(out, containsString("__cxa_throw"));
} }
conn.success();
} }
} }
@@ -1040,6 +1078,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
String out = conn.executeCapture("breakpoint list"); String out = conn.executeCapture("breakpoint list");
assertThat(out, containsString("disabled")); assertThat(out, containsString("disabled"));
} }
conn.success();
} }
} }
@@ -1064,6 +1103,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
String out = conn.executeCapture("breakpoint list"); String out = conn.executeCapture("breakpoint list");
assertThat(out, containsString("disabled")); assertThat(out, containsString("disabled"));
} }
conn.success();
} }
} }
@@ -1087,6 +1127,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
String out = conn.executeCapture("breakpoint list"); String out = conn.executeCapture("breakpoint list");
assertThat(out, containsString("No breakpoints")); assertThat(out, containsString("No breakpoints"));
} }
conn.success();
} }
} }
@@ -1117,6 +1158,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
out = conn.executeCapture("watchpoint list"); out = conn.executeCapture("watchpoint list");
assertThat(out, containsString("No watchpoints")); assertThat(out, containsString("No watchpoints"));
} }
conn.success();
} }
} }