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