mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-3872: Port scripting API to Trace RMI
This commit is contained in:
parent
77923fa693
commit
ad6cb5892d
32 changed files with 3135 additions and 2063 deletions
|
@ -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 =
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue