mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-617: Fixing stepping. Fixes for object tree.
This commit is contained in:
parent
97b43a4c4e
commit
3ae09277f0
40 changed files with 773 additions and 260 deletions
|
@ -55,7 +55,8 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
|
|||
STEP_INSTRUCTION("step-instruction"),
|
||||
/** Equivalent to {@code until} in the CLI */
|
||||
UNTIL("until"),
|
||||
;
|
||||
/** User-defined */
|
||||
EXTENDED("until"),;
|
||||
|
||||
final String str;
|
||||
|
||||
|
@ -332,8 +333,12 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
|
|||
*
|
||||
* <p>
|
||||
* This waits for a prompt from GDB unless the last line printed is already a prompt. This is
|
||||
* generally not necessary following normal commands, but may be necessary after interrupting a
|
||||
* running inferior, or after waiting for an inferior to reach a stopped state.
|
||||
* generally not necessary following normal commands. Note that depending on circumstances and
|
||||
* GDB version, the MI console may produce a prompt before it produces all of the events
|
||||
* associated with an interrupt. If the <em>last</em> line is not currently a prompt, then the
|
||||
* returned future will not be complete. In other words, this is not a reliable way of verifying
|
||||
* GDB is waiting for a command. It's primary use is confirming that GDB has started
|
||||
* successfully and is awaiting its first command.
|
||||
*
|
||||
* @return a future which completes when GDB presents a prompt
|
||||
*/
|
||||
|
@ -393,7 +398,10 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
|
|||
* <p>
|
||||
* The output will not be printed to the CLI console. To ensure a certain thread or inferior has
|
||||
* focus for a console command, see {@link GdbThread#consoleCapture(String)} and
|
||||
* {@link GdbInferior#consoleCapture(String)}.
|
||||
* {@link GdbInferior#consoleCapture(String)}. The caller should take care that other commands
|
||||
* or events are not actively producing console output. If they are, those lines may be captured
|
||||
* though they are unrelated to the given command. Generally, this can be achieved by assuring
|
||||
* that GDB is in the {@link GdbState#STOPPED} state using {@link #waitForState(GdbState)}.
|
||||
*
|
||||
* @param command the command to execute
|
||||
* @return a future that completes with the captured output when GDB has executed the command
|
||||
|
@ -404,14 +412,14 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
|
|||
* Interrupt the GDB session
|
||||
*
|
||||
* <p>
|
||||
* This is equivalent to typing Ctrl-C in the CLI. This typically results in the target being
|
||||
* interrupted, either because GDB and the target have the same controlling TTY, or because GDB
|
||||
* will "forward" the interrupt to the target.
|
||||
*
|
||||
* <p>
|
||||
* For whatever reason, interrupting the session does not always reliably interrupt the target.
|
||||
* The manager will send Ctrl-C to the pseudo-terminal up to three times, waiting about 10ms
|
||||
* between each, until GDB issues a stopped event and presents a new prompt.
|
||||
* The manager may employ a variety of mechanisms depending on the current configuration. If
|
||||
* multiple interpreters are available, it will issue an "interrupt" command on whichever
|
||||
* interpreter it believes is responsive -- usually the opposite of the one issuing the last
|
||||
* run, continue, step, etc. command. Otherwise, it sends Ctrl-C to GDB's TTY, which
|
||||
* unfortunately is notoriously unreliable. The manager will send Ctrl-C to the TTY up to three
|
||||
* times, waiting about 10ms between each, until GDB issues a stopped event and presents a new
|
||||
* prompt. If that fails, it is up to the user to find an alternative means to interrupt the
|
||||
* target, e.g., issuing {@code kill [pid]} from the a terminal on the target's host.
|
||||
*
|
||||
* @return a future that completes when GDB has entered the stopped state
|
||||
*/
|
||||
|
|
|
@ -19,7 +19,6 @@ import agent.gdb.manager.GdbInferior;
|
|||
import agent.gdb.manager.evt.*;
|
||||
import agent.gdb.manager.impl.*;
|
||||
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Implementation of {@link GdbInferior#cont()}
|
||||
|
@ -29,56 +28,6 @@ public class GdbContinueCommand extends AbstractGdbCommandWithThreadId<Void> {
|
|||
super(manager, threadId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(String threadPart) {
|
||||
switch (getInterpreter()) {
|
||||
case CLI:
|
||||
return "continue";
|
||||
case MI2:
|
||||
return "-exec-continue" + threadPart;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(GdbEvent<?> evt, GdbPendingCommand<?> pending) {
|
||||
if (evt instanceof AbstractGdbCompletedCommandEvent) {
|
||||
if (!pending.hasAny(AbstractGdbCompletedCommandEvent.class)) {
|
||||
pending.claim(evt);
|
||||
}
|
||||
return evt instanceof GdbCommandErrorEvent || pending.hasAny(GdbRunningEvent.class);
|
||||
}
|
||||
else if (evt instanceof GdbRunningEvent) {
|
||||
// Event happens no matter which interpreter received the command
|
||||
pending.claim(evt);
|
||||
return pending.hasAny(AbstractGdbCompletedCommandEvent.class);
|
||||
}
|
||||
else if (evt instanceof GdbConsoleOutputEvent) {
|
||||
Msg.debug(this, "EXAMINING: " + evt);
|
||||
if (pending.hasAny(GdbCommandRunningEvent.class)) {
|
||||
// Only attempt to process/claim the first line after our command
|
||||
return false;
|
||||
}
|
||||
GdbConsoleOutputEvent out = (GdbConsoleOutputEvent) evt;
|
||||
if (out.getOutput().trim().equals("continue")) {
|
||||
// Echoed back my command
|
||||
return false;
|
||||
}
|
||||
pending.claim(evt);
|
||||
if (out.getOutput().trim().startsWith("Continuing") &&
|
||||
!pending.hasAny(GdbCommandRunningEvent.class)) {
|
||||
pending.claim(new GdbCommandRunningEvent());
|
||||
return pending.hasAny(GdbRunningEvent.class);
|
||||
}
|
||||
else {
|
||||
pending.claim(GdbCommandErrorEvent.fromMessage(out.getOutput()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Interpreter getInterpreter() {
|
||||
if (manager.hasCli()) {
|
||||
|
@ -87,6 +36,38 @@ public class GdbContinueCommand extends AbstractGdbCommandWithThreadId<Void> {
|
|||
return Interpreter.MI2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(String threadPart) {
|
||||
switch (getInterpreter()) {
|
||||
case CLI:
|
||||
// The significance is the Pty, not so much the actual command
|
||||
// Using MI2 simplifies event processing (no console output parsing)
|
||||
return "interpreter-exec mi2 \"-exec-continue" + threadPart + "\"";
|
||||
case MI2:
|
||||
return "-exec-continue" + threadPart;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(GdbEvent<?> evt, GdbPendingCommand<?> pending) {
|
||||
if (evt instanceof GdbCommandRunningEvent) {
|
||||
pending.claim(evt);
|
||||
return pending.hasAny(GdbRunningEvent.class);
|
||||
}
|
||||
else if (evt instanceof AbstractGdbCompletedCommandEvent) {
|
||||
pending.claim(evt);
|
||||
return true; // Not the expected Completed event
|
||||
}
|
||||
else if (evt instanceof GdbRunningEvent) {
|
||||
// Event happens no matter which interpreter received the command
|
||||
pending.claim(evt);
|
||||
return pending.hasAny(GdbCommandRunningEvent.class);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void complete(GdbPendingCommand<?> pending) {
|
||||
pending.checkCompletion(GdbCommandRunningEvent.class);
|
||||
|
|
|
@ -33,7 +33,7 @@ public class GdbGetThreadInfoCommand extends AbstractGdbCommandWithThreadId<GdbT
|
|||
|
||||
@Override
|
||||
protected String encode(String threadPart) {
|
||||
return "-thread-info" + threadPart;
|
||||
return "-thread-info " + threadId; // Note the trailing space
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -67,7 +67,8 @@ public class GdbInterruptCommand extends AbstractGdbCommand<Void> {
|
|||
|
||||
@Override
|
||||
public Void complete(GdbPendingCommand<?> pending) {
|
||||
pending.findSingleOf(GdbStoppedEvent.class);
|
||||
// When using -exec-interrupt, ^done will come before *stopped
|
||||
//pending.findSingleOf(GdbStoppedEvent.class);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import agent.gdb.manager.GdbInferior;
|
|||
import agent.gdb.manager.GdbThread;
|
||||
import agent.gdb.manager.evt.*;
|
||||
import agent.gdb.manager.impl.*;
|
||||
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
|
||||
|
||||
/**
|
||||
* Implementation of {@link GdbInferior#run()}
|
||||
|
@ -29,9 +30,26 @@ public class GdbRunCommand extends AbstractGdbCommand<GdbThread> {
|
|||
super(manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Interpreter getInterpreter() {
|
||||
if (manager.hasCli()) {
|
||||
return Interpreter.CLI;
|
||||
}
|
||||
return Interpreter.MI2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode() {
|
||||
return "-exec-run";
|
||||
switch (getInterpreter()) {
|
||||
case CLI:
|
||||
// The significance is the Pty, not so much the actual command
|
||||
// Using MI2 simplifies event processing (no console output parsing)
|
||||
return "interpreter-exec mi2 \"-exec-run\"";
|
||||
case MI2:
|
||||
return "-exec-run";
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -40,7 +58,7 @@ public class GdbRunCommand extends AbstractGdbCommand<GdbThread> {
|
|||
pending.claim(evt);
|
||||
return pending.hasAny(GdbRunningEvent.class);
|
||||
}
|
||||
if (evt instanceof AbstractGdbCompletedCommandEvent) {
|
||||
else if (evt instanceof AbstractGdbCompletedCommandEvent) {
|
||||
pending.claim(evt);
|
||||
return true; // Not the expected Completed event
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import agent.gdb.manager.GdbManager.ExecSuffix;
|
|||
import agent.gdb.manager.GdbThread;
|
||||
import agent.gdb.manager.evt.*;
|
||||
import agent.gdb.manager.impl.*;
|
||||
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
|
||||
|
||||
/**
|
||||
* Implementation of {@link GdbThread#stepInstruction()}
|
||||
|
@ -31,9 +32,27 @@ public class GdbStepCommand extends AbstractGdbCommandWithThreadId<Void> {
|
|||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Interpreter getInterpreter() {
|
||||
if (manager.hasCli()) {
|
||||
return Interpreter.CLI;
|
||||
}
|
||||
return Interpreter.MI2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String encode(String threadPart) {
|
||||
return "-exec-" + suffix + threadPart;
|
||||
String mi2Cmd = "-exec-" + suffix + threadPart;
|
||||
switch (getInterpreter()) {
|
||||
case CLI:
|
||||
// The significance is the Pty, not so much the actual command
|
||||
// Using MI2 simplifies event processing (no console output parsing)
|
||||
return "interpreter-exec mi2 \"" + mi2Cmd + "\"";
|
||||
case MI2:
|
||||
return mi2Cmd;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -33,11 +33,12 @@ import ghidra.dbg.util.PathUtils;
|
|||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Inferior", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "Inferior",
|
||||
elements = {
|
||||
@TargetElementType(type = Void.class) },
|
||||
attributes = {
|
||||
@TargetAttributeType(type = Void.class) })
|
||||
public class GdbModelTargetInferior
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetInferiorContainer> implements //
|
||||
TargetProcess<GdbModelTargetInferior>, //
|
||||
|
@ -92,20 +93,19 @@ public class GdbModelTargetInferior
|
|||
this.registers = new GdbModelTargetRegisterContainer(this);
|
||||
this.threads = new GdbModelTargetThreadContainer(this);
|
||||
|
||||
changeAttributes(List.of(),
|
||||
List.of(
|
||||
environment,
|
||||
memory,
|
||||
modules,
|
||||
registers,
|
||||
threads),
|
||||
Map.of(
|
||||
STATE_ATTRIBUTE_NAME, TargetExecutionState.INACTIVE,
|
||||
DISPLAY_ATTRIBUTE_NAME, updateDisplay(),
|
||||
TargetMethod.PARAMETERS_ATTRIBUTE_NAME, TargetCmdLineLauncher.PARAMETERS,
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED,
|
||||
SUPPORTED_ATTACH_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS,
|
||||
SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, GdbModelTargetThread.SUPPORTED_KINDS),
|
||||
changeAttributes(List.of(), //
|
||||
List.of( //
|
||||
environment, //
|
||||
memory, //
|
||||
modules, //
|
||||
registers, //
|
||||
threads), //
|
||||
Map.of(STATE_ATTRIBUTE_NAME, TargetExecutionState.INACTIVE, //
|
||||
DISPLAY_ATTRIBUTE_NAME, updateDisplay(), //
|
||||
TargetMethod.PARAMETERS_ATTRIBUTE_NAME, TargetCmdLineLauncher.PARAMETERS, //
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED, //
|
||||
SUPPORTED_ATTACH_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS, //
|
||||
SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, GdbModelTargetThread.SUPPORTED_KINDS), //
|
||||
"Initialized");
|
||||
}
|
||||
|
||||
|
@ -160,6 +160,8 @@ public class GdbModelTargetInferior
|
|||
return ExecSuffix.RETURN;
|
||||
case UNTIL:
|
||||
return ExecSuffix.UNTIL;
|
||||
case EXTENDED:
|
||||
return ExecSuffix.EXTENDED;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
|
|
@ -26,11 +26,12 @@ import ghidra.dbg.target.schema.*;
|
|||
import ghidra.dbg.util.CollectionUtils.Delta;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "RegisterValue", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "RegisterValue",
|
||||
elements = {
|
||||
@TargetElementType(type = Void.class) },
|
||||
attributes = {
|
||||
@TargetAttributeType(type = Void.class) })
|
||||
public class GdbModelTargetStackFrameRegister
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetStackFrameRegisterContainer> {
|
||||
|
||||
|
@ -57,7 +58,8 @@ public class GdbModelTargetStackFrameRegister
|
|||
|
||||
changeAttributes(List.of(), Map.of( //
|
||||
DISPLAY_ATTRIBUTE_NAME, getName(), //
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED //
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED, //
|
||||
MODIFIED_ATTRIBUTE_NAME, false //
|
||||
), "Initialized");
|
||||
}
|
||||
|
||||
|
@ -72,11 +74,11 @@ public class GdbModelTargetStackFrameRegister
|
|||
boolean modified = (bigNewVal.longValue() != 0 && value.equals(oldval));
|
||||
|
||||
String newval = getName() + " : " + value;
|
||||
Delta<?, ?> delta = changeAttributes(List.of(), Map.of(
|
||||
VALUE_ATTRIBUTE_NAME, value,
|
||||
DISPLAY_ATTRIBUTE_NAME, newval,
|
||||
MODIFIED_ATTRIBUTE_NAME, modified),
|
||||
"Value Updated");
|
||||
Delta<?, ?> delta = changeAttributes(List.of(), Map.of( //
|
||||
VALUE_ATTRIBUTE_NAME, value, //
|
||||
DISPLAY_ATTRIBUTE_NAME, newval, //
|
||||
MODIFIED_ATTRIBUTE_NAME, modified //
|
||||
), "Value Updated");
|
||||
if (delta.added.containsKey(DISPLAY_ATTRIBUTE_NAME)) {
|
||||
listeners.fire.displayChanged(this, newval);
|
||||
}
|
||||
|
|
|
@ -32,18 +32,25 @@ import ghidra.dbg.util.PathUtils;
|
|||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Thread", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "Thread",
|
||||
elements = {
|
||||
@TargetElementType(type = Void.class) },
|
||||
attributes = {
|
||||
@TargetAttributeType(type = Void.class) })
|
||||
public class GdbModelTargetThread
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetThreadContainer> implements
|
||||
TargetThread<GdbModelTargetThread>, TargetExecutionStateful<GdbModelTargetThread>,
|
||||
TargetSteppable<GdbModelTargetThread>, GdbModelSelectableObject {
|
||||
protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( //
|
||||
TargetStepKind.ADVANCE, TargetStepKind.FINISH, TargetStepKind.LINE, TargetStepKind.OVER,
|
||||
TargetStepKind.OVER_LINE, TargetStepKind.RETURN, TargetStepKind.UNTIL);
|
||||
TargetStepKind.ADVANCE, //
|
||||
TargetStepKind.FINISH, //
|
||||
TargetStepKind.LINE, //
|
||||
TargetStepKind.OVER, //
|
||||
TargetStepKind.OVER_LINE, //
|
||||
TargetStepKind.RETURN, //
|
||||
TargetStepKind.UNTIL, //
|
||||
TargetStepKind.EXTENDED);
|
||||
|
||||
protected static String indexThread(int threadId) {
|
||||
return PathUtils.makeIndex(threadId);
|
||||
|
@ -75,16 +82,14 @@ public class GdbModelTargetThread
|
|||
|
||||
this.stack = new GdbModelTargetStack(this, inferior);
|
||||
|
||||
changeAttributes(List.of(),
|
||||
List.of(
|
||||
stack),
|
||||
Map.of(
|
||||
STATE_ATTRIBUTE_NAME, convertState(thread.getState()),
|
||||
SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS,
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(),
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED,
|
||||
stack.getName(), stack),
|
||||
"Initialized");
|
||||
changeAttributes(List.of(), List.of(stack), Map.of( //
|
||||
STATE_ATTRIBUTE_NAME, convertState(thread.getState()), //
|
||||
SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS, //
|
||||
SHORT_DISPLAY_ATTRIBUTE_NAME, shortDisplay = computeShortDisplay(), //
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(), //
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED, //
|
||||
stack.getName(), stack //
|
||||
), "Initialized");
|
||||
|
||||
updateInfo().exceptionally(ex -> {
|
||||
Msg.error(this, "Could not initialize thread info");
|
||||
|
@ -110,11 +115,9 @@ public class GdbModelTargetThread
|
|||
|
||||
protected String computeDisplay() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(shortDisplay);
|
||||
if (info != null) {
|
||||
sb.append(shortDisplay);
|
||||
sb.append(" ");
|
||||
//sb.append(info.getTargetId());
|
||||
//sb.append(" ");
|
||||
sb.append(info.getInferiorName());
|
||||
sb.append(" ");
|
||||
sb.append(info.getState());
|
||||
|
@ -127,22 +130,23 @@ public class GdbModelTargetThread
|
|||
sb.append(" in ");
|
||||
sb.append(frame.getFunc());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
sb.append(thread.getId());
|
||||
sb.append(" ");
|
||||
sb.append(stack.inferior.inferior.getDescriptor());
|
||||
sb.append(" ");
|
||||
sb.append(stack.inferior.inferior.getExecutable());
|
||||
GdbModelTargetStackFrame top = stack.framesByLevel.get(0);
|
||||
if (top == null) {
|
||||
return sb.toString();
|
||||
else {
|
||||
sb.append(" ");
|
||||
String executableName = stack.inferior.inferior.getExecutable();
|
||||
if (executableName != null) {
|
||||
sb.append(executableName);
|
||||
}
|
||||
GdbModelTargetStackFrame top = stack.framesByLevel.get(0);
|
||||
if (top == null) {
|
||||
return sb.toString();
|
||||
}
|
||||
sb.append(" 0x");
|
||||
sb.append(top.frame.getAddress().toString(16));
|
||||
sb.append(" in ");
|
||||
sb.append(top.frame.getFunction());
|
||||
sb.append(" ()");
|
||||
}
|
||||
sb.append(" 0x");
|
||||
sb.append(top.frame.getAddress().toString(16));
|
||||
sb.append(" in ");
|
||||
sb.append(top.frame.getFunction());
|
||||
sb.append(" ()");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
@ -151,10 +155,15 @@ public class GdbModelTargetThread
|
|||
sb.append("[");
|
||||
sb.append(inferior.getId());
|
||||
sb.append(".");
|
||||
sb.append(info.getId());
|
||||
if (info.getTid() != null) {
|
||||
sb.append(":");
|
||||
sb.append(info.getTid());
|
||||
if (info == null) {
|
||||
sb.append(thread.getId());
|
||||
}
|
||||
else {
|
||||
sb.append(info.getId());
|
||||
if (info.getTid() != null) {
|
||||
sb.append(":");
|
||||
sb.append(info.getTid());
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
|
@ -205,6 +214,8 @@ public class GdbModelTargetThread
|
|||
return ExecSuffix.RETURN;
|
||||
case UNTIL:
|
||||
return ExecSuffix.UNTIL;
|
||||
case EXTENDED:
|
||||
return ExecSuffix.EXTENDED;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.nio.ByteBuffer;
|
|||
import java.nio.ByteOrder;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.*;
|
||||
|
@ -183,6 +184,30 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
}
|
||||
}
|
||||
|
||||
public static class LibraryWaiter extends CompletableFuture<String>
|
||||
implements GdbEventsListenerAdapter {
|
||||
protected final Predicate<String> predicate;
|
||||
|
||||
public LibraryWaiter(Predicate<String> predicate) {
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void libraryLoaded(GdbInferior inferior, String name, GdbCause cause) {
|
||||
if (predicate.test(name)) {
|
||||
complete(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void assertResponsive(GdbManager mgr) throws Throwable {
|
||||
//Msg.debug(this, "Waiting for prompt");
|
||||
//waitOn(mgr.waitForPrompt());
|
||||
Msg.debug(this, "Testing echo test");
|
||||
String out = waitOn(mgr.consoleCapture("echo test"));
|
||||
assertEquals("test", out.trim());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartInterrupt() throws Throwable {
|
||||
assumeFalse("I know no way to get this to pass with these conditions",
|
||||
|
@ -194,15 +219,8 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
* and the time its signal handlers are installed. It seems waiting for libc to load
|
||||
* guarantees that GDB is ready to interrupt the process.
|
||||
*/
|
||||
CompletableFuture<Void> libcLoaded = new CompletableFuture<>();
|
||||
mgr.addEventsListener(new GdbEventsListenerAdapter() {
|
||||
@Override
|
||||
public void libraryLoaded(GdbInferior inferior, String name, GdbCause cause) {
|
||||
if (name.contains("libc")) {
|
||||
libcLoaded.complete(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
LibraryWaiter libcLoaded = new LibraryWaiter(name -> name.contains("libc"));
|
||||
mgr.addEventsListener(libcLoaded);
|
||||
waitOn(startManager(mgr));
|
||||
waitOn(mgr.currentInferior().fileExecAndSymbols("/usr/bin/sleep"));
|
||||
waitOn(mgr.currentInferior().console("set args 3"));
|
||||
|
@ -211,13 +229,40 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
Thread.sleep(100); // TODO: Why?
|
||||
Msg.debug(this, "Interrupting");
|
||||
waitOn(mgr.interrupt());
|
||||
Msg.debug(this, "Waiting for prompt");
|
||||
waitOn(mgr.waitForPrompt());
|
||||
Msg.debug(this, "Testing echo test");
|
||||
String out = waitOn(mgr.consoleCapture("echo test"));
|
||||
// Check that we have a responsive console, now.
|
||||
// Otherwise, the interrupt failed
|
||||
assertEquals("test", out.trim());
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
assertResponsive(mgr);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepSyscallInterrupt() throws Throwable {
|
||||
assumeFalse("I know no way to get this to pass with these conditions",
|
||||
this instanceof JoinedGdbManagerTest);
|
||||
// Repeat the start-interrupt sequence, then verify we're preparing to step a syscall
|
||||
try (GdbManager mgr = GdbManager.newInstance()) {
|
||||
LibraryWaiter libcLoaded = new LibraryWaiter(name -> name.contains("libc"));
|
||||
mgr.addEventsListener(libcLoaded);
|
||||
waitOn(startManager(mgr));
|
||||
waitOn(mgr.currentInferior().fileExecAndSymbols("/usr/bin/sleep"));
|
||||
waitOn(mgr.currentInferior().console("set args 5"));
|
||||
waitOn(mgr.currentInferior().run());
|
||||
waitOn(libcLoaded);
|
||||
Thread.sleep(100); // TODO: Why?
|
||||
Msg.debug(this, "Interrupting");
|
||||
waitOn(mgr.interrupt());
|
||||
Msg.debug(this, "Verifying at syscall");
|
||||
String out = waitOn(mgr.consoleCapture("x/1i $pc-2"));
|
||||
// TODO: This is x86-specific
|
||||
assertTrue("Didn't stop at syscall", out.contains("syscall"));
|
||||
|
||||
// Now the real test
|
||||
waitOn(mgr.currentInferior().step(ExecSuffix.STEP_INSTRUCTION));
|
||||
CompletableFuture<Void> stopped = mgr.waitForState(GdbState.STOPPED);
|
||||
Thread.sleep(100); // NB: Not exactly reliable, but verify we're waiting
|
||||
assertFalse(stopped.isDone());
|
||||
waitOn(mgr.interrupt());
|
||||
waitOn(stopped);
|
||||
assertResponsive(mgr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,7 +274,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
waitOn(mgr.insertBreakpoint("main"));
|
||||
waitOn(mgr.currentInferior().run());
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
waitOn(mgr.waitForPrompt());
|
||||
//waitOn(mgr.waitForPrompt());
|
||||
waitOn(mgr.currentInferior().setVar("$rax=", "0xdeadbeef")); // Corrupts it
|
||||
String val = waitOn(mgr.currentInferior().evaluate("$rax+1"));
|
||||
assertEquals(0xdeadbeef + 1, Integer.parseUnsignedInt(val));
|
||||
|
@ -270,7 +315,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
waitOn(mgr.insertBreakpoint("main"));
|
||||
GdbThread thread = waitOn(mgr.currentInferior().run());
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
waitOn(mgr.waitForPrompt());
|
||||
//waitOn(mgr.waitForPrompt());
|
||||
GdbRegisterSet regs = waitOn(thread.listRegisters());
|
||||
Set<GdbRegister> toRead = new HashSet<>();
|
||||
toRead.add(regs.get("eflags"));
|
||||
|
@ -306,7 +351,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
waitOn(mgr.insertBreakpoint("main"));
|
||||
GdbThread thread = waitOn(mgr.currentInferior().run());
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
waitOn(mgr.waitForPrompt());
|
||||
//waitOn(mgr.waitForPrompt());
|
||||
String str = waitOn(mgr.currentInferior().evaluate("(long)main"));
|
||||
long addr = Long.parseLong(str);
|
||||
ByteBuffer buf = ByteBuffer.allocate(1024);
|
||||
|
@ -336,7 +381,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
waitOn(mgr.insertBreakpoint("main"));
|
||||
GdbThread thread = waitOn(mgr.currentInferior().run());
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
waitOn(mgr.waitForPrompt());
|
||||
//waitOn(mgr.waitForPrompt());
|
||||
waitOn(thread.cont());
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
assertEquals(0L, (long) mgr.currentInferior().getExitCode());
|
||||
|
@ -351,7 +396,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
waitOn(mgr.insertBreakpoint("main"));
|
||||
GdbThread thread = waitOn(mgr.currentInferior().run());
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
waitOn(mgr.waitForPrompt());
|
||||
//waitOn(mgr.waitForPrompt());
|
||||
waitOn(thread.step(ExecSuffix.NEXT_INSTRUCTION));
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
assertNull(mgr.currentInferior().getExitCode());
|
||||
|
@ -366,7 +411,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
waitOn(mgr.insertBreakpoint("main"));
|
||||
GdbThread thread = waitOn(mgr.currentInferior().run());
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
waitOn(mgr.waitForPrompt());
|
||||
//waitOn(mgr.waitForPrompt());
|
||||
waitOn(thread.select());
|
||||
}
|
||||
}
|
||||
|
@ -379,7 +424,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
waitOn(mgr.insertBreakpoint("main"));
|
||||
GdbThread thread = waitOn(mgr.currentInferior().run());
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
waitOn(mgr.waitForPrompt());
|
||||
//waitOn(mgr.waitForPrompt());
|
||||
waitOn(mgr.insertBreakpoint("write"));
|
||||
waitOn(mgr.currentInferior().cont());
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue