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), // STEP_INTO(true, ExecutionState.RUNNING, 5), //
BREAK(false, ExecutionState.STOPPED, 0), // BREAK(false, ExecutionState.STOPPED, 0), //
NO_DEBUGGEE(true, null, 1), // shouldWait is true to handle process creation 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), // IGNORE_EVENT(false, null, 11), //
RESTART_REQUESTED(true, null, 12), // RESTART_REQUESTED(true, null, 12), //
REVERSE_GO(true, null, 0xff), // REVERSE_GO(true, null, 0xff), //

View file

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

View file

@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
import agent.dbgeng.dbgeng.DebugProcessId; import agent.dbgeng.dbgeng.DebugProcessId;
import agent.dbgeng.dbgeng.DebugThreadId; import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.impl.DbgSectionImpl; import agent.dbgeng.manager.impl.DbgSectionImpl;
import ghidra.dbg.attributes.TypedTargetObjectRef; import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.TargetAttachable; import ghidra.dbg.target.TargetAttachable;
@ -182,6 +183,32 @@ public interface DbgProcess extends DbgMemoryOperations {
*/ */
CompletableFuture<Void> cont(); 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 * Evaluate an expression
* *

View file

@ -16,6 +16,7 @@
package agent.dbgeng.manager; package agent.dbgeng.manager;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import agent.dbgeng.dbgeng.DebugThreadId; import agent.dbgeng.dbgeng.DebugThreadId;
@ -106,6 +107,19 @@ public interface DbgThread
*/ */
CompletableFuture<Void> step(ExecSuffix suffix); 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 * Detach from the entire process
* *

View file

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

View file

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

View file

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

View file

@ -15,25 +15,40 @@
*/ */
package agent.dbgeng.manager.cmd; package agent.dbgeng.manager.cmd;
import agent.dbgeng.dbgeng.DebugClient.DebugStatus; import java.util.Map;
import agent.dbgeng.dbgeng.DebugControl; import agent.dbgeng.dbgeng.DebugControl;
import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.manager.DbgEvent; import agent.dbgeng.manager.DbgEvent;
import agent.dbgeng.manager.DbgManager.ExecSuffix; import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.DbgThread; import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.manager.evt.*; import agent.dbgeng.manager.evt.*;
import agent.dbgeng.manager.impl.DbgManagerImpl; import agent.dbgeng.manager.impl.DbgManagerImpl;
import agent.dbgeng.manager.impl.DbgThreadImpl;
import ghidra.util.Msg;
/** /**
* Implementation of {@link DbgThread#stepInstruction()} * Implementation of {@link DbgThread#stepInstruction()}
*/ */
public class DbgStepCommand extends AbstractDbgCommand<Void> { 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); super(manager);
this.id = id;
this.suffix = suffix; 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 @Override
public boolean handle(DbgEvent<?> evt, DbgPendingCommand<?> pending) { public boolean handle(DbgEvent<?> evt, DbgPendingCommand<?> pending) {
if (evt instanceof AbstractDbgCompletedCommandEvent && pending.getCommand().equals(this)) { if (evt instanceof AbstractDbgCompletedCommandEvent && pending.getCommand().equals(this)) {
@ -48,17 +63,53 @@ public class DbgStepCommand extends AbstractDbgCommand<Void> {
return false; 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 @Override
public void invoke() { public void invoke() {
String cmd = "";
String prefix = id == null ? "" : "~" + id.id + " ";
DebugControl control = manager.getControl(); DebugControl control = manager.getControl();
if (suffix.equals(ExecSuffix.STEP_INSTRUCTION)) { 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)) { 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)) { 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; import agent.dbgeng.manager.impl.DbgManagerImpl;
public class DbgThreadSelectCommand extends AbstractDbgCommand<Void> { public class DbgThreadSelectCommand extends AbstractDbgCommand<Void> {
private DbgThread thread; private DbgThread thread;
/** /**

View file

@ -66,6 +66,6 @@ public class DbgWriteRegistersCommand extends AbstractDbgCommand<Void> {
} }
} }
registers.setValues(DebugRegisterSource.DEBUG_REGSRC_DEBUGGEE, values); 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.*;
import agent.dbgeng.dbgeng.DebugClient.DebugAttachFlags; import agent.dbgeng.dbgeng.DebugClient.DebugAttachFlags;
import agent.dbgeng.manager.*; import agent.dbgeng.manager.*;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.cmd.*; import agent.dbgeng.manager.cmd.*;
import ghidra.async.TypeSpec; import ghidra.async.TypeSpec;
import ghidra.comm.util.BitmaskSet; import ghidra.comm.util.BitmaskSet;
@ -304,6 +305,24 @@ public class DbgProcessImpl implements DbgProcess {
}).finish(); }).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( protected <T> CompletableFuture<T> preferThread(
Function<DbgThreadImpl, CompletableFuture<T>> viaThread, Function<DbgThreadImpl, CompletableFuture<T>> viaThread,
Supplier<CompletableFuture<T>> viaThis) { Supplier<CompletableFuture<T>> viaThis) {

View file

@ -220,7 +220,16 @@ public class DbgThreadImpl implements DbgThread {
return sequence(TypeSpec.VOID).then((seq) -> { return sequence(TypeSpec.VOID).then((seq) -> {
select().handle(seq::next); select().handle(seq::next);
}).then((seq) -> { }).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(); }).finish();
} }

View file

@ -15,11 +15,12 @@
*/ */
package agent.dbgeng.model.iface1; package agent.dbgeng.model.iface1;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import agent.dbgeng.manager.DbgManager.ExecSuffix; import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.DbgThread; import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.model.iface2.DbgModelTargetObject; import agent.dbgeng.model.iface2.*;
import ghidra.dbg.target.TargetSteppable; import ghidra.dbg.target.TargetSteppable;
/** /**
@ -48,6 +49,8 @@ public interface DbgModelTargetSteppable<T extends TargetSteppable<T>>
return ExecSuffix.RETURN; return ExecSuffix.RETURN;
case UNTIL: case UNTIL:
return ExecSuffix.UNTIL; return ExecSuffix.UNTIL;
case EXTENDED:
return ExecSuffix.EXTENDED;
default: default:
throw new AssertionError(); throw new AssertionError();
} }
@ -62,7 +65,21 @@ public interface DbgModelTargetSteppable<T extends TargetSteppable<T>>
case ADVANCE: // Why no exec-advance in dbgeng? case ADVANCE: // Why no exec-advance in dbgeng?
return thread.console("advance"); return thread.console("advance");
default: 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)); 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 @Override
public CompletableFuture<Void> step(TargetStepKind kind) { public CompletableFuture<Void> step(TargetStepKind kind) {
DbgThread thread = getManager().getCurrentThread();
switch (kind) { switch (kind) {
case SKIP: case SKIP:
throw new UnsupportedOperationException(kind.name()); throw new UnsupportedOperationException(kind.name());
case ADVANCE: // Why no exec-advance in dbgeng? case ADVANCE: // Why no exec-advance in dbgeng?
return thread.console("advance"); throw new UnsupportedOperationException(kind.name());
default: default:
return thread.step(convertToDbg(kind)); return process.step(convertToDbg(kind));
} }
} }
@Override
public CompletableFuture<Void> step(Map<String, ?> args) {
return process.step(args);
}
@Override @Override
public void processStarted(Long pid) { public void processStarted(Long pid) {
if (pid != null) { if (pid != null) {

View file

@ -87,9 +87,24 @@ public class DbgModelTargetRootImpl extends DbgModelDefaultTargetModelRoot
boolean doFire; boolean doFire;
synchronized (this) { synchronized (this) {
doFire = !Objects.equals(this.focus, sel); 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) { if (doFire) {
this.focus = sel;
changeAttributes(List.of(), List.of(), Map.of( // changeAttributes(List.of(), List.of(), Map.of( //
TargetFocusScope.FOCUS_ATTRIBUTE_NAME, focus // TargetFocusScope.FOCUS_ATTRIBUTE_NAME, focus //
), "Focus changed"); ), "Focus changed");

View file

@ -22,7 +22,6 @@ import java.util.concurrent.atomic.AtomicReference;
import agent.dbgeng.dbgeng.DebugThreadId; import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.manager.*; import agent.dbgeng.manager.*;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.cmd.DbgThreadSelectCommand; import agent.dbgeng.manager.cmd.DbgThreadSelectCommand;
import agent.dbgeng.manager.impl.DbgManagerImpl; import agent.dbgeng.manager.impl.DbgManagerImpl;
import agent.dbgeng.model.iface1.DbgModelTargetFocusScope; import agent.dbgeng.model.iface1.DbgModelTargetFocusScope;
@ -46,8 +45,14 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetThread { implements DbgModelTargetThread {
protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( // protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( //
TargetStepKind.ADVANCE, TargetStepKind.FINISH, TargetStepKind.LINE, TargetStepKind.OVER, TargetStepKind.ADVANCE, //
TargetStepKind.OVER_LINE, TargetStepKind.RETURN, TargetStepKind.UNTIL); TargetStepKind.FINISH, //
TargetStepKind.LINE, //
TargetStepKind.OVER, //
TargetStepKind.OVER_LINE, //
TargetStepKind.RETURN, //
TargetStepKind.UNTIL, //
TargetStepKind.EXTENDED);
protected static String indexThread(DebugThreadId debugThreadId) { protected static String indexThread(DebugThreadId debugThreadId) {
return PathUtils.makeIndex(debugThreadId.id); return PathUtils.makeIndex(debugThreadId.id);
@ -117,33 +122,12 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl
TargetExecutionState targetState = convertState(state); TargetExecutionState targetState = convertState(state);
String executionType = thread.getExecutingProcessorType().description; String executionType = thread.getExecutingProcessorType().description;
changeAttributes(List.of(), List.of(), Map.of( // changeAttributes(List.of(), List.of(), Map.of( //
STATE_ATTRIBUTE_NAME, targetState, //
TargetEnvironment.ARCH_ATTRIBUTE_NAME, executionType // TargetEnvironment.ARCH_ATTRIBUTE_NAME, executionType //
), reason.desc()); ), reason.desc());
setExecutionState(targetState, 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 @Override
public CompletableFuture<Void> step(TargetStepKind kind) { public CompletableFuture<Void> step(TargetStepKind kind) {
switch (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 @Override
public CompletableFuture<Void> select() { public CompletableFuture<Void> select() {
DbgManagerImpl manager = getManager(); DbgManagerImpl manager = getManager();

View file

@ -96,9 +96,24 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
boolean doFire; boolean doFire;
synchronized (this) { synchronized (this) {
doFire = !Objects.equals(this.focus, sel); 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) { if (doFire) {
this.focus = sel;
changeAttributes(List.of(), List.of(), Map.of( // changeAttributes(List.of(), List.of(), Map.of( //
TargetFocusScope.FOCUS_ATTRIBUTE_NAME, focus // TargetFocusScope.FOCUS_ATTRIBUTE_NAME, focus //
), "Focus changed"); ), "Focus changed");

View file

@ -55,7 +55,8 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
STEP_INSTRUCTION("step-instruction"), STEP_INSTRUCTION("step-instruction"),
/** Equivalent to {@code until} in the CLI */ /** Equivalent to {@code until} in the CLI */
UNTIL("until"), UNTIL("until"),
; /** User-defined */
EXTENDED("until"),;
final String str; final String str;
@ -332,8 +333,12 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
* *
* <p> * <p>
* This waits for a prompt from GDB unless the last line printed is already a prompt. This is * 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 * generally not necessary following normal commands. Note that depending on circumstances and
* running inferior, or after waiting for an inferior to reach a stopped state. * 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 * @return a future which completes when GDB presents a prompt
*/ */
@ -393,7 +398,10 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
* <p> * <p>
* The output will not be printed to the CLI console. To ensure a certain thread or inferior has * 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 * 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 * @param command the command to execute
* @return a future that completes with the captured output when GDB has executed the command * @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 * Interrupt the GDB session
* *
* <p> * <p>
* This is equivalent to typing Ctrl-C in the CLI. This typically results in the target being * The manager may employ a variety of mechanisms depending on the current configuration. If
* interrupted, either because GDB and the target have the same controlling TTY, or because GDB * multiple interpreters are available, it will issue an "interrupt" command on whichever
* will "forward" the interrupt to the target. * 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
* <p> * unfortunately is notoriously unreliable. The manager will send Ctrl-C to the TTY up to three
* For whatever reason, interrupting the session does not always reliably interrupt the target. * times, waiting about 10ms between each, until GDB issues a stopped event and presents a new
* The manager will send Ctrl-C to the pseudo-terminal up to three times, waiting about 10ms * prompt. If that fails, it is up to the user to find an alternative means to interrupt the
* between each, until GDB issues a stopped event and presents a new prompt. * 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 * @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.evt.*;
import agent.gdb.manager.impl.*; import agent.gdb.manager.impl.*;
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter; import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
import ghidra.util.Msg;
/** /**
* Implementation of {@link GdbInferior#cont()} * Implementation of {@link GdbInferior#cont()}
@ -29,56 +28,6 @@ public class GdbContinueCommand extends AbstractGdbCommandWithThreadId<Void> {
super(manager, threadId); 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 @Override
public Interpreter getInterpreter() { public Interpreter getInterpreter() {
if (manager.hasCli()) { if (manager.hasCli()) {
@ -87,6 +36,38 @@ public class GdbContinueCommand extends AbstractGdbCommandWithThreadId<Void> {
return Interpreter.MI2; 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 @Override
public Void complete(GdbPendingCommand<?> pending) { public Void complete(GdbPendingCommand<?> pending) {
pending.checkCompletion(GdbCommandRunningEvent.class); pending.checkCompletion(GdbCommandRunningEvent.class);

View file

@ -33,7 +33,7 @@ public class GdbGetThreadInfoCommand extends AbstractGdbCommandWithThreadId<GdbT
@Override @Override
protected String encode(String threadPart) { protected String encode(String threadPart) {
return "-thread-info" + threadPart; return "-thread-info " + threadId; // Note the trailing space
} }
@Override @Override

View file

@ -67,7 +67,8 @@ public class GdbInterruptCommand extends AbstractGdbCommand<Void> {
@Override @Override
public Void complete(GdbPendingCommand<?> pending) { public Void complete(GdbPendingCommand<?> pending) {
pending.findSingleOf(GdbStoppedEvent.class); // When using -exec-interrupt, ^done will come before *stopped
//pending.findSingleOf(GdbStoppedEvent.class);
return null; return null;
} }

View file

@ -19,6 +19,7 @@ import agent.gdb.manager.GdbInferior;
import agent.gdb.manager.GdbThread; import agent.gdb.manager.GdbThread;
import agent.gdb.manager.evt.*; import agent.gdb.manager.evt.*;
import agent.gdb.manager.impl.*; import agent.gdb.manager.impl.*;
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
/** /**
* Implementation of {@link GdbInferior#run()} * Implementation of {@link GdbInferior#run()}
@ -29,9 +30,26 @@ public class GdbRunCommand extends AbstractGdbCommand<GdbThread> {
super(manager); super(manager);
} }
@Override
public Interpreter getInterpreter() {
if (manager.hasCli()) {
return Interpreter.CLI;
}
return Interpreter.MI2;
}
@Override @Override
public String encode() { 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 @Override
@ -40,7 +58,7 @@ public class GdbRunCommand extends AbstractGdbCommand<GdbThread> {
pending.claim(evt); pending.claim(evt);
return pending.hasAny(GdbRunningEvent.class); return pending.hasAny(GdbRunningEvent.class);
} }
if (evt instanceof AbstractGdbCompletedCommandEvent) { else if (evt instanceof AbstractGdbCompletedCommandEvent) {
pending.claim(evt); pending.claim(evt);
return true; // Not the expected Completed event 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.GdbThread;
import agent.gdb.manager.evt.*; import agent.gdb.manager.evt.*;
import agent.gdb.manager.impl.*; import agent.gdb.manager.impl.*;
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
/** /**
* Implementation of {@link GdbThread#stepInstruction()} * Implementation of {@link GdbThread#stepInstruction()}
@ -31,9 +32,27 @@ public class GdbStepCommand extends AbstractGdbCommandWithThreadId<Void> {
this.suffix = suffix; this.suffix = suffix;
} }
@Override
public Interpreter getInterpreter() {
if (manager.hasCli()) {
return Interpreter.CLI;
}
return Interpreter.MI2;
}
@Override @Override
protected String encode(String threadPart) { 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 @Override

View file

@ -33,11 +33,12 @@ import ghidra.dbg.util.PathUtils;
import ghidra.lifecycle.Internal; import ghidra.lifecycle.Internal;
import ghidra.util.Msg; import ghidra.util.Msg;
@TargetObjectSchemaInfo(name = "Inferior", elements = { @TargetObjectSchemaInfo(
@TargetElementType(type = Void.class) name = "Inferior",
}, attributes = { elements = {
@TargetAttributeType(type = Void.class) @TargetElementType(type = Void.class) },
}) attributes = {
@TargetAttributeType(type = Void.class) })
public class GdbModelTargetInferior public class GdbModelTargetInferior
extends DefaultTargetObject<TargetObject, GdbModelTargetInferiorContainer> implements // extends DefaultTargetObject<TargetObject, GdbModelTargetInferiorContainer> implements //
TargetProcess<GdbModelTargetInferior>, // TargetProcess<GdbModelTargetInferior>, //
@ -92,20 +93,19 @@ public class GdbModelTargetInferior
this.registers = new GdbModelTargetRegisterContainer(this); this.registers = new GdbModelTargetRegisterContainer(this);
this.threads = new GdbModelTargetThreadContainer(this); this.threads = new GdbModelTargetThreadContainer(this);
changeAttributes(List.of(), changeAttributes(List.of(), //
List.of( List.of( //
environment, environment, //
memory, memory, //
modules, modules, //
registers, registers, //
threads), threads), //
Map.of( Map.of(STATE_ATTRIBUTE_NAME, TargetExecutionState.INACTIVE, //
STATE_ATTRIBUTE_NAME, TargetExecutionState.INACTIVE, DISPLAY_ATTRIBUTE_NAME, updateDisplay(), //
DISPLAY_ATTRIBUTE_NAME, updateDisplay(), TargetMethod.PARAMETERS_ATTRIBUTE_NAME, TargetCmdLineLauncher.PARAMETERS, //
TargetMethod.PARAMETERS_ATTRIBUTE_NAME, TargetCmdLineLauncher.PARAMETERS, UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED, //
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED, SUPPORTED_ATTACH_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS, //
SUPPORTED_ATTACH_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS, SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, GdbModelTargetThread.SUPPORTED_KINDS), //
SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, GdbModelTargetThread.SUPPORTED_KINDS),
"Initialized"); "Initialized");
} }
@ -160,6 +160,8 @@ public class GdbModelTargetInferior
return ExecSuffix.RETURN; return ExecSuffix.RETURN;
case UNTIL: case UNTIL:
return ExecSuffix.UNTIL; return ExecSuffix.UNTIL;
case EXTENDED:
return ExecSuffix.EXTENDED;
default: default:
throw new AssertionError(); throw new AssertionError();
} }

View file

@ -26,11 +26,12 @@ import ghidra.dbg.target.schema.*;
import ghidra.dbg.util.CollectionUtils.Delta; import ghidra.dbg.util.CollectionUtils.Delta;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
@TargetObjectSchemaInfo(name = "RegisterValue", elements = { @TargetObjectSchemaInfo(
@TargetElementType(type = Void.class) name = "RegisterValue",
}, attributes = { elements = {
@TargetAttributeType(type = Void.class) @TargetElementType(type = Void.class) },
}) attributes = {
@TargetAttributeType(type = Void.class) })
public class GdbModelTargetStackFrameRegister public class GdbModelTargetStackFrameRegister
extends DefaultTargetObject<TargetObject, GdbModelTargetStackFrameRegisterContainer> { extends DefaultTargetObject<TargetObject, GdbModelTargetStackFrameRegisterContainer> {
@ -57,7 +58,8 @@ public class GdbModelTargetStackFrameRegister
changeAttributes(List.of(), Map.of( // changeAttributes(List.of(), Map.of( //
DISPLAY_ATTRIBUTE_NAME, getName(), // DISPLAY_ATTRIBUTE_NAME, getName(), //
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED // UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED, //
MODIFIED_ATTRIBUTE_NAME, false //
), "Initialized"); ), "Initialized");
} }
@ -72,11 +74,11 @@ public class GdbModelTargetStackFrameRegister
boolean modified = (bigNewVal.longValue() != 0 && value.equals(oldval)); boolean modified = (bigNewVal.longValue() != 0 && value.equals(oldval));
String newval = getName() + " : " + value; String newval = getName() + " : " + value;
Delta<?, ?> delta = changeAttributes(List.of(), Map.of( Delta<?, ?> delta = changeAttributes(List.of(), Map.of( //
VALUE_ATTRIBUTE_NAME, value, VALUE_ATTRIBUTE_NAME, value, //
DISPLAY_ATTRIBUTE_NAME, newval, DISPLAY_ATTRIBUTE_NAME, newval, //
MODIFIED_ATTRIBUTE_NAME, modified), MODIFIED_ATTRIBUTE_NAME, modified //
"Value Updated"); ), "Value Updated");
if (delta.added.containsKey(DISPLAY_ATTRIBUTE_NAME)) { if (delta.added.containsKey(DISPLAY_ATTRIBUTE_NAME)) {
listeners.fire.displayChanged(this, newval); listeners.fire.displayChanged(this, newval);
} }

View file

@ -32,18 +32,25 @@ import ghidra.dbg.util.PathUtils;
import ghidra.lifecycle.Internal; import ghidra.lifecycle.Internal;
import ghidra.util.Msg; import ghidra.util.Msg;
@TargetObjectSchemaInfo(name = "Thread", elements = { @TargetObjectSchemaInfo(
@TargetElementType(type = Void.class) name = "Thread",
}, attributes = { elements = {
@TargetAttributeType(type = Void.class) @TargetElementType(type = Void.class) },
}) attributes = {
@TargetAttributeType(type = Void.class) })
public class GdbModelTargetThread public class GdbModelTargetThread
extends DefaultTargetObject<TargetObject, GdbModelTargetThreadContainer> implements extends DefaultTargetObject<TargetObject, GdbModelTargetThreadContainer> implements
TargetThread<GdbModelTargetThread>, TargetExecutionStateful<GdbModelTargetThread>, TargetThread<GdbModelTargetThread>, TargetExecutionStateful<GdbModelTargetThread>,
TargetSteppable<GdbModelTargetThread>, GdbModelSelectableObject { TargetSteppable<GdbModelTargetThread>, GdbModelSelectableObject {
protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( // protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( //
TargetStepKind.ADVANCE, TargetStepKind.FINISH, TargetStepKind.LINE, TargetStepKind.OVER, TargetStepKind.ADVANCE, //
TargetStepKind.OVER_LINE, TargetStepKind.RETURN, TargetStepKind.UNTIL); TargetStepKind.FINISH, //
TargetStepKind.LINE, //
TargetStepKind.OVER, //
TargetStepKind.OVER_LINE, //
TargetStepKind.RETURN, //
TargetStepKind.UNTIL, //
TargetStepKind.EXTENDED);
protected static String indexThread(int threadId) { protected static String indexThread(int threadId) {
return PathUtils.makeIndex(threadId); return PathUtils.makeIndex(threadId);
@ -75,16 +82,14 @@ public class GdbModelTargetThread
this.stack = new GdbModelTargetStack(this, inferior); this.stack = new GdbModelTargetStack(this, inferior);
changeAttributes(List.of(), changeAttributes(List.of(), List.of(stack), Map.of( //
List.of( STATE_ATTRIBUTE_NAME, convertState(thread.getState()), //
stack), SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS, //
Map.of( SHORT_DISPLAY_ATTRIBUTE_NAME, shortDisplay = computeShortDisplay(), //
STATE_ATTRIBUTE_NAME, convertState(thread.getState()), DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(), //
SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS, UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED, //
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(), stack.getName(), stack //
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED, ), "Initialized");
stack.getName(), stack),
"Initialized");
updateInfo().exceptionally(ex -> { updateInfo().exceptionally(ex -> {
Msg.error(this, "Could not initialize thread info"); Msg.error(this, "Could not initialize thread info");
@ -110,11 +115,9 @@ public class GdbModelTargetThread
protected String computeDisplay() { protected String computeDisplay() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(shortDisplay);
if (info != null) { if (info != null) {
sb.append(shortDisplay);
sb.append(" "); sb.append(" ");
//sb.append(info.getTargetId());
//sb.append(" ");
sb.append(info.getInferiorName()); sb.append(info.getInferiorName());
sb.append(" "); sb.append(" ");
sb.append(info.getState()); sb.append(info.getState());
@ -127,22 +130,23 @@ public class GdbModelTargetThread
sb.append(" in "); sb.append(" in ");
sb.append(frame.getFunc()); sb.append(frame.getFunc());
} }
return sb.toString();
} }
sb.append(thread.getId()); else {
sb.append(" "); sb.append(" ");
sb.append(stack.inferior.inferior.getDescriptor()); String executableName = stack.inferior.inferior.getExecutable();
sb.append(" "); if (executableName != null) {
sb.append(stack.inferior.inferior.getExecutable()); sb.append(executableName);
GdbModelTargetStackFrame top = stack.framesByLevel.get(0); }
if (top == null) { GdbModelTargetStackFrame top = stack.framesByLevel.get(0);
return sb.toString(); 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(); return sb.toString();
} }
@ -151,10 +155,15 @@ public class GdbModelTargetThread
sb.append("["); sb.append("[");
sb.append(inferior.getId()); sb.append(inferior.getId());
sb.append("."); sb.append(".");
sb.append(info.getId()); if (info == null) {
if (info.getTid() != null) { sb.append(thread.getId());
sb.append(":"); }
sb.append(info.getTid()); else {
sb.append(info.getId());
if (info.getTid() != null) {
sb.append(":");
sb.append(info.getTid());
}
} }
sb.append("]"); sb.append("]");
return sb.toString(); return sb.toString();
@ -205,6 +214,8 @@ public class GdbModelTargetThread
return ExecSuffix.RETURN; return ExecSuffix.RETURN;
case UNTIL: case UNTIL:
return ExecSuffix.UNTIL; return ExecSuffix.UNTIL;
case EXTENDED:
return ExecSuffix.EXTENDED;
default: default:
throw new AssertionError(); throw new AssertionError();
} }

View file

@ -25,6 +25,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.junit.*; 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 @Test
public void testStartInterrupt() throws Throwable { public void testStartInterrupt() throws Throwable {
assumeFalse("I know no way to get this to pass with these conditions", 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 * and the time its signal handlers are installed. It seems waiting for libc to load
* guarantees that GDB is ready to interrupt the process. * guarantees that GDB is ready to interrupt the process.
*/ */
CompletableFuture<Void> libcLoaded = new CompletableFuture<>(); LibraryWaiter libcLoaded = new LibraryWaiter(name -> name.contains("libc"));
mgr.addEventsListener(new GdbEventsListenerAdapter() { mgr.addEventsListener(libcLoaded);
@Override
public void libraryLoaded(GdbInferior inferior, String name, GdbCause cause) {
if (name.contains("libc")) {
libcLoaded.complete(null);
}
}
});
waitOn(startManager(mgr)); waitOn(startManager(mgr));
waitOn(mgr.currentInferior().fileExecAndSymbols("/usr/bin/sleep")); waitOn(mgr.currentInferior().fileExecAndSymbols("/usr/bin/sleep"));
waitOn(mgr.currentInferior().console("set args 3")); waitOn(mgr.currentInferior().console("set args 3"));
@ -211,13 +229,40 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
Thread.sleep(100); // TODO: Why? Thread.sleep(100); // TODO: Why?
Msg.debug(this, "Interrupting"); Msg.debug(this, "Interrupting");
waitOn(mgr.interrupt()); waitOn(mgr.interrupt());
Msg.debug(this, "Waiting for prompt"); waitOn(mgr.waitForState(GdbState.STOPPED));
waitOn(mgr.waitForPrompt()); assertResponsive(mgr);
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 @Test
assertEquals("test", out.trim()); 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.insertBreakpoint("main"));
waitOn(mgr.currentInferior().run()); waitOn(mgr.currentInferior().run());
waitOn(mgr.waitForState(GdbState.STOPPED)); waitOn(mgr.waitForState(GdbState.STOPPED));
waitOn(mgr.waitForPrompt()); //waitOn(mgr.waitForPrompt());
waitOn(mgr.currentInferior().setVar("$rax=", "0xdeadbeef")); // Corrupts it waitOn(mgr.currentInferior().setVar("$rax=", "0xdeadbeef")); // Corrupts it
String val = waitOn(mgr.currentInferior().evaluate("$rax+1")); String val = waitOn(mgr.currentInferior().evaluate("$rax+1"));
assertEquals(0xdeadbeef + 1, Integer.parseUnsignedInt(val)); assertEquals(0xdeadbeef + 1, Integer.parseUnsignedInt(val));
@ -270,7 +315,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
waitOn(mgr.insertBreakpoint("main")); waitOn(mgr.insertBreakpoint("main"));
GdbThread thread = waitOn(mgr.currentInferior().run()); GdbThread thread = waitOn(mgr.currentInferior().run());
waitOn(mgr.waitForState(GdbState.STOPPED)); waitOn(mgr.waitForState(GdbState.STOPPED));
waitOn(mgr.waitForPrompt()); //waitOn(mgr.waitForPrompt());
GdbRegisterSet regs = waitOn(thread.listRegisters()); GdbRegisterSet regs = waitOn(thread.listRegisters());
Set<GdbRegister> toRead = new HashSet<>(); Set<GdbRegister> toRead = new HashSet<>();
toRead.add(regs.get("eflags")); toRead.add(regs.get("eflags"));
@ -306,7 +351,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
waitOn(mgr.insertBreakpoint("main")); waitOn(mgr.insertBreakpoint("main"));
GdbThread thread = waitOn(mgr.currentInferior().run()); GdbThread thread = waitOn(mgr.currentInferior().run());
waitOn(mgr.waitForState(GdbState.STOPPED)); waitOn(mgr.waitForState(GdbState.STOPPED));
waitOn(mgr.waitForPrompt()); //waitOn(mgr.waitForPrompt());
String str = waitOn(mgr.currentInferior().evaluate("(long)main")); String str = waitOn(mgr.currentInferior().evaluate("(long)main"));
long addr = Long.parseLong(str); long addr = Long.parseLong(str);
ByteBuffer buf = ByteBuffer.allocate(1024); ByteBuffer buf = ByteBuffer.allocate(1024);
@ -336,7 +381,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
waitOn(mgr.insertBreakpoint("main")); waitOn(mgr.insertBreakpoint("main"));
GdbThread thread = waitOn(mgr.currentInferior().run()); GdbThread thread = waitOn(mgr.currentInferior().run());
waitOn(mgr.waitForState(GdbState.STOPPED)); waitOn(mgr.waitForState(GdbState.STOPPED));
waitOn(mgr.waitForPrompt()); //waitOn(mgr.waitForPrompt());
waitOn(thread.cont()); waitOn(thread.cont());
waitOn(mgr.waitForState(GdbState.STOPPED)); waitOn(mgr.waitForState(GdbState.STOPPED));
assertEquals(0L, (long) mgr.currentInferior().getExitCode()); assertEquals(0L, (long) mgr.currentInferior().getExitCode());
@ -351,7 +396,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
waitOn(mgr.insertBreakpoint("main")); waitOn(mgr.insertBreakpoint("main"));
GdbThread thread = waitOn(mgr.currentInferior().run()); GdbThread thread = waitOn(mgr.currentInferior().run());
waitOn(mgr.waitForState(GdbState.STOPPED)); waitOn(mgr.waitForState(GdbState.STOPPED));
waitOn(mgr.waitForPrompt()); //waitOn(mgr.waitForPrompt());
waitOn(thread.step(ExecSuffix.NEXT_INSTRUCTION)); waitOn(thread.step(ExecSuffix.NEXT_INSTRUCTION));
waitOn(mgr.waitForState(GdbState.STOPPED)); waitOn(mgr.waitForState(GdbState.STOPPED));
assertNull(mgr.currentInferior().getExitCode()); assertNull(mgr.currentInferior().getExitCode());
@ -366,7 +411,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
waitOn(mgr.insertBreakpoint("main")); waitOn(mgr.insertBreakpoint("main"));
GdbThread thread = waitOn(mgr.currentInferior().run()); GdbThread thread = waitOn(mgr.currentInferior().run());
waitOn(mgr.waitForState(GdbState.STOPPED)); waitOn(mgr.waitForState(GdbState.STOPPED));
waitOn(mgr.waitForPrompt()); //waitOn(mgr.waitForPrompt());
waitOn(thread.select()); waitOn(thread.select());
} }
} }
@ -379,7 +424,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
waitOn(mgr.insertBreakpoint("main")); waitOn(mgr.insertBreakpoint("main"));
GdbThread thread = waitOn(mgr.currentInferior().run()); GdbThread thread = waitOn(mgr.currentInferior().run());
waitOn(mgr.waitForState(GdbState.STOPPED)); waitOn(mgr.waitForState(GdbState.STOPPED));
waitOn(mgr.waitForPrompt()); //waitOn(mgr.waitForPrompt());
waitOn(mgr.insertBreakpoint("write")); waitOn(mgr.insertBreakpoint("write"));
waitOn(mgr.currentInferior().cont()); waitOn(mgr.currentInferior().cont());
waitOn(mgr.waitForState(GdbState.STOPPED)); waitOn(mgr.waitForState(GdbState.STOPPED));

View file

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

View file

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

View file

@ -57,8 +57,14 @@ public class JdiModelTargetThread extends JdiModelTargetObjectReference implemen
JdiModelSelectableObject { JdiModelSelectableObject {
protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( // protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( //
TargetStepKind.ADVANCE, TargetStepKind.FINISH, TargetStepKind.LINE, TargetStepKind.OVER, TargetStepKind.ADVANCE, //
TargetStepKind.OVER_LINE, TargetStepKind.RETURN, TargetStepKind.UNTIL); TargetStepKind.FINISH, //
TargetStepKind.LINE, //
TargetStepKind.OVER, //
TargetStepKind.OVER_LINE, //
TargetStepKind.RETURN, //
TargetStepKind.UNTIL, //
TargetStepKind.EXTENDED);
private EventRequestManager eventManager; private EventRequestManager eventManager;
protected final ThreadReference thread; 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 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 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= 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 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 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 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 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> 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> <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> <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> <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 <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 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; 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 ENABLED_ICON = ResourceManager.loadImage("images/enabled.png");
private static final Icon DISABLED_ICON = ResourceManager.loadImage("images/disabled.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) { public void traceOpened(Trace trace) {
refresh(); //refresh();
repeatLastSet.run(); repeatLastSet.run();
} }
@ -660,6 +667,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
TargetObject targetObject = container.getTargetObject(); TargetObject targetObject = container.getTargetObject();
if (targetObject != null) { if (targetObject != null) {
String key = targetObject.getJoinedPath(PATH_JOIN_CHAR); String key = targetObject.getJoinedPath(PATH_JOIN_CHAR);
container.subscribe();
targetMap.put(key, container); targetMap.put(key, container);
refSet.add(targetObject); refSet.add(targetObject);
if (targetObject instanceof TargetInterpreter) { if (targetObject instanceof TargetInterpreter) {
@ -754,6 +762,18 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
super.closeComponent(); 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 @Override
public void update(ObjectContainer container) { public void update(ObjectContainer container) {
if (pane != null) { if (pane != null) {
@ -875,8 +895,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
groupTargetIndex++; groupTargetIndex++;
actionToggleSelectionOnly = new ToggleActionBuilder("Act on Selection Only", plugin.getName()) actionToggleSelectionOnly = new ToggleActionBuilder("Enable By Selection Only", plugin.getName())
.menuPath("Maintenance","Act on &Selection Only") .menuPath("Maintenance","Enable By &Selection Only")
.menuGroup(DebuggerResources.GROUP_TARGET, "M" + groupTargetIndex) .menuGroup(DebuggerResources.GROUP_TARGET, "M" + groupTargetIndex)
.helpLocation(new HelpLocation(plugin.getName(), "act_on_selection_only")) .helpLocation(new HelpLocation(plugin.getName(), "act_on_selection_only"))
.onAction(ctx -> performToggleSelectionOnly(ctx)) .onAction(ctx -> performToggleSelectionOnly(ctx))
@ -1094,24 +1114,43 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
groupTargetIndex++; groupTargetIndex++;
new ActionBuilder("Step Finish", plugin.getName()) new ActionBuilder("Finish", plugin.getName())
.keyBinding("F12") .keyBinding("F12")
.toolBarGroup(DebuggerResources.GROUP_CONTROL, "C" + groupTargetIndex) .toolBarGroup(DebuggerResources.GROUP_CONTROL, "C" + groupTargetIndex)
.toolBarIcon(AbstractStepFinishAction.ICON) .toolBarIcon(AbstractStepFinishAction.ICON)
.popupMenuPath("&Step Finish") .popupMenuPath("&Finish")
.popupMenuGroup(DebuggerResources.GROUP_CONTROL, "C" + groupTargetIndex) .popupMenuGroup(DebuggerResources.GROUP_CONTROL, "C" + groupTargetIndex)
.popupMenuIcon(AbstractStepFinishAction.ICON) .popupMenuIcon(AbstractStepFinishAction.ICON)
.helpLocation(AbstractStepFinishAction.help(plugin)) .helpLocation(AbstractStepFinishAction.help(plugin))
//.withContext(ObjectActionContext.class) //.withContext(ObjectActionContext.class)
.enabledWhen(ctx -> .enabledWhen(ctx ->
isInstance(ctx, TargetSteppable.tclass) && isStopped(ctx)) isInstance(ctx, TargetSteppable.tclass) && isStopped(ctx))
.popupWhen(ctx -> .popupWhen(ctx ->
isInstance(ctx, TargetSteppable.tclass) && isStopped(ctx)) isInstance(ctx, TargetSteppable.tclass) && isStopped(ctx))
.onAction(ctx -> performStepFinish(ctx)) .onAction(ctx -> performStepFinish(ctx))
.enabled(false) .enabled(false)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
groupTargetIndex++; 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()) actionAddBreakpoint = new ActionBuilder("Add Breakpoint", plugin.getName())
.keyBinding("F3") .keyBinding("F3")
@ -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) { public void performSetBreakpoint(ActionContext context) {
TargetObject obj = getObjectFromContext(context); TargetObject obj = getObjectFromContext(context);
if (!isLocalOnly()) { if (!isLocalOnly()) {
@ -1553,7 +1612,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
public void displayChanged(TargetObject object, String display) { public void displayChanged(TargetObject object, String display) {
//System.err.println("displayChanged: " + display); //System.err.println("displayChanged: " + display);
if (ObjectContainer.visibleByDefault(object.getName())) { 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 @Override
public void memoryUpdated(TargetMemory<?> memory, Address address, byte[] data) { public void memoryUpdated(TargetMemory<?> memory, Address address, byte[] data) {
System.err.println("memoryUpdated"); //System.err.println("memoryUpdated");
} }
@Override @Override

View file

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

View file

@ -44,6 +44,7 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode
private String name; private String name;
private ObjectTree tree; private ObjectTree tree;
private Set<GTreeNode> oldChildren; private Set<GTreeNode> oldChildren;
private boolean restructured = false;
public ObjectNode(ObjectTree tree, ObjectContainer parent, ObjectContainer container) { public ObjectNode(ObjectTree tree, ObjectContainer parent, ObjectContainer container) {
this.tree = tree; this.tree = tree;
@ -76,7 +77,6 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode
} }
@Override @Override
//public List<GTreeNode> generateChildren() {
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException { public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
if (!container.isImmutable() || isInProgress()) { if (!container.isImmutable() || isInProgress()) {
@ -184,20 +184,61 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode
} }
public void markExpanded() { public void markExpanded() {
container.subscribe(); //container.subscribe();
} }
public void markCollapsed() { public void markCollapsed() {
container.unsubscribe(); //container.unsubscribe();
} }
public void cleanUpOldChildren(List<GTreeNode> newChildren) { public void cleanUpOldChildren(List<GTreeNode> newChildren) {
if (oldChildren != null) { if (oldChildren != null) {
oldChildren.removeAll(newChildren); synchronized (oldChildren) {
for (GTreeNode node : oldChildren) { oldChildren.removeAll(newChildren);
tree.cleanupOldNode((ObjectNode) node); for (GTreeNode node : oldChildren) {
setRestructured(true);
tree.cleanupOldNode((ObjectNode) node);
}
} }
} }
oldChildren = new HashSet<>(newChildren); 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 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); public void signalUpdate(ObjectContainer container);

View file

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

View file

@ -26,6 +26,7 @@ import javax.swing.JComponent;
import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.TreePath; import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import docking.widgets.tree.GTree; import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeNode;
@ -92,12 +93,32 @@ public class ObjectTree implements ObjectPane {
} }
} }
provider.getTool().contextChanged(provider); provider.getTool().contextChanged(provider);
if (e.getEventOrigin() == EventOrigin.INTERNAL_GENERATED) { if (e.getEventOrigin() != EventOrigin.INTERNAL_GENERATED) {
restoreTreeStateManager.updateLater(); currentExpandedPaths = tree.getExpandedPaths();
} if (e.getEventOrigin() == EventOrigin.USER_GENERATED) {
else { currentSelectionPaths = tree.getSelectionPaths();
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())); 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); tree.setSelectedNode(root);
} }
@ -209,10 +231,19 @@ public class ObjectTree implements ObjectPane {
} }
@Override @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(() -> { Swing.runIfSwingOrRunLater(() -> {
ObjectNode node = nodeMap.get(path(container)); ObjectNode node = nodeMap.get(path(container));
if (node != null) { if (node != null) {
node.setContainer(this, container.getParent(), container);
node.fireNodeChanged(node.getParent(), node); node.fireNodeChanged(node.getParent(), node);
} }
}); });
@ -288,13 +319,14 @@ public class ObjectTree implements ObjectPane {
public List<GTreeNode> update(ObjectContainer container) { public List<GTreeNode> update(ObjectContainer container) {
ObjectNode node = nodeMap.get(path(container)); ObjectNode node = nodeMap.get(path(container));
if (node == null) { if (node == null) {
System.err.println("Missing node: " + path(container)); Msg.warn(this, "Missing node: " + path(container));
return new ArrayList<>(); return new ArrayList<>();
} }
Set<ObjectContainer> currentChildren = container.getCurrentChildren(); Set<ObjectContainer> currentChildren = container.getCurrentChildren();
List<GTreeNode> childList = new ArrayList<GTreeNode>(); List<GTreeNode> childList = new ArrayList<GTreeNode>();
node.setRestructured(false);
for (ObjectContainer c : currentChildren) { for (ObjectContainer c : currentChildren) {
ObjectNode nc; ObjectNode nc;
String path = path(c); String path = path(c);
@ -305,6 +337,7 @@ public class ObjectTree implements ObjectPane {
nc.setContainer(this, container, c); nc.setContainer(this, container, c);
} }
else { else {
node.setRestructured(true);
nc = new ObjectNode(this, container, c); nc = new ObjectNode(this, container, c);
} }
childList.add(nc); childList.add(nc);
@ -326,10 +359,6 @@ public class ObjectTree implements ObjectPane {
public void setFocus(TargetFocusScope<?> object, TargetObjectRef focused) { public void setFocus(TargetFocusScope<?> object, TargetObjectRef focused) {
Swing.runIfSwingOrRunLater(() -> { Swing.runIfSwingOrRunLater(() -> {
List<String> path = focused.getPath(); List<String> path = focused.getPath();
ObjectContainer container = getProvider().getContainerByPath(path);
if (container != null) {
container.subscribe();
}
tree.setSelectedNodeByNamePath(addRootNameToPath(path)); 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 static ghidra.lifecycle.Unfinished.TODO;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; 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. * line of source code after the line which generated the instruction about to be executed.
*/ */
UNTIL, UNTIL,
/**
* Step until some condition is met.
*/
EXTENDED,
} }
String SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "supported_step_kinds"; 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 * @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() { public default TargetStepKindSet getSupportedStepKinds() {
return getTypedAttributeNowByName(SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, return getTypedAttributeNowByName(SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME,
TargetStepKindSet.class, TargetStepKindSet.of()); TargetStepKindSet.class, TargetStepKindSet.of());
@ -244,11 +253,21 @@ public interface TargetSteppable<T extends TargetSteppable<T>> extends TypedTarg
*/ */
public CompletableFuture<Void> step(TargetStepKind kind); 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 * Step a single instruction
* *
* <p> * <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 #step(TargetStepKind)
* @see TargetStepKind#INTO * @see TargetStepKind#INTO

View file

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