mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
Merge remote-tracking branch 'origin/GP-4914_d-millar_better_hover_RB260518--SQUASHED'
This commit is contained in:
@@ -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():
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user