mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-3754: post-review review
GP-3754: last pass GP-3754: most review issues address (ymm0/testSave not); tests pass GP-3754: review - eval/exec GP-3754: moved registers to thread GP-3754: review pass 7 - register w/o banks GP-3754: review pass 6 - basic clean-up items GP-3754: pass 5 - tests all pass (minus commented-out ones) GP-3754: review pass 4(?) GP-3754: review pass 3 GP-3754: review pass 2 GP-3754: review pass 1 GP-3754: ref Pybag GP-3754: clean-up GP-3754: testing post-changes in hooks GP-3754: fix for env GP-3754: convenience method for get_debugger GP-3754: tests all running successfully GP-3754: hook tests running GP-3754: hook tests: memory changed not wokring, bpt modified does nothing GP-3754: lot of work to get testExited to pass GP-3754: start on hooks tests; mods to methods not-runnable while running GP-3754: methods tests working GP-3754: bulk of bpt tests running GP-3754: whittling down the list GP-3754: more passing method tests GP-3754: at least a few method tests working (don't run batch) GP-3754: cmd tests pass w/o closing stdin GP-3754: command test basically running GP-3754: 3 cmd tests failing; 3 commented out GP-3754: ghidra_trace_set_values uses broken except for in testGetValues GP-3754: whittling down the command tests GP-3754: tests esp. SetValue GP-3754: testMinimal works but cannot 'execute' GP-3754: JUnits still don't run GP-3754: breakpoints in the list GP-3754: continued work on hooks; bpts not registering as bpts GP-3754: templates for hooks GP-3754: tests, first viable hook (module_load)' GP-3754: first pass at methods GP-3754: memory/regs working GP-3754: most of the puts done GP-3754: modules/regions working GP-3754: process/threads working GP-3754: added to manifest GP-3754: minimal shell: arch faked out / thru activate w/o push
This commit is contained in:
parent
f64c38ef7f
commit
abbc18f927
20 changed files with 6266 additions and 2 deletions
|
@ -0,0 +1,424 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.rmi;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.junit.Before;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteAsyncResult;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiAcceptor;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin;
|
||||
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.framework.TestApplicationUtils;
|
||||
import ghidra.framework.main.ApplicationLevelOnlyPlugin;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.PluginsConfiguration;
|
||||
import ghidra.framework.plugintool.util.PluginDescription;
|
||||
import ghidra.framework.plugintool.util.PluginException;
|
||||
import ghidra.framework.plugintool.util.PluginPackage;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRangeImpl;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.NumericUtilities;
|
||||
|
||||
public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
/**
|
||||
* Some features have to be disabled to avoid permissions issues in the test container. Namely,
|
||||
* don't try to disable ASLR.
|
||||
*/
|
||||
public static final String PREAMBLE = """
|
||||
from ghidradbg.commands import *
|
||||
""";
|
||||
// Connecting should be the first thing the script does, so use a tight timeout.
|
||||
protected static final int CONNECT_TIMEOUT_MS = 3000;
|
||||
protected static final int TIMEOUT_SECONDS = 300;
|
||||
protected static final int QUIT_TIMEOUT_MS = 1000;
|
||||
|
||||
protected TraceRmiService traceRmi;
|
||||
private Path pythonPath;
|
||||
private Path outFile;
|
||||
private Path errFile;
|
||||
|
||||
//@BeforeClass
|
||||
public static void setupPython() throws Throwable {
|
||||
new ProcessBuilder("gradle", "Debugger-agent-dbgeng:assemblePyPackage")
|
||||
.directory(TestApplicationUtils.getInstallationDirectory())
|
||||
.inheritIO()
|
||||
.start()
|
||||
.waitFor();
|
||||
}
|
||||
|
||||
protected void setPythonPath(ProcessBuilder pb) throws IOException {
|
||||
String sep =
|
||||
OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS ? ";" : ":";
|
||||
String rmiPyPkg = Application.getModuleSubDirectory("Debugger-rmi-trace",
|
||||
"build/pypkg/src").getAbsolutePath();
|
||||
String gdbPyPkg = Application.getModuleSubDirectory("Debugger-agent-dbgeng",
|
||||
"build/pypkg/src").getAbsolutePath();
|
||||
String add = rmiPyPkg + sep + gdbPyPkg;
|
||||
pb.environment().compute("PYTHONPATH", (k, v) -> v == null ? add : (v + sep + add));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setupTraceRmi() throws Throwable {
|
||||
traceRmi = addPlugin(tool, TraceRmiPlugin.class);
|
||||
|
||||
try {
|
||||
pythonPath = Paths.get(DummyProc.which("python3"));
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
pythonPath = Paths.get(DummyProc.which("python"));
|
||||
}
|
||||
outFile = Files.createTempFile("pydbgout", null);
|
||||
errFile = Files.createTempFile("pydbgerr", null);
|
||||
}
|
||||
|
||||
protected void addAllDebuggerPlugins() throws PluginException {
|
||||
PluginsConfiguration plugConf = new PluginsConfiguration() {
|
||||
@Override
|
||||
protected boolean accepts(Class<? extends Plugin> pluginClass) {
|
||||
return !ApplicationLevelOnlyPlugin.class.isAssignableFrom(pluginClass);
|
||||
}
|
||||
};
|
||||
|
||||
for (PluginDescription pd : plugConf
|
||||
.getPluginDescriptions(PluginPackage.getPluginPackage("Debugger"))) {
|
||||
addPlugin(tool, pd.getPluginClass());
|
||||
}
|
||||
}
|
||||
|
||||
protected static String addrToStringForPython(InetAddress address) {
|
||||
if (address.isAnyLocalAddress()) {
|
||||
return "127.0.0.1"; // Can't connect to 0.0.0.0 as such. Choose localhost.
|
||||
}
|
||||
return address.getHostAddress();
|
||||
}
|
||||
|
||||
protected static String sockToStringForPython(SocketAddress address) {
|
||||
if (address instanceof InetSocketAddress tcp) {
|
||||
return addrToStringForPython(tcp.getAddress()) + ":" + tcp.getPort();
|
||||
}
|
||||
throw new AssertionError("Unhandled address type " + address);
|
||||
}
|
||||
|
||||
protected record PythonResult(boolean timedOut, int exitCode, String stdout, String stderr) {
|
||||
protected String handle() {
|
||||
if (stderr.contains("Error") || (0 != exitCode && 1 != exitCode && 143 != exitCode)) {
|
||||
throw new PythonError(exitCode, stdout, stderr);
|
||||
}
|
||||
return stdout;
|
||||
}
|
||||
}
|
||||
|
||||
protected record ExecInPython(Process python, CompletableFuture<PythonResult> future) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource") // Do not close stdin
|
||||
protected ExecInPython execInPython(String script) throws IOException {
|
||||
ProcessBuilder pb = new ProcessBuilder(pythonPath.toString(), "-i");
|
||||
setPythonPath(pb);
|
||||
|
||||
// If commands come from file, Python will quit after EOF.
|
||||
Msg.info(this, "outFile: " + outFile);
|
||||
Msg.info(this, "errFile: " + errFile);
|
||||
|
||||
//pb.inheritIO();
|
||||
pb.redirectInput(ProcessBuilder.Redirect.PIPE);
|
||||
pb.redirectOutput(outFile.toFile());
|
||||
pb.redirectError(errFile.toFile());
|
||||
Process pyproc = pb.start();
|
||||
OutputStream stdin = pyproc.getOutputStream();
|
||||
stdin.write(script.getBytes());
|
||||
stdin.flush();
|
||||
//stdin.close();
|
||||
return new ExecInPython(pyproc, CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
if (!pyproc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
|
||||
Msg.error(this, "Timed out waiting for Python");
|
||||
pyproc.destroyForcibly();
|
||||
pyproc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
return new PythonResult(true, -1, Files.readString(outFile),
|
||||
Files.readString(errFile));
|
||||
}
|
||||
Msg.info(this, "Python exited with code " + pyproc.exitValue());
|
||||
return new PythonResult(false, pyproc.exitValue(), Files.readString(outFile),
|
||||
Files.readString(errFile));
|
||||
}
|
||||
catch (Exception e) {
|
||||
return ExceptionUtils.rethrow(e);
|
||||
}
|
||||
finally {
|
||||
pyproc.destroyForcibly();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static class PythonError extends RuntimeException {
|
||||
public final int exitCode;
|
||||
public final String stdout;
|
||||
public final String stderr;
|
||||
|
||||
public PythonError(int exitCode, String stdout, String stderr) {
|
||||
super("""
|
||||
exitCode=%d:
|
||||
----stdout----
|
||||
%s
|
||||
----stderr----
|
||||
%s
|
||||
""".formatted(exitCode, stdout, stderr));
|
||||
this.exitCode = exitCode;
|
||||
this.stdout = stdout;
|
||||
this.stderr = stderr;
|
||||
}
|
||||
}
|
||||
|
||||
protected String runThrowError(String script) throws Exception {
|
||||
CompletableFuture<PythonResult> result = execInPython(script).future;
|
||||
return result.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
|
||||
}
|
||||
|
||||
protected record PythonAndHandler(ExecInPython exec, TraceRmiHandler handler)
|
||||
implements AutoCloseable {
|
||||
protected RemoteMethod getMethod(String name) {
|
||||
return Objects.requireNonNull(handler.getMethods().get(name));
|
||||
}
|
||||
|
||||
public void execute(String cmd) {
|
||||
RemoteMethod execute = getMethod("execute");
|
||||
execute.invoke(Map.of("cmd", cmd));
|
||||
}
|
||||
|
||||
public RemoteAsyncResult executeAsync(String cmd) {
|
||||
RemoteMethod execute = getMethod("execute");
|
||||
return execute.invokeAsync(Map.of("cmd", cmd));
|
||||
}
|
||||
|
||||
public String executeCapture(String expr) {
|
||||
RemoteMethod execute = getMethod("evaluate");
|
||||
return (String) execute.invoke(Map.of("expr", expr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
Msg.info(this, "Cleaning up python");
|
||||
exec.python().destroy();
|
||||
try {
|
||||
PythonResult r = exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
r.handle();
|
||||
waitForPass(() -> assertTrue(handler.isClosed()));
|
||||
}
|
||||
finally {
|
||||
exec.python.destroyForcibly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected PythonAndHandler startAndConnectPython(Function<String, String> scriptSupplier)
|
||||
throws Exception {
|
||||
TraceRmiAcceptor acceptor = traceRmi.acceptOne(null);
|
||||
ExecInPython exec =
|
||||
execInPython(scriptSupplier.apply(sockToStringForPython(acceptor.getAddress())));
|
||||
acceptor.setTimeout(CONNECT_TIMEOUT_MS);
|
||||
try {
|
||||
TraceRmiHandler handler = acceptor.accept();
|
||||
return new PythonAndHandler(exec, handler);
|
||||
}
|
||||
catch (SocketTimeoutException e) {
|
||||
exec.python.destroyForcibly();
|
||||
exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
protected PythonAndHandler startAndConnectPython() throws Exception {
|
||||
return startAndConnectPython(addr -> """
|
||||
%s
|
||||
ghidra_trace_connect('%s')
|
||||
""".formatted(PREAMBLE, addr));
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
protected String runThrowError(Function<String, String> scriptSupplier)
|
||||
throws Exception {
|
||||
PythonAndHandler conn = startAndConnectPython(scriptSupplier);
|
||||
PythonResult r = conn.exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
String stdout = r.handle();
|
||||
waitForPass(() -> assertTrue(conn.handler.isClosed()));
|
||||
return stdout;
|
||||
}
|
||||
|
||||
protected void waitStopped() {
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
|
||||
waitForPass(() -> assertEquals("STOPPED", tb.objValue(proc, 0, "_state")));
|
||||
waitTxDone();
|
||||
}
|
||||
|
||||
protected void waitRunning() {
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
|
||||
waitForPass(() -> assertEquals("RUNNING", tb.objValue(proc, 0, "_state")));
|
||||
waitTxDone();
|
||||
}
|
||||
|
||||
protected String extractOutSection(String out, String head) {
|
||||
String[] split = out.split("\n");
|
||||
String xout = "";
|
||||
for (String s : split) {
|
||||
if (!s.startsWith("(python)") && !s.equals("")) {
|
||||
xout += s + "\n";
|
||||
}
|
||||
}
|
||||
return xout.split(head)[1].split("---")[0].replace("(python)", "").trim();
|
||||
}
|
||||
|
||||
record MemDump(long address, byte[] data) {
|
||||
}
|
||||
|
||||
protected MemDump parseHexDump(String dump) throws IOException {
|
||||
// First, get the address. Assume contiguous, so only need top line.
|
||||
List<String> lines = List.of(dump.split("\n"));
|
||||
List<String> toksLine0 = List.of(lines.get(0).split("\\s+"));
|
||||
String addrstr = toksLine0.get(0);
|
||||
if (addrstr.contains(":")) {
|
||||
addrstr = addrstr.substring(0, addrstr.indexOf(":"));
|
||||
}
|
||||
long address = Long.parseLong(addrstr, 16);
|
||||
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
for (String l : lines) {
|
||||
List<String> parts = List.of(l.split(":"));
|
||||
assertEquals(2, parts.size());
|
||||
String hex = parts.get(1).substring(0, 48);
|
||||
byte[] lineData = NumericUtilities.convertStringToBytes(hex);
|
||||
assertNotNull("Converted to null: " + hex, parts.get(1));
|
||||
buf.write(lineData);
|
||||
}
|
||||
return new MemDump(address, buf.toByteArray());
|
||||
}
|
||||
|
||||
record RegDump() {
|
||||
}
|
||||
|
||||
protected RegDump parseRegDump(String dump) {
|
||||
return new RegDump();
|
||||
}
|
||||
|
||||
protected ManagedDomainObject openDomainObject(String path) throws Exception {
|
||||
DomainFile df = env.getProject().getProjectData().getFile(path);
|
||||
assertNotNull(df);
|
||||
return new ManagedDomainObject(df, false, false, monitor);
|
||||
}
|
||||
|
||||
protected ManagedDomainObject waitDomainObject(String path) throws Exception {
|
||||
DomainFile df;
|
||||
long start = System.currentTimeMillis();
|
||||
while (true) {
|
||||
df = env.getProject().getProjectData().getFile(path);
|
||||
if (df != null) {
|
||||
return new ManagedDomainObject(df, false, false, monitor);
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
if (System.currentTimeMillis() - start > 30000) {
|
||||
throw new TimeoutException("30 seconds expired waiting for domain file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertBreakLoc(TraceObjectValue locVal, String key, Address addr, int len,
|
||||
Set<TraceBreakpointKind> kinds, String expression) throws Exception {
|
||||
assertEquals(key, locVal.getEntryKey());
|
||||
TraceObject loc = locVal.getChild();
|
||||
TraceObject spec = loc;
|
||||
assertEquals(new AddressRangeImpl(addr, len), loc.getValue(0, "_range").getValue());
|
||||
assertEquals(TraceBreakpointKindSet.encode(kinds), spec.getValue(0, "_kinds").getValue());
|
||||
assertTrue(spec.getValue(0, "_expression").getValue().toString().contains(expression));
|
||||
}
|
||||
|
||||
protected void assertWatchLoc(TraceObjectValue locVal, String key, Address addr, int len,
|
||||
Set<TraceBreakpointKind> kinds, String expression) throws Exception {
|
||||
assertEquals(key, locVal.getEntryKey());
|
||||
TraceObject loc = locVal.getChild();
|
||||
assertEquals(new AddressRangeImpl(addr, len), loc.getValue(0, "_range").getValue());
|
||||
assertEquals(TraceBreakpointKindSet.encode(kinds), loc.getValue(0, "_kinds").getValue());
|
||||
}
|
||||
|
||||
protected void waitTxDone() {
|
||||
waitFor(() -> tb.trace.getCurrentTransactionInfo() == null);
|
||||
}
|
||||
|
||||
public static void waitForPass(Runnable runnable, long timeoutMs, long retryDelayMs) {
|
||||
long start = System.currentTimeMillis();
|
||||
AssertionError lastError = null;
|
||||
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||
try {
|
||||
runnable.run();
|
||||
return;
|
||||
}
|
||||
catch (AssertionError e) {
|
||||
lastError = e;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(retryDelayMs);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// Retry sooner, I guess.
|
||||
}
|
||||
}
|
||||
if (lastError == null) {
|
||||
throw new AssertionError("Timed out before first try?");
|
||||
}
|
||||
throw lastError;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,380 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.rmi;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod;
|
||||
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
|
||||
public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||
private static final long RUN_TIMEOUT_MS = 20000;
|
||||
private static final long RETRY_MS = 500;
|
||||
|
||||
record PythonAndTrace(PythonAndHandler conn, ManagedDomainObject mdo) implements AutoCloseable {
|
||||
public void execute(String cmd) {
|
||||
conn.execute(cmd);
|
||||
}
|
||||
|
||||
public String executeCapture(String cmd) {
|
||||
return conn.executeCapture(cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
conn.close();
|
||||
mdo.close();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
protected PythonAndTrace startAndSyncPython(String exec) throws Exception {
|
||||
PythonAndHandler conn = startAndConnectPython();
|
||||
try {
|
||||
ManagedDomainObject mdo;
|
||||
conn.execute("from ghidradbg.commands import *");
|
||||
conn.execute(
|
||||
"util.set_convenience_variable('ghidra-language', 'x86:LE:64:default')");
|
||||
if (exec != null) {
|
||||
start(conn, exec);
|
||||
mdo = waitDomainObject("/New Traces/pydbg/"+exec);
|
||||
}
|
||||
else {
|
||||
conn.execute("ghidra_trace_start()");
|
||||
mdo = waitDomainObject("/New Traces/pydbg/noname");
|
||||
}
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
return new PythonAndTrace(conn, mdo);
|
||||
}
|
||||
catch (Exception e) {
|
||||
conn.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
protected long lastSnap(PythonAndTrace conn) {
|
||||
return conn.conn.handler().getLastSnapshot(tb.trace);
|
||||
}
|
||||
|
||||
@Test // The 10s wait makes this a pretty expensive test
|
||||
public void testOnNewThread() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
conn.execute("from ghidradbg.commands import *");
|
||||
txPut(conn, "processes");
|
||||
|
||||
waitForPass(() -> {
|
||||
TraceObject proc = tb.objAny("Processes[]");
|
||||
assertNotNull(proc);
|
||||
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
txPut(conn, "threads");
|
||||
waitForPass(() -> assertEquals(4,
|
||||
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
|
||||
RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
conn.execute("dbg().go(10)");
|
||||
|
||||
waitForPass(() -> assertTrue(tb.objValues(lastSnap(conn), "Processes[].Threads[]").size() > 4),
|
||||
RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnThreadSelected() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
txPut(conn, "processes");
|
||||
|
||||
waitForPass(() -> {
|
||||
TraceObject inf = tb.objAny("Processes[]");
|
||||
assertNotNull(inf);
|
||||
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
txPut(conn, "threads");
|
||||
waitForPass(() -> assertEquals(4,
|
||||
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
|
||||
RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
// Now the real test
|
||||
conn.execute("util.select_thread(1)");
|
||||
waitForPass(() -> {
|
||||
String tnum = conn.executeCapture("util.selected_thread()");
|
||||
assertTrue(tnum.contains("1"));
|
||||
String threadIndex = threadIndex(traceManager.getCurrentObject());
|
||||
assertTrue(tnum.contains(threadIndex));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
conn.execute("util.select_thread(2)");
|
||||
waitForPass(() -> {
|
||||
String tnum = conn.executeCapture("util.selected_thread()");
|
||||
assertTrue(tnum.contains("2"));
|
||||
String threadIndex = threadIndex(traceManager.getCurrentObject());
|
||||
assertTrue(tnum.contains(threadIndex));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
conn.execute("util.select_thread(0)");
|
||||
waitForPass(() -> {
|
||||
String tnum = conn.executeCapture("util.selected_thread()");
|
||||
assertTrue(tnum.contains("0"));
|
||||
String threadIndex = threadIndex(traceManager.getCurrentObject());
|
||||
assertTrue(tnum.contains(threadIndex));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getIndex(TraceObject object, String pattern, int n) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
PathPattern pat = PathPredicates.parse(pattern).getSingletonPattern();
|
||||
// if (pat.countWildcards() != 1) {
|
||||
// throw new IllegalArgumentException("Exactly one wildcard required");
|
||||
// }
|
||||
List<String> path = object.getCanonicalPath().getKeyList();
|
||||
if (path.size() < pat.asPath().size()) {
|
||||
return null;
|
||||
}
|
||||
List<String> matched = pat.matchKeys(path.subList(0, pat.asPath().size()));
|
||||
if (matched == null) {
|
||||
return null;
|
||||
}
|
||||
if (matched.size() <= n) {
|
||||
return null;
|
||||
}
|
||||
return matched.get(n);
|
||||
}
|
||||
|
||||
protected String threadIndex(TraceObject object) {
|
||||
return getIndex(object, "Processes[].Threads[]", 1);
|
||||
}
|
||||
|
||||
protected String frameIndex(TraceObject object) {
|
||||
return getIndex(object, "Processes[].Threads[].Stack[]", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testOnSyscallMemory() throws Exception {
|
||||
// TODO: Need a specimen
|
||||
// FWIW, I've already seen this getting exercised in other tests.
|
||||
}
|
||||
|
||||
//@Test - dbgeng has limited support via DEBUG_CDS_DATA,
|
||||
// but expensive to implement anything here
|
||||
public void testOnMemoryChanged() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
|
||||
conn.execute("ghidra_trace_txstart('Tx')");
|
||||
conn.execute("ghidra_trace_putmem('$pc 10')");
|
||||
conn.execute("ghidra_trace_txcommit()");
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
conn.execute("util.get_debugger().write("+address+", b'\\x7f')");
|
||||
|
||||
waitForPass(() -> {
|
||||
ByteBuffer buf = ByteBuffer.allocate(10);
|
||||
tb.trace.getMemoryManager().getBytes(lastSnap(conn), tb.addr(address), buf);
|
||||
assertEquals(0x7f, buf.get(0));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnRegisterChanged() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
|
||||
conn.execute("ghidra_trace_txstart('Tx')");
|
||||
conn.execute("ghidra_trace_putreg()");
|
||||
conn.execute("ghidra_trace_txcommit()");
|
||||
conn.execute("util.get_debugger().reg._set_register('rax', 0x1234)");
|
||||
conn.execute("util.get_debugger().stepi()");
|
||||
|
||||
String path = "Processes[].Threads[].Registers";
|
||||
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
|
||||
AddressSpace space = tb.trace.getBaseAddressFactory()
|
||||
.getAddressSpace(registers.getCanonicalPath().toString());
|
||||
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(space, false);
|
||||
waitForPass(() -> assertEquals("1234",
|
||||
regs.getValue(lastSnap(conn), tb.reg("RAX")).getUnsignedValue().toString(16)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnCont() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
txPut(conn, "processes");
|
||||
|
||||
conn.execute("util.get_debugger()._control.SetExecutionStatus(DbgEng.DEBUG_STATUS_GO)");
|
||||
waitRunning();
|
||||
|
||||
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
|
||||
waitForPass(() -> {
|
||||
assertEquals("RUNNING", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStop() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
txPut(conn, "processes");
|
||||
|
||||
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
|
||||
waitForPass(() -> {
|
||||
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnExited() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("netstat.exe")) {
|
||||
txPut(conn, "processes");
|
||||
waitStopped();
|
||||
|
||||
conn.execute("util.get_debugger().go()");
|
||||
|
||||
waitForPass(() -> {
|
||||
TraceSnapshot snapshot =
|
||||
tb.trace.getTimeManager().getSnapshot(lastSnap(conn), false);
|
||||
assertNotNull(snapshot);
|
||||
assertEquals("Exited with code 0", snapshot.getDescription());
|
||||
|
||||
TraceObject proc = tb.objAny("Processes[]");
|
||||
assertNotNull(proc);
|
||||
Object val = tb.objValue(proc, lastSnap(conn), "_exit_code");
|
||||
assertThat(val, instanceOf(Number.class));
|
||||
assertEquals(0, ((Number) val).longValue());
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnBreakpointCreated() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
||||
|
||||
conn.execute("dbg = util.get_debugger()");
|
||||
conn.execute("pc = dbg.reg.get_pc()");
|
||||
conn.execute("dbg.bp(expr=pc)");
|
||||
conn.execute("dbg.stepi()");
|
||||
|
||||
waitForPass(() -> {
|
||||
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
|
||||
assertEquals(1, brks.size());
|
||||
return (TraceObject) brks.get(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnBreakpointModified() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
||||
|
||||
conn.execute("dbg = util.get_debugger()");
|
||||
conn.execute("pc = dbg.reg.get_pc()");
|
||||
conn.execute("dbg.bp(expr=pc)");
|
||||
conn.execute("dbg.stepi()");
|
||||
|
||||
TraceObject brk = waitForPass(() -> {
|
||||
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
|
||||
assertEquals(1, brks.size());
|
||||
return (TraceObject) brks.get(0);
|
||||
});
|
||||
assertEquals(true, tb.objValue(brk, lastSnap(conn), "Enabled"));
|
||||
conn.execute("dbg.bd(0)");
|
||||
conn.execute("dbg.stepi()");
|
||||
assertEquals(false, tb.objValue(brk, lastSnap(conn), "Enabled"));
|
||||
|
||||
/* Not currently enabled
|
||||
assertEquals("", tb.objValue(brk, lastSnap(conn), "Command"));
|
||||
conn.execute("dbg.bp(expr=pc, windbgcmd='bl')");
|
||||
conn.execute("dbg.stepi()");
|
||||
assertEquals("bl", tb.objValue(brk, lastSnap(conn), "Command"));
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnBreakpointDeleted() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
||||
|
||||
conn.execute("dbg = util.get_debugger()");
|
||||
conn.execute("pc = dbg.reg.get_pc()");
|
||||
conn.execute("dbg.bp(expr=pc)");
|
||||
conn.execute("dbg.stepi()");
|
||||
|
||||
TraceObject brk = waitForPass(() -> {
|
||||
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
|
||||
assertEquals(1, brks.size());
|
||||
return (TraceObject) brks.get(0);
|
||||
});
|
||||
|
||||
conn.execute("dbg.cmd('bc %s')".formatted(brk.getCanonicalPath().index()));
|
||||
conn.execute("dbg.stepi()");
|
||||
|
||||
waitForPass(
|
||||
() -> assertEquals(0,
|
||||
tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()));
|
||||
}
|
||||
}
|
||||
|
||||
private void start(PythonAndHandler conn, String obj) {
|
||||
conn.execute("from ghidradbg.commands import *");
|
||||
if (obj != null)
|
||||
conn.execute("ghidra_trace_create('"+obj+"')");
|
||||
else
|
||||
conn.execute("ghidra_trace_create()");
|
||||
conn.execute("ghidra_trace_sync_enable()");
|
||||
}
|
||||
|
||||
private void txPut(PythonAndTrace conn, String obj) {
|
||||
conn.execute("ghidra_trace_txstart('Tx" + obj + "')");
|
||||
conn.execute("ghidra_trace_put_" + obj +"()");
|
||||
conn.execute("ghidra_trace_txcommit()");
|
||||
}
|
||||
|
||||
private long getAddressAtOffset(PythonAndTrace conn, int offset) {
|
||||
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+"+offset+")";
|
||||
String ret = conn.executeCapture(inst);
|
||||
String[] split = ret.split("\\s+"); // get target
|
||||
return Long.decode(split[1]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,959 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.rmi;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.ValueDecoder;
|
||||
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.modules.TraceModule;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
|
||||
public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||
|
||||
@Test
|
||||
public void testEvaluate() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
RemoteMethod evaluate = conn.getMethod("evaluate");
|
||||
assertEquals("11",
|
||||
evaluate.invoke(Map.of("expr", "3+4*2")));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteCapture() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
RemoteMethod execute = conn.getMethod("execute");
|
||||
assertEquals(false,
|
||||
execute.parameters().get("to_string").defaultValue().get(ValueDecoder.DEFAULT));
|
||||
assertEquals("11\n",
|
||||
execute.invoke(Map.of("cmd", "print(3+4*2)", "to_string", true)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
conn.execute("ghidra_trace_kill()");
|
||||
}
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
// Just confirm it's present
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshAvailable() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, null);
|
||||
txCreate(conn, "Available");
|
||||
|
||||
RemoteMethod refreshAvailable = conn.getMethod("refresh_available");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject available = Objects.requireNonNull(tb.objAny("Available"));
|
||||
|
||||
refreshAvailable.invoke(Map.of("node", available));
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
List<TraceObject> list = tb.trace.getObjectManager()
|
||||
.getValuePaths(Lifespan.at(0), PathPredicates.parse("Available[]"))
|
||||
.map(p -> p.getDestination(null))
|
||||
.toList();
|
||||
assertThat(list.size(), greaterThan(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshBreakpoints() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod refreshBreakpoints = conn.getMethod("refresh_breakpoints");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
conn.execute("dbg = util.get_debugger()");
|
||||
conn.execute("pc = dbg.reg.get_pc()");
|
||||
conn.execute("dbg.bp(expr=pc)");
|
||||
conn.execute("dbg.ba(expr=pc+4)");
|
||||
txPut(conn, "breakpoints");
|
||||
TraceObject breakpoints = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
|
||||
refreshBreakpoints.invoke(Map.of("node", breakpoints));
|
||||
|
||||
List<TraceObjectValue> procBreakLocVals = tb.trace.getObjectManager()
|
||||
.getValuePaths(Lifespan.at(0),
|
||||
PathPredicates.parse("Processes[].Breakpoints[]"))
|
||||
.map(p -> p.getLastEntry())
|
||||
.toList();
|
||||
assertEquals(2, procBreakLocVals.size());
|
||||
AddressRange rangeMain =
|
||||
procBreakLocVals.get(0).getChild().getValue(0, "_range").castValue();
|
||||
Address main = rangeMain.getMinAddress();
|
||||
|
||||
assertBreakLoc(procBreakLocVals.get(0), "[0]", main, 1,
|
||||
Set.of(TraceBreakpointKind.SW_EXECUTE),
|
||||
"ntdll!LdrInit");
|
||||
assertBreakLoc(procBreakLocVals.get(1), "[1]", main.add(4), 1,
|
||||
Set.of(TraceBreakpointKind.HW_EXECUTE),
|
||||
"ntdll!LdrInit");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshBreakpoints2() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "all");
|
||||
|
||||
RemoteMethod refreshProcWatchpoints = conn.getMethod("refresh_breakpoints");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
conn.execute("dbg = util.get_debugger()");
|
||||
conn.execute("pc = dbg.reg.get_pc()");
|
||||
conn.execute("dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)");
|
||||
conn.execute("dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)");
|
||||
conn.execute("dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)");
|
||||
TraceObject locations =
|
||||
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
|
||||
refreshProcWatchpoints.invoke(Map.of("node", locations));
|
||||
|
||||
List<TraceObjectValue> procBreakVals = tb.trace.getObjectManager()
|
||||
.getValuePaths(Lifespan.at(0),
|
||||
PathPredicates.parse("Processes[].Breakpoints[]"))
|
||||
.map(p -> p.getLastEntry())
|
||||
.toList();
|
||||
assertEquals(3, procBreakVals.size());
|
||||
AddressRange rangeMain0 =
|
||||
procBreakVals.get(0).getChild().getValue(0, "_range").castValue();
|
||||
Address main0 = rangeMain0.getMinAddress();
|
||||
AddressRange rangeMain1 =
|
||||
procBreakVals.get(1).getChild().getValue(0, "_range").castValue();
|
||||
Address main1 = rangeMain1.getMinAddress();
|
||||
AddressRange rangeMain2 =
|
||||
procBreakVals.get(2).getChild().getValue(0, "_range").castValue();
|
||||
Address main2 = rangeMain2.getMinAddress();
|
||||
|
||||
assertWatchLoc(procBreakVals.get(0), "[0]", main0, (int) rangeMain0.getLength(),
|
||||
Set.of(TraceBreakpointKind.HW_EXECUTE),
|
||||
"main");
|
||||
assertWatchLoc(procBreakVals.get(1), "[1]", main1, (int) rangeMain1.getLength(),
|
||||
Set.of(TraceBreakpointKind.WRITE),
|
||||
"main+4");
|
||||
assertWatchLoc(procBreakVals.get(2), "[2]", main2, (int) rangeMain1.getLength(),
|
||||
Set.of(TraceBreakpointKind.READ),
|
||||
"main+8");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshProcesses() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, null);
|
||||
txCreate(conn, "Processes");
|
||||
txCreate(conn, "Processes[1]");
|
||||
|
||||
RemoteMethod refreshProcesses = conn.getMethod("refresh_processes");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject processes = Objects.requireNonNull(tb.objAny("Processes"));
|
||||
|
||||
refreshProcesses.invoke(Map.of("node", processes));
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
List<TraceObject> list = tb.trace.getObjectManager()
|
||||
.getValuePaths(Lifespan.at(0), PathPredicates.parse("Processes[]"))
|
||||
.map(p -> p.getDestination(null))
|
||||
.toList();
|
||||
assertEquals(1, list.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshEnvironment() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
String path = "Processes[].Environment";
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "all");
|
||||
|
||||
RemoteMethod refreshEnvironment = conn.getMethod("refresh_environment");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject env = Objects.requireNonNull(tb.objAny(path));
|
||||
|
||||
refreshEnvironment.invoke(Map.of("node", env));
|
||||
|
||||
// Assumes pydbg on Windows amd64
|
||||
assertEquals("pydbg", env.getValue(0, "_debugger").getValue());
|
||||
assertEquals("x86_64", env.getValue(0, "_arch").getValue());
|
||||
assertEquals("windows", env.getValue(0, "_os").getValue());
|
||||
assertEquals("little", env.getValue(0, "_endian").getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshThreads() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
String path = "Processes[].Threads";
|
||||
start(conn, "notepad.exe");
|
||||
txCreate(conn, path);
|
||||
|
||||
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject threads = Objects.requireNonNull(tb.objAny(path));
|
||||
|
||||
refreshThreads.invoke(Map.of("node", threads));
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
int listSize = tb.trace.getThreadManager().getAllThreads().size();
|
||||
assertEquals(4, listSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshStack() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
String path = "Processes[].Threads[].Stack";
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod refreshStack = conn.getMethod("refresh_stack");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
txPut(conn, "frames");
|
||||
TraceObject stack = Objects.requireNonNull(tb.objAny(path));
|
||||
refreshStack.invoke(Map.of("node", stack));
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
List<TraceObject> list = tb.trace.getObjectManager()
|
||||
.getValuePaths(Lifespan.at(0),
|
||||
PathPredicates.parse("Processes[].Threads[].Stack[]"))
|
||||
.map(p -> p.getDestination(null))
|
||||
.toList();
|
||||
assertTrue(list.size() > 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshRegisters() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
String path = "Processes[].Threads[].Registers";
|
||||
start(conn, "notepad.exe");
|
||||
conn.execute("ghidra_trace_txstart('Tx')");
|
||||
conn.execute("ghidra_trace_putreg()");
|
||||
conn.execute("ghidra_trace_txcommit()");
|
||||
|
||||
RemoteMethod refreshRegisters = conn.getMethod("refresh_registers");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
conn.execute("regs = util.get_debugger().reg");
|
||||
conn.execute("regs._set_register('rax', int(0xdeadbeef))");
|
||||
|
||||
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
|
||||
refreshRegisters.invoke(Map.of("node", registers));
|
||||
|
||||
long snap = 0;
|
||||
AddressSpace t1f0 = tb.trace.getBaseAddressFactory()
|
||||
.getAddressSpace(registers.getCanonicalPath().toString());
|
||||
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(t1f0, false);
|
||||
RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
|
||||
assertEquals("deadbeef", rax.getUnsignedValue().toString(16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshMappings() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
String path = "Processes[].Memory";
|
||||
start(conn, "notepad.exe");
|
||||
txCreate(conn, path);
|
||||
|
||||
RemoteMethod refreshMappings = conn.getMethod("refresh_mappings");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject memory = Objects.requireNonNull(tb.objAny(path));
|
||||
|
||||
refreshMappings.invoke(Map.of("node", memory));
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
Collection<? extends TraceMemoryRegion> all =
|
||||
tb.trace.getMemoryManager().getAllRegions();
|
||||
assertThat(all.size(), greaterThan(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshModules() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
String path = "Processes[].Modules";
|
||||
start(conn, "notepad.exe");
|
||||
txCreate(conn, path);
|
||||
|
||||
RemoteMethod refreshModules = conn.getMethod("refresh_modules");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject modules = Objects.requireNonNull(tb.objAny(path));
|
||||
|
||||
refreshModules.invoke(Map.of("node", modules));
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
Collection<? extends TraceModule> all = tb.trace.getModuleManager().getAllModules();
|
||||
TraceModule modBash =
|
||||
Unique.assertOne(all.stream().filter(m -> m.getName().contains("notepad.exe")));
|
||||
assertNotEquals(tb.addr(0), Objects.requireNonNull(modBash.getBase()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateThread() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod activateThread = conn.getMethod("activate_thread");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
txPut(conn, "threads");
|
||||
|
||||
PathPattern pattern =
|
||||
PathPredicates.parse("Processes[].Threads[]").getSingletonPattern();
|
||||
List<TraceObject> list = tb.trace.getObjectManager()
|
||||
.getValuePaths(Lifespan.at(0), pattern)
|
||||
.map(p -> p.getDestination(null))
|
||||
.toList();
|
||||
assertEquals(4, list.size());
|
||||
|
||||
for (TraceObject t : list) {
|
||||
activateThread.invoke(Map.of("thread", t));
|
||||
String out = conn.executeCapture("util.get_debugger().get_thread()");
|
||||
List<String> indices = pattern.matchKeys(t.getCanonicalPath().getKeyList());
|
||||
assertEquals(out, "%s".formatted(indices.get(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveProcess() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "netstat.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod removeProcess = conn.getMethod("remove_process");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/netstat.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
removeProcess.invoke(Map.of("process", proc2));
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("[]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttachObj() throws Exception {
|
||||
try (DummyProc dproc = DummyProc.run("notepad.exe")) {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, null);
|
||||
txPut(conn, "available");
|
||||
|
||||
RemoteMethod attachObj = conn.getMethod("attach_obj");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject target =
|
||||
Objects.requireNonNull(tb.obj("Available[%d]".formatted(dproc.pid)));
|
||||
attachObj.invoke(Map.of("target", target));
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("%d".formatted(dproc.pid)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttachPid() throws Exception {
|
||||
try (DummyProc dproc = DummyProc.run("notepad.exe")) {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, null);
|
||||
txPut(conn, "available");
|
||||
|
||||
RemoteMethod attachPid = conn.getMethod("attach_pid");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
Objects.requireNonNull(tb.objAny("Available["+dproc.pid+"]", Lifespan.at(0)));
|
||||
attachPid.invoke(Map.of("pid", dproc.pid));
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("%d".formatted(dproc.pid)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetach() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "netstat.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod detach = conn.getMethod("detach");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/netstat.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
detach.invoke(Map.of("process", proc));
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("[]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchEntry() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, null);
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod launch = conn.getMethod("launch_loader");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
launch.invoke(Map.ofEntries(
|
||||
Map.entry("file", "notepad.exe")));
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("notepad.exe"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test //Can't do this test because create(xxx, initial_break=False) doesn't return
|
||||
public void testLaunch() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, null);
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod launch = conn.getMethod("launch");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
launch.invoke(Map.ofEntries(
|
||||
Map.entry("timeout", 1L),
|
||||
Map.entry("file", "notepad.exe")));
|
||||
|
||||
txPut(conn, "processes");
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("notepad.exe"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKill() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod kill = conn.getMethod("kill");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
kill.invoke(Map.of("process", proc));
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("[]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepInto() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod step_into = conn.getMethod("step_into");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
txPut(conn, "threads");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
|
||||
while (!getInst(conn).contains("call")) {
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
}
|
||||
|
||||
String disCall = getInst(conn);
|
||||
// lab0:
|
||||
// -> addr0
|
||||
//
|
||||
// lab1:
|
||||
// addr1
|
||||
String[] split = disCall.split("\\s+"); // get target
|
||||
long pcCallee = Long.decode(split[split.length-1]);
|
||||
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
long pc = getAddressAtOffset(conn, 0);
|
||||
assertEquals(pcCallee, pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepOver() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod step_over = conn.getMethod("step_over");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
txPut(conn, "threads");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
|
||||
while (!getInst(conn).contains("call")) {
|
||||
step_over.invoke(Map.of("thread", thread));
|
||||
}
|
||||
|
||||
String disCall = getInst(conn);
|
||||
String[] split = disCall.split("\\s+"); // get target
|
||||
long pcCallee = Long.decode(split[split.length-1]);
|
||||
|
||||
step_over.invoke(Map.of("thread", thread));
|
||||
long pc = getAddressAtOffset(conn, 0);
|
||||
assertNotEquals(pcCallee, pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepTo() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
|
||||
RemoteMethod step_into = conn.getMethod("step_into");
|
||||
RemoteMethod step_to = conn.getMethod("step_to");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
txPut(conn, "threads");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
while (!getInst(conn).contains("call")) {
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
}
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
|
||||
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
|
||||
for (int i = 0; i < 4; i++) {
|
||||
sz += Integer.parseInt(getInstSizeAtOffset(conn, sz));
|
||||
}
|
||||
|
||||
long pcNext = getAddressAtOffset(conn, sz);
|
||||
|
||||
boolean success = (boolean) step_to.invoke(Map.of("thread", thread, "address", tb.addr(pcNext), "max", 10));
|
||||
assertTrue(success);
|
||||
|
||||
long pc = getAddressAtOffset(conn, 0);
|
||||
assertEquals(pcNext, pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepOut() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod step_into = conn.getMethod("step_into");
|
||||
RemoteMethod step_out = conn.getMethod("step_out");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
txPut(conn, "threads");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
|
||||
while (!getInst(conn).contains("call")) {
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
}
|
||||
|
||||
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
|
||||
long pcNext = getAddressAtOffset(conn, sz);
|
||||
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
step_out.invoke(Map.of("thread", thread));
|
||||
long pc = getAddressAtOffset(conn, 0);
|
||||
assertEquals(pcNext, pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakAddress() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakAddress = conn.getMethod("break_address");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString(Long.toHexString(address)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakExpression() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "entry"));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("entry"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakHardwareAddress() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakAddress = conn.getMethod("break_hw_address");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString(Long.toHexString(address)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakHardwareExpression() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_hw_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "entry"));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("entry"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakReadRange() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakRange = conn.getMethod("break_read_range");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
AddressRange range = tb.range(address, address + 3); // length 4
|
||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("%x".formatted(address)));
|
||||
assertThat(out, containsString("sz=4"));
|
||||
assertThat(out, containsString("type=r"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakReadExpression() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_read_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString(Long.toHexString(address>>24)));
|
||||
assertThat(out, containsString("sz=1"));
|
||||
assertThat(out, containsString("type=r"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakWriteRange() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakRange = conn.getMethod("break_write_range");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
AddressRange range = tb.range(address, address + 3); // length 4
|
||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("%x".formatted(address)));
|
||||
assertThat(out, containsString("sz=4"));
|
||||
assertThat(out, containsString("type=w"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakWriteExpression() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_write_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString(Long.toHexString(address>>24)));
|
||||
assertThat(out, containsString("sz=1"));
|
||||
assertThat(out, containsString("type=w"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakAccessRange() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakRange = conn.getMethod("break_access_range");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
AddressRange range = tb.range(address, address + 3); // length 4
|
||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("%x".formatted(address)));
|
||||
assertThat(out, containsString("sz=4"));
|
||||
assertThat(out, containsString("type=rw"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakAccessExpression() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_access_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString(Long.toHexString(address>>24)));
|
||||
assertThat(out, containsString("sz=1"));
|
||||
assertThat(out, containsString("type=rw"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToggleBreakpoint() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakAddress = conn.getMethod("break_address");
|
||||
RemoteMethod toggleBreakpoint = conn.getMethod("toggle_breakpoint");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||
|
||||
txPut(conn, "breakpoints");
|
||||
TraceObject bpt = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints[]"));
|
||||
|
||||
toggleBreakpoint.invoke(Map.of("breakpoint", bpt, "enabled", false));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("disabled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteBreakpoint() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakAddress = conn.getMethod("break_address");
|
||||
RemoteMethod deleteBreakpoint = conn.getMethod("delete_breakpoint");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||
|
||||
txPut(conn, "breakpoints");
|
||||
TraceObject bpt = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints[]"));
|
||||
|
||||
deleteBreakpoint.invoke(Map.of("breakpoint", bpt));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("[]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void start(PythonAndHandler conn, String obj) {
|
||||
conn.execute("from ghidradbg.commands import *");
|
||||
if (obj != null)
|
||||
conn.execute("ghidra_trace_create('"+obj+"')");
|
||||
else
|
||||
conn.execute("ghidra_trace_create()"); }
|
||||
|
||||
private void txPut(PythonAndHandler conn, String obj) {
|
||||
conn.execute("ghidra_trace_txstart('Tx')");
|
||||
conn.execute("ghidra_trace_put_" + obj + "()");
|
||||
conn.execute("ghidra_trace_txcommit()");
|
||||
}
|
||||
|
||||
private void txCreate(PythonAndHandler conn, String path) {
|
||||
conn.execute("ghidra_trace_txstart('Fake')");
|
||||
conn.execute("ghidra_trace_create_obj('%s')".formatted(path));
|
||||
conn.execute("ghidra_trace_txcommit()");
|
||||
}
|
||||
|
||||
private String getInst(PythonAndHandler conn) {
|
||||
return getInstAtOffset(conn, 0);
|
||||
}
|
||||
|
||||
private String getInstAtOffset(PythonAndHandler conn, int offset) {
|
||||
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+"+offset+")";
|
||||
String ret = conn.executeCapture(inst);
|
||||
return ret.substring(1, ret.length()-1); // remove <>
|
||||
}
|
||||
|
||||
private String getInstSizeAtOffset(PythonAndHandler conn, int offset) {
|
||||
String instSize = "util.get_inst_sz(util.get_debugger().reg.get_pc()+"+offset+")";
|
||||
return conn.executeCapture(instSize);
|
||||
}
|
||||
|
||||
private long getAddressAtOffset(PythonAndHandler conn, int offset) {
|
||||
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+"+offset+")";
|
||||
String ret = conn.executeCapture(inst);
|
||||
String[] split = ret.split("\\s+"); // get target
|
||||
return Long.decode(split[1]);
|
||||
}
|
||||
|
||||
}
|
|
@ -134,7 +134,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
|
|||
ghidra_trace_connect %s
|
||||
file bash
|
||||
script ghidralldb.util.set_convenience_variable('ghidra-language','Toy:BE:64:default')
|
||||
script ghidralldb.util.set_convenience_varaible('ghidra-compiler','default')
|
||||
script ghidralldb.util.set_convenience_variable('ghidra-compiler','default')
|
||||
ghidra_trace_start myToy
|
||||
quit
|
||||
"""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue