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