Merge remote-tracking branch 'origin/GP-4914_d-millar_better_hover_RB260518--SQUASHED'

This commit is contained in:
Ryan Kurtz
2026-05-29 05:52:32 -04:00
28 changed files with 547 additions and 327 deletions

View File

@@ -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():

View File

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

View File

@@ -195,6 +195,7 @@
<attribute name="Instruction Offset" schema="ADDRESS" required="yes"/>
<attribute-alias from="_pc" to="Instruction Offset"/>
<attribute name="Stack Offset" schema="ADDRESS"/>
<attribute-alias from="_sp" to="Stack Offset"/>
<attribute name="Return Offset" schema="ADDRESS"/>
<attribute name="Frame Offset" schema="ADDRESS"/>
</schema>

View File

@@ -198,6 +198,7 @@
<attribute name="Instruction Offset" schema="ADDRESS" required="yes"/>
<attribute-alias from="_pc" to="Instruction Offset"/>
<attribute name="Stack Offset" schema="ADDRESS"/>
<attribute-alias from="_sp" to="Stack Offset"/>
<attribute name="Return Offset" schema="ADDRESS"/>
<attribute name="Frame Offset" schema="ADDRESS"/>
</schema>

View File

@@ -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():

View File

@@ -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():

View File

@@ -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():

View File

@@ -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():

View File

@@ -223,6 +223,7 @@
<attribute name="Instruction Offset" schema="ADDRESS" required="yes"/>
<attribute-alias from="_pc" to="Instruction Offset"/>
<attribute name="Stack Offset" schema="ADDRESS"/>
<attribute-alias from="_sp" to="Stack Offset"/>
<attribute name="Return Offset" schema="ADDRESS"/>
<attribute name="Frame Offset" schema="ADDRESS"/>
</schema>

View File

@@ -26,6 +26,7 @@
<attribute name="Instruction Offset" schema="ADDRESS" required="yes" />
<attribute-alias from="_pc" to="Instruction Offset" />
<attribute name="Stack Offset" schema="ADDRESS" />
<attribute-alias from="_sp" to="Stack Offset" />
<attribute name="Return Offset" schema="ADDRESS" />
<attribute name="Frame Offset" schema="ADDRESS" />
<attribute schema="ANY" />

View File

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

View File

@@ -98,6 +98,47 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceStac
return pc;
}
private static class FrameSpColumn extends TraceValueObjectAttributeColumn<Address> {
public FrameSpColumn() {
super(TraceStackFrame.KEY_SP, Address.class);
}
@Override
public String getColumnName() {
return "SP";
}
@Override
public ValueProperty<Address> 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<TraceStac
descriptor.addVisibleColumn(new FramePcColumn());
descriptor.addVisibleColumn(new FrameFunctionColumn());
descriptor.addVisibleColumn(new FrameModuleColumn());
descriptor.addVisibleColumn(new FrameSpColumn());
return descriptor;
}
}

View File

@@ -76,6 +76,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
@AutoServiceConsumed // TODO: Add listener for mapping changes to refresh table
DebuggerStaticMappingService mappingService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;

View File

@@ -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.
@@ -35,7 +35,8 @@ import ghidra.framework.plugintool.util.PluginStatus;
},
servicesProvided = {
ListingHoverService.class,
DecompilerHoverService.class
DecompilerHoverService.class,
VariableValueHoverService.class
})
public class VariableValueHoverPlugin extends Plugin {
private VariableValueHoverService hoverService;
@@ -45,6 +46,7 @@ public class VariableValueHoverPlugin extends Plugin {
hoverService = new VariableValueHoverService(tool);
registerServiceProvided(ListingHoverService.class, hoverService);
registerServiceProvided(DecompilerHoverService.class, hoverService);
registerServiceProvided(VariableValueHoverService.class, hoverService);
}
public VariableValueHoverService getHoverService() {

View File

@@ -118,6 +118,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover
}
};
private Map<Program, UnwindAnalysis> 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);
}
}
}

View File

@@ -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}
*
* <p>
* 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
*
* <p>
* 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<UnwoundFrame<WatchValue>> unwound;
private FakeUnwoundFrame<WatchValue> 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<WatchValue> 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<WatchValue> 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<WatchValue> currentFrame =
unwinder.findMatchForFunction(function, coordinates, warnings, monitor);
if (currentFrame != null) {
return currentFrame;
}
for (UnwoundFrame<WatchValue> 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;
}
}

View File

@@ -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;
*
* <p>
* 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 <T> the type of values retrievable from the unwound frame
* {@link StackUnwinder#getFrames(DebuggerCoordinates, TaskMonitor)}.
*/
public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
@@ -64,7 +56,7 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
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<T> extends AbstractUnwoundFrame<T> {
* @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<T> state, int level, Address pcVal, Address spVal,
PcodeExecutorState<T> 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<T> extends AbstractUnwoundFrame<T> {
* @throws CancelledException if the monitor is cancelled
* @throws UnwindException if unwinding fails
*/
public AnalysisUnwoundFrame<T> unwindNext(TaskMonitor monitor) throws CancelledException {
public AnalysisUnwoundFrame<T> 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<T>) unwinder.getFrame(coordinates, state, level + 1, null,
monitor);
}
@Override
@@ -334,8 +321,13 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
"Frame " + level + " has lenght 0");
return null;
}
for (TraceBookmark existing : bm.getBookmarksIntersecting(span,
new AddressRangeImpl(spPlusParams, spPlusParams.add(structure.getLength() - 1)))) {
List<TraceBookmark> 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()) {

View File

@@ -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<ThreadAndSnap, TreeMap<Integer, AnalysisUnwoundFrame<WatchValue>>> 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
*
* <p>
* 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<WatchValue> 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 <T> 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 <T> AnalysisUnwoundFrame<T> start(DebuggerCoordinates coordinates,
PcodeExecutorState<T> state, TaskMonitor monitor) throws CancelledException {
return start(coordinates, coordinates.getFrame(), state, monitor);
public AnalysisUnwoundFrame<WatchValue> 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 <T> AnalysisUnwoundFrame<T> start(DebuggerCoordinates coordinates, int level,
PcodeExecutorState<T> state, TaskMonitor monitor) throws CancelledException {
Address pcVal = null;
public AnalysisUnwoundFrame<WatchValue> 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<WatchValue> unwindStack(DebuggerCoordinates coordinates,
int targetLevel, StackUnwindWarningSet warnings, TaskMonitor monitor) {
WatchValuePcodeExecutorState state = null;
SavedRegisterMap registerMap = null;
AnalysisUnwoundFrame<WatchValue> 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<Integer, AnalysisUnwoundFrame<WatchValue>> treeMap = unwound.computeIfAbsent(
tas, t -> new TreeMap<Integer, AnalysisUnwoundFrame<WatchValue>>());
AnalysisUnwoundFrame<WatchValue> 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<WatchValue> 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<WatchValue> 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));
}
}
<T> AnalysisUnwoundFrame<T> 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));
}
<T> AnalysisUnwoundFrame<T> unwind(DebuggerCoordinates coordinates, Address pcVal,
Address spVal, PcodeExecutorState<T> 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 <T> 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 <T> Iterable<AnalysisUnwoundFrame<T>> frames(DebuggerCoordinates coordinates,
PcodeExecutorState<T> state, TaskMonitor monitor) {
return new Iterable<>() {
@Override
public Iterator<AnalysisUnwoundFrame<T>> iterator() {
return new Iterator<>() {
AnalysisUnwoundFrame<T> next = tryStart();
@Override
public boolean hasNext() {
return next != null;
}
@Override
public AnalysisUnwoundFrame<T> next() {
AnalysisUnwoundFrame<T> cur = next;
next = tryNext();
return cur;
}
private AnalysisUnwoundFrame<T> tryStart() {
try {
return start(coordinates, state, monitor);
}
catch (UnwindException | CancelledException e) {
return null;
}
}
private AnalysisUnwoundFrame<T> 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<AnalysisUnwoundFrame<WatchValue>> frames(DebuggerCoordinates coordinates,
TaskMonitor monitor) {
return frames(coordinates, getState(tool, coordinates), monitor);
public void invalidateCache() {
unwound.clear();
}
public Map<Integer, AnalysisUnwoundFrame<WatchValue>> 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<WatchValue> findMatchForFunction(Function function,
DebuggerCoordinates coordinates, StackUnwindWarningSet warnings, TaskMonitor monitor) {
unwindStack(coordinates, getTargetReportedMaxFrame(coordinates), warnings, monitor);
AnalysisUnwoundFrame<WatchValue> candidate = null;
ThreadAndSnap tas = new ThreadAndSnap(coordinates.getThread(), coordinates.getViewSnap());
for (Entry<Integer, AnalysisUnwoundFrame<WatchValue>> entry : unwound.get(tas).entrySet()) {
AnalysisUnwoundFrame<WatchValue> 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;
}
}

View File

@@ -43,6 +43,8 @@ import ghidra.util.task.TaskMonitor;
*/
public class UnwindAnalysis {
private Map<Address, UnwindInfo> 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;
}
}

View File

@@ -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);
}
/**

View File

@@ -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<Trace> {
try {
StackUnwinder unwinder = new StackUnwinder(tool, where.getPlatform());
int prevParamSize = 0;
for (AnalysisUnwoundFrame<WatchValue> 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<Integer, AnalysisUnwoundFrame<WatchValue>> frames =
unwinder.getFrames(where, monitor);
for (int i = 0; i < frames.size(); i++) {
AnalysisUnwoundFrame<WatchValue> 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;

View File

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

View File

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

View File

@@ -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, "<RETURN>");
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: <RETURN>", 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",

View File

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

View File

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

View File

@@ -1021,14 +1021,17 @@ public interface TraceObjectSchema {
* {@link TraceStack} with a single index between. There <em>must</em> 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));
}
}

View File

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