GP-617: Fixing stepping. Fixes for object tree.

This commit is contained in:
d-millar 2021-01-22 17:50:10 +00:00 committed by Dan
parent 97b43a4c4e
commit 3ae09277f0
40 changed files with 773 additions and 260 deletions

View File

@ -41,7 +41,7 @@ public interface DebugClient extends DebugClientReentrant {
STEP_INTO(true, ExecutionState.RUNNING, 5), //
BREAK(false, ExecutionState.STOPPED, 0), //
NO_DEBUGGEE(true, null, 1), // shouldWait is true to handle process creation
STEP_BRANCH(true, null, 6), //
STEP_BRANCH(true, ExecutionState.RUNNING, 6), //
IGNORE_EVENT(false, null, 11), //
RESTART_REQUESTED(true, null, 12), //
REVERSE_GO(true, null, 0xff), //

View File

@ -45,7 +45,9 @@ public interface DbgManager extends AutoCloseable, DbgBreakpointInsertions {
/** Equivalent to {@code stepi} in the CLI */
STEP_INSTRUCTION("step-instruction"),
/** Equivalent to {@code until} in the CLI */
UNTIL("until"),;
UNTIL("until"),
/** Equivalent to {@code ext} in the CLI */
EXTENDED("ext"),;
final String str;

View File

@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
import agent.dbgeng.dbgeng.DebugProcessId;
import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.impl.DbgSectionImpl;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.TargetAttachable;
@ -182,6 +183,32 @@ public interface DbgProcess extends DbgMemoryOperations {
*/
CompletableFuture<Void> cont();
/**
* Step the process
*
* Note that the command can complete before the process has finished stepping. The command
* completes as soon as the process is running. A separate stop event is emitted when the step is
* completed.
*
* @param suffix specifies how far to step, or on what conditions stepping ends.
*
* @return a future that completes once the process is running
*/
CompletableFuture<Void> step(ExecSuffix suffix);
/**
* Step the process
*
* Note that the command can complete before the process has finished stepping. The command
* completes as soon as the process is running. A separate stop event is emitted when the step is
* completed.
*
* @param args specifies how far to step, or on what conditions stepping ends.
*
* @return a future that completes once the process is running
*/
CompletableFuture<Void> step(Map<String, ?> args);
/**
* Evaluate an expression
*

View File

@ -16,6 +16,7 @@
package agent.dbgeng.manager;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import agent.dbgeng.dbgeng.DebugThreadId;
@ -106,6 +107,19 @@ public interface DbgThread
*/
CompletableFuture<Void> step(ExecSuffix suffix);
/**
* Step the thread
*
* Note that the command can complete before the thread has finished stepping. The command
* completes as soon as the thread is running. A separate stop event is emitted when the step is
* completed.
*
* @param args specifies how far to step, or on what conditions stepping ends.
*
* @return a future that completes once the thread is running
*/
CompletableFuture<Void> step(Map<String, ?> args);
/**
* Detach from the entire process
*

View File

@ -20,6 +20,7 @@ import agent.dbgeng.manager.DbgProcess;
import agent.dbgeng.manager.impl.DbgManagerImpl;
public class DbgProcessSelectCommand extends AbstractDbgCommand<Void> {
private DbgProcess process;
/**

View File

@ -55,7 +55,7 @@ public class DbgReadRegistersCommand extends AbstractDbgCommand<Map<DbgRegister,
}
}
}
so.setCurrentThreadId(previous);
//so.setCurrentThreadId(previous);
return result;
}

View File

@ -63,6 +63,6 @@ public class DbgStackListFramesCommand extends AbstractDbgCommand<List<DbgStackF
tf.Params[3].longValue());
result.add(frame);
}
so.setCurrentThreadId(previous);
//so.setCurrentThreadId(previous);
}
}

View File

@ -15,25 +15,40 @@
*/
package agent.dbgeng.manager.cmd;
import agent.dbgeng.dbgeng.DebugClient.DebugStatus;
import java.util.Map;
import agent.dbgeng.dbgeng.DebugControl;
import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.manager.DbgEvent;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.manager.evt.*;
import agent.dbgeng.manager.impl.DbgManagerImpl;
import agent.dbgeng.manager.impl.DbgThreadImpl;
import ghidra.util.Msg;
/**
* Implementation of {@link DbgThread#stepInstruction()}
*/
public class DbgStepCommand extends AbstractDbgCommand<Void> {
protected final ExecSuffix suffix;
public DbgStepCommand(DbgManagerImpl manager, ExecSuffix suffix) {
private DebugThreadId id;
protected final ExecSuffix suffix;
private String lastCommand = "tct";
public DbgStepCommand(DbgManagerImpl manager, DebugThreadId id, ExecSuffix suffix) {
super(manager);
this.id = id;
this.suffix = suffix;
}
public DbgStepCommand(DbgManagerImpl manager, DebugThreadId id, Map<String, ?> args) {
super(manager);
this.id = id;
this.suffix = ExecSuffix.EXTENDED;
this.lastCommand = (String) args.get("Command");
}
@Override
public boolean handle(DbgEvent<?> evt, DbgPendingCommand<?> pending) {
if (evt instanceof AbstractDbgCompletedCommandEvent && pending.getCommand().equals(this)) {
@ -48,17 +63,53 @@ public class DbgStepCommand extends AbstractDbgCommand<Void> {
return false;
}
// NB: Would really prefer to do this through the API, but the API does
// not appear to support freeze/unfreeze and suspend/resume thread. These appear
// to be applied via the kernel32 API. Worse, the Windbg/KD API appears to lack
// commands to query the freeze/suspend count for a given thread. Rather than
// wrestle with the underlying API, we're going to just use the WIndbg commands.
// Note that the thread-restricted form is used iff we're stepping a thread other
// then the event thread.
@Override
public void invoke() {
String cmd = "";
String prefix = id == null ? "" : "~" + id.id + " ";
DebugControl control = manager.getControl();
if (suffix.equals(ExecSuffix.STEP_INSTRUCTION)) {
control.setExecutionStatus(DebugStatus.STEP_INTO);
cmd = "t";
//control.setExecutionStatus(DebugStatus.STEP_INTO);
}
else if (suffix.equals(ExecSuffix.NEXT_INSTRUCTION)) {
control.setExecutionStatus(DebugStatus.STEP_OVER);
cmd = "p";
//control.setExecutionStatus(DebugStatus.STEP_OVER);
}
else if (suffix.equals(ExecSuffix.FINISH)) {
control.setExecutionStatus(DebugStatus.STEP_BRANCH);
cmd = "gu";
//control.setExecutionStatus(DebugStatus.STEP_BRANCH);
}
else if (suffix.equals(ExecSuffix.EXTENDED)) {
cmd = getLastCommand();
}
DbgThreadImpl eventThread = manager.getEventThread();
if (eventThread != null && eventThread.getId().equals(id)) {
control.execute(cmd);
}
else {
if (manager.isKernelMode()) {
Msg.info(this, "Thread-specific stepping ignored in kernel-mode");
control.execute(cmd);
}
else {
control.execute(prefix + cmd);
}
}
}
public String getLastCommand() {
return lastCommand;
}
public void setLastCommand(String lastCommand) {
this.lastCommand = lastCommand;
}
}

View File

@ -0,0 +1,73 @@
/* ###
* 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.manager.cmd;
import agent.dbgeng.dbgeng.DebugControl;
import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.manager.impl.DbgManagerImpl;
public class DbgThreadHoldCommand extends AbstractDbgCommand<Void> {
static final String FREEZE_ALL_THREADS_COMMAND = "~* f";
static final String FREEZE_CURRENT_THREAD_COMMAND = "~. f";
static final String UNFREEZE_CURRENT_THREAD_COMMAND = "~. u";
static final String UNFREEZE_ALL_THREADS_COMMAND = "~* u";
static final String SUSPEND_ALL_THREADS_COMMAND = "~* n";
static final String SUSPEND_CURRENT_THREAD_COMMAND = "~. n";
static final String RESUME_CURRENT_THREAD_COMMAND = "~. m";
static final String RESUME_ALL_THREADS_COMMAND = "~* m";
static final Boolean preferFreeze = true;
private DbgThread thread;
private Boolean set;
/**
* Select the given thread and frame level
*
* To simply select a thread, you should use frame 0 as the default.
*
* @param manager the manager to execute the command
* @param thread the desired thread
* @param set hold or release
*/
public DbgThreadHoldCommand(DbgManagerImpl manager, DbgThread thread, Boolean set) {
super(manager);
this.thread = thread;
this.set = set;
}
@Override
public void invoke() {
DebugThreadId id = thread.getId();
if (id != null) {
manager.getSystemObjects().setCurrentThreadId(id);
if (!manager.isKernelMode()) {
DebugControl control = manager.getControl();
if (preferFreeze) {
control.execute(
set ? FREEZE_CURRENT_THREAD_COMMAND : UNFREEZE_CURRENT_THREAD_COMMAND);
}
else {
control.execute(
set ? SUSPEND_CURRENT_THREAD_COMMAND : RESUME_CURRENT_THREAD_COMMAND);
}
}
}
}
}

View File

@ -20,6 +20,7 @@ import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.manager.impl.DbgManagerImpl;
public class DbgThreadSelectCommand extends AbstractDbgCommand<Void> {
private DbgThread thread;
/**

View File

@ -66,6 +66,6 @@ public class DbgWriteRegistersCommand extends AbstractDbgCommand<Void> {
}
}
registers.setValues(DebugRegisterSource.DEBUG_REGSRC_DEBUGGEE, values);
so.setCurrentThreadId(previous);
//so.setCurrentThreadId(previous);
}
}

View File

@ -28,6 +28,7 @@ import com.google.common.collect.RangeSet;
import agent.dbgeng.dbgeng.*;
import agent.dbgeng.dbgeng.DebugClient.DebugAttachFlags;
import agent.dbgeng.manager.*;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.cmd.*;
import ghidra.async.TypeSpec;
import ghidra.comm.util.BitmaskSet;
@ -304,6 +305,24 @@ public class DbgProcessImpl implements DbgProcess {
}).finish();
}
@Override
public CompletableFuture<Void> step(ExecSuffix suffix) {
return sequence(TypeSpec.VOID).then((seq) -> {
select().handle(seq::next);
}).then((seq) -> {
manager.execute(new DbgStepCommand(manager, null, suffix)).handle(seq::exit);
}).finish();
}
@Override
public CompletableFuture<Void> step(Map<String, ?> args) {
return sequence(TypeSpec.VOID).then((seq) -> {
select().handle(seq::next);
}).then((seq) -> {
manager.execute(new DbgStepCommand(manager, null, args)).handle(seq::exit);
}).finish();
}
protected <T> CompletableFuture<T> preferThread(
Function<DbgThreadImpl, CompletableFuture<T>> viaThread,
Supplier<CompletableFuture<T>> viaThis) {

View File

@ -220,7 +220,16 @@ public class DbgThreadImpl implements DbgThread {
return sequence(TypeSpec.VOID).then((seq) -> {
select().handle(seq::next);
}).then((seq) -> {
manager.execute(new DbgStepCommand(manager, suffix)).handle(seq::exit);
manager.execute(new DbgStepCommand(manager, id, suffix)).handle(seq::exit);
}).finish();
}
@Override
public CompletableFuture<Void> step(Map<String, ?> args) {
return sequence(TypeSpec.VOID).then((seq) -> {
select().handle(seq::next);
}).then((seq) -> {
manager.execute(new DbgStepCommand(manager, id, args)).handle(seq::exit);
}).finish();
}

View File

@ -15,11 +15,12 @@
*/
package agent.dbgeng.model.iface1;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.model.iface2.DbgModelTargetObject;
import agent.dbgeng.model.iface2.*;
import ghidra.dbg.target.TargetSteppable;
/**
@ -48,6 +49,8 @@ public interface DbgModelTargetSteppable<T extends TargetSteppable<T>>
return ExecSuffix.RETURN;
case UNTIL:
return ExecSuffix.UNTIL;
case EXTENDED:
return ExecSuffix.EXTENDED;
default:
throw new AssertionError();
}
@ -62,7 +65,21 @@ public interface DbgModelTargetSteppable<T extends TargetSteppable<T>>
case ADVANCE: // Why no exec-advance in dbgeng?
return thread.console("advance");
default:
if (this instanceof DbgModelTargetThread) {
DbgModelTargetThread targetThread = (DbgModelTargetThread) this;
return targetThread.getThread().step(convertToDbg(kind));
}
if (this instanceof DbgModelTargetProcess) {
DbgModelTargetProcess targetProcess = (DbgModelTargetProcess) this;
return targetProcess.getProcess().step(convertToDbg(kind));
}
return thread.step(convertToDbg(kind));
}
}
default CompletableFuture<Void> step(Map<String, ?> args) {
DbgThread thread = getManager().getCurrentThread();
return thread.step(args);
}
}

View File

@ -180,17 +180,21 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl
@Override
public CompletableFuture<Void> step(TargetStepKind kind) {
DbgThread thread = getManager().getCurrentThread();
switch (kind) {
case SKIP:
throw new UnsupportedOperationException(kind.name());
case ADVANCE: // Why no exec-advance in dbgeng?
return thread.console("advance");
throw new UnsupportedOperationException(kind.name());
default:
return thread.step(convertToDbg(kind));
return process.step(convertToDbg(kind));
}
}
@Override
public CompletableFuture<Void> step(Map<String, ?> args) {
return process.step(args);
}
@Override
public void processStarted(Long pid) {
if (pid != null) {

View File

@ -87,9 +87,24 @@ public class DbgModelTargetRootImpl extends DbgModelDefaultTargetModelRoot
boolean doFire;
synchronized (this) {
doFire = !Objects.equals(this.focus, sel);
this.focus = sel;
if (doFire && focus != null) {
List<String> focusPath = focus.getPath();
List<String> selPath = sel.getPath();
for (int i = 0; i < focusPath.size(); i++) {
if (i >= selPath.size()) {
doFire = false;
break;
}
if (!focusPath.get(i).equals(selPath.get(i))) {
doFire = true;
break;
}
}
//doFire = !focusPath.containsAll(selPath);
}
}
if (doFire) {
this.focus = sel;
changeAttributes(List.of(), List.of(), Map.of( //
TargetFocusScope.FOCUS_ATTRIBUTE_NAME, focus //
), "Focus changed");

View File

@ -22,7 +22,6 @@ import java.util.concurrent.atomic.AtomicReference;
import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.manager.*;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.cmd.DbgThreadSelectCommand;
import agent.dbgeng.manager.impl.DbgManagerImpl;
import agent.dbgeng.model.iface1.DbgModelTargetFocusScope;
@ -46,8 +45,14 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetThread {
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(DebugThreadId debugThreadId) {
return PathUtils.makeIndex(debugThreadId.id);
@ -117,33 +122,12 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl
TargetExecutionState targetState = convertState(state);
String executionType = thread.getExecutingProcessorType().description;
changeAttributes(List.of(), List.of(), Map.of( //
STATE_ATTRIBUTE_NAME, targetState, //
TargetEnvironment.ARCH_ATTRIBUTE_NAME, executionType //
), reason.desc());
setExecutionState(targetState, reason.desc());
}
@Override
public ExecSuffix convertToDbg(TargetStepKind kind) {
switch (kind) {
case FINISH:
return ExecSuffix.FINISH;
case INTO:
return ExecSuffix.STEP_INSTRUCTION;
case LINE:
return ExecSuffix.STEP;
case OVER:
return ExecSuffix.NEXT_INSTRUCTION;
case OVER_LINE:
return ExecSuffix.NEXT;
case RETURN:
return ExecSuffix.RETURN;
case UNTIL:
return ExecSuffix.UNTIL;
default:
throw new AssertionError();
}
}
@Override
public CompletableFuture<Void> step(TargetStepKind kind) {
switch (kind) {
@ -156,6 +140,11 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl
}
}
@Override
public CompletableFuture<Void> step(Map<String, ?> args) {
return thread.step(args);
}
@Override
public CompletableFuture<Void> select() {
DbgManagerImpl manager = getManager();

View File

@ -96,9 +96,24 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
boolean doFire;
synchronized (this) {
doFire = !Objects.equals(this.focus, sel);
this.focus = sel;
if (doFire && focus != null) {
List<String> focusPath = focus.getPath();
List<String> selPath = sel.getPath();
for (int i = 0; i < focusPath.size(); i++) {
if (i >= selPath.size()) {
doFire = false;
break;
}
if (!focusPath.get(i).equals(selPath.get(i))) {
doFire = true;
break;
}
}
//doFire = !focusPath.containsAll(selPath);
}
}
if (doFire) {
this.focus = sel;
changeAttributes(List.of(), List.of(), Map.of( //
TargetFocusScope.FOCUS_ATTRIBUTE_NAME, focus //
), "Focus changed");

View File

@ -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
*/

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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
}

View File

@ -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

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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));

View File

@ -15,7 +15,7 @@
*/
package ghidra.dbg.gadp.util;
import static ghidra.lifecycle.Unfinished.TODO;
import static ghidra.lifecycle.Unfinished.*;
import java.util.*;
import java.util.Map.Entry;
@ -159,6 +159,8 @@ public enum GadpValueUtils {
return TargetStepKind.SKIP;
case SK_UNTIL:
return TargetStepKind.UNTIL;
case SK_EXTENDED:
return TargetStepKind.EXTENDED;
default:
throw new IllegalArgumentException();
}
@ -184,6 +186,8 @@ public enum GadpValueUtils {
return Gadp.StepKind.SK_SKIP;
case UNTIL:
return Gadp.StepKind.SK_UNTIL;
case EXTENDED:
return Gadp.StepKind.SK_EXTENDED;
default:
throw new IllegalArgumentException();
}

View File

@ -98,6 +98,7 @@ enum StepKind {
SK_SKIP = 6;
SK_RETURN = 7;
SK_UNTIL = 8;
SK_EXTENDED = 9;
}
message StepKindsSet {

View File

@ -57,8 +57,14 @@ public class JdiModelTargetThread extends JdiModelTargetObjectReference implemen
JdiModelSelectableObject {
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);
private EventRequestManager eventManager;
protected final ThreadReference thread;

View File

@ -27,11 +27,11 @@
pull-down, and pop-up menus. Commands are enabled in two ways: (1) the object selected has a
property that marks it as a logical target for that command, or (2) the object has an ancestor
for which the command makes sense. The <A href=
"DebuggerObjectsPlugin.html#act_on_selection_only">Act on Selection Only</A> function
"DebuggerObjectsPlugin.html#act_on_selection_only">Enable By Selection Only</A> function
determines whether both options are in play. For example, threads and inferiors/processes are
both <B>resumable</B>, so the <A href="DebuggerObjectsPlugin.html#resume">Resume</A> action
works on both. For many of our targets, processes are <B>interruptible</B> while threads are
not. Nevertheless, if <B>Act On Selection Only</B> is off, you can interrupt a thread because
not. Nevertheless, if <B>Enable By Selection Only</B> is off, you can interrupt a thread because
it descends from an inferior or process. In almost every case, the selection directly or
indirectly determines the set of valid or enabled actions.</P>
@ -152,7 +152,7 @@
<P>Step the current target to the next instruction in the current subroutine.</P>
<H3><A name="step_finish"></A><IMG alt="" src="images/stepout.png"> Step Finish</H3>
<H3><A name="step_finish"></A><IMG alt="" src="images/stepout.png"> Finish</H3>
<P>Allow the current target to finish the current subroutine, pausing after.</P>
@ -298,7 +298,7 @@
<P>Asks the recorder to include or exclude the current object from the trace.</P>
<H3><A name="act_on_selection_only"></A>Act on Selection Only</H3>
<H3><A name="act_on_selection_only"></A>Enable By Selection Only</H3>
<P>Toggles the scope of the other action menus. If "selection only" is chosen, the current
object must have the property used to enable the particular action. For instance, the

View File

@ -180,6 +180,13 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
)
Color linkForegroundColor = Color.GREEN;
@AutoOptionDefined( //
name = "Default Extended Step", //
description = "The default string for the extended step command" //
//help = @HelpInfo(anchor = "colors") //
)
String extendedStep = "";
private static final Icon ENABLED_ICON = ResourceManager.loadImage("images/enabled.png");
private static final Icon DISABLED_ICON = ResourceManager.loadImage("images/disabled.png");
@ -432,7 +439,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
}
public void traceOpened(Trace trace) {
refresh();
//refresh();
repeatLastSet.run();
}
@ -660,6 +667,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
TargetObject targetObject = container.getTargetObject();
if (targetObject != null) {
String key = targetObject.getJoinedPath(PATH_JOIN_CHAR);
container.subscribe();
targetMap.put(key, container);
refSet.add(targetObject);
if (targetObject instanceof TargetInterpreter) {
@ -754,6 +762,18 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
super.closeComponent();
}
public void signalDataChanged(ObjectContainer container) {
if (pane != null) {
pane.signalDataChanged(container);
}
}
public void signalContentsChanged(ObjectContainer container) {
if (pane != null) {
pane.signalContentsChanged(container);
}
}
@Override
public void update(ObjectContainer container) {
if (pane != null) {
@ -875,8 +895,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
groupTargetIndex++;
actionToggleSelectionOnly = new ToggleActionBuilder("Act on Selection Only", plugin.getName())
.menuPath("Maintenance","Act on &Selection Only")
actionToggleSelectionOnly = new ToggleActionBuilder("Enable By Selection Only", plugin.getName())
.menuPath("Maintenance","Enable By &Selection Only")
.menuGroup(DebuggerResources.GROUP_TARGET, "M" + groupTargetIndex)
.helpLocation(new HelpLocation(plugin.getName(), "act_on_selection_only"))
.onAction(ctx -> performToggleSelectionOnly(ctx))
@ -1094,25 +1114,44 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
groupTargetIndex++;
new ActionBuilder("Step Finish", plugin.getName())
.keyBinding("F12")
.toolBarGroup(DebuggerResources.GROUP_CONTROL, "C" + groupTargetIndex)
.toolBarIcon(AbstractStepFinishAction.ICON)
.popupMenuPath("&Step Finish")
.popupMenuGroup(DebuggerResources.GROUP_CONTROL, "C" + groupTargetIndex)
.popupMenuIcon(AbstractStepFinishAction.ICON)
.helpLocation(AbstractStepFinishAction.help(plugin))
//.withContext(ObjectActionContext.class)
.enabledWhen(ctx ->
isInstance(ctx, TargetSteppable.tclass) && isStopped(ctx))
.popupWhen(ctx ->
isInstance(ctx, TargetSteppable.tclass) && isStopped(ctx))
.onAction(ctx -> performStepFinish(ctx))
.enabled(false)
.buildAndInstallLocal(this);
groupTargetIndex++;
new ActionBuilder("Finish", plugin.getName())
.keyBinding("F12")
.toolBarGroup(DebuggerResources.GROUP_CONTROL, "C" + groupTargetIndex)
.toolBarIcon(AbstractStepFinishAction.ICON)
.popupMenuPath("&Finish")
.popupMenuGroup(DebuggerResources.GROUP_CONTROL, "C" + groupTargetIndex)
.popupMenuIcon(AbstractStepFinishAction.ICON)
.helpLocation(AbstractStepFinishAction.help(plugin))
//.withContext(ObjectActionContext.class)
.enabledWhen(ctx ->
isInstance(ctx, TargetSteppable.tclass) && isStopped(ctx))
.popupWhen(ctx ->
isInstance(ctx, TargetSteppable.tclass) && isStopped(ctx))
.onAction(ctx -> performStepFinish(ctx))
.enabled(false)
.buildAndInstallLocal(this);
groupTargetIndex++;
new ActionBuilder("Step Last", plugin.getName())
.keyBinding("ALT F8")
.toolBarGroup(DebuggerResources.GROUP_CONTROL, "C" + groupTargetIndex)
.toolBarIcon(AbstractStepFinishAction.ICON)
.popupMenuPath("&Step Last")
.popupMenuGroup(DebuggerResources.GROUP_CONTROL, "C" + groupTargetIndex)
.popupMenuIcon(AbstractStepFinishAction.ICON)
.helpLocation(AbstractStepFinishAction.help(plugin))
//.withContext(ObjectActionContext.class)
.enabledWhen(ctx ->
isInstance(ctx, TargetSteppable.tclass) && isStopped(ctx))
.popupWhen(ctx ->
isInstance(ctx, TargetSteppable.tclass) && isStopped(ctx))
.onAction(ctx -> performStepLast(ctx))
.enabled(false)
.buildAndInstallLocal(this);
groupTargetIndex++;
actionAddBreakpoint = new ActionBuilder("Add Breakpoint", plugin.getName())
.keyBinding("F3")
.toolBarGroup(DebuggerResources.GROUP_CONTROL, "C" + groupTargetIndex)
@ -1475,6 +1514,26 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
}
}
public void performStepLast(ActionContext context) {
TargetObject obj = getObjectFromContext(context);
if (!isLocalOnly()) {
DebugModelConventions.findSuitable(TargetSteppable.class, obj).thenAccept(steppable -> {
steppable.step(TargetStepKind.EXTENDED);
}).exceptionally(DebuggerResources.showError(getComponent(), "Couldn't step"));
}
else {
TargetSteppable<?> steppable = (TargetSteppable<?>) obj;
if (extendedStep.equals("")) {
steppable.step(TargetStepKind.EXTENDED);
}
else {
Map<String, String> args = new HashMap<String, String>();
args.put("Command", extendedStep);
steppable.step(args);
}
}
}
public void performSetBreakpoint(ActionContext context) {
TargetObject obj = getObjectFromContext(context);
if (!isLocalOnly()) {
@ -1553,7 +1612,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
public void displayChanged(TargetObject object, String display) {
//System.err.println("displayChanged: " + display);
if (ObjectContainer.visibleByDefault(object.getName())) {
pane.signalDataChange(getContainerByPath(object.getPath()));
pane.signalDataChanged(getContainerByPath(object.getPath()));
}
}
@ -1579,7 +1638,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
@Override
public void memoryUpdated(TargetMemory<?> memory, Address address, byte[] data) {
System.err.println("memoryUpdated");
//System.err.println("memoryUpdated");
}
@Override

View File

@ -212,11 +212,13 @@ public class ObjectContainer implements Comparable {
public void augmentElements(Collection<String> elementsRemoved,
Map<String, ? extends TargetObject> elementsAdded) {
Set<ObjectContainer> result = new TreeSet<ObjectContainer>();
boolean structureChanged = false;
synchronized (elementMap) {
for (ObjectContainer child : currentChildren) {
String name = child.getName();
if (elementsRemoved.contains(name) && !elementsAdded.containsKey(name)) {
elementMap.remove(name);
structureChanged = true;
continue;
}
result.add(child);
@ -224,12 +226,21 @@ public class ObjectContainer implements Comparable {
for (String key : elementsAdded.keySet()) {
TargetObject val = elementsAdded.get(key);
ObjectContainer child =
DebuggerObjectsProvider.buildContainerFromObject(targetObject, key, val, true);
DebuggerObjectsProvider.buildContainerFromObject(targetObject, key, val, false);
if (!elementMap.containsKey(key)) {
structureChanged = true;
}
else {
provider.signalDataChanged(child);
}
elementMap.put(key, val);
result.add(child);
}
}
currentChildren = result;
if (structureChanged) {
provider.signalContentsChanged(this);
}
provider.fireObjectUpdated(this);
//provider.update(this);
}
@ -237,11 +248,13 @@ public class ObjectContainer implements Comparable {
public void augmentAttributes(Collection<String> attributesRemoved,
Map<String, ?> attributesAdded) {
Set<ObjectContainer> result = new TreeSet<ObjectContainer>();
boolean structureChanged = false;
synchronized (attributeMap) {
for (ObjectContainer child : currentChildren) {
String name = child.getName();
if (attributesRemoved.contains(name) && !attributesAdded.containsKey(name)) {
attributeMap.remove(name);
structureChanged = true;
continue;
}
result.add(child);
@ -250,16 +263,22 @@ public class ObjectContainer implements Comparable {
Object val = attributesAdded.get(key);
ObjectContainer child =
DebuggerObjectsProvider.buildContainerFromObject(targetObject, key, val, true);
if (child == null) {
Msg.error(this, "Null container for " + key);
}
else {
if (child != null) {
if (!attributeMap.containsKey(key)) {
structureChanged = true;
}
else {
provider.signalDataChanged(child);
}
attributeMap.put(key, val);
result.add(child);
}
}
}
currentChildren = result;
if (structureChanged) {
provider.signalContentsChanged(this);
}
provider.fireObjectUpdated(this);
//provider.update(this);
}

View File

@ -44,6 +44,7 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode
private String name;
private ObjectTree tree;
private Set<GTreeNode> oldChildren;
private boolean restructured = false;
public ObjectNode(ObjectTree tree, ObjectContainer parent, ObjectContainer container) {
this.tree = tree;
@ -76,7 +77,6 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode
}
@Override
//public List<GTreeNode> generateChildren() {
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
if (!container.isImmutable() || isInProgress()) {
@ -184,20 +184,61 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode
}
public void markExpanded() {
container.subscribe();
//container.subscribe();
}
public void markCollapsed() {
container.unsubscribe();
//container.unsubscribe();
}
public void cleanUpOldChildren(List<GTreeNode> newChildren) {
if (oldChildren != null) {
oldChildren.removeAll(newChildren);
for (GTreeNode node : oldChildren) {
tree.cleanupOldNode((ObjectNode) node);
synchronized (oldChildren) {
oldChildren.removeAll(newChildren);
for (GTreeNode node : oldChildren) {
setRestructured(true);
tree.cleanupOldNode((ObjectNode) node);
}
}
}
oldChildren = new HashSet<>(newChildren);
}
public void callUpdate() {
// NB: this has to be in its own thread
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
List<GTreeNode> updateNodes = tree.update(container);
if (isRestructured()) {
setChildren(updateNodes);
}
// Unnecessary: fireNodeStructureChanged(ObjectNode.this);
}
});
thread.start();
}
public void callModified() {
// NB: this has to be in its own thread
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
List<GTreeNode> updateNodes = tree.update(container);
for (GTreeNode n : updateNodes) {
n.fireNodeChanged(ObjectNode.this, n);
}
}
});
thread.start();
}
public boolean isRestructured() {
return restructured;
}
public void setRestructured(boolean restructured) {
this.restructured = restructured;
}
}

View File

@ -38,7 +38,9 @@ public interface ObjectPane {
public List<? extends Object> update(ObjectContainer container);
public void signalDataChange(ObjectContainer container);
public void signalDataChanged(ObjectContainer container);
public void signalContentsChanged(ObjectContainer container);
public void signalUpdate(ObjectContainer container);

View File

@ -109,7 +109,14 @@ public class ObjectTable<R> implements ObjectPane {
}
@Override
public void signalDataChange(ObjectContainer oc) {
public void signalDataChanged(ObjectContainer oc) {
Swing.runIfSwingOrRunLater(() -> {
update(oc);
});
}
@Override
public void signalContentsChanged(ObjectContainer oc) {
Swing.runIfSwingOrRunLater(() -> {
update(oc);
});

View File

@ -26,6 +26,7 @@ import javax.swing.JComponent;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
@ -92,12 +93,32 @@ public class ObjectTree implements ObjectPane {
}
}
provider.getTool().contextChanged(provider);
if (e.getEventOrigin() == EventOrigin.INTERNAL_GENERATED) {
restoreTreeStateManager.updateLater();
}
else {
currentSelectionPaths = tree.getSelectionPaths();
if (e.getEventOrigin() != EventOrigin.INTERNAL_GENERATED) {
currentExpandedPaths = tree.getExpandedPaths();
if (e.getEventOrigin() == EventOrigin.USER_GENERATED) {
currentSelectionPaths = tree.getSelectionPaths();
currentViewPosition = tree.getViewPosition();
}
else {
TreePath[] selectionPaths = tree.getSelectionPaths();
if (currentSelectionPaths != null && currentSelectionPaths.length > 0) {
if (selectionPaths != null && selectionPaths.length > 0) {
TreePath currentPath = currentSelectionPaths[0];
TreePath selectedPath = selectionPaths[0];
// NB. isDescendant == has a descendent
if (currentPath.isDescendant(selectedPath)) {
currentSelectionPaths = selectionPaths;
currentViewPosition = tree.getViewPosition();
}
else if (!selectedPath.isDescendant(currentPath)) {
currentSelectionPaths = selectionPaths;
currentViewPosition = tree.getViewPosition();
}
}
}
}
}
restoreTreeStateManager.updateLater();
}
});
tree.setCellRenderer(new ObjectTreeCellRenderer(root.getProvider()));
@ -146,6 +167,7 @@ public class ObjectTree implements ObjectPane {
}
});
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setSelectedNode(root);
}
@ -209,10 +231,19 @@ public class ObjectTree implements ObjectPane {
}
@Override
public void signalDataChange(ObjectContainer container) {
public void signalContentsChanged(ObjectContainer container) {
ObjectNode node = nodeMap.get(path(container));
if (node != null) {
node.callUpdate();
}
}
@Override
public void signalDataChanged(ObjectContainer container) {
Swing.runIfSwingOrRunLater(() -> {
ObjectNode node = nodeMap.get(path(container));
if (node != null) {
node.setContainer(this, container.getParent(), container);
node.fireNodeChanged(node.getParent(), node);
}
});
@ -288,13 +319,14 @@ public class ObjectTree implements ObjectPane {
public List<GTreeNode> update(ObjectContainer container) {
ObjectNode node = nodeMap.get(path(container));
if (node == null) {
System.err.println("Missing node: " + path(container));
Msg.warn(this, "Missing node: " + path(container));
return new ArrayList<>();
}
Set<ObjectContainer> currentChildren = container.getCurrentChildren();
List<GTreeNode> childList = new ArrayList<GTreeNode>();
node.setRestructured(false);
for (ObjectContainer c : currentChildren) {
ObjectNode nc;
String path = path(c);
@ -305,6 +337,7 @@ public class ObjectTree implements ObjectPane {
nc.setContainer(this, container, c);
}
else {
node.setRestructured(true);
nc = new ObjectNode(this, container, c);
}
childList.add(nc);
@ -326,10 +359,6 @@ public class ObjectTree implements ObjectPane {
public void setFocus(TargetFocusScope<?> object, TargetObjectRef focused) {
Swing.runIfSwingOrRunLater(() -> {
List<String> path = focused.getPath();
ObjectContainer container = getProvider().getContainerByPath(path);
if (container != null) {
container.subscribe();
}
tree.setSelectedNodeByNamePath(addRootNameToPath(path));
});
}
@ -398,5 +427,4 @@ public class ObjectTree implements ObjectPane {
}
}
}
}

View File

@ -17,6 +17,7 @@ package ghidra.dbg.target;
import static ghidra.lifecycle.Unfinished.TODO;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@ -193,6 +194,10 @@ public interface TargetSteppable<T extends TargetSteppable<T>> extends TypedTarg
* line of source code after the line which generated the instruction about to be executed.
*/
UNTIL,
/**
* Step until some condition is met.
*/
EXTENDED,
}
String SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "supported_step_kinds";
@ -206,7 +211,11 @@ public interface TargetSteppable<T extends TargetSteppable<T>> extends TypedTarg
*
* @return the set of supported multi-step operations
*/
@TargetAttributeType(name = SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
@TargetAttributeType(
name = SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME,
required = true,
fixed = true,
hidden = true)
public default TargetStepKindSet getSupportedStepKinds() {
return getTypedAttributeNowByName(SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME,
TargetStepKindSet.class, TargetStepKindSet.of());
@ -244,11 +253,21 @@ public interface TargetSteppable<T extends TargetSteppable<T>> extends TypedTarg
*/
public CompletableFuture<Void> step(TargetStepKind kind);
/**
* Step a target using the given arguments
*
* @param args the map of arguments.
* @return a future which completes when the command is completed
*/
public default CompletableFuture<Void> step(Map<String, ?> args) {
return step(TargetStepKind.INTO);
}
/**
* Step a single instruction
*
* <p>
* This convenience is exactly equivalent to calling {@code step(TargetStepKind.INSTRUCTION)}
* This convenience is exactly equivalent to calling {@code step(TargetStepKind.INTO)}
*
* @see #step(TargetStepKind)
* @see TargetStepKind#INTO

View File

@ -251,6 +251,9 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
@Override
public void delete(R row) {
int rowIndex = modelData.indexOf(row);
if (rowIndex == -1) {
return;
}
modelData.remove(rowIndex);
fireTableRowsDeleted(rowIndex, rowIndex);
}