diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugClient.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugClient.java index 9f16050120..b4cac3b939 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugClient.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugClient.java @@ -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), // diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/DbgManager.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/DbgManager.java index 075f719bf5..c6c7b05ca9 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/DbgManager.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/DbgManager.java @@ -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; diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/DbgProcess.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/DbgProcess.java index ed71ca9be1..9607778443 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/DbgProcess.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/DbgProcess.java @@ -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 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 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 step(Map args); + /** * Evaluate an expression * diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/DbgThread.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/DbgThread.java index f6c5bc211d..267e19dcd2 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/DbgThread.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/DbgThread.java @@ -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 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 step(Map args); + /** * Detach from the entire process * diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgProcessSelectCommand.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgProcessSelectCommand.java index cd060c0772..34ad3dcd83 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgProcessSelectCommand.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgProcessSelectCommand.java @@ -20,6 +20,7 @@ import agent.dbgeng.manager.DbgProcess; import agent.dbgeng.manager.impl.DbgManagerImpl; public class DbgProcessSelectCommand extends AbstractDbgCommand { + private DbgProcess process; /** diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgReadRegistersCommand.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgReadRegistersCommand.java index 24d8dd96b2..02800b28ae 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgReadRegistersCommand.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgReadRegistersCommand.java @@ -55,7 +55,7 @@ public class DbgReadRegistersCommand extends AbstractDbgCommand { - 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 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 { 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; + } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgThreadHoldCommand.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgThreadHoldCommand.java new file mode 100644 index 0000000000..dcbdc9ff7c --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgThreadHoldCommand.java @@ -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 { + + 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); + } + } + } + } +} diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgThreadSelectCommand.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgThreadSelectCommand.java index 97bb53bf95..af7e294a71 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgThreadSelectCommand.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgThreadSelectCommand.java @@ -20,6 +20,7 @@ import agent.dbgeng.manager.DbgThread; import agent.dbgeng.manager.impl.DbgManagerImpl; public class DbgThreadSelectCommand extends AbstractDbgCommand { + private DbgThread thread; /** diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgWriteRegistersCommand.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgWriteRegistersCommand.java index efcfa0ec5d..f7f902cf86 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgWriteRegistersCommand.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgWriteRegistersCommand.java @@ -66,6 +66,6 @@ public class DbgWriteRegistersCommand extends AbstractDbgCommand { } } registers.setValues(DebugRegisterSource.DEBUG_REGSRC_DEBUGGEE, values); - so.setCurrentThreadId(previous); + //so.setCurrentThreadId(previous); } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgProcessImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgProcessImpl.java index d12c298dbb..3ee7b3bc84 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgProcessImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgProcessImpl.java @@ -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 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 step(Map 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 CompletableFuture preferThread( Function> viaThread, Supplier> viaThis) { diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgThreadImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgThreadImpl.java index 6c0ca2b86f..d70bca3a66 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgThreadImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgThreadImpl.java @@ -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 step(Map args) { + return sequence(TypeSpec.VOID).then((seq) -> { + select().handle(seq::next); + }).then((seq) -> { + manager.execute(new DbgStepCommand(manager, id, args)).handle(seq::exit); }).finish(); } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetSteppable.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetSteppable.java index d4cf40d290..dec9c6a18f 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetSteppable.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetSteppable.java @@ -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> return ExecSuffix.RETURN; case UNTIL: return ExecSuffix.UNTIL; + case EXTENDED: + return ExecSuffix.EXTENDED; default: throw new AssertionError(); } @@ -62,7 +65,21 @@ public interface DbgModelTargetSteppable> 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 step(Map args) { + DbgThread thread = getManager().getCurrentThread(); + return thread.step(args); + } + } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessImpl.java index 7e3d5037a8..3ae45ac129 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessImpl.java @@ -180,17 +180,21 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl @Override public CompletableFuture 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 step(Map args) { + return process.step(args); + } + @Override public void processStarted(Long pid) { if (pid != null) { diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRootImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRootImpl.java index 04152b9a9c..fa0a240833 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRootImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRootImpl.java @@ -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 focusPath = focus.getPath(); + List 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"); diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadImpl.java index 7f6ebdd52e..3dce54358a 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadImpl.java @@ -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 step(TargetStepKind kind) { switch (kind) { @@ -156,6 +140,11 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl } } + @Override + public CompletableFuture step(Map args) { + return thread.step(args); + } + @Override public CompletableFuture select() { DbgManagerImpl manager = getManager(); diff --git a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2TargetRootImpl.java b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2TargetRootImpl.java index e3886196ce..9908e9509d 100644 --- a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2TargetRootImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2TargetRootImpl.java @@ -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 focusPath = focus.getPath(); + List 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"); diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/GdbManager.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/GdbManager.java index 32f793fdba..39fdf49785 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/GdbManager.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/GdbManager.java @@ -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 { * *

* 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 last 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 { *

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

- * 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. - * - *

- * 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 */ diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbContinueCommand.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbContinueCommand.java index b5033d65eb..82ccfa3782 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbContinueCommand.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbContinueCommand.java @@ -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 { 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 { 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); diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbGetThreadInfoCommand.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbGetThreadInfoCommand.java index b41eb937ab..bd726d8354 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbGetThreadInfoCommand.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbGetThreadInfoCommand.java @@ -33,7 +33,7 @@ public class GdbGetThreadInfoCommand extends AbstractGdbCommandWithThreadId { @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; } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbRunCommand.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbRunCommand.java index d37fda1cb1..b2eea78bce 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbRunCommand.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbRunCommand.java @@ -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 { 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 { 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 } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbStepCommand.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbStepCommand.java index a56c752c9d..51d17e5257 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbStepCommand.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/cmd/GdbStepCommand.java @@ -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 { 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 diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferior.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferior.java index 80195c894e..19dff2b858 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferior.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferior.java @@ -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 implements // TargetProcess, // @@ -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(); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStackFrameRegister.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStackFrameRegister.java index e75f4fc0a8..8678b0bdc7 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStackFrameRegister.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStackFrameRegister.java @@ -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 { @@ -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); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java index bdd6b99eaa..6800ea9e87 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java @@ -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 implements TargetThread, TargetExecutionStateful, TargetSteppable, 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(); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/AbstractGdbManagerTest.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/AbstractGdbManagerTest.java index c637ac9176..d4eb228ca5 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/AbstractGdbManagerTest.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/AbstractGdbManagerTest.java @@ -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 + implements GdbEventsListenerAdapter { + protected final Predicate predicate; + + public LibraryWaiter(Predicate 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 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 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 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)); diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/util/GadpValueUtils.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/util/GadpValueUtils.java index 442c20e52a..dd63ebe0bc 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/util/GadpValueUtils.java +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/util/GadpValueUtils.java @@ -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(); } diff --git a/Ghidra/Debug/Debugger-gadp/src/main/proto/gadp.proto b/Ghidra/Debug/Debugger-gadp/src/main/proto/gadp.proto index af875ddeb9..e5c9676c5c 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/proto/gadp.proto +++ b/Ghidra/Debug/Debugger-gadp/src/main/proto/gadp.proto @@ -98,6 +98,7 @@ enum StepKind { SK_SKIP = 6; SK_RETURN = 7; SK_UNTIL = 8; + SK_EXTENDED = 9; } message StepKindsSet { diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThread.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThread.java index 2f265f6241..7ad6bc47a8 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThread.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThread.java @@ -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; diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html index ca3ee27dbf..7812fd03e3 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html @@ -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 Act on Selection Only function + "DebuggerObjectsPlugin.html#act_on_selection_only">Enable By Selection Only function determines whether both options are in play. For example, threads and inferiors/processes are both resumable, so the Resume action works on both. For many of our targets, processes are interruptible while threads are - not. Nevertheless, if Act On Selection Only is off, you can interrupt a thread because + not. Nevertheless, if Enable By Selection Only 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.

@@ -152,7 +152,7 @@

Step the current target to the next instruction in the current subroutine.

-

Step Finish

+

Finish

Allow the current target to finish the current subroutine, pausing after.

@@ -298,7 +298,7 @@

Asks the recorder to include or exclude the current object from the trace.

-

Act on Selection Only

+

Enable By Selection Only

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 diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java index 29888028de..09b76791a5 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java @@ -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 args = new HashMap(); + 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 diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/ObjectContainer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/ObjectContainer.java index df02f50ca2..71ac7baea6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/ObjectContainer.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/ObjectContainer.java @@ -212,11 +212,13 @@ public class ObjectContainer implements Comparable { public void augmentElements(Collection elementsRemoved, Map elementsAdded) { Set result = new TreeSet(); + 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 attributesRemoved, Map attributesAdded) { Set result = new TreeSet(); + 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); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectNode.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectNode.java index 1a601c8380..b373f91eae 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectNode.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectNode.java @@ -44,6 +44,7 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode private String name; private ObjectTree tree; private Set 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 generateChildren() { public List 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 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 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 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; + } + } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectPane.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectPane.java index 2a7b69bcfc..9ee2ef355b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectPane.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectPane.java @@ -38,7 +38,9 @@ public interface ObjectPane { public List update(ObjectContainer container); - public void signalDataChange(ObjectContainer container); + public void signalDataChanged(ObjectContainer container); + + public void signalContentsChanged(ObjectContainer container); public void signalUpdate(ObjectContainer container); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectTable.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectTable.java index 3ac94175a8..6db1f5b3b5 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectTable.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectTable.java @@ -109,7 +109,14 @@ public class ObjectTable 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); }); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectTree.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectTree.java index f7dd39b420..090a40579e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectTree.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectTree.java @@ -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 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 currentChildren = container.getCurrentChildren(); List childList = new ArrayList(); + 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 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 { } } } - } diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetSteppable.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetSteppable.java index ca3c5db303..9fef9bb8e5 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetSteppable.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetSteppable.java @@ -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> 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> 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> extends TypedTarg */ public CompletableFuture 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 step(Map args) { + return step(TargetStepKind.INTO); + } + /** * Step a single instruction * *

- * 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 diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnTableModel.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnTableModel.java index 957aa87a87..a4c76301d3 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnTableModel.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnTableModel.java @@ -251,6 +251,9 @@ public class DefaultEnumeratedColumnTableModel & EnumeratedTab @Override public void delete(R row) { int rowIndex = modelData.indexOf(row); + if (rowIndex == -1) { + return; + } modelData.remove(rowIndex); fireTableRowsDeleted(rowIndex, rowIndex); }