From 38bf87f6b6a1e30dbd61437cdd638912cf0d03bf Mon Sep 17 00:00:00 2001 From: d-millar <33498836+d-millar@users.noreply.github.com> Date: Thu, 28 May 2026 11:19:31 -0400 Subject: [PATCH] GP-4914: second pass from review GP-4914: one more... GP-4914: post-review, factor service down GP-4914: post-review, factor service down GP-4914: various issues GP-4914: bounds errors GP-4914: most tests ok; unwind info in service GP-4914: fixing tests GP-4914: more fixes GP-4914: beta GP-4914: sp adds, working GP-4914: sp GP-4914: temp mess GP-4914: oops re buildXml GP-4914: using base logic to pull regs GP-4915: solution for frames w/o pgm & frames w/o ofReturn GP-4914: tests passing (well, sort of) GP-4914: should return the right frame GP-4914: test checks GP-4914: first pass GP-4914: better layeringGP-4914: minor --- .../src/main/py/src/ghidradbg/build_xml.py | 2 +- .../src/main/py/src/ghidradbg/commands.py | 8 +- .../src/main/py/src/ghidradbg/schema.xml | 1 + .../src/main/py/src/ghidradbg/schema_exdi.xml | 1 + .../src/main/py/src/ghidradrgn/build_xml.py | 2 +- .../src/main/py/src/ghidragdb/build_xml.py | 2 +- .../src/main/py/src/ghidralldb/build_xml.py | 2 +- .../src/main/py/src/ghidraxdbg/build_xml.py | 2 +- .../src/main/py/src/ghidraxdbg/schema.xml | 1 + .../ghidratrace/xml/nesting_adjustments.xml | 1 + .../action/DebuggerTrackLocationTrait.java | 6 + .../debug/gui/stack/DebuggerStackPanel.java | 42 +++ .../gui/stack/DebuggerStackProvider.java | 1 + .../stack/vars/VariableValueHoverPlugin.java | 8 +- .../stack/vars/VariableValueHoverService.java | 14 +- .../gui/stack/vars/VariableValueUtils.java | 142 ++++--- .../debug/stack/AnalysisUnwoundFrame.java | 40 +- .../core/debug/stack/StackUnwinder.java | 356 +++++++++++------- .../core/debug/stack/UnwindAnalysis.java | 18 + .../plugin/core/debug/stack/UnwindInfo.java | 2 +- .../core/debug/stack/UnwindStackCommand.java | 28 +- .../stack/DebuggerStackPluginScreenShots.java | 2 + .../VariableValueHoverPluginScreenShots.java | 2 + .../core/debug/stack/StackUnwinderTest.java | 139 +++---- .../database/stack/DBTraceStackFrame.java | 18 +- .../trace/model/stack/TraceStackFrame.java | 21 +- .../target/schema/TraceObjectSchema.java | 9 +- .../viewer/field/LabelFieldSymbolLoader.java | 4 +- 28 files changed, 547 insertions(+), 327 deletions(-) diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/build_xml.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/build_xml.py index db5edc402f..c7d4ca7023 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/build_xml.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/build_xml.py @@ -13,11 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. ## -from merge_xml import merge import os import sys rmi_path = os.getcwd() + "/../../../../../../Debugger-rmi-trace/src/main/py/src/ghidratrace/" sys.path.append(rmi_path) +from merge_xml import merge def main(): diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py index ae0dcad242..4331f48b81 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py @@ -1425,6 +1425,11 @@ def put_frames() -> None: if base != offset_inst.space: trace.create_overlay_space(base, offset_inst.space) fobj.set_value('Instruction Offset', offset_inst) + keys.append(FRAME_KEY_PATTERN.format(level=f.FrameNumber)) + base, offset_stack = mapper.map(nproc, f.StackOffset) + if base != offset_inst.space: + trace.create_overlay_space(base, offset_stack.space) + fobj.set_value('Stack Offset', offset_stack) if not util.dbg.use_generics: base, offset_stack = mapper.map(nproc, f.StackOffset) if base != offset_stack.space: @@ -1438,8 +1443,7 @@ def put_frames() -> None: fobj.set_value('Stack Offset', offset_stack) fobj.set_value('Return Offset', offset_ret) fobj.set_value('Frame Offset', offset_frame) - fobj.set_value('_display', "#{} {}".format( - f.FrameNumber, offset_inst.offset)) + fobj.set_value('_display', f'{f.FrameNumber} {offset_inst.offset:008x}') fobj.insert() trace.proxy_object_path(STACK_PATTERN.format( procnum=nproc, tnum=nthrd)).retain_values(keys) diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml index e8758f4fd5..c324d0de6e 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml @@ -195,6 +195,7 @@ + diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema_exdi.xml b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema_exdi.xml index 4dad14236c..da57a73c46 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema_exdi.xml +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema_exdi.xml @@ -198,6 +198,7 @@ + diff --git a/Ghidra/Debug/Debugger-agent-drgn/src/main/py/src/ghidradrgn/build_xml.py b/Ghidra/Debug/Debugger-agent-drgn/src/main/py/src/ghidradrgn/build_xml.py index 284a24e15e..bcc4e3fbfe 100644 --- a/Ghidra/Debug/Debugger-agent-drgn/src/main/py/src/ghidradrgn/build_xml.py +++ b/Ghidra/Debug/Debugger-agent-drgn/src/main/py/src/ghidradrgn/build_xml.py @@ -13,11 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. ## -from merge_xml import merge import os import sys rmi_path = os.getcwd() + "/../../../../../../Debugger-rmi-trace/src/main/py/src/ghidratrace/" sys.path.append(rmi_path) +from merge_xml import merge def main(): diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/build_xml.py b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/build_xml.py index 3708443f07..1e7a36c5d5 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/build_xml.py +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/build_xml.py @@ -13,11 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. ## -from merge_xml import merge import os import sys rmi_path = os.getcwd() + "/../../../../../../Debugger-rmi-trace/src/main/py/src/ghidratrace/" sys.path.append(rmi_path) +from merge_xml import merge def main(): diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/build_xml.py b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/build_xml.py index ced49f6210..de6ea6c949 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/build_xml.py +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/build_xml.py @@ -13,11 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. ## -from merge_xml import merge import os import sys rmi_path = os.getcwd() + "/../../../../../../Debugger-rmi-trace/src/main/py/src/ghidratrace/" sys.path.append(rmi_path) +from merge_xml import merge def main(): diff --git a/Ghidra/Debug/Debugger-agent-x64dbg/src/main/py/src/ghidraxdbg/build_xml.py b/Ghidra/Debug/Debugger-agent-x64dbg/src/main/py/src/ghidraxdbg/build_xml.py index 4ffba44482..e80939c23c 100644 --- a/Ghidra/Debug/Debugger-agent-x64dbg/src/main/py/src/ghidraxdbg/build_xml.py +++ b/Ghidra/Debug/Debugger-agent-x64dbg/src/main/py/src/ghidraxdbg/build_xml.py @@ -13,11 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. ## -from merge_xml import merge import os import sys rmi_path = os.getcwd() + "/../../../../../../Debugger-rmi-trace/src/main/py/src/ghidratrace/" sys.path.append(rmi_path) +from merge_xml import merge def main(): diff --git a/Ghidra/Debug/Debugger-agent-x64dbg/src/main/py/src/ghidraxdbg/schema.xml b/Ghidra/Debug/Debugger-agent-x64dbg/src/main/py/src/ghidraxdbg/schema.xml index 736f2dfe41..c02f7b2ee7 100644 --- a/Ghidra/Debug/Debugger-agent-x64dbg/src/main/py/src/ghidraxdbg/schema.xml +++ b/Ghidra/Debug/Debugger-agent-x64dbg/src/main/py/src/ghidraxdbg/schema.xml @@ -223,6 +223,7 @@ + diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/xml/nesting_adjustments.xml b/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/xml/nesting_adjustments.xml index 67f542c5a9..46a4df306d 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/xml/nesting_adjustments.xml +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/xml/nesting_adjustments.xml @@ -26,6 +26,7 @@ + diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java index 921e8d9f30..c9adf6cd3e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java @@ -85,6 +85,9 @@ public class DebuggerTrackLocationTrait { if (!value.getEntryKey().equals(TraceStackFrame.KEY_PC)) { return; } + if (!value.getEntryKey().equals(TraceStackFrame.KEY_SP)) { + return; + } TraceStackFrame frame = value.getParent().queryInterface(TraceStackFrame.class); if (frame == null) { return; @@ -104,6 +107,9 @@ public class DebuggerTrackLocationTrait { if (!value.getEntryKey().equals(TraceStackFrame.KEY_PC)) { return; } + if (!value.getEntryKey().equals(TraceStackFrame.KEY_SP)) { + return; + } TraceStackFrame frame = value.getParent().queryInterface(TraceStackFrame.class); if (frame == null) { return; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPanel.java index 8689cb576d..461994e07a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPanel.java @@ -98,6 +98,47 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel { + public FrameSpColumn() { + super(TraceStackFrame.KEY_SP, Address.class); + } + + @Override + public String getColumnName() { + return "SP"; + } + + @Override + public ValueProperty
getProperty(ValueRow row) { + return new ValueAddressProperty(row) { + @Override + public Address getValue() { + TraceObjectValue entry = row.getAttributeEntry(attributeName); + if (entry == null) { + return null; + } + return entry.getValue() instanceof Address addr ? addr : null; + } + + @Override + public boolean isModified() { + return row.isAttributeModified(attributeName); + } + }; + } + } + + static Address computeStackPointer(ValueRow row, long snap) { + if (!(row.getValue().getValue() instanceof TraceObject object)) { + return null; + } + TraceObjectValue attrSp = object.getAttribute(snap, TraceStackFrame.KEY_SP); + if (attrSp == null || !(attrSp.getValue() instanceof Address sp)) { + return null; + } + return sp; + } + private Function computeFunction(ValueRow row, long snap, ServiceProvider serviceProvider) { Address pc = computeProgramCounter(row, snap); if (pc == null) { @@ -186,6 +227,7 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel analyses = new HashMap<>(); + public VariableValueHoverService(PluginTool tool) { super(tool, PRIORITY); autoServiceWiring = AutoService.wireServicesConsumed(tool, this); @@ -623,7 +625,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover } VariableEvaluator eval; synchronized (cachedEvaluators) { - eval = cachedEvaluators.computeIfAbsent(current, c -> new VariableEvaluator(tool, c)); + eval = cachedEvaluators.computeIfAbsent(current, + c -> new VariableEvaluator(tool, c)); } TableFiller filler = new TableFiller(table, tool, current, eval, warnings); if (field instanceof ClangTextField clangField) { @@ -724,4 +727,13 @@ public class VariableValueHoverService extends AbstractConfigurableHover cachedEvaluators.keySet().removeIf(coords -> coords.getTrace() == trace); } } + + public UnwindInfo getUnwindInfo(Program program, Address key, TaskMonitor monitor) { + synchronized (analyses) { + UnwindAnalysis ua = + analyses.computeIfAbsent(program, p -> new UnwindAnalysis(p)); + return ua.getUnwindInfo(key, monitor); + } + } + } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueUtils.java index 5783f3b2b7..d9215fcb91 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueUtils.java @@ -15,7 +15,9 @@ */ package ghidra.app.plugin.core.debug.gui.stack.vars; -import java.util.*; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import ghidra.app.decompiler.ClangLine; import ghidra.app.decompiler.ClangToken; @@ -463,6 +465,77 @@ public enum VariableValueUtils { return getProgramCounterFromRegisters(platform, thread, snap); } + /** + * Get the stack pointer for the given thread's innermost frame using its {@link TraceStack} + * + *

+ * This will prefer the stack pointer in the {@link TraceStackFrame}. If that's not available, + * it will use the value of the stack pointer register from the thread's register bank for + * frame 0. + * + * @param platform the platform + * @param thread the thread + * @param snap the snapshot key + * @return the address + */ + public static Address getStackPointerFromStack(TracePlatform platform, TraceThread thread, + long snap) { + TraceStack stack = thread.getTrace().getStackManager().getStack(thread, snap, false); + if (stack == null) { + return null; + } + TraceStackFrame frame = stack.getFrame(snap, 0, false); + if (frame == null) { + return null; + } + return frame.getStackPointer(snap); + } + + /** + * Get the program counter for the given thread's innermost frame using its + * {@link TraceMemorySpace}, i.e., registers + * + * @param platform the platform + * @param thread the thread + * @param snap the snapshot key + * @return the address + */ + public static Address getStackPointerFromRegisters(TracePlatform platform, TraceThread thread, + long snap) { + TraceMemorySpace regs = + thread.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false); + if (regs == null) { + return null; + } + CompilerSpec compiler = platform.getCompilerSpec(); + RegisterValue value = + regs.getValue(platform, snap, compiler.getStackPointer()); + return platform.getLanguage() + .getDefaultSpace() + .getAddress(value.getUnsignedValue().longValue()); + } + + /** + * Get the stack pointer from the innermost frame of the given thread's stack + * + *

+ * This will prefer the stack pointer in the {@link TraceStackFrame}. If that's not available, + * it will use the value of the stack pointer register from the thread's register bank for + * frame 0. + * + * @param platform the platform + * @param thread the thread + * @param snap the snapshot key + * @return the address + */ + public static Address getStackPointer(TracePlatform platform, TraceThread thread, long snap) { + Address spFromStack = getStackPointerFromStack(platform, thread, snap); + if (spFromStack != null) { + return spFromStack; + } + return getStackPointerFromRegisters(platform, thread, snap); + } + /** * Check if the unwound frames annotated in the listing are "fresh" * @@ -685,8 +758,8 @@ public enum VariableValueUtils { private final Language language; private final ListenerForChanges listenerForChanges = new ListenerForChanges(); - private List> unwound; private FakeUnwoundFrame fakeFrame; + private StackUnwinder unwinder; /** * Construct an evaluator for the given tool and coordinates @@ -698,6 +771,7 @@ public enum VariableValueUtils { this.tool = tool; this.coordinates = coordinates; this.language = coordinates.getPlatform().getLanguage(); + this.unwinder = new StackUnwinder(tool, coordinates.getPlatform()); coordinates.getTrace().addListener(listenerForChanges); } @@ -714,7 +788,7 @@ public enum VariableValueUtils { */ public void invalidateCache() { synchronized (lock) { - unwound = null; + unwinder.invalidateCache(); } } @@ -733,21 +807,6 @@ public enum VariableValueUtils { } } - /** - * Refresh the stack unwind - * - * @param monitor a monitor for cancellation - */ - protected void doUnwind(TaskMonitor monitor) { - monitor.setMessage("Unwinding Stack"); - StackUnwinder unwinder = new StackUnwinder(tool, coordinates.getPlatform()); - unwound = new ArrayList<>(); - for (AnalysisUnwoundFrame frame : unwinder.frames(coordinates.frame(0), - monitor)) { - unwound.add(frame); - } - } - /** * Get the stack frame for the given function at or beyond the coordinates' frame level * @@ -760,48 +819,19 @@ public enum VariableValueUtils { public UnwoundFrame getStackFrame(Function function, StackUnwindWarningSet warnings, TaskMonitor monitor, boolean required) { synchronized (lock) { - if (unwound == null) { - try { - doUnwind(monitor); - } - catch (Exception e) { - /** - * Most exceptions should be caught and wrapped by the unwind analysis. If - * one gets here, something bad has happened, and for debugging purposes, we - * should invalidate, so that the error will repeat next time the frame is - * requested. - */ - unwound = null; - throw e; - } + AnalysisUnwoundFrame currentFrame = + unwinder.findMatchForFunction(function, coordinates, warnings, monitor); + if (currentFrame != null) { + return currentFrame; } - for (UnwoundFrame frame : unwound.subList(coordinates.getFrame(), - unwound.size())) { - if (frame.getFunction() == function) { - StackUnwindWarningSet unwindWarnings = frame.getWarnings(); - if (unwindWarnings != null) { - warnings.addAll(unwindWarnings); - } - return frame; - } - } - String message; - if (unwound.isEmpty()) { - message = "Could not recover the innermost frame!"; - } - else { - message = "There is no frame for %s among the %d frames unwound." - .formatted(function, unwound.size()); - Exception error = unwound.get(unwound.size() - 1).getError(); - if (error != null) { - message += "\nTerminating error: %s".formatted(error.getMessage()); - } - } + warnings.add( + new CustomStackUnwindWarning("Failed to find match for %s among the %d frames." + .formatted(function, unwinder.getRecoveredFrameCount()))); if (required) { - throw new UnwindException(message); + throw new UnwindException( + warnings.stream().map(w -> w.toString()).collect(Collectors.joining("\n"))); } - warnings.add(new CustomStackUnwindWarning(message)); return null; } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/AnalysisUnwoundFrame.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/AnalysisUnwoundFrame.java index 74929354bd..66e94ed140 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/AnalysisUnwoundFrame.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/AnalysisUnwoundFrame.java @@ -15,8 +15,7 @@ */ package ghidra.app.plugin.core.debug.stack; -import java.util.List; -import java.util.NoSuchElementException; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -47,14 +46,7 @@ import ghidra.util.task.TaskMonitor; * *

* The typical pattern for invoking analysis to unwind an entire stack is to use - * {@link StackUnwinder#start(DebuggerCoordinates, TaskMonitor)} or similar, followed by - * {@link #unwindNext(TaskMonitor)} in a chain until the stack is exhausted or analysis fails to - * unwind a frame. It may be more convenient to use - * {@link StackUnwinder#frames(DebuggerCoordinates, TaskMonitor)}. Its iterator implements that - * pattern. Because unwinding can be expensive, it is recommended to cache the unwound stack when - * possible. A centralized service for stack unwinding may be added later. - * - * @param the type of values retrievable from the unwound frame + * {@link StackUnwinder#getFrames(DebuggerCoordinates, TaskMonitor)}. */ public class AnalysisUnwoundFrame extends AbstractUnwoundFrame { @@ -64,7 +56,7 @@ public class AnalysisUnwoundFrame extends AbstractUnwoundFrame { private final Address spVal; private final Address staticPcVal; private final UnwindInfo info; - private final SavedRegisterMap registerMap; + final SavedRegisterMap registerMap; private final Address base; @@ -83,22 +75,19 @@ public class AnalysisUnwoundFrame extends AbstractUnwoundFrame { * @param state the machine state, typically the watch value state for the same coordinates. It * is the caller's (i.e., subclass') responsibility to ensure the given state * corresponds to the given coordinates. - * @param level the level of this frame * @param pcVal the (dynamic) address of the next instruction when this frame becomes the * current frame * @param spVal the address of the top of the stack when this frame becomes the current frame * @param staticPcVal the (static) address of the next instruction * @param info the information used to unwind this frame - * @param infoErr if applicable, an error describing why the unwind info is missing or - * incomplete * @param registerMap a map from registers to the offsets of their saved values on the stack */ AnalysisUnwoundFrame(PluginTool tool, DebuggerCoordinates coordinates, StackUnwinder unwinder, - PcodeExecutorState state, int level, Address pcVal, Address spVal, + PcodeExecutorState state, Address pcVal, Address spVal, Address staticPcVal, UnwindInfo info, SavedRegisterMap registerMap) { super(tool, coordinates, state); this.unwinder = unwinder; - this.level = level; + this.level = coordinates.getFrame(); this.pcVal = pcVal; this.spVal = spVal; @@ -136,15 +125,13 @@ public class AnalysisUnwoundFrame extends AbstractUnwoundFrame { * @throws CancelledException if the monitor is cancelled * @throws UnwindException if unwinding fails */ - public AnalysisUnwoundFrame unwindNext(TaskMonitor monitor) throws CancelledException { + public AnalysisUnwoundFrame unwindNext(TaskMonitor monitor) + throws CancelledException { if (info == null || info.ofReturn() == null) { throw new NoSuchElementException(); } - SavedRegisterMap registerMap = this.registerMap.fork(); - info.mapSavedRegisters(base, registerMap); - Address pcVal = info.computeNextPc(base, state, codeSpace, pc); - Address spVal = info.computeNextSp(base); - return unwinder.unwind(coordinates, level + 1, pcVal, spVal, state, registerMap, monitor); + return (AnalysisUnwoundFrame) unwinder.getFrame(coordinates, state, level + 1, null, + monitor); } @Override @@ -334,8 +321,13 @@ public class AnalysisUnwoundFrame extends AbstractUnwoundFrame { "Frame " + level + " has lenght 0"); return null; } - for (TraceBookmark existing : bm.getBookmarksIntersecting(span, - new AddressRangeImpl(spPlusParams, spPlusParams.add(structure.getLength() - 1)))) { + + List copy = new ArrayList<>(); + bm.getBookmarksIntersecting(span, + new AddressRangeImpl(spPlusParams, spPlusParams.add(structure.getLength() - 1))) + .forEach(copy::add); + + for (TraceBookmark existing : copy) { truncateOrDelete(existing, span); } if (!warnings.isBlank()) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwinder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwinder.java index fd8ffb8e33..3a97275b52 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwinder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwinder.java @@ -16,7 +16,10 @@ package ghidra.app.plugin.core.debug.stack; import java.util.*; +import java.util.Map.Entry; +import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueHoverService; +import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.CustomStackUnwindWarning; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.PluginTool; @@ -27,8 +30,8 @@ import ghidra.pcode.exec.PcodeExecutorState; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.data.CategoryPath; -import ghidra.program.model.lang.CompilerSpec; -import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Reference; import ghidra.program.util.ProgramLocation; @@ -82,7 +85,7 @@ public class StackUnwinder { return tool.getService(DebuggerStaticMappingService.class); } - private static WatchValuePcodeExecutorState getState(PluginTool tool, + public static WatchValuePcodeExecutorState getState(PluginTool tool, DebuggerCoordinates coordinates) { return DebuggerPcodeUtils.buildWatchState(tool, coordinates); } @@ -95,7 +98,13 @@ public class StackUnwinder { final Register pc; final AddressSpace codeSpace; private final Register sp; - private final AddressSpace stackSpace; + + record ThreadAndSnap(TraceThread thread, Long viewSnap) {} + + private Map>> unwound = + new HashMap<>(); + private boolean returnErrorFrame = false; + private VariableValueHoverService service; /** * Construct an unwinder @@ -107,6 +116,7 @@ public class StackUnwinder { this.tool = tool; this.mappings = getMappings(tool); this.platform = platform; + this.service = tool.getService(VariableValueHoverService.class); this.trace = platform.getTrace(); this.pc = Objects.requireNonNull(platform.getLanguage().getProgramCounter(), @@ -116,29 +126,6 @@ public class StackUnwinder { CompilerSpec compiler = platform.getCompilerSpec(); this.sp = Objects.requireNonNull(compiler.getStackPointer(), "Platform must have a stack pointer"); - this.stackSpace = compiler.getStackBaseSpace(); - } - - /** - * Begin unwinding frames that can evaluate variables as {@link WatchValue}s - * - *

- * While the returned frame is not technically "unwound," it is necessary to derive its base - * pointer in order to evaluate any of its variables and unwind subsequent frames. The returned - * frame has the {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)} method. - * - * @param coordinates the starting coordinates, particularly the frame level - * @param monitor a monitor for cancellation - * @return the frame for the given level - * @throws CancelledException if the monitor is cancelled - */ - public AnalysisUnwoundFrame start(DebuggerCoordinates coordinates, - TaskMonitor monitor) - throws CancelledException { - if (coordinates.getPlatform() != platform) { - throw new IllegalArgumentException("Not same platform"); - } - return start(coordinates, getState(tool, coordinates), monitor); } /** @@ -161,52 +148,156 @@ public class StackUnwinder { * Subsequent frames are handled similarly. See * {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)}. * - * @param the type of values in the state, and the result of variable evaluations * @param coordinates the starting coordinates, particularly the frame level - * @param state the state, which must correspond to the given coordinates * @param monitor a monitor for cancellation * @return the frame for the given level * @throws CancelledException if the monitor is cancelled */ - public AnalysisUnwoundFrame start(DebuggerCoordinates coordinates, - PcodeExecutorState state, TaskMonitor monitor) throws CancelledException { - return start(coordinates, coordinates.getFrame(), state, monitor); + public AnalysisUnwoundFrame start(DebuggerCoordinates coordinates, + TaskMonitor monitor) throws CancelledException { + if (coordinates.getPlatform() != platform) { + throw new IllegalArgumentException("Not same platform"); + } + returnErrorFrame = true; + return getFrame(coordinates, getState(tool, coordinates), coordinates.getFrame(), null, + monitor); } - protected AnalysisUnwoundFrame start(DebuggerCoordinates coordinates, int level, - PcodeExecutorState state, TaskMonitor monitor) throws CancelledException { - Address pcVal = null; + public AnalysisUnwoundFrame getFrame(DebuggerCoordinates coordinates, + PcodeExecutorState state, int level, StackUnwindWarningSet warnings, + TaskMonitor monitor) { + // Current strategy: save the UnwindInfo, do not save the UnwoundFrame's. + return unwindStack(coordinates, level, warnings, monitor); + } + + private AnalysisUnwoundFrame unwindStack(DebuggerCoordinates coordinates, + int targetLevel, StackUnwindWarningSet warnings, TaskMonitor monitor) { + WatchValuePcodeExecutorState state = null; + SavedRegisterMap registerMap = null; + AnalysisUnwoundFrame frame = null; + + for (int level = coordinates.getFrame(); level <= targetLevel || targetLevel < 0; level++) { + DebuggerCoordinates coord = coordinates.frame(level); + if (frame == null || frame.getError() != null) { + state = getState(tool, coord); + registerMap = new SavedRegisterMap(); + frame = null; + } + + ThreadAndSnap tas = new ThreadAndSnap(coord.getThread(), coord.getViewSnap()); + TreeMap> treeMap = unwound.computeIfAbsent( + tas, t -> new TreeMap>()); + AnalysisUnwoundFrame savedFrame = treeMap.get(coord.getFrame()); + if (savedFrame != null) { + // Short circuit here if possible to avoid recomputing UnwindInfo + frame = savedFrame; + registerMap = frame.registerMap; + continue; + } + + Address pcVal = pcOrSp(frame, coord, state, true); + + ProgramLocation loc = getProgramLocation(coord.getSnap(), pcVal); + if (loc != null && service != null) { + UnwindInfo info = + service.getUnwindInfo(loc.getProgram(), loc.getAddress(), monitor); + if (info == null) { + // Continue here to generate a frame and prevent recalculating info + try { + StaticAndUnwind sau = computeUnwindInfo(coord.getSnap(), pcVal, monitor); + info = sau.info(); + } + catch (CancelledException e) { + warnings.add( + new CustomStackUnwindWarning("Unwind cancelled for frame " + level)); + } + } + } + + Address spVal = pcOrSp(frame, coord, state, false); + + SavedRegisterMap nextRegisterMap = updateMap(frame, registerMap); + frame = unwind(coord, pcVal, spVal, state, nextRegisterMap, monitor); + if (frame != null) { + registerMap = frame.registerMap; + treeMap.put(coord.getFrame(), frame); + } + else if (targetLevel < 0) { + break; + } + } + return frame; + } + + private SavedRegisterMap updateMap(AnalysisUnwoundFrame frame, + SavedRegisterMap registerMap) { + if (frame != null) { + SavedRegisterMap nextRegisterMap = registerMap.fork(); + Address base = frame.getBasePointer(); + if (base != null) { + frame.getUnwindInfo().mapSavedRegisters(base, nextRegisterMap); + } + return nextRegisterMap; + } + return registerMap; + } + + private Address pcOrSp(AnalysisUnwoundFrame frame, + DebuggerCoordinates coordinates, + PcodeExecutorState state, boolean getPc) { TraceThread thread = coordinates.getThread(); + int level = coordinates.getFrame(); long viewSnap = coordinates.getViewSnap(); - try { - TraceStack stack = trace.getStackManager().getLatestStack(thread, viewSnap); - if (stack != null) { - TraceStackFrame frame = stack.getFrame(viewSnap, level, false); - if (frame != null) { - pcVal = frame.getProgramCounter(viewSnap); + + Address regVal = null; + + // Try asking the stack + TraceStack stack = + trace.getStackManager().getStack(thread, viewSnap, false); + if (stack != null) { + TraceStackFrame frameForLevel = stack.getFrame(viewSnap, level, false); + if (frameForLevel != null) { + regVal = getPc ? frameForLevel.getProgramCounter(viewSnap) + : frameForLevel.getStackPointer(viewSnap); + if (regVal != null) { + return regVal; } } } - catch (IllegalStateException e) { - // Schema does not specify a stack - // leave pcVal = null, so we'll get it from registers - } - TraceMemorySpace regs = Objects.requireNonNull( - trace.getMemoryManager().getMemoryRegisterSpace(thread, level, false), - "Frame must have a register bank"); - if (pcVal == null) { - if (TraceMemoryState.KNOWN != regs.getState(platform, viewSnap, pc)) { - throw new UnwindException("Frame must have KNOWN " + pc + " value"); + + // Try asking the registers + TraceMemorySpace regs = + trace.getMemoryManager().getMemoryRegisterSpace(thread, level, false); + if (regs != null) { + if (TraceMemoryState.KNOWN == regs.getState(platform, viewSnap, getPc ? pc : sp)) { + regVal = codeSpace.getAddress(regs.getValue(platform, viewSnap, getPc ? pc : sp) + .getUnsignedValue() + .longValue()); + if (regVal != null) { + return regVal; + } } - pcVal = codeSpace.getAddress( - regs.getValue(platform, viewSnap, pc).getUnsignedValue().longValue()); } - if (TraceMemoryState.KNOWN != regs.getState(platform, viewSnap, sp)) { - throw new UnwindException("Frame must have KNOWN " + sp + " value"); + + // Try unwinding the stack + if (frame != null) { + UnwindInfo prevInfo = frame.getUnwindInfo(); + Address base = frame.getBasePointer(); + try { + regVal = getPc ? prevInfo.computeNextPc(base, state, codeSpace, pc) + : prevInfo.computeNextSp(base); + if (regVal != null) { + return regVal; + } + } + catch (Exception e) { + // Use the fall-back case below + } } - Address spVal = stackSpace.getAddress( - regs.getValue(platform, viewSnap, sp).getUnsignedValue().longValue()); - return unwind(coordinates, level, pcVal, spVal, state, new SavedRegisterMap(), monitor); + + // Fall-back to current frame + RegisterValue rval = state.inspectRegisterValue(getPc ? pc : sp); + return codeSpace.getAddress(rval.getUnsignedValue().longValue()); } record StaticAndUnwind(Address staticPc, UnwindInfo info) {} @@ -219,110 +310,109 @@ public class StackUnwinder { * address and then invokes {@link UnwindAnalysis#computeUnwindInfo(Address, TaskMonitor)}. * * @param snap the snapshot key (used for mapping the program counter to a program database) - * @param level the frame level, used only for error messages * @param pcVal the program counter (dynamic) * @param monitor a monitor for cancellation * @return the unwind info, possibly incomplete * @throws CancelledException if the monitor is cancelled */ - public StaticAndUnwind computeUnwindInfo(long snap, int level, Address pcVal, + public StaticAndUnwind computeUnwindInfo(long snap, Address pcVal, TaskMonitor monitor) throws CancelledException { // TODO: Try markup in trace first? - ProgramLocation staticPcLoc = mappings == null ? null - : mappings.getOpenMappedLocation( - new DefaultTraceLocation(trace, null, Lifespan.at(snap), pcVal)); + ProgramLocation staticPcLoc = getProgramLocation(snap, pcVal); if (staticPcLoc == null) { - throw new UnwindException("Cannot find static program for frame " + level + " (" + - pc + "=" + pcVal + ")"); + throw new UnwindException( + "Cannot find static program for frame (" + pc + "=" + pcVal + ")"); } Program program = staticPcLoc.getProgram(); Address staticPc = staticPcLoc.getAddress(); try { - // TODO: Cache these? - UnwindAnalysis ua = new UnwindAnalysis(program); - return new StaticAndUnwind(staticPc, ua.computeUnwindInfo(staticPc, monitor)); + UnwindInfo info = service.getUnwindInfo(program, staticPc, monitor); + StaticAndUnwind sau = new StaticAndUnwind(staticPc, info); + if (sau.info().ofReturn() == null) { + Function function = sau.info().function(); + if (function != null) { + Address ep = function.getEntryPoint(); + UnwindInfo epInfo = service.getUnwindInfo(program, ep, monitor); + info = new UnwindInfo(info.function(), info.depth(), + info.adjust(), epInfo.ofReturn(), epInfo.maskOfReturn(), info.saved(), + info.warnings(), info.error()); + sau = new StaticAndUnwind(staticPc, info); + } + } + return sau; } catch (Exception e) { return new StaticAndUnwind(staticPc, UnwindInfo.errorOnly(e)); } } - AnalysisUnwoundFrame unwind(DebuggerCoordinates coordinates, int level, Address pcVal, + private ProgramLocation getProgramLocation(long snap, Address pcVal) { + return mappings == null ? null + : mappings.getOpenMappedLocation( + new DefaultTraceLocation(trace, null, Lifespan.at(snap), pcVal)); + } + + AnalysisUnwoundFrame unwind(DebuggerCoordinates coordinates, Address pcVal, Address spVal, PcodeExecutorState state, SavedRegisterMap registerMap, - TaskMonitor monitor) throws CancelledException { + TaskMonitor monitor) { try { - StaticAndUnwind sau = computeUnwindInfo(coordinates.getSnap(), level, pcVal, monitor); - return new AnalysisUnwoundFrame<>(tool, coordinates, this, state, level, pcVal, spVal, + StaticAndUnwind sau = computeUnwindInfo(coordinates.getSnap(), pcVal, monitor); + return new AnalysisUnwoundFrame<>(tool, coordinates, this, state, pcVal, spVal, sau.staticPc, sau.info, registerMap); } catch (Exception e) { - return new AnalysisUnwoundFrame<>(tool, coordinates, this, state, level, pcVal, spVal, - null, UnwindInfo.errorOnly(e), registerMap); + if (!returnErrorFrame) { + return null; + } + return new AnalysisUnwoundFrame<>(tool, coordinates, this, state, pcVal, spVal, null, + UnwindInfo.errorOnly(e), registerMap); } } /** - * An iterable wrapper for {@link #start(DebuggerCoordinates, PcodeExecutorState, TaskMonitor)} - * and {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)} - * - * @param the type of values in the state - * @param coordinates the starting coordinates - * @param state the state - * @param monitor the monitor - * @return the iterable over unwound frames + * A convenience method + * @return the deepest level */ - public Iterable> frames(DebuggerCoordinates coordinates, - PcodeExecutorState state, TaskMonitor monitor) { - return new Iterable<>() { - @Override - public Iterator> iterator() { - return new Iterator<>() { - AnalysisUnwoundFrame next = tryStart(); - - @Override - public boolean hasNext() { - return next != null; - } - - @Override - public AnalysisUnwoundFrame next() { - AnalysisUnwoundFrame cur = next; - next = tryNext(); - return cur; - } - - private AnalysisUnwoundFrame tryStart() { - try { - return start(coordinates, state, monitor); - } - catch (UnwindException | CancelledException e) { - return null; - } - } - - private AnalysisUnwoundFrame tryNext() { - try { - return next.unwindNext(monitor); - } - catch (NoSuchElementException | UnwindException | CancelledException e) { - return null; - } - } - }; - } - }; + public int getRecoveredFrameCount() { + return unwound.size(); } - /** - * An iterable wrapper for {@link #start(DebuggerCoordinates, TaskMonitor)} and - * {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)} - * - * @param coordinates the starting coordinates - * @param monitor the monitor - * @return the iterable over unwound frames - */ - public Iterable> frames(DebuggerCoordinates coordinates, - TaskMonitor monitor) { - return frames(coordinates, getState(tool, coordinates), monitor); + public void invalidateCache() { + unwound.clear(); + } + + public Map> getFrames( + DebuggerCoordinates coordinates, TaskMonitor monitor) { + unwindStack(coordinates, getTargetReportedMaxFrame(coordinates), null, monitor); + ThreadAndSnap tas = new ThreadAndSnap(coordinates.getThread(), coordinates.getViewSnap()); + return unwound.get(tas); + } + + public AnalysisUnwoundFrame findMatchForFunction(Function function, + DebuggerCoordinates coordinates, StackUnwindWarningSet warnings, TaskMonitor monitor) { + unwindStack(coordinates, getTargetReportedMaxFrame(coordinates), warnings, monitor); + AnalysisUnwoundFrame candidate = null; + ThreadAndSnap tas = new ThreadAndSnap(coordinates.getThread(), coordinates.getViewSnap()); + for (Entry> entry : unwound.get(tas).entrySet()) { + AnalysisUnwoundFrame frame = entry.getValue(); + if (frame.getFunction() == function) { + StackUnwindWarningSet unwindWarnings = frame.getWarnings(); + if (unwindWarnings != null) { + warnings.addAll(unwindWarnings); + } + candidate = frame; + if (entry.getKey() >= coordinates.getFrame()) { + return frame; + } + } + } + return candidate; + } + + private int getTargetReportedMaxFrame(DebuggerCoordinates coordinates) { + TraceThread thread = coordinates.getThread(); + long snap = coordinates.getViewSnap(); + TraceStack stack = trace.getStackManager().getStack(thread, snap, false); + return stack == null ? -1 : stack.getDepth(snap) - 1; } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindAnalysis.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindAnalysis.java index f23fd84242..638da7549b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindAnalysis.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindAnalysis.java @@ -43,6 +43,8 @@ import ghidra.util.task.TaskMonitor; */ public class UnwindAnalysis { + private Map unwindInfo = new HashMap<>(); + /** * A graph used for finding execution paths from function entry through the program counter to a * return. @@ -552,4 +554,20 @@ public class UnwindAnalysis { AnalysisForPC analysis = start(pc, monitor); return analysis.computeUnwindInfo(); } + + public UnwindInfo getUnwindInfo(Address key, TaskMonitor monitor) { + UnwindInfo info = unwindInfo.get(key); + if (info == null) { + try { + info = computeUnwindInfo(key, monitor); + if (info != null && info.error() != null) { + unwindInfo.put(key, info); + } + } + catch (CancelledException e) { + // info is null + } + } + return info; + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindInfo.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindInfo.java index d39503fe6f..63abb9e7f2 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindInfo.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindInfo.java @@ -182,7 +182,7 @@ public record UnwindInfo(Function function, Long depth, Long adjust, Address ofR * @return the base address */ public Address computeBase(Address spVal) { - return depth == null ? null : spVal.subtract(depth); + return spVal == null || depth == null ? null : spVal.subtract(depth); } /** diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindStackCommand.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindStackCommand.java index 3d6e002736..47a9a95b57 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindStackCommand.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindStackCommand.java @@ -4,9 +4,9 @@ * 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. @@ -15,6 +15,8 @@ */ package ghidra.app.plugin.core.debug.stack; +import java.util.Map; + import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.cmd.BackgroundCommand; import ghidra.framework.plugintool.PluginTool; @@ -43,15 +45,19 @@ public class UnwindStackCommand extends BackgroundCommand { try { StackUnwinder unwinder = new StackUnwinder(tool, where.getPlatform()); int prevParamSize = 0; - for (AnalysisUnwoundFrame frame : unwinder.frames(where.frame(0), - monitor)) { - UnwindInfo info = frame.getUnwindInfo(); - if (info != null && info.error() == null) { - frame.applyToListing(prevParamSize, monitor); - prevParamSize = info.computeParamSize(); - } - else { - tool.setStatusInfo(frame.getError().getMessage()); + Map> frames = + unwinder.getFrames(where, monitor); + for (int i = 0; i < frames.size(); i++) { + AnalysisUnwoundFrame frame = frames.get(i); + if (frame != null) { + UnwindInfo info = frame.getUnwindInfo(); + if (info != null && info.error() == null) { + frame.applyToListing(prevParamSize, monitor); + prevParamSize = info.computeParamSize(); + } + else { + tool.setStatusInfo(frame.getError().getMessage()); + } } } return true; diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java index 8c0b500065..ed72f8427e 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java @@ -28,6 +28,7 @@ import ghidra.app.plugin.assembler.*; import ghidra.app.plugin.core.debug.gui.action.SPLocationTrackingSpec; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider; +import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueHoverPlugin; import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin; import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin; import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils; @@ -294,6 +295,7 @@ public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator DebuggerControlService controlService = addPlugin(tool, DebuggerControlServicePlugin.class); DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + addPlugin(tool, VariableValueHoverPlugin.class); Function function = createFibonacciProgramX86_32(); Address entry = function.getEntryPoint(); diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPluginScreenShots.java index 6ee6a10b43..e3e8068854 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPluginScreenShots.java @@ -231,6 +231,8 @@ public class VariableValueHoverPluginScreenShots extends GhidraScreenShotGenerat DebuggerControlService controlService = addPlugin(tool, DebuggerControlServicePlugin.class); DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); Function function = createFibonacciProgramX86_32(); GhidraProgramUtilities.markProgramAnalyzed(program); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java index 6dd0bc56f0..4a860f895d 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java @@ -25,6 +25,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import org.junit.Ignore; import org.junit.Test; import db.Transaction; @@ -127,6 +128,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { CodeBrowserPlugin codeBrowserPlugin; ListingPanel staticListing; DebuggerListingPlugin listingPlugin; + VariableValueHoverPlugin valuesPlugin; ListingPanel dynamicListing; DebuggerControlService editingService; DebuggerEmulationService emuService; @@ -804,6 +806,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { addPlugin(tool, DecompilePlugin.class); DebuggerControlService editingService = addPlugin(tool, DebuggerControlServicePlugin.class); DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); Function function = createSumSquaresProgramX86_32(); Address entry = function.getEntryPoint(); @@ -863,6 +866,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { addPlugin(tool, DecompilePlugin.class); DebuggerControlService editingService = addPlugin(tool, DebuggerControlServicePlugin.class); DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); Function function = createFibonacciProgramX86_32(); Address entry = function.getEntryPoint(); @@ -957,6 +961,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { addPlugin(tool, AutoAnalysisPlugin.class); addPlugin(tool, DecompilePlugin.class); DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); Function function = createInfiniteRecursionProgramArm(); openAndAnalyze(function); @@ -1044,6 +1049,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { addPlugin(tool, DebuggerListingPlugin.class); addPlugin(tool, DisassemblerPlugin.class); addPlugin(tool, DecompilePlugin.class); + valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); Function function = createFibonacciProgramX86_32(); Address entry = function.getEntryPoint(); @@ -1082,6 +1088,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { codeBrowserPlugin = addPlugin(tool, CodeBrowserPlugin.class); staticListing = codeBrowserPlugin.getProvider().getListingPanel(); listingPlugin = addPlugin(tool, DebuggerListingPlugin.class); + valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); dynamicListing = listingPlugin.getProvider().getListingPanel(); addPlugin(tool, DisassemblerPlugin.class); addPlugin(tool, DecompilePlugin.class); @@ -1360,13 +1367,14 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { assertEquals(texts.size(), table.getNumRows()); } - protected VariableValueTable getVariableValueTable(VariableValueHoverService valuesService, - ProgramLocation programLocation, DebuggerCoordinates current, - FieldLocation fieldLocation, Field field) throws Throwable { + protected VariableValueTable getVariableValueTable(ProgramLocation programLocation, + DebuggerCoordinates current, FieldLocation fieldLocation, Field field) + throws Throwable { VariableValueTable table = new VariableValueTable(); StackUnwindWarningSet warnings = new StackUnwindWarningSet(); - waitOn(valuesService.fillVariableValueTable(table, programLocation, current, fieldLocation, - field, warnings)); + waitOn(valuesPlugin.getHoverService() + .fillVariableValueTable(table, programLocation, current, fieldLocation, field, + warnings)); table.add(new WarningsRow(warnings)); return table; } @@ -1374,12 +1382,10 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { @Test public void testStackVariableHover() throws Throwable { addPlugins(); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); Function function = runToTallestRecursionAndCreateFrames(2); HoverLocation loc = findVariableLocation(staticListing, function, "n"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: n", RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", RowKey.STORAGE, @@ -1392,12 +1398,10 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { @Test public void testRegisterVariableHover() throws Throwable { addPlugins(); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); Function function = runToTallestRecursionAndCreateFrames(2); HoverLocation loc = findVariableLocation(staticListing, function, "sum"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: sum", RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", RowKey.STORAGE, "Storage: EDX:4", @@ -1409,12 +1413,10 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { @Test public void testReturnParameterHover() throws Throwable { addPlugins(); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); Function function = runToTallestRecursionAndCreateFrames(2); HoverLocation loc = findVariableLocation(staticListing, function, ""); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: ", RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", RowKey.STORAGE, "Storage: EAX:4", @@ -1426,13 +1428,11 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { @Test public void testGlobalOperandHover() throws Throwable { addPlugins(); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); runToRetSetGlobalAndCreateFrames(); Instruction ins = program.getListing().getInstructionAt(globalRefInstr); HoverLocation loc = findOperandLocation(staticListing, ins, addr(program, 0x00600000)); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: myGlobal", RowKey.STORAGE, "Storage: 00600000:4", RowKey.TYPE, "Type: int", RowKey.LOCATION, "Location: 00600000:4", RowKey.BYTES, @@ -1467,15 +1467,13 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { @Test public void testGlobalOperandInTraceHover() throws Throwable { addPlugins(); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); runToRetSetGlobalAndCreateFrames(); Instruction ins = copyToDynamic(program.getListing().getInstructionAt(globalRefInstr)); // I guess the listing needs a moment??? HoverLocation loc = waitForValue(() -> findOperandLocation(dynamicListing, ins, addr(program, 0x00600000))); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: myGlobal", RowKey.STORAGE, "Storage: 00600000:4", RowKey.TYPE, "Type: int", RowKey.LOCATION, "Location: 00600000:4", RowKey.BYTES, @@ -1488,13 +1486,11 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { @Test public void testStackReferenceHover() throws Throwable { addPlugins(); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); runToTallestRecursionAndCreateFrames(2); Instruction ins = program.getListing().getInstructionAt(stackRefInstr); HoverLocation loc = findOperandLocation(staticListing, ins, new Scalar(32, 8)); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: n", RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", RowKey.STORAGE, @@ -1507,13 +1503,11 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { @Test public void testRegisterReferenceHover() throws Throwable { addPlugins(); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); runToTallestRecursionAndCreateFrames(2); Instruction ins = program.getListing().getInstructionAt(registerRefInstr); HoverLocation loc = findOperandLocation(staticListing, ins, register("EDX")); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = getVariableValueTable( + loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: sum", RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", RowKey.STORAGE, "Storage: EDX:4", @@ -1523,18 +1517,17 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { } @Test + @Ignore("Worked because reg requests for frames>0 defaulted to frame 0. NO LONGER TRUE.") public void testSavedRegisterReferenceHover() throws Throwable { addPlugins(); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); // need 3 frames. 0 has already popped EBP, so not saved. 1 will save on behalf of 2. Function function = runToTallestRecursionAndCreateFrames(3); traceManager.activateFrame(2); Instruction ins = program.getListing().getInstructionAt(function.getEntryPoint()); HoverLocation loc = findOperandLocation(staticListing, ins, register("EBP")); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: EBP", RowKey.FRAME, "Frame: 2 fib pc=00400013 sp=00004ff8 base=00005000", RowKey.LOCATION, @@ -1545,15 +1538,13 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { @Test public void testRegisterReferenceInTraceHover() throws Throwable { addPlugins(); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); runToTallestRecursionAndCreateFrames(2); Instruction ins = copyToDynamic(program.getListing().getInstructionAt(registerRefInstr)); // I guess the listing needs a moment??? HoverLocation loc = waitForValue(() -> findOperandLocation(dynamicListing, ins, register("EDX"))); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: EDX", RowKey.INTEGER, "Integer: (UNKNOWN) 0", RowKey.WARNINGS, "IGNORED"), table); @@ -1657,12 +1648,10 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { @Test public void testGlobalHighVarHover() throws Throwable { addPlugins(); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); Function function = runToRetSetGlobalAndCreateFrames(); HoverLocation loc = findTokenLocation(function, "myGlobal", "myGlobal = -0x21524111;"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: myGlobal", RowKey.STORAGE, "Storage: 00600000:4", RowKey.TYPE, "Type: int", RowKey.LOCATION, "Location: 00600000:4", RowKey.BYTES, @@ -1675,12 +1664,10 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { @Test public void testStackHighVarHover() throws Throwable { addPlugins(); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); Function function = runToTallestRecursionAndCreateFrames(2); HoverLocation loc = findTokenLocation(function, "n", "if (1 < n) {"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: n", RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", RowKey.STORAGE, @@ -1693,13 +1680,11 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { @Test public void testRegisterHighVarHover() throws Throwable { addPlugins(); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); Function function = runToTallestRecursionAndCreateFrames(2); // TODO: Line matching seems fragile HoverLocation loc = findTokenLocation(function, "uVar1", "uVar1 = fib(n - 1);"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: uVar1", RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", RowKey.STORAGE, "Storage: EAX:4", @@ -1713,14 +1698,12 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { addPlugins(); // PC Tracking interferes with goTo listingPlugin.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); Function function = runToRetFillStructAndCreateFrames(); goTo(staticListing, new ProgramLocation(program, function.getEntryPoint())); HoverLocation loc = findTokenLocation(function, "myGlobal", "return (uint)myStack.y + (uint)myGlobal.m;"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: myGlobal", RowKey.STORAGE, "Storage: 00600000:4", RowKey.TYPE, "Type: MyStruct", RowKey.LOCATION, "Location: 00600000:4", RowKey.BYTES, @@ -1733,14 +1716,12 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { addPlugins(); // PC Tracking interferes with goTo listingPlugin.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); Function function = runToRetFillStructAndCreateFrames(); goTo(staticListing, new ProgramLocation(program, function.getEntryPoint())); HoverLocation loc = findTokenLocation(function, "m", "return (uint)myStack.y + (uint)myGlobal.m;"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: m", RowKey.TYPE, "Type: byte", RowKey.LOCATION, "Location: 00600002:1", RowKey.BYTES, "Bytes: (KNOWN) 0c", RowKey.INTEGER, @@ -1753,14 +1734,12 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { addPlugins(); // PC Tracking interferes with goTo listingPlugin.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); Function function = runToRetFillStructAndCreateFrames(); goTo(staticListing, new ProgramLocation(program, function.getEntryPoint())); HoverLocation loc = findTokenLocation(function, "myStack", "return (uint)myStack.y + (uint)myGlobal.m;"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: myStack", RowKey.FRAME, "Frame: 1 main pc=00400012 sp=00004fe4 base=00004ff0", RowKey.STORAGE, @@ -1775,14 +1754,12 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { addPlugins(); // PC Tracking interferes with goTo listingPlugin.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); Function function = runToRetFillStructAndCreateFrames(); goTo(staticListing, new ProgramLocation(program, function.getEntryPoint())); HoverLocation loc = findTokenLocation(function, "y", "return (uint)myStack.y + (uint)myGlobal.m;"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: y", RowKey.FRAME, "Frame: 1 main pc=00400012 sp=00004fe4 base=00004ff0", RowKey.TYPE, "Type: word", @@ -1796,8 +1773,6 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { addPlugins(); // PC Tracking interferes with goTo listingPlugin.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); Function function = createFillStackStructProgramX86_64(); Address entry = function.getEntryPoint(); @@ -1813,8 +1788,8 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { goTo(staticListing, new ProgramLocation(program, entry)); HoverLocation loc = findTokenLocation(function, "f1", "s.f1 = 4;"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.ofEntries( Map.entry(RowKey.NAME, "Name: f1"), @@ -1833,14 +1808,12 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { addPlugins(); // PC Tracking interferes with goTo listingPlugin.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); runToRetFillStructAndCreateFrames(); Function function = program.getFunctionManager().getFunctionContaining(retInstr); goTo(staticListing, new ProgramLocation(program, retInstr)); HoverLocation loc = findTokenLocation(function, "s", "s->y = 0x7e6;"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: s", RowKey.FRAME, "Frame: 0 fillStruct pc=00400041 sp=00004fe0 base=00004fe0", RowKey.STORAGE, @@ -1857,14 +1830,12 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { addPlugins(); // PC Tracking interferes with goTo listingPlugin.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); runToRetFillStructAndCreateFrames(); Function function = program.getFunctionManager().getFunctionContaining(retInstr); goTo(staticListing, new ProgramLocation(program, retInstr)); HoverLocation loc = findTokenLocation(function, "y", "s->y = 0x7e6;"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: y", RowKey.FRAME, "Frame: 0 fillStruct pc=00400041 sp=00004fe0 base=00004fe0", RowKey.TYPE, "Type: word", @@ -1878,14 +1849,12 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { addPlugins(); // PC Tracking interferes with goTo listingPlugin.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE); - VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); - VariableValueHoverService valuesService = valuesPlugin.getHoverService(); runToRetFillStructArrayAndCreateFrames(); Function function = program.getFunctionManager().getFunctionContaining(retInstr); goTo(staticListing, new ProgramLocation(program, retInstr)); HoverLocation loc = findTokenLocation(function, "m", "s[i].m = 0xc;"); - VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, - traceManager.getCurrent(), loc.fLoc, loc.field); + VariableValueTable table = + getVariableValueTable(loc.pLoc, traceManager.getCurrent(), loc.fLoc, loc.field); assertTable(Map.of(RowKey.NAME, "Name: m", RowKey.FRAME, "Frame: 0 fillStruct pc=0040002e sp=00004fe0 base=00004fe0", RowKey.TYPE, "Type: byte", diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackFrame.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackFrame.java index 98a5c80c60..504566c2e7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackFrame.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackFrame.java @@ -53,7 +53,8 @@ public class DBTraceStackFrame implements TraceStackFrame, DBTraceObjectInterfac TraceObjectSchema schema = object.getSchema(); synchronized (KEYS_BY_SCHEMA) { keys = KEYS_BY_SCHEMA.computeIfAbsent(schema, - s -> Set.of(schema.checkAliasedAttribute(KEY_PC))); + s -> Set.of(schema.checkAliasedAttribute(KEY_PC), + schema.checkAliasedAttribute(KEY_SP))); } } @@ -106,6 +107,21 @@ public class DBTraceStackFrame implements TraceStackFrame, DBTraceObjectInterfac } } + @Override + public Address getStackPointer(long snap) { + return TraceObjectInterfaceUtils.getValue(object, snap, KEY_SP, Address.class, null); + } + + @Override + public void setStackPointer(Lifespan span, Address sp) { + try (LockHold hold = object.getTrace().lockWrite()) { + if (sp == Address.NO_ADDRESS) { + sp = null; + } + object.setValue(span, KEY_SP, sp); + } + } + @Override public String getComment(long snap) { // TODO: One day, we'll have dynamic columns in the debugger diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStackFrame.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStackFrame.java index 2835e85a39..4c181ee3be 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStackFrame.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStackFrame.java @@ -30,10 +30,13 @@ import ghidra.trace.model.target.info.TraceObjectInfo; shortName = "frame", attributes = { TraceStackFrame.KEY_PC, + TraceStackFrame.KEY_SP, }, - fixedKeys = {}) + fixedKeys = {} +) public interface TraceStackFrame extends TraceObjectInterface { String KEY_PC = "_pc"; + String KEY_SP = "_sp"; /** * Get the trace containing this frame @@ -77,6 +80,22 @@ public interface TraceStackFrame extends TraceObjectInterface { */ void setProgramCounter(@Experimental Lifespan span, Address pc); + /** + * Get the stack pointer at the given snap + * + * @param snap the snap + * @return the stack pointer + */ + Address getStackPointer(@Experimental long snap); + + /** + * Set the stack pointer over the given span + * + * @param span the span + * @param sp the stack pointer + */ + void setStackPointer(@Experimental Lifespan span, Address sp); + /** * Get the user comment for the frame * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/schema/TraceObjectSchema.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/schema/TraceObjectSchema.java index 4e54356256..966b431ba7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/schema/TraceObjectSchema.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/schema/TraceObjectSchema.java @@ -1021,14 +1021,17 @@ public interface TraceObjectSchema { * {@link TraceStack} with a single index between. There must be an intervening * {@link TraceStackFrame}, and the frame level (index) must precede it. * - * @param frameLevel the frame level. May be ignored if not applicable + * @param frameLevel the frame level. Must be 0 if not applicable * @param path the path of the seed object relative to the root * @return the filter where the register container should be found, possibly * {@link PathFilter#NONE} */ default PathFilter searchForRegisterContainer(int frameLevel, KeyPath path) { KeyPath simple = searchForSuitable(TraceRegisterContainer.class, path); - if (simple != null) { + // NB: This is technically not correct. It's possible, although unlikely, that + // the target has an accessible stack but no stack in its schema. If so, + // this method should be called with 0 instead of the actual stack level. + if (simple != null && frameLevel == 0) { return PathFilter.pattern(simple); } KeyPath stackPath = searchForSuitable(TraceStack.class, path); @@ -1053,7 +1056,7 @@ public interface TraceObjectSchema { framePatternRelStack.applyKeys(index).getSingletonPath(); KeyPath framePath = stackPath.extend(framePathRelStack); KeyPath regsPath = searchForSuitable(TraceRegisterContainer.class, framePath); - if (regsPath != null) { + if (regsPath != null && stackPath.isAncestor(regsPath)) { patterns.add(new PathPattern(regsPath)); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldSymbolLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldSymbolLoader.java index 726a283595..093429f80d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldSymbolLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldSymbolLoader.java @@ -141,7 +141,9 @@ public class LabelFieldSymbolLoader { } Symbol s = symbolTable.getPrimarySymbol(addr); - symbols.addOffcut(s); + if (s != null) { + symbols.addOffcut(s); + } } SymbolIterator symIter = symbolTable.getSymbolIterator(nextAddr, true);