GP-3872: Port scripting API to Trace RMI

This commit is contained in:
Dan 2024-03-25 15:20:38 -04:00
parent 77923fa693
commit ad6cb5892d
32 changed files with 3135 additions and 2063 deletions

View file

@ -127,6 +127,8 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
protected TestTraceRmiConnection rmiCx;
protected TestRemoteMethod rmiMethodExecute;
protected TestRemoteMethod rmiMethodResume;
protected TestRemoteMethod rmiMethodInterrupt;
protected TestRemoteMethod rmiMethodKill;
@ -145,41 +147,53 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
protected TestRemoteMethod rmiMethodReadRegs;
protected TestRemoteMethod rmiMethodWriteReg;
protected TestRemoteMethod rmiMethodReadMem;
protected TestRemoteMethod rmiMethodWriteMem;
protected void createRmiConnection() {
rmiCx = new TestTraceRmiConnection();
}
protected void addExecuteMethod() {
rmiMethodExecute = new TestRemoteMethod("execute", ActionName.EXECUTE, "Execute",
"Execut a CLI command", EnumerableTargetObjectSchema.STRING,
new TestRemoteParameter("cmd", EnumerableTargetObjectSchema.STRING, true, null,
"Command", "The command to execute"),
new TestRemoteParameter("to_string", EnumerableTargetObjectSchema.BOOL, true, false,
"To String", "Capture output to string"));
rmiCx.getMethods().add(rmiMethodExecute);
}
protected void addControlMethods() {
rmiMethodResume = new TestRemoteMethod("resume", ActionName.RESUME, "Resume",
"Resume the target", EnumerableTargetObjectSchema.VOID.getName(),
"Resume the target", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
"The process to resume"));
rmiMethodInterrupt = new TestRemoteMethod("interrupt", ActionName.INTERRUPT, "Interrupt",
"Interrupt the target", EnumerableTargetObjectSchema.VOID.getName(),
"Interrupt the target", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
"The process to interrupt"));
rmiMethodKill = new TestRemoteMethod("kill", ActionName.KILL, "Kill",
"Kill the target", EnumerableTargetObjectSchema.VOID.getName(),
"Kill the target", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
"The process to kill"));
rmiMethodStepInto = new TestRemoteMethod("step_into", ActionName.STEP_INTO, "Step Into",
"Step the thread, descending into subroutines",
EnumerableTargetObjectSchema.VOID.getName(),
"Step the thread, descending into subroutines", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
"The thread to step"));
rmiMethodStepOver = new TestRemoteMethod("step_over", ActionName.STEP_OVER, "Step Over",
"Step the thread, without descending into subroutines",
EnumerableTargetObjectSchema.VOID.getName(),
EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
"The thread to step"));
rmiMethodStepOut = new TestRemoteMethod("step_out", ActionName.STEP_OUT, "Step Out",
"Allow the thread to finish the current subroutine",
EnumerableTargetObjectSchema.VOID.getName(),
"Allow the thread to finish the current subroutine", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
"The thread to step"));
@ -194,56 +208,51 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
protected void addBreakpointMethods() {
rmiMethodSetHwBreak = new TestRemoteMethod("set_hw_break", ActionName.BREAK_HW_EXECUTE,
"Hardware Breakpoint",
"Place a hardware execution breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
"Hardware Breakpoint", "Place a hardware execution breakpoint",
EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
"The process in which to place the breakpoint"),
new TestRemoteParameter("address", EnumerableTargetObjectSchema.ADDRESS.getName(), true,
new TestRemoteParameter("address", EnumerableTargetObjectSchema.ADDRESS, true,
null, "Address", "The desired address"));
rmiMethodSetSwBreak = new TestRemoteMethod("set_sw_break", ActionName.BREAK_SW_EXECUTE,
"Software Breakpoint",
"Place a software execution breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
"Software Breakpoint", "Place a software execution breakpoint",
EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
"The process in which to place the breakpoint"),
new TestRemoteParameter("address", EnumerableTargetObjectSchema.ADDRESS.getName(), true,
new TestRemoteParameter("address", EnumerableTargetObjectSchema.ADDRESS, true,
null, "Address", "The desired address"));
rmiMethodSetReadBreak = new TestRemoteMethod("set_read_break", ActionName.BREAK_READ,
"Read Breakpoint",
"Place a read breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
"Read Breakpoint", "Place a read breakpoint", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
"The process in which to place the breakpoint"),
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE.getName(), true,
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE, true,
null, "Range", "The desired address range"));
rmiMethodSetWriteBreak = new TestRemoteMethod("set_write_break", ActionName.BREAK_WRITE,
"Write Breakpoint",
"Place a write breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
"Write Breakpoint", "Place a write breakpoint", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
"The process in which to place the breakpoint"),
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE.getName(), true,
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE, true,
null, "Range", "The desired address range"));
rmiMethodSetAccessBreak = new TestRemoteMethod("set_acc_break", ActionName.BREAK_ACCESS,
"Access Breakpoint",
"Place an access breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
"Access Breakpoint", "Place an access breakpoint", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
"The process in which to place the breakpoint"),
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE.getName(), true,
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE, true,
null, "Range", "The desired address range"));
rmiMethodToggleBreak = new TestRemoteMethod("toggle_break", ActionName.TOGGLE,
"Toggle Breakpoint",
"Toggle a breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
"Toggle Breakpoint", "Toggle a breakpoint", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("breakpoint", new SchemaName("BreakpointSpec"), true, null,
"Breakpoint", "The breakpoint to toggle"),
new TestRemoteParameter("enabled", EnumerableTargetObjectSchema.BOOL.getName(), true,
new TestRemoteParameter("enabled", EnumerableTargetObjectSchema.BOOL, true,
null, "Enable", "True to enable. False to disable"));
rmiMethodDeleteBreak = new TestRemoteMethod("delete_break", ActionName.DELETE,
"Delete Breakpoint",
"Delete a breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
"Delete Breakpoint", "Delete a breakpoint", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("breakpoint", new SchemaName("BreakpointSpec"), true, null,
"Breakpoint", "The breakpoint to delete"));
@ -259,17 +268,17 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
protected void addRegisterMethods() {
rmiMethodReadRegs = new TestRemoteMethod("read_regs", ActionName.REFRESH, "Read Registers",
"Read registers", EnumerableTargetObjectSchema.VOID.getName(),
"Read registers", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("container", new SchemaName("RegisterContainer"), true, null,
"Registers", "The registers node to read"));
rmiMethodWriteReg = new TestRemoteMethod("write_reg", ActionName.WRITE_REG,
"Write Register", "Write a register", EnumerableTargetObjectSchema.VOID.getName(),
"Write Register", "Write a register", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("frame", new SchemaName("Frame"), false, 0, "Frame",
"The frame to write to"),
new TestRemoteParameter("name", EnumerableTargetObjectSchema.STRING.getName(), true,
new TestRemoteParameter("name", EnumerableTargetObjectSchema.STRING, true,
null, "Register", "The name of the register to write"),
new TestRemoteParameter("value", EnumerableTargetObjectSchema.BYTE_ARR.getName(), true,
new TestRemoteParameter("value", EnumerableTargetObjectSchema.BYTE_ARR, true,
null, "Value", "The desired value"));
TestRemoteMethodRegistry reg = rmiCx.getMethods();
@ -277,6 +286,28 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
reg.add(rmiMethodWriteReg);
}
protected void addMemoryMethods() {
rmiMethodReadMem = new TestRemoteMethod("read_mem", ActionName.READ_MEM, "Read Memory",
"Read memory", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("process", new SchemaName("Process"), true, null,
"Process", "The process whose memory to read"),
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE, true, null,
"Range", "The address range to read"));
rmiMethodWriteMem = new TestRemoteMethod("write_mem", ActionName.WRITE_MEM, "Write Memory",
"Write memory", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("process", new SchemaName("Process"), true, null,
"Process", "The process whose memory to read"),
new TestRemoteParameter("start", EnumerableTargetObjectSchema.ADDRESS, true, null,
"Start", "The address to start writing"),
new TestRemoteParameter("data", EnumerableTargetObjectSchema.BYTE_ARR, true, null,
"Data", "The data to write"));
TestRemoteMethodRegistry reg = rmiCx.getMethods();
reg.add(rmiMethodReadMem);
reg.add(rmiMethodWriteMem);
}
protected TraceObject addMemoryRegion(TraceObjectManager objs, Lifespan lifespan,
AddressRange range, String name, String flags) {
String pathStr =

View file

@ -0,0 +1,94 @@
/* ###
* 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 ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import static org.junit.Assert.assertEquals;
import java.net.SocketAddress;
import java.util.*;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.debug.api.tracermi.TerminalSession;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
import ghidra.program.model.listing.Program;
import ghidra.util.task.TaskMonitor;
public class TestTraceRmiLaunchOpinion implements TraceRmiLaunchOpinion {
public static class TestTraceRmiLaunchOffer extends AbstractTraceRmiLaunchOffer {
private static final ParameterDescription<String> PARAM_DESC_IMAGE =
ParameterDescription.create(String.class, "image", true, "",
PARAM_DISPLAY_IMAGE, "Image to execute");
public TestTraceRmiLaunchOffer(TraceRmiLauncherServicePlugin plugin, Program program) {
super(plugin, program);
}
public Program getProgram() {
return program;
}
@Override
public String getConfigName() {
return "TEST";
}
@Override
public String getTitle() {
return "Test";
}
@Override
public String getDescription() {
return "Test launch offer";
}
@Override
public Map<String, ParameterDescription<?>> getParameters() {
return Map.ofEntries(Map.entry(PARAM_DESC_IMAGE.name, PARAM_DESC_IMAGE));
}
@Override
public boolean requiresImage() {
return false;
}
@Override
protected void launchBackEnd(TaskMonitor monitor, Map<String, TerminalSession> sessions,
Map<String, ?> args, SocketAddress address) throws Exception {
}
@Override
public LaunchResult launchProgram(TaskMonitor monitor, LaunchConfigurator configurator) {
assertEquals(PromptMode.NEVER, configurator.getPromptMode());
Map<String, ?> args =
configurator.configureLauncher(this, loadLastLauncherArgs(false), RelPrompt.NONE);
return new LaunchResult(program, null, null, null, null,
new RuntimeException("Test launcher cannot launch " + args.get("image")));
}
public void saveLauncherArgs(Map<String, ?> args) {
super.saveLauncherArgs(args, getParameters());
}
}
@Override
public Collection<TraceRmiLaunchOffer> getOffers(TraceRmiLauncherServicePlugin plugin,
Program program) {
return List.of(new TestTraceRmiLaunchOffer(plugin, program));
}
}

View file

@ -0,0 +1,293 @@
/* ###
* 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 ghidra.debug.flatapi;
import static org.junit.Assert.*;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.junit.Before;
import org.junit.Test;
import db.Transaction;
import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerIntegrationTest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin;
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin;
import ghidra.app.services.*;
import ghidra.debug.api.control.ControlMode;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.thread.TraceThread;
public abstract class AbstractFlatDebuggerAPITest<API extends FlatDebuggerAPI>
extends AbstractGhidraHeadedDebuggerIntegrationTest {
protected DebuggerLogicalBreakpointService breakpointService;
protected DebuggerStaticMappingService mappingService;
protected DebuggerEmulationService emulationService;
protected DebuggerListingService listingService;
protected DebuggerControlService editingService;
protected API api;
protected abstract API newFlatAPI();
@Before
public void setUpFlatAPITest() throws Throwable {
breakpointService = addPlugin(tool, DebuggerLogicalBreakpointServicePlugin.class);
mappingService = tool.getService(DebuggerStaticMappingService.class);
emulationService = addPlugin(tool, DebuggerEmulationServicePlugin.class);
listingService = addPlugin(tool, DebuggerListingPlugin.class);
editingService = addPlugin(tool, DebuggerControlServicePlugin.class);
api = newFlatAPI();
// TODO: This seems to hold up the task manager.
waitForComponentProvider(DebuggerListingProvider.class).setAutoDisassemble(false);
}
@Override
protected void createProgram(Language lang, CompilerSpec cSpec) throws IOException {
super.createProgram(lang, cSpec);
api.getState().setCurrentProgram(program);
}
protected TraceThread createTraceWithThreadAndStack(boolean open) throws Throwable {
if (open) {
createAndOpenTrace();
}
else {
createTrace();
}
TraceThread thread;
try (Transaction tx = tb.startTransaction()) {
thread = tb.getOrAddThread("Threads[0]", 0);
TraceStack stack = tb.trace.getStackManager().getStack(thread, 0, true);
stack.setDepth(3, true);
}
waitForSwing();
return thread;
}
protected void createTraceWithBinText() throws Throwable {
createAndOpenTrace();
try (Transaction tx = tb.startTransaction()) {
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
mm.createRegion("Memory[bin.text]", 0, tb.range(0x00400000, 0x0040ffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
mm.putBytes(0, tb.addr(0x00400000), tb.buf(1, 2, 3, 4, 5, 6, 7, 8));
}
traceManager.activateTrace(tb.trace);
waitForSwing();
}
protected void createMappedTraceAndProgram() throws Throwable {
createAndOpenTrace();
createProgramFromTrace();
intoProject(program);
intoProject(tb.trace);
programManager.openProgram(program);
traceManager.activateTrace(tb.trace);
try (Transaction tx = program.openTransaction("add block")) {
program.getMemory()
.createInitializedBlock(".text", addr(program, 0x00400000), 4096, (byte) 0,
monitor, false);
}
CompletableFuture<Void> changesSettled;
try (Transaction tx = tb.startTransaction()) {
tb.trace.getMemoryManager()
.createRegion("Memory[bin.text]", 0, tb.range(0x00400000, 0x00400fff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
changesSettled = mappingService.changesSettled();
mappingService.addIdentityMapping(tb.trace, program, Lifespan.nowOn(0), true);
}
waitForSwing();
waitOn(changesSettled);
}
protected Address createEmulatableProgram() throws Throwable {
createProgram();
programManager.openProgram(program);
Address entry = addr(program, 0x00400000);
try (Transaction start = program.openTransaction("init")) {
program.getMemory()
.createInitializedBlock(".text", entry, 4096, (byte) 0,
monitor, false);
Assembler asm = Assemblers.getAssembler(program);
asm.assemble(entry, "imm r0,#123");
}
// Emulate launch will create a static mapping
intoProject(program);
return entry;
}
@Test
public void testReadMemoryBuffer() throws Throwable {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
byte[] data = new byte[1024];
assertEquals(1024, api.readMemory(tb.addr(0x00400000), data, monitor));
assertArrayEquals(new byte[1024], data);
}
@Test
public void testReadMemoryLength() throws Throwable {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
byte[] data = api.readMemory(tb.addr(0x00400000), 1024, monitor);
assertArrayEquals(new byte[1024], data);
}
@Test
public void testReadRegister() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(true);
traceManager.activateThread(thread);
Register r0 = tb.language.getRegister("r0");
assertEquals(new RegisterValue(r0), api.readRegister("r0"));
}
@Test(expected = IllegalArgumentException.class)
public void testReadRegisterInvalidNameErr() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(true);
traceManager.activateThread(thread);
api.readRegister("THERE_IS_NO_SUCH_REGISTER");
}
@Test
public void testReadRegisters() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(true);
traceManager.activateThread(thread);
waitForSwing();
Register r0 = tb.language.getRegister("r0");
Register r1 = tb.language.getRegister("r1");
assertEquals(List.of(
new RegisterValue(r0),
new RegisterValue(r1)),
api.readRegistersNamed(List.of("r0", "r1")));
}
@Test(expected = IllegalArgumentException.class)
public void testReadRegistersInvalidNameErr() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(true);
traceManager.activateThread(thread);
api.readRegistersNamed(Set.of("THERE_IS_NO_SUCH_REGISTER"));
}
@Test
public void testWriteMemoryGivenContext() throws Throwable {
createTraceWithBinText();
editingService.setCurrentMode(tb.trace, ControlMode.RW_TRACE);
assertTrue(api.writeMemory(tb.trace, 0, tb.addr(0x00400123), tb.arr(3, 2, 1)));
ByteBuffer buf = ByteBuffer.allocate(3);
assertEquals(3, tb.trace.getMemoryManager().getBytes(0, tb.addr(0x00400123), buf));
assertArrayEquals(tb.arr(3, 2, 1), buf.array());
}
@Test
public void testWriteMemoryCurrentContext() throws Throwable {
createTraceWithBinText();
editingService.setCurrentMode(tb.trace, ControlMode.RW_TRACE);
assertTrue(api.writeMemory(tb.addr(0x00400123), tb.arr(3, 2, 1)));
ByteBuffer buf = ByteBuffer.allocate(3);
assertEquals(3, tb.trace.getMemoryManager().getBytes(0, tb.addr(0x00400123), buf));
assertArrayEquals(tb.arr(3, 2, 1), buf.array());
}
@Test
public void testWriteRegisterGivenContext() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(true);
editingService.setCurrentMode(tb.trace, ControlMode.RW_TRACE);
traceManager.activateThread(thread);
waitForSwing();
assertTrue(api.writeRegister(thread, 0, 0, "r0", BigInteger.valueOf(0x0102030405060708L)));
DBTraceMemorySpace regs =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
assertNotNull(regs);
Register r0 = tb.language.getRegister("r0");
assertEquals(new RegisterValue(r0, BigInteger.valueOf(0x0102030405060708L)),
regs.getValue(0, r0));
}
@Test
public void testWriteRegisterCurrentContext() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(true);
editingService.setCurrentMode(tb.trace, ControlMode.RW_TRACE);
traceManager.activateThread(thread);
waitForSwing();
assertTrue(api.writeRegister("r0", BigInteger.valueOf(0x0102030405060708L)));
DBTraceMemorySpace regs =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
assertNotNull(regs);
Register r0 = tb.language.getRegister("r0");
assertEquals(new RegisterValue(r0, BigInteger.valueOf(0x0102030405060708L)),
regs.getValue(0, r0));
}
protected void createProgramWithText() throws Throwable {
createProgram();
programManager.openProgram(program);
waitForSwing();
try (Transaction tx = program.openTransaction("Add block")) {
program.getMemory()
.createInitializedBlock(
".text", addr(program, 0x00400000), 1024, (byte) 0, monitor, false);
}
}
protected void createProgramWithBreakpoint() throws Throwable {
createProgramWithText();
CompletableFuture<Void> changesSettled = breakpointService.changesSettled();
waitOn(breakpointService.placeBreakpointAt(program, addr(program, 0x00400000), 1,
Set.of(TraceBreakpointKind.SW_EXECUTE), "name"));
waitForSwing();
waitOn(changesSettled);
}
}

View file

@ -0,0 +1,101 @@
/* ###
* 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 ghidra.debug.flatapi;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import org.junit.Test;
import ghidra.app.script.GhidraState;
public abstract class AbstractLiveFlatDebuggerAPITest<API extends FlatDebuggerAPI>
extends AbstractFlatDebuggerAPITest<API> {
protected class TestFlatAPI implements FlatDebuggerAPI {
protected final GhidraState state =
new GhidraState(env.getTool(), env.getProject(), program, null, null, null);
@Override
public GhidraState getState() {
return state;
}
}
protected abstract void runTestResume(BooleanSupplier resume) throws Throwable;
@Test
public void testResumeGivenThread() throws Throwable {
runTestResume(() -> api.resume(api.getCurrentThread()));
}
@Test
public void testResumeGivenTrace() throws Throwable {
runTestResume(() -> api.resume(api.getCurrentTrace()));
}
@Test
public void testResume() throws Throwable {
runTestResume(api::resume);
}
protected abstract void runTestInterrupt(BooleanSupplier interrupt) throws Throwable;
@Test
public void testInterruptGivenThread() throws Throwable {
runTestInterrupt(() -> api.interrupt(api.getCurrentThread()));
}
@Test
public void testInterruptGivenTrace() throws Throwable {
runTestInterrupt(() -> api.interrupt(api.getCurrentTrace()));
}
@Test
public void testInterrupt() throws Throwable {
runTestInterrupt(api::interrupt);
}
protected abstract void runTestKill(BooleanSupplier kill) throws Throwable;
@Test
public void testKillGivenThread() throws Throwable {
runTestKill(() -> api.kill(api.getCurrentThread()));
}
@Test
public void testKillGivenTrace() throws Throwable {
runTestKill(() -> api.kill(api.getCurrentTrace()));
}
@Test
public void testKill() throws Throwable {
runTestKill(api::kill);
}
protected abstract void runTestExecuteCapture(Function<String, String> executeCapture)
throws Throwable;
@Test
public void testExecuteCaptureGivenTrace() throws Throwable {
runTestExecuteCapture(cmd -> api.executeCapture(api.getCurrentTrace(), cmd));
}
@Test
public void testExecuteCapture() throws Throwable {
runTestExecuteCapture(api::executeCapture);
}
}

View file

@ -0,0 +1,539 @@
/* ###
* 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 ghidra.debug.flatapi;
import static org.junit.Assert.*;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.junit.Test;
import db.Transaction;
import generic.Unique;
import ghidra.app.script.GhidraState;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.breakpoint.LogicalBreakpoint;
import ghidra.debug.api.breakpoint.LogicalBreakpoint.State;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.program.model.address.Address;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule;
public class DeadFlatDebuggerAPITest extends AbstractFlatDebuggerAPITest<FlatDebuggerAPI> {
protected class TestFlatAPI implements FlatDebuggerAPI {
protected final GhidraState state =
new GhidraState(env.getTool(), env.getProject(), program, null, null, null);
@Override
public GhidraState getState() {
return state;
}
}
@Override
protected FlatDebuggerAPI newFlatAPI() {
return new TestFlatAPI();
}
@Test
public void testRequireService() throws Throwable {
assertEquals(traceManager, api.requireService(DebuggerTraceManagerService.class));
}
interface NoSuchService {
}
@Test(expected = IllegalStateException.class)
public void testRequireServiceAbsentErr() {
api.requireService(NoSuchService.class);
}
@Test
public void testGetCurrentDebuggerCoordinates() throws Throwable {
assertSame(DebuggerCoordinates.NOWHERE, api.getCurrentDebuggerCoordinates());
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
assertEquals(DebuggerCoordinates.NOWHERE.trace(tb.trace),
api.getCurrentDebuggerCoordinates());
}
@Test
public void testGetCurrentTrace() throws Throwable {
assertNull(api.getCurrentTrace());
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
assertEquals(tb.trace, api.getCurrentTrace());
}
@Test(expected = IllegalStateException.class)
public void testRequireCurrentTraceAbsentErr() {
api.requireCurrentTrace();
}
@Test
public void testGetCurrentThread() throws Throwable {
assertNull(api.getCurrentThread());
createAndOpenTrace();
TraceThread thread;
try (Transaction tx = tb.startTransaction()) {
thread = tb.getOrAddThread("Threads[0]", 0);
}
waitForSwing();
traceManager.activateTrace(tb.trace);
assertEquals(thread, api.getCurrentThread());
}
@Test
public void testGetCurrentView() throws Throwable {
assertNull(api.getCurrentView());
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
assertEquals(tb.trace.getProgramView(), api.getCurrentView());
}
@Test(expected = IllegalStateException.class)
public void testRequireCurrentViewAbsentErr() {
api.requireCurrentView();
}
@Test
public void testGetCurrentFrame() throws Throwable {
assertEquals(0, api.getCurrentFrame());
createAndOpenTrace();
TraceThread thread;
try (Transaction tx = tb.startTransaction()) {
thread = tb.getOrAddThread("Threads[0]", 0);
TraceStack stack = tb.trace.getStackManager().getStack(thread, 0, true);
stack.setDepth(3, true);
}
waitForSwing();
traceManager.activateThread(thread);
traceManager.activateFrame(1);
assertEquals(1, api.getCurrentFrame());
}
@Test
public void testGetCurrentSnap() throws Throwable {
assertEquals(0L, api.getCurrentSnap());
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
traceManager.activateSnap(1);
assertEquals(1L, api.getCurrentSnap());
}
@Test
public void testGetCurrentEmulationSchedule() throws Throwable {
assertEquals(TraceSchedule.parse("0"), api.getCurrentEmulationSchedule());
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
traceManager.activateSnap(1);
assertEquals(TraceSchedule.parse("1"), api.getCurrentEmulationSchedule());
}
@Test
public void testActivateTrace() throws Throwable {
createAndOpenTrace();
api.activateTrace(tb.trace);
assertEquals(tb.trace, traceManager.getCurrentTrace());
}
@Test
public void testActivateTraceNull() throws Throwable {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals(tb.trace, traceManager.getCurrentTrace());
api.activateTrace(null);
assertEquals(null, traceManager.getCurrentTrace());
}
@Test
public void testActivateTraceNotOpen() throws Throwable {
createTrace();
assertFalse(traceManager.getOpenTraces().contains(tb.trace));
api.activateTrace(tb.trace);
assertTrue(traceManager.getOpenTraces().contains(tb.trace));
assertEquals(tb.trace, traceManager.getCurrentTrace());
}
@Test
public void testGetCurrentProgram() throws Throwable {
assertEquals(null, api.getCurrentProgram());
createProgram();
programManager.openProgram(program);
assertEquals(program, api.getCurrentProgram());
}
@Test(expected = IllegalStateException.class)
public void testRequireCurrentProgramAbsentErr() throws Throwable {
api.requireCurrentProgram();
}
@Test
public void testActivateThread() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(true);
api.activateThread(thread);
assertEquals(thread, traceManager.getCurrentThread());
}
@Test
public void testActivateThreadNull() throws Throwable {
api.activateThread(null);
assertEquals(null, traceManager.getCurrentThread());
TraceThread thread = createTraceWithThreadAndStack(true);
traceManager.activateThread(thread);
waitForSwing();
assertEquals(thread, traceManager.getCurrentThread());
api.activateThread(null);
assertNull(traceManager.getCurrentThread());
}
@Test
public void testActivateThreadNotOpen() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(false);
assertFalse(traceManager.getOpenTraces().contains(tb.trace));
api.activateThread(thread);
assertTrue(traceManager.getOpenTraces().contains(tb.trace));
assertEquals(thread, traceManager.getCurrentThread());
}
@Test
public void testActivateFrame() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(true);
traceManager.activateThread(thread);
waitForSwing();
api.activateFrame(1);
assertEquals(1, traceManager.getCurrentFrame());
}
@Test
public void testActivateSnap() throws Throwable {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
waitForSwing();
api.activateSnap(1);
assertEquals(1L, traceManager.getCurrentSnap());
}
@Test
public void testGetCurrentDebuggerAddress() throws Throwable {
assertEquals(null, api.getCurrentDebuggerAddress());
createTraceWithBinText();
assertEquals(tb.addr(0x00400000), api.getCurrentDebuggerAddress());
}
@Test
public void testGoToDynamic() throws Throwable {
createTraceWithBinText();
assertTrue(api.goToDynamic("00400123"));
assertEquals(tb.addr(0x00400123), listingService.getCurrentLocation().getAddress());
assertTrue(api.goToDynamic(tb.addr(0x00400321)));
assertEquals(tb.addr(0x00400321), listingService.getCurrentLocation().getAddress());
}
@Test
public void testTranslateStaticToDynamic() throws Throwable {
createMappedTraceAndProgram();
assertEquals(api.dynamicLocation("00400123"),
api.translateStaticToDynamic(api.staticLocation("00400123")));
assertNull(api.translateStaticToDynamic(api.staticLocation("00600123")));
assertEquals(tb.addr(0x00400123), api.translateStaticToDynamic(addr(program, 0x00400123)));
assertNull(api.translateStaticToDynamic(addr(program, 0x00600123)));
}
@Test
public void testTranslateDynamicToStatic() throws Throwable {
createMappedTraceAndProgram();
assertEquals(api.staticLocation("00400123"),
api.translateDynamicToStatic(api.dynamicLocation("00400123")));
assertNull(api.translateDynamicToStatic(api.dynamicLocation("00600123")));
assertEquals(addr(program, 0x00400123), api.translateDynamicToStatic(tb.addr(0x00400123)));
assertNull(api.translateDynamicToStatic(tb.addr(0x00600123)));
}
@Test
public void testEmulateLaunch() throws Throwable {
Address entry = createEmulatableProgram();
Trace trace = api.emulateLaunch(entry);
assertEquals(trace, traceManager.getCurrentTrace());
}
@Test
public void testEmulate() throws Throwable {
Address entry = createEmulatableProgram();
api.emulateLaunch(entry);
TraceSchedule schedule =
traceManager.getCurrent().getTime().steppedForward(traceManager.getCurrentThread(), 1);
api.emulate(schedule, monitor);
assertEquals(schedule, traceManager.getCurrent().getTime());
}
@Test
public void testStepEmuInstruction() throws Throwable {
Address entry = createEmulatableProgram();
api.emulateLaunch(entry);
TraceSchedule schedule =
traceManager.getCurrent().getTime().steppedForward(traceManager.getCurrentThread(), 1);
api.stepEmuInstruction(1, monitor);
assertEquals(schedule, traceManager.getCurrent().getTime());
api.stepEmuInstruction(-1, monitor);
assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime());
}
@Test
public void testStepEmuPcodeOp() throws Throwable {
Address entry = createEmulatableProgram();
api.emulateLaunch(entry);
TraceSchedule schedule = traceManager.getCurrent()
.getTime()
.steppedPcodeForward(traceManager.getCurrentThread(), 1);
api.stepEmuPcodeOp(1, monitor);
assertEquals(schedule, traceManager.getCurrent().getTime());
api.stepEmuPcodeOp(-1, monitor);
assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime());
}
@Test
public void testSkipEmuInstruction() throws Throwable {
Address entry = createEmulatableProgram();
api.emulateLaunch(entry);
TraceSchedule schedule =
traceManager.getCurrent().getTime().skippedForward(traceManager.getCurrentThread(), 1);
api.skipEmuInstruction(1, monitor);
assertEquals(schedule, traceManager.getCurrent().getTime());
api.skipEmuInstruction(-1, monitor);
assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime());
}
@Test
public void testSkipEmuPcodeOp() throws Throwable {
Address entry = createEmulatableProgram();
api.emulateLaunch(entry);
TraceSchedule schedule = traceManager.getCurrent()
.getTime()
.skippedPcodeForward(traceManager.getCurrentThread(), 1);
api.skipEmuPcodeOp(1, monitor);
assertEquals(schedule, traceManager.getCurrent().getTime());
api.skipEmuPcodeOp(-1, monitor);
assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime());
}
@Test
public void testPatchEmu() throws Throwable {
Address entry = createEmulatableProgram();
api.emulateLaunch(entry);
TraceSchedule schedule = traceManager.getCurrent()
.getTime()
.patched(traceManager.getCurrentThread(),
traceManager.getCurrentPlatform().getLanguage(), "r0=0x321");
api.patchEmu("r0=0x321", monitor);
assertEquals(schedule, traceManager.getCurrent().getTime());
api.stepEmuInstruction(-1, monitor);
assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime());
}
@Test
public void testSearchMemory() throws Throwable {
createTraceWithBinText();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals(tb.addr(0x00400003), api.searchMemory(tb.trace, 2, tb.range(0L, -1L),
tb.arr(4, 5, 6, 7), null, true, monitor));
assertEquals(tb.addr(0x00400003), api.searchMemory(tb.trace, 2, tb.range(0L, -1L),
tb.arr(4, 5, 6, 7), tb.arr(-1, -1, -1, -1), true, monitor));
}
@Test
public void testGetAllBreakpoints() throws Throwable {
createProgramWithBreakpoint();
assertEquals(1, api.getAllBreakpoints().size());
}
@Test
public void testGetBreakpointsAt() throws Throwable {
createProgramWithBreakpoint();
assertEquals(1, api.getBreakpointsAt(api.staticLocation("00400000")).size());
assertEquals(0, api.getBreakpointsAt(api.staticLocation("00400001")).size());
}
@Test
public void testGetBreakpointsNamed() throws Throwable {
createProgramWithBreakpoint();
assertEquals(1, api.getBreakpointsNamed("name").size());
assertEquals(0, api.getBreakpointsNamed("miss").size());
}
@Test
public void testBreakpointsToggle() throws Throwable {
createProgramWithBreakpoint();
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
assertEquals(State.INEFFECTIVE_ENABLED, lb.computeState());
assertEquals(Set.of(lb), api.breakpointsToggle(api.staticLocation("00400000")));
assertEquals(State.INEFFECTIVE_DISABLED, lb.computeState());
}
@Test
public void testBreakpointSetSoftwareExecute() throws Throwable {
createProgramWithText();
LogicalBreakpoint lb = Unique.assertOne(
api.breakpointSetSoftwareExecute(api.staticLocation("00400000"), "name"));
assertEquals(addr(program, 0x00400000), lb.getAddress());
assertEquals(TraceBreakpointKindSet.SW_EXECUTE, lb.getKinds());
assertEquals(1, lb.getLength());
}
@Test
public void testBreakpointSetHardwareExecute() throws Throwable {
createProgramWithText();
LogicalBreakpoint lb = Unique.assertOne(
api.breakpointSetHardwareExecute(api.staticLocation("00400000"), "name"));
assertEquals(addr(program, 0x00400000), lb.getAddress());
assertEquals(TraceBreakpointKindSet.HW_EXECUTE, lb.getKinds());
assertEquals(1, lb.getLength());
}
@Test
public void testBreakpointSetRead() throws Throwable {
createProgramWithText();
LogicalBreakpoint lb = Unique.assertOne(
api.breakpointSetRead(api.staticLocation("00400000"), 4, "name"));
assertEquals(addr(program, 0x00400000), lb.getAddress());
assertEquals(TraceBreakpointKindSet.READ, lb.getKinds());
assertEquals(4, lb.getLength());
}
@Test
public void testBreakpointSetWrite() throws Throwable {
createProgramWithText();
LogicalBreakpoint lb = Unique.assertOne(
api.breakpointSetWrite(api.staticLocation("00400000"), 4, "name"));
assertEquals(addr(program, 0x00400000), lb.getAddress());
assertEquals(TraceBreakpointKindSet.WRITE, lb.getKinds());
assertEquals(4, lb.getLength());
}
@Test
public void testBreakpointSetAccess() throws Throwable {
createProgramWithText();
LogicalBreakpoint lb = Unique.assertOne(
api.breakpointSetAccess(api.staticLocation("00400000"), 4, "name"));
assertEquals(addr(program, 0x00400000), lb.getAddress());
assertEquals(TraceBreakpointKindSet.ACCESS, lb.getKinds());
assertEquals(4, lb.getLength());
}
@Test
public void testBreakpointsEnable() throws Throwable {
createProgramWithBreakpoint();
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
CompletableFuture<Void> changesSettled = breakpointService.changesSettled();
waitOn(lb.disable());
waitForSwing();
waitOn(changesSettled);
assertEquals(State.INEFFECTIVE_DISABLED, lb.computeState());
assertEquals(Set.of(lb), api.breakpointsEnable(api.staticLocation("00400000")));
assertEquals(State.INEFFECTIVE_ENABLED, lb.computeState());
}
@Test
public void testBreakpointsDisable() throws Throwable {
createProgramWithBreakpoint();
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
assertEquals(State.INEFFECTIVE_ENABLED, lb.computeState());
assertEquals(Set.of(lb), api.breakpointsDisable(api.staticLocation("00400000")));
assertEquals(State.INEFFECTIVE_DISABLED, lb.computeState());
}
@Test
public void testBreakpointsClear() throws Throwable {
createProgramWithBreakpoint();
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
assertTrue(api.breakpointsClear(api.staticLocation("00400000")));
assertTrue(lb.isEmpty());
assertEquals(0, breakpointService.getAllBreakpoints().size());
}
}

View file

@ -0,0 +1,502 @@
/* ###
* 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 ghidra.debug.flatapi;
import static org.junit.Assert.*;
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import org.junit.Test;
import generic.Unique;
import ghidra.app.plugin.core.debug.service.model.TestDebuggerProgramLaunchOpinion.TestDebuggerProgramLaunchOffer;
import ghidra.app.plugin.core.debug.service.model.launch.AbstractDebuggerProgramLaunchOffer;
import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.model.*;
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.debug.api.model.DebuggerProgramLaunchOffer;
import ghidra.debug.api.model.DebuggerProgramLaunchOffer.LaunchResult;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
public class FlatDebuggerRecorderAPITest
extends AbstractLiveFlatDebuggerAPITest<FlatDebuggerRecorderAPI> {
protected static class TestFactory implements DebuggerModelFactory {
private final DebuggerObjectModel model;
public TestFactory(DebuggerObjectModel model) {
this.model = model;
}
@Override
public CompletableFuture<? extends DebuggerObjectModel> build() {
return CompletableFuture.completedFuture(model);
}
}
protected class TestOffer extends AbstractDebuggerProgramLaunchOffer {
public TestOffer(Program program, DebuggerModelFactory factory) {
super(program, env.getTool(), factory);
}
public TestOffer(Program program, DebuggerObjectModel model) {
this(program, new TestFactory(model));
}
@Override
public String getConfigName() {
return "TEST";
}
@Override
public String getMenuTitle() {
return "in Test Debugger";
}
}
protected static class TestModelBuilder extends TestDebuggerModelBuilder {
private final TestDebuggerObjectModel model;
public TestModelBuilder(TestDebuggerObjectModel model) {
this.model = model;
}
@Override
protected TestDebuggerObjectModel newModel(String typeHint) {
return model;
}
}
protected class TestFlatRecorderAPI extends TestFlatAPI implements FlatDebuggerRecorderAPI {
}
@Override
protected FlatDebuggerRecorderAPI newFlatAPI() {
return new TestFlatRecorderAPI();
}
protected TraceRecorder recordTarget(TargetObject target)
throws LanguageNotFoundException, CompilerSpecNotFoundException, IOException {
return modelService.recordTargetAndActivateTrace(target,
new TestDebuggerTargetTraceMapper(target));
}
@Test
public void testReadLiveMemory() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
mb.testProcess1.memory.writeMemory(mb.addr(0x00400000), mb.arr(1, 2, 3, 4, 5, 6, 7, 8));
waitOn(mb.testModel.flushEvents());
TraceRecorder recorder = recordTarget(mb.testProcess1);
waitRecorder(recorder);
useTrace(recorder.getTrace());
waitForSwing();
byte[] data = api.readMemory(tb.addr(0x00400000), 8, monitor);
assertArrayEquals(tb.arr(1, 2, 3, 4, 5, 6, 7, 8), data);
}
@Test
public void testReadLiveRegister() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
mb.createTestThreadRegisterBanks();
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(), r -> true);
mb.testBank1.writeRegister("r0", mb.arr(1, 2, 3, 4, 5, 6, 7, 8));
waitOn(mb.testModel.flushEvents());
TraceRecorder recorder = recordTarget(mb.testProcess1);
waitRecorder(recorder);
useTrace(recorder.getTrace());
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
RegisterValue rv = api.readRegister("r0");
assertEquals(BigInteger.valueOf(0x0102030405060708L), rv.getUnsignedValue());
}
@Test
public void testReadLiveRegisters() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
mb.createTestThreadRegisterBanks();
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(), r -> true);
mb.testBank1.writeRegister("r0", mb.arr(1, 2, 3, 4, 5, 6, 7, 8));
mb.testBank1.writeRegister("r1", mb.arr(8, 7, 6, 5, 4, 3, 2, 1));
waitOn(mb.testModel.flushEvents());
TraceRecorder recorder = recordTarget(mb.testProcess1);
waitRecorder(recorder);
useTrace(recorder.getTrace());
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
Register r0 = tb.language.getRegister("r0");
Register r1 = tb.language.getRegister("r1");
assertEquals(List.of(
new RegisterValue(r0, BigInteger.valueOf(0x0102030405060708L)),
new RegisterValue(r1, BigInteger.valueOf(0x0807060504030201L))),
api.readRegistersNamed(List.of("r0", "r1")));
}
@Test
public void testGetLaunchOffers() throws Throwable {
createProgram();
programManager.openProgram(program);
waitForSwing();
Unique.assertOne(api.getLaunchOffers().stream().mapMulti((o, m) -> {
if (o instanceof TestDebuggerProgramLaunchOffer to) {
m.accept(to);
}
}));
}
@Test
public void testLaunchCustomCommandLine() throws Throwable {
createProgram();
programManager.openProgram(program);
waitForSwing();
var model = new TestDebuggerObjectModel() {
Map<String, ?> observedParams;
@Override
protected TestTargetSession newTestTargetSession(String rootHint) {
return new TestTargetSession(this, "Session", ROOT_SCHEMA) {
@Override
public CompletableFuture<Void> launch(Map<String, ?> params) {
observedParams = params;
throw new CancellationException();
}
};
}
};
DebuggerProgramLaunchOffer offer = new TestOffer(program, model);
LaunchResult result = api.launch(offer, "custom command line", monitor);
assertEquals("custom command line",
model.observedParams.get(TargetCmdLineLauncher.CMDLINE_ARGS_NAME));
assertNotNull(result.model());
assertNull(result.target());
assertEquals(CancellationException.class, result.exception().getClass());
}
@Test
public void testGetTarget() throws Exception {
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = recordTarget(mb.testProcess1);
assertEquals(mb.testProcess1, api.getTarget(recorder.getTrace()));
}
@Test
public void testGetTargetThread() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = recordTarget(mb.testProcess1);
waitRecorder(recorder);
Trace trace = recorder.getTrace();
TraceThread thread =
trace.getThreadManager()
.getLiveThreadByPath(recorder.getSnap(), "Processes[1].Threads[1]");
assertNotNull(thread);
assertEquals(mb.testThread1, api.getTargetThread(thread));
}
@Test
public void testGetTargetFocus() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = recordTarget(mb.testProcess1);
waitRecorder(recorder);
waitOn(mb.testModel.requestFocus(mb.testThread2));
waitRecorder(recorder);
assertEquals(mb.testThread2, api.getTargetFocus(recorder.getTrace()));
}
protected void runTestStep(BooleanSupplier step, TargetStepKind kind) throws Throwable {
var model = new TestDebuggerObjectModel() {
TestTargetThread observedThread;
TargetStepKind observedKind;
@Override
protected TestTargetThread newTestTargetThread(TestTargetThreadContainer container,
int tid) {
return new TestTargetThread(container, tid) {
@Override
public CompletableFuture<Void> step(TargetStepKind kind) {
observedThread = this;
observedKind = kind;
return super.step(kind);
}
};
}
};
mb = new TestModelBuilder(model);
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = recordTarget(mb.testProcess1);
waitRecorder(recorder);
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
waitRecorder(recorder);
assertTrue(step.getAsBoolean());
waitRecorder(recorder);
assertEquals(mb.testThread2, model.observedThread);
assertEquals(kind, model.observedKind);
}
@Test
public void testStepGivenThread() throws Throwable {
runTestStep(() -> api.step(api.getCurrentThread(), TargetStepKind.INTO),
TargetStepKind.INTO);
}
@Test
public void testStepInto() throws Throwable {
runTestStep(api::stepInto, TargetStepKind.INTO);
}
@Test
public void testStepOver() throws Throwable {
runTestStep(api::stepOver, TargetStepKind.OVER);
}
@Test
public void testStepOut() throws Throwable {
runTestStep(api::stepOut, TargetStepKind.FINISH);
}
@Override
protected void runTestResume(BooleanSupplier resume) throws Throwable {
var model = new TestDebuggerObjectModel() {
TestTargetThread observedThread;
@Override
protected TestTargetThread newTestTargetThread(TestTargetThreadContainer container,
int tid) {
return new TestTargetThread(container, tid) {
@Override
public CompletableFuture<Void> resume() {
observedThread = this;
return super.resume();
}
};
}
};
mb = new TestModelBuilder(model);
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = recordTarget(mb.testProcess1);
waitRecorder(recorder);
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
waitRecorder(recorder);
assertTrue(resume.getAsBoolean());
waitRecorder(recorder);
assertEquals(mb.testThread2, model.observedThread);
}
@Override
protected void runTestInterrupt(BooleanSupplier interrupt) throws Throwable {
var model = new TestDebuggerObjectModel() {
TestTargetThread observedThread;
@Override
protected TestTargetThread newTestTargetThread(TestTargetThreadContainer container,
int tid) {
return new TestTargetThread(container, tid) {
@Override
public CompletableFuture<Void> interrupt() {
observedThread = this;
return super.interrupt();
}
};
}
};
mb = new TestModelBuilder(model);
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = recordTarget(mb.testProcess1);
waitRecorder(recorder);
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
waitRecorder(recorder);
assertTrue(interrupt.getAsBoolean());
waitRecorder(recorder);
assertEquals(mb.testThread2, model.observedThread);
}
@Override
protected void runTestKill(BooleanSupplier kill) throws Throwable {
var model = new TestDebuggerObjectModel() {
TestTargetThread observedThread;
@Override
protected TestTargetThread newTestTargetThread(TestTargetThreadContainer container,
int tid) {
return new TestTargetThread(container, tid) {
@Override
public CompletableFuture<Void> kill() {
observedThread = this;
return super.kill();
}
};
}
};
mb = new TestModelBuilder(model);
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = recordTarget(mb.testProcess1);
waitRecorder(recorder);
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
waitRecorder(recorder);
assertTrue(kill.getAsBoolean());
waitRecorder(recorder);
assertEquals(mb.testThread2, model.observedThread);
}
@Override
protected void runTestExecuteCapture(Function<String, String> executeCapture) throws Throwable {
// NOTE: Can't use TestTargetInterpreter.queueExecute stuff, since flat API waits
var model = new TestDebuggerObjectModel() {
@Override
protected TestTargetInterpreter newTestTargetInterpreter(TestTargetSession session) {
return new TestTargetInterpreter(session) {
@Override
public CompletableFuture<String> executeCapture(String cmd) {
return CompletableFuture.completedFuture("Response to " + cmd);
}
};
}
};
mb = new TestModelBuilder(model);
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = recordTarget(mb.testProcess1);
waitRecorder(recorder);
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
waitRecorder(recorder);
assertEquals("Response to cmd", executeCapture.apply("cmd"));
}
@Test
public void testGetModelValue() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
recordTarget(mb.testProcess1);
assertEquals(mb.testThread2, api.getModelValue("Processes[1].Threads[2]"));
}
@Test
public void testRefreshObjectChildren() throws Throwable {
var model = new TestDebuggerObjectModel() {
Set<TestTargetProcess> observed = new HashSet<>();
@Override
protected TestTargetProcess newTestTargetProcess(TestTargetProcessContainer container,
int pid, AddressSpace space) {
return new TestTargetProcess(container, pid, space) {
@Override
public CompletableFuture<Void> resync(RefreshBehavior refreshAttributes,
RefreshBehavior refreshElements) {
observed.add(this);
return super.resync(refreshAttributes, refreshElements);
}
};
}
};
mb = new TestModelBuilder(model);
createTestModel();
mb.createTestProcessesAndThreads();
api.refreshObjectChildren(mb.testProcess1);
assertEquals(Set.of(mb.testProcess1), model.observed);
}
@Test
public void testRefreshSubtree() throws Throwable {
var model = new TestDebuggerObjectModel() {
Set<TestTargetObject> observed = new HashSet<>();
@Override
protected TestTargetProcess newTestTargetProcess(TestTargetProcessContainer container,
int pid, AddressSpace space) {
return new TestTargetProcess(container, pid, space) {
@Override
public CompletableFuture<Void> resync(RefreshBehavior refreshAttributes,
RefreshBehavior refreshElements) {
observed.add(this);
return super.resync(refreshAttributes, refreshElements);
}
};
}
@Override
protected TestTargetThread newTestTargetThread(TestTargetThreadContainer container,
int tid) {
return new TestTargetThread(container, tid) {
@Override
public CompletableFuture<Void> resync(RefreshBehavior refreshAttributes,
RefreshBehavior refreshElements) {
observed.add(this);
return super.resync(refreshAttributes, refreshElements);
}
};
}
};
mb = new TestModelBuilder(model);
createTestModel();
mb.createTestProcessesAndThreads();
api.refreshSubtree(mb.testModel.session);
assertEquals(Set.of(mb.testProcess1, mb.testProcess3, mb.testThread1, mb.testThread2,
mb.testThread3, mb.testThread4), model.observed);
}
@Test
public void testFlushAsyncPipelines() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = recordTarget(mb.testProcess1);
// Ensure it works whether or not there are pending events
for (int i = 0; i < 10; i++) {
api.flushAsyncPipelines(recorder.getTrace());
}
}
}

View file

@ -0,0 +1,282 @@
/* ###
* 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 ghidra.debug.flatapi;
import static org.junit.Assert.*;
import java.math.BigInteger;
import java.util.*;
import java.util.function.*;
import org.junit.Before;
import org.junit.Test;
import db.Transaction;
import generic.Unique;
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TestTraceRmiLaunchOpinion.TestTraceRmiLaunchOffer;
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLauncherServicePlugin;
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.TestRemoteMethod;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.LaunchResult;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.database.target.DBTraceObjectManager;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
public class FlatDebuggerRmiAPITest extends AbstractLiveFlatDebuggerAPITest<FlatDebuggerRmiAPI> {
protected TraceRmiLauncherServicePlugin rmiLaunchPlugin;
protected class TestFlatRmiAPI extends TestFlatAPI implements FlatDebuggerRmiAPI {
}
@Before
public void setUpRmiTest() throws Throwable {
rmiLaunchPlugin = addPlugin(tool, TraceRmiLauncherServicePlugin.class);
}
@Override
protected FlatDebuggerRmiAPI newFlatAPI() {
return new TestFlatRmiAPI();
}
protected TraceRmiTarget createTarget() throws Throwable {
createRmiConnection();
addExecuteMethod();
addControlMethods();
addMemoryMethods();
addRegisterMethods();
createTrace();
try (Transaction tx = tb.startTransaction()) {
DBTraceObjectManager objs = tb.trace.getObjectManager();
objs.createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
tb.createObjectsFramesAndRegs(
tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class),
Lifespan.nowOn(0), tb.host, 2);
addMemoryRegion(objs, Lifespan.nowOn(0), tb.range(0x00400000, 0x00400fff), ".text",
"rx");
}
TraceRmiTarget target = rmiCx.publishTarget(tool, tb.trace);
traceManager.openTrace(tb.trace);
// Do not activate, as this pollutes the method invocation queues
waitForSwing();
return target;
}
@Test
public void testReadLiveMemory() throws Throwable {
TraceRmiTarget target = createTarget();
var args = rmiMethodReadMem.expect(a -> {
try (Transaction tx = tb.startTransaction()) {
tb.trace.getMemoryManager()
.putBytes(target.getSnap(), tb.addr(0x00400000),
tb.buf(1, 2, 3, 4, 5, 6, 7, 8));
}
return null;
});
byte[] data = api.readMemory(tb.trace, target.getSnap(), tb.addr(0x00400000), 8, monitor);
assertEquals(Map.ofEntries(
Map.entry("process", tb.obj("Processes[1]")),
// Framework quantizes to page
Map.entry("range", tb.range(0x00400000, 0x00400fff))),
waitOn(args));
assertArrayEquals(tb.arr(1, 2, 3, 4, 5, 6, 7, 8), data);
}
@Test
public void testReadLiveRegister() throws Throwable {
TraceRmiTarget target = createTarget();
TraceThread thread =
tb.trace.getThreadManager().getLiveThreadByPath(0, "Processes[1].Threads[1]");
Register r0 = tb.reg("r0");
var args = rmiMethodReadRegs.expect(a -> {
try (Transaction tx = tb.startTransaction()) {
DBTraceMemorySpace regs =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
regs.setValue(target.getSnap(), new RegisterValue(r0, new BigInteger("1234")));
}
return null;
});
RegisterValue value = api.readRegister(tb.host, thread, 0, target.getSnap(), r0);
assertEquals(Map.ofEntries(
Map.entry("container", tb.obj("Processes[1].Threads[1].Stack[0].Registers"))),
waitOn(args));
assertEquals(new RegisterValue(r0, new BigInteger("1234")), value);
}
@Test
public void testReadLiveRegisters() throws Throwable {
TraceRmiTarget target = createTarget();
TraceThread thread =
tb.trace.getThreadManager().getLiveThreadByPath(0, "Processes[1].Threads[1]");
Register r0 = tb.reg("r0");
Register r1 = tb.reg("r1");
var args = rmiMethodReadRegs.expect(a -> {
try (Transaction tx = tb.startTransaction()) {
DBTraceMemorySpace regs =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
regs.setValue(target.getSnap(), new RegisterValue(r0, new BigInteger("1234")));
regs.setValue(target.getSnap(), new RegisterValue(r1, new BigInteger("5678")));
}
return null;
});
List<RegisterValue> values =
api.readRegisters(tb.host, thread, 0, target.getSnap(), List.of(r0, r1));
assertEquals(Map.ofEntries(
Map.entry("container", tb.obj("Processes[1].Threads[1].Stack[0].Registers"))),
waitOn(args));
assertEquals(List.of(
new RegisterValue(r0, new BigInteger("1234")),
new RegisterValue(r1, new BigInteger("5678"))),
values);
}
protected <T, U extends T> List<U> filter(Collection<T> col, Class<U> cls) {
return col.stream().<U> mapMulti((e, m) -> {
if (cls.isInstance(e)) {
m.accept(cls.cast(e));
}
}).toList();
}
@Test
public void testGetLaunchOffers() throws Throwable {
createProgram();
programManager.openProgram(program);
waitForSwing();
TestTraceRmiLaunchOffer offer =
Unique.assertOne(filter(api.getLaunchOffers(), TestTraceRmiLaunchOffer.class));
assertEquals(program, offer.getProgram());
}
@Test
public void testGetSavedLaunchOffers() throws Throwable {
createProgram();
programManager.openProgram(program);
waitForSwing();
assertEquals(List.of(), api.getSavedLaunchOffers());
TestTraceRmiLaunchOffer offer =
Unique.assertOne(filter(api.getLaunchOffers(), TestTraceRmiLaunchOffer.class));
offer.saveLauncherArgs(Map.of("image", "/test/image"));
assertEquals(List.of(offer), api.getSavedLaunchOffers());
}
@Test
public void testLaunchCustomCommandLine() throws Throwable {
TestTraceRmiLaunchOffer offer =
Unique.assertOne(filter(api.getLaunchOffers(), TestTraceRmiLaunchOffer.class));
offer.saveLauncherArgs(Map.of("image", "/test/image"));
LaunchResult result = api.launch(monitor);
assertEquals("Test launcher cannot launch /test/image", result.exception().getMessage());
}
protected void runTestStep(Predicate<TraceThread> step, Supplier<TestRemoteMethod> method)
throws Throwable {
createTarget();
TraceObjectThread thread =
tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class);
traceManager.activateThread(thread);
waitForSwing();
var args = method.get().expect(a -> null);
assertTrue(step.test(thread));
assertEquals(Map.ofEntries(
Map.entry("thread", thread.getObject())),
waitOn(args));
}
@Test
public void testStepGivenThread() throws Throwable {
runTestStep(api::stepInto, () -> rmiMethodStepInto);
}
@Test
public void testStepInto() throws Throwable {
runTestStep(t -> api.stepInto(), () -> rmiMethodStepInto);
}
@Test
public void testStepOver() throws Throwable {
runTestStep(t -> api.stepOver(), () -> rmiMethodStepOver);
}
@Test
public void testStepOut() throws Throwable {
runTestStep(t -> api.stepOut(), () -> rmiMethodStepOut);
}
@Override
protected void runTestResume(BooleanSupplier resume) throws Throwable {
createTarget();
traceManager.activateTrace(tb.trace);
waitForSwing();
var args = rmiMethodResume.expect(a -> null);
assertTrue(resume.getAsBoolean());
assertEquals(Map.ofEntries(
Map.entry("process", tb.obj("Processes[1]"))),
waitOn(args));
}
@Override
protected void runTestInterrupt(BooleanSupplier interrupt) throws Throwable {
createTarget();
traceManager.activateTrace(tb.trace);
waitForSwing();
var args = rmiMethodInterrupt.expect(a -> null);
assertTrue(interrupt.getAsBoolean());
assertEquals(Map.ofEntries(
Map.entry("process", tb.obj("Processes[1]"))),
waitOn(args));
}
@Override
protected void runTestKill(BooleanSupplier kill) throws Throwable {
createTarget();
traceManager.activateTrace(tb.trace);
waitForSwing();
var args = rmiMethodKill.expect(a -> null);
assertTrue(kill.getAsBoolean());
assertEquals(Map.ofEntries(
Map.entry("process", tb.obj("Processes[1]"))),
waitOn(args));
}
@Override
protected void runTestExecuteCapture(Function<String, String> executeCapture) throws Throwable {
createTarget();
traceManager.activateTrace(tb.trace);
waitForSwing();
var args = rmiMethodExecute.expect(a -> "result");
assertEquals("result", api.executeCapture("some command"));
assertEquals(Map.ofEntries(
Map.entry("cmd", "some command"),
Map.entry("to_string", true)),
waitOn(args));
}
}