mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
Merge remote-tracking branch 'origin/patch'
Conflicts: Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
package ghidra.app.plugin.core.debug.gui.stack.vars;
|
||||
|
||||
import java.awt.Window;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -34,6 +36,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueRow.*;
|
||||
import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueUtils.VariableEvaluator;
|
||||
import ghidra.app.plugin.core.debug.stack.*;
|
||||
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.CustomStackUnwindWarning;
|
||||
import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService;
|
||||
import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService.TaskOpt;
|
||||
import ghidra.app.plugin.core.hover.AbstractConfigurableHover;
|
||||
@@ -147,13 +150,13 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
private final VariableValueTable table;
|
||||
private final PluginTool tool;
|
||||
private final DebuggerCoordinates current;
|
||||
private final List<String> warnings;
|
||||
private final StackUnwindWarningSet warnings;
|
||||
|
||||
private final DebuggerStaticMappingService mappingService;
|
||||
private final VariableEvaluator eval;
|
||||
|
||||
public TableFiller(VariableValueTable table, PluginTool tool, DebuggerCoordinates current,
|
||||
VariableEvaluator eval, List<String> warnings) {
|
||||
VariableEvaluator eval, StackUnwindWarningSet warnings) {
|
||||
this.table = table;
|
||||
this.tool = tool;
|
||||
this.current = current;
|
||||
@@ -205,7 +208,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
if (stUnit instanceof Instruction stIns) {
|
||||
fillDefinedData(dynData);
|
||||
table.add(new InstructionRow(stIns));
|
||||
table.add(new WarningsRow("Instruction taken from static listing"));
|
||||
table.add(new WarningsRow(
|
||||
StackUnwindWarningSet.custom("Instruction taken from static listing")));
|
||||
return table;
|
||||
}
|
||||
throw new AssertionError();
|
||||
@@ -304,10 +308,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
}
|
||||
Variable variable = VariableValueUtils.findStackVariable(function, stackAddress);
|
||||
return executeBackground(monitor -> {
|
||||
UnwoundFrame<WatchValue> frame = eval.getStackFrame(function, warnings, monitor);
|
||||
if (frame == null) {
|
||||
throw new UnwindException("Cannot find frame for " + function);
|
||||
}
|
||||
UnwoundFrame<WatchValue> frame =
|
||||
eval.getStackFrame(function, warnings, monitor, true);
|
||||
if (variable != null) {
|
||||
return fillFrameStorage(frame, variable.getName(), variable.getDataType(),
|
||||
variable.getProgram(), variable.getVariableStorage());
|
||||
@@ -363,15 +365,14 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
return executeBackground(monitor -> {
|
||||
UnwoundFrame<WatchValue> frame;
|
||||
if (function == null) {
|
||||
warnings.add("Instruction is not in a function. Using innermost frame.");
|
||||
warnings.add(new CustomStackUnwindWarning(
|
||||
"Instruction is not in a function. Using innermost frame."));
|
||||
frame = VariableValueUtils.locateInnermost(tool, current);
|
||||
}
|
||||
else {
|
||||
frame = eval.getStackFrame(function, warnings, monitor);
|
||||
frame = eval.getStackFrame(function, warnings, monitor, false);
|
||||
}
|
||||
if (frame == null) {
|
||||
warnings.add(
|
||||
"Could not locate " + function + " in stack. Using innermost frame.");
|
||||
return fillRegisterNoFrame(register);
|
||||
}
|
||||
|
||||
@@ -423,11 +424,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
return executeBackground(monitor -> {
|
||||
UnwoundFrame<WatchValue> frame =
|
||||
VariableValueUtils.requiresFrame(program, storage, symbolStorage)
|
||||
? eval.getStackFrame(function, warnings, monitor)
|
||||
? eval.getStackFrame(function, warnings, monitor, true)
|
||||
: eval.getGlobalsFakeFrame();
|
||||
if (frame == null) {
|
||||
throw new UnwindException("Cannot find frame for " + function);
|
||||
}
|
||||
return fillFrameStorage(frame, name, type, program, storage);
|
||||
});
|
||||
}
|
||||
@@ -436,11 +434,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
DataType type, PcodeOp op, AddressSetView symbolStorage) {
|
||||
return executeBackground(monitor -> {
|
||||
UnwoundFrame<WatchValue> frame = VariableValueUtils.requiresFrame(op, symbolStorage)
|
||||
? eval.getStackFrame(function, warnings, monitor)
|
||||
? eval.getStackFrame(function, warnings, monitor, true)
|
||||
: eval.getGlobalsFakeFrame();
|
||||
if (frame == null) {
|
||||
throw new UnwindException("Cannot find frame for " + function);
|
||||
}
|
||||
return fillFrameOp(frame, function.getProgram(), name, type, op, symbolStorage);
|
||||
});
|
||||
}
|
||||
@@ -578,10 +573,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
public CompletableFuture<VariableValueTable> fillVariable(Variable variable) {
|
||||
Function function = variable.getFunction();
|
||||
return executeBackground(monitor -> {
|
||||
UnwoundFrame<WatchValue> frame = eval.getStackFrame(function, warnings, monitor);
|
||||
if (frame == null) {
|
||||
throw new UnwindException("Cannot find frame for " + function);
|
||||
}
|
||||
UnwoundFrame<WatchValue> frame =
|
||||
eval.getStackFrame(function, warnings, monitor, true);
|
||||
return fillFrameStorage(frame, variable.getName(), variable.getDataType(),
|
||||
variable.getProgram(), variable.getVariableStorage());
|
||||
});
|
||||
@@ -590,7 +583,7 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
|
||||
public CompletableFuture<VariableValueTable> fillVariableValueTable(VariableValueTable table,
|
||||
ProgramLocation programLocation, DebuggerCoordinates current,
|
||||
FieldLocation fieldLocation, Field field, List<String> warnings) {
|
||||
FieldLocation fieldLocation, Field field, StackUnwindWarningSet warnings) {
|
||||
if (traceManager == null || mappingService == null || current.getPlatform() == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -630,7 +623,7 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
return null;
|
||||
}
|
||||
VariableValueTable table = new VariableValueTable();
|
||||
List<String> warnings = new ArrayList<>();
|
||||
StackUnwindWarningSet warnings = new StackUnwindWarningSet();
|
||||
CompletableFuture<VariableValueTable> future;
|
||||
try {
|
||||
future = fillVariableValueTable(table, programLocation,
|
||||
@@ -638,7 +631,9 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
}
|
||||
catch (Exception e) {
|
||||
table.add(new ErrorRow(e));
|
||||
return createTooltipComponent("<html>" + table.toHtml());
|
||||
JComponent component = createTooltipComponent("<html>" + table.toHtml());
|
||||
addErrorDetailsListener(component, table);
|
||||
return component;
|
||||
}
|
||||
if (future == null) {
|
||||
return null;
|
||||
@@ -650,6 +645,7 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
if (!(component instanceof JToolTip tooltip)) {
|
||||
throw new AssertionError("Expected a JToolTip");
|
||||
}
|
||||
addErrorDetailsListener(component, table);
|
||||
future.handleAsync((__, ex) -> {
|
||||
table.remove(RowKey.STATUS);
|
||||
if (ex != null) {
|
||||
@@ -668,6 +664,27 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
protected void addErrorDetailsListener(JComponent component, VariableValueTable table) {
|
||||
component.addMouseListener(new MouseAdapter() {
|
||||
private boolean isShiftDoubleClick(MouseEvent evt) {
|
||||
if (evt.getClickCount() != 2) {
|
||||
return false;
|
||||
}
|
||||
if ((evt.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent evt) {
|
||||
if (isShiftDoubleClick(evt)) {
|
||||
table.reportDetails();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void traceClosed(Trace trace) {
|
||||
synchronized (cachedEvaluators) {
|
||||
cachedEvaluators.keySet().removeIf(coords -> coords.getTrace() == trace);
|
||||
|
||||
@@ -18,13 +18,11 @@ package ghidra.app.plugin.core.debug.gui.stack.vars;
|
||||
import static ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueRow.*;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import generic.theme.GColor;
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
import ghidra.app.plugin.core.debug.stack.UnwoundFrame;
|
||||
import ghidra.app.plugin.core.debug.stack.*;
|
||||
import ghidra.pcode.exec.DebuggerPcodeUtils.PrettyBytes;
|
||||
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
|
||||
import ghidra.pcode.exec.ValueLocation;
|
||||
@@ -40,6 +38,7 @@ import ghidra.trace.model.listing.TraceCodeUnit;
|
||||
import ghidra.trace.model.memory.*;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.InvalidInputException;
|
||||
|
||||
/**
|
||||
@@ -61,7 +60,8 @@ public interface VariableValueRow {
|
||||
* @return the HTML-styled string
|
||||
*/
|
||||
static String styleSimple(Object obj) {
|
||||
return obj == null ? htmlFg(COLOR_ERROR, "None") : HTMLUtilities.escapeHTML(obj.toString());
|
||||
return obj == null ? htmlFg(COLOR_ERROR, "None")
|
||||
: HTMLUtilities.escapeHTML(obj.toString()).replace("\n", "<br>");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -566,19 +566,7 @@ public interface VariableValueRow {
|
||||
* A row to display the warnings encountered while unwinding the frame used to evaluate the
|
||||
* variable
|
||||
*/
|
||||
record WarningsRow(String warnings) implements VariableValueRow {
|
||||
/**
|
||||
* Create a row from the given list of warnings
|
||||
*
|
||||
* @param warnings the warnings
|
||||
*/
|
||||
public WarningsRow(List<String> warnings) {
|
||||
this(warnings.stream()
|
||||
.map(String::trim)
|
||||
.filter(w -> !w.isBlank())
|
||||
.collect(Collectors.joining("\n")));
|
||||
}
|
||||
|
||||
record WarningsRow(StackUnwindWarningSet warnings) implements VariableValueRow {
|
||||
@Override
|
||||
public RowKey key() {
|
||||
return RowKey.WARNINGS;
|
||||
@@ -591,8 +579,8 @@ public interface VariableValueRow {
|
||||
|
||||
@Override
|
||||
public String valueToHtml() {
|
||||
String[] split = warnings.split("\n");
|
||||
String formatted = Stream.of(split)
|
||||
String formatted = warnings.summarize()
|
||||
.stream()
|
||||
.map(w -> String.format("<li>%s</li>", HTMLUtilities.escapeHTML(w)))
|
||||
.collect(Collectors.joining("\n "));
|
||||
return String.format("""
|
||||
@@ -604,17 +592,22 @@ public interface VariableValueRow {
|
||||
|
||||
@Override
|
||||
public String valueToSimpleString() {
|
||||
return warnings;
|
||||
return warnings.summarize().stream().collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtml() {
|
||||
if (warnings.isBlank()) {
|
||||
if (warnings.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return String.format("<tr><td valign='top'><b>%s</b></td><td>%s</td></tr>",
|
||||
keyToHtml(), valueToHtml());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetails() {
|
||||
warnings.reportDetails();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -633,11 +626,17 @@ public interface VariableValueRow {
|
||||
|
||||
@Override
|
||||
public String valueToHtml() {
|
||||
if (error instanceof EvaluationException || error instanceof UnwindException) {
|
||||
return styleSimple(error.getMessage());
|
||||
}
|
||||
return styleSimple(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String valueToSimpleString() {
|
||||
if (error instanceof EvaluationException || error instanceof UnwindException) {
|
||||
return error.getMessage();
|
||||
}
|
||||
return error.toString();
|
||||
}
|
||||
|
||||
@@ -646,5 +645,13 @@ public interface VariableValueRow {
|
||||
return String.format("<tr><td valign='top'><b>%s</b></td><td>%s</td></tr>",
|
||||
keyToHtml(), valueToHtml());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetails() {
|
||||
Msg.showError(this, null, "Details", error.getMessage(), error);
|
||||
}
|
||||
}
|
||||
|
||||
default void reportDetails() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,4 +112,12 @@ public class VariableValueTable {
|
||||
rows.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportDetails() {
|
||||
synchronized (rows) {
|
||||
for (VariableValueRow row : rows.values()) {
|
||||
row.reportDetails();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import ghidra.app.decompiler.ClangLine;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.stack.*;
|
||||
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.CustomStackUnwindWarning;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.docking.settings.SettingsDefinition;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
@@ -71,6 +72,9 @@ public enum VariableValueUtils {
|
||||
|
||||
@Override
|
||||
protected boolean isLeaf(Varnode vn) {
|
||||
if (vn.getDef() == null && (vn.isRegister() || vn.isAddress())) {
|
||||
return true;
|
||||
}
|
||||
return vn.isConstant() ||
|
||||
symbolStorage.contains(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1));
|
||||
}
|
||||
@@ -516,7 +520,7 @@ public enum VariableValueUtils {
|
||||
* Collect the addresses used for storage by any symbol in the given line of decompiled C code
|
||||
*
|
||||
* <p>
|
||||
* It's not the greatest, but an variable to be evaluated should only be expressed in terms of
|
||||
* It's not the greatest, but any variable to be evaluated should only be expressed in terms of
|
||||
* symbols on the same line (at least by the decompiler's definition, wrapping shouldn't count
|
||||
* against us). This can be used to determine where evaluation should cease descending into
|
||||
* defining p-code ops. See {@link #requiresFrame(PcodeOp, AddressSetView)}, and
|
||||
@@ -528,16 +532,24 @@ public enum VariableValueUtils {
|
||||
public static AddressSet collectSymbolStorage(ClangLine line) {
|
||||
AddressSet storage = new AddressSet();
|
||||
for (ClangToken tok : line.getAllTokens()) {
|
||||
Varnode vn = tok.getVarnode();
|
||||
if (vn != null) {
|
||||
storage.add(rangeFromVarnode(vn));
|
||||
}
|
||||
HighVariable hVar = tok.getHighVariable();
|
||||
if (hVar == null) {
|
||||
continue;
|
||||
}
|
||||
Varnode rep = hVar.getRepresentative();
|
||||
if (rep != null) {
|
||||
storage.add(rangeFromVarnode(rep));
|
||||
}
|
||||
HighSymbol hSym = hVar.getSymbol();
|
||||
if (hSym == null) {
|
||||
continue;
|
||||
}
|
||||
for (Varnode vn : hSym.getStorage().getVarnodes()) {
|
||||
storage.add(rangeFromVarnode(vn));
|
||||
for (Varnode stVn : hSym.getStorage().getVarnodes()) {
|
||||
storage.add(rangeFromVarnode(stVn));
|
||||
}
|
||||
}
|
||||
return storage;
|
||||
@@ -725,25 +737,54 @@ public enum VariableValueUtils {
|
||||
* @param function the desired function
|
||||
* @param warnings a place to emit warnings
|
||||
* @param monitor a monitor for cancellation
|
||||
* @param required whether to throw an exception or register a warning
|
||||
* @return the frame if found, or null
|
||||
*/
|
||||
public UnwoundFrame<WatchValue> getStackFrame(Function function, List<String> warnings,
|
||||
TaskMonitor monitor) {
|
||||
public UnwoundFrame<WatchValue> getStackFrame(Function function,
|
||||
StackUnwindWarningSet warnings, TaskMonitor monitor, boolean required) {
|
||||
synchronized (lock) {
|
||||
if (unwound == null) {
|
||||
doUnwind(monitor);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
for (UnwoundFrame<WatchValue> frame : unwound.subList(coordinates.getFrame(),
|
||||
unwound.size())) {
|
||||
if (frame.getFunction() == function) {
|
||||
String unwindWarnings = frame.getWarnings();
|
||||
if (unwindWarnings != null && !unwindWarnings.isBlank()) {
|
||||
warnings.add(unwindWarnings);
|
||||
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());
|
||||
}
|
||||
}
|
||||
if (required) {
|
||||
throw new UnwindException(message);
|
||||
}
|
||||
warnings.add(new CustomStackUnwindWarning(message));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,10 @@ public abstract class AbstractUnwoundFrame<T> implements UnwoundFrame<T> {
|
||||
protected Address translateMemory(Program program, Address address) {
|
||||
TraceLocation location = mappingService.getOpenMappedLocation(trace,
|
||||
new ProgramLocation(program, address), snap);
|
||||
return location == null ? null : location.getAddress();
|
||||
if (location == null) {
|
||||
throw new DynamicMappingException(program, address);
|
||||
}
|
||||
return location.getAddress();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +100,10 @@ public abstract class AbstractUnwoundFrame<T> implements UnwoundFrame<T> {
|
||||
protected Address translateMemory(Program program, Address address) {
|
||||
TraceLocation location = mappingService.getOpenMappedLocation(trace,
|
||||
new ProgramLocation(program, address), snap);
|
||||
return location == null ? null : location.getAddress();
|
||||
if (location == null) {
|
||||
throw new DynamicMappingException(program, address);
|
||||
}
|
||||
return location.getAddress();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +137,9 @@ public abstract class AbstractUnwoundFrame<T> implements UnwoundFrame<T> {
|
||||
|
||||
@Override
|
||||
protected boolean isLeaf(Varnode vn) {
|
||||
if (vn.getDef() == null && (vn.isRegister() || vn.isAddress())) {
|
||||
return true;
|
||||
}
|
||||
return vn.isConstant() ||
|
||||
symbolStorage.contains(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1));
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.stack;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.plugin.core.bookmark.BookmarkNavigator;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
@@ -61,8 +62,8 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
private final int level;
|
||||
private final Address pcVal;
|
||||
private final Address spVal;
|
||||
private final Address staticPcVal;
|
||||
private final UnwindInfo info;
|
||||
private final UnwindException infoErr;
|
||||
private final SavedRegisterMap registerMap;
|
||||
|
||||
private final Address base;
|
||||
@@ -83,35 +84,29 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
* 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 address of the next instruction when this frame becomes the current 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, UnwindInfo info,
|
||||
UnwindException infoErr, SavedRegisterMap registerMap) {
|
||||
PcodeExecutorState<T> state, int level, Address pcVal, Address spVal,
|
||||
Address staticPcVal, UnwindInfo info, SavedRegisterMap registerMap) {
|
||||
super(tool, coordinates, state);
|
||||
if ((info == null) == (infoErr == null)) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
this.unwinder = unwinder;
|
||||
this.level = level;
|
||||
|
||||
this.pcVal = pcVal;
|
||||
this.spVal = spVal;
|
||||
this.staticPcVal = staticPcVal;
|
||||
this.info = info;
|
||||
this.infoErr = infoErr;
|
||||
this.registerMap = registerMap;
|
||||
|
||||
if (info != null) {
|
||||
this.base = info.computeBase(spVal);
|
||||
}
|
||||
else {
|
||||
this.base = null;
|
||||
}
|
||||
this.base = info.computeBase(spVal);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -156,8 +151,10 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
@Override
|
||||
protected Address applyBase(long offset) {
|
||||
if (base == null) {
|
||||
throw new UnwindException("Cannot compute stack address for offset " + offset,
|
||||
infoErr);
|
||||
throw new UnwindException(
|
||||
"Cannot compute stack address for offset %d.\nFrame error: %s".formatted(offset,
|
||||
info.error().getMessage()),
|
||||
info.error());
|
||||
}
|
||||
return base.add(offset);
|
||||
}
|
||||
@@ -194,8 +191,11 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return String.format("%s %s pc=%s sp=%s base=%s", level, info.function(),
|
||||
pcVal.toString(false), spVal.toString(false), base.toString(false));
|
||||
return String.format("%s %s pc=%s sp=%s base=%s",
|
||||
level, info.function(),
|
||||
pcVal == null ? null : pcVal.toString(false),
|
||||
spVal == null ? null : spVal.toString(false),
|
||||
base == null ? null : base.toString(false));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -225,7 +225,7 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
*/
|
||||
protected Structure generateStructure(int prevParamSize) {
|
||||
FrameStructureBuilder builder =
|
||||
new FrameStructureBuilder(language, pcVal, info, prevParamSize);
|
||||
new FrameStructureBuilder(language, staticPcVal, info, prevParamSize);
|
||||
return builder.build(StackUnwinder.FRAMES_PATH, "frame_" + pcVal.toString(false),
|
||||
trace.getDataTypeManager());
|
||||
}
|
||||
@@ -324,7 +324,7 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
TraceBookmarkManager bm = trace.getBookmarkManager();
|
||||
TraceBookmarkType btWarn = getWarningBookmarkType();
|
||||
Lifespan span = Lifespan.nowOnMaybeScratch(viewSnap);
|
||||
String warnings = info.warnings().summarize();
|
||||
String warnings = info.warnings().summarize().stream().collect(Collectors.joining("\n"));
|
||||
Structure structure = resolveStructure(prevParamSize);
|
||||
if (structure == null || structure.isZeroLength()) {
|
||||
for (TraceBookmark existing : bm.getBookmarksAt(viewSnap, spPlusParams)) {
|
||||
@@ -374,27 +374,13 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the unwind information is absent or incomplete, get the error explaining why.
|
||||
*
|
||||
* <p>
|
||||
* When analysis is incomplete, the frame may still be partially unwound, meaning only certain
|
||||
* variables can be evaluated, and the return address may not be available. Typically, a
|
||||
* partially unwound frame is the last frame that can be recovered in the stack. If the base
|
||||
* pointer could not be recovered, then only register variables and static variables can be
|
||||
* evaluated.
|
||||
*
|
||||
* @return the error
|
||||
*/
|
||||
public UnwindException getError() {
|
||||
return infoErr;
|
||||
@Override
|
||||
public StackUnwindWarningSet getWarnings() {
|
||||
return info.warnings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWarnings() {
|
||||
if (info == null) {
|
||||
return "";
|
||||
}
|
||||
return info.warnings().summarize();
|
||||
public Exception getError() {
|
||||
return info.error();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.stack;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
public class DynamicMappingException extends EvaluationException {
|
||||
private final Program program;
|
||||
private final Address address;
|
||||
|
||||
public DynamicMappingException(Program program, Address address) {
|
||||
super("Cannot map %s:%s to dynamic adress".formatted(program.getName(), address));
|
||||
this.program = program;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public Program getProgram() {
|
||||
return program;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.stack;
|
||||
|
||||
public class EvaluationException extends RuntimeException {
|
||||
public EvaluationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -87,8 +87,13 @@ public class FakeUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWarnings() {
|
||||
return "";
|
||||
public StackUnwindWarningSet getWarnings() {
|
||||
return new StackUnwindWarningSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Exception getError() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package ghidra.app.plugin.core.debug.stack;
|
||||
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.CustomStackUnwindWarning;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.exec.DebuggerPcodeUtils;
|
||||
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
|
||||
@@ -263,14 +264,28 @@ public class ListingUnwoundFrame extends AbstractUnwoundFrame<WatchValue> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWarnings() {
|
||||
public StackUnwindWarningSet getWarnings() {
|
||||
StackUnwindWarningSet warnings = new StackUnwindWarningSet();
|
||||
for (TraceBookmark bookmark : frame.getTrace()
|
||||
.getBookmarkManager()
|
||||
.getBookmarksAt(frame.getStartSnap(), frame.getMinAddress())) {
|
||||
if (bookmark.getTypeString().equals(BookmarkType.WARNING)) {
|
||||
return bookmark.getComment();
|
||||
if (!bookmark.getTypeString().equals(BookmarkType.WARNING)) {
|
||||
continue;
|
||||
}
|
||||
String comment = bookmark.getComment();
|
||||
if (comment == null) {
|
||||
continue;
|
||||
}
|
||||
for (String line : comment.split("\n")) {
|
||||
warnings.add(new CustomStackUnwindWarning(line));
|
||||
}
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Exception getError() {
|
||||
// TODO: Can this be deserialized from a bookmark?
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,8 @@ import java.util.stream.Stream;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
import ghidra.program.model.pcode.PcodeOpAST;
|
||||
import ghidra.program.model.pcode.VarnodeAST;
|
||||
import ghidra.program.model.pcode.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* A warning issued while unwinding a stack
|
||||
@@ -67,6 +66,12 @@ public interface StackUnwindWarning {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* For diagnostics, report any error details indicated by this warning, usually via {@link Msg}.
|
||||
*/
|
||||
default void reportDetails() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The unwind analyzer could not find an exit path from the frame's program counter.
|
||||
*/
|
||||
@@ -85,10 +90,17 @@ public interface StackUnwindWarning {
|
||||
/**
|
||||
* The unwind analyzer discovered at last one exit path, but none could be analyzed.
|
||||
*/
|
||||
public record OpaqueReturnPathStackUnwindWarning(Address pc) implements StackUnwindWarning {
|
||||
public record OpaqueReturnPathStackUnwindWarning(Address pc, Exception last)
|
||||
implements StackUnwindWarning {
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "Could not analyze any path from " + pc + " to a return";
|
||||
return "Could not analyze any path from " + pc + " to a return.\nLast error: " +
|
||||
last.getMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetails() {
|
||||
Msg.showError(this, null, "Details", getMessage(), last);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +200,8 @@ public interface StackUnwindWarning {
|
||||
}
|
||||
|
||||
/**
|
||||
* While analyzing an indirect call, couldn't get the function signature because its input doesn't have a high variable.
|
||||
* While analyzing an indirect call, couldn't get the function signature because its input
|
||||
* doesn't have a high variable.
|
||||
*/
|
||||
public record NoHighVariableFromTargetPointerTypeUnwindWarning(VarnodeAST vn)
|
||||
implements StackUnwindWarning {
|
||||
@@ -208,4 +221,15 @@ public interface StackUnwindWarning {
|
||||
return "Could not recover signature of indirect call: " + op;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom warning, either because a specific type is too onerous, or because the message was
|
||||
* deserialized and the specific type and info cannot be recovered.
|
||||
*/
|
||||
public record CustomStackUnwindWarning(String message) implements StackUnwindWarning {
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.Combinable;
|
||||
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.CustomStackUnwindWarning;
|
||||
|
||||
/**
|
||||
* A bucket of warnings
|
||||
@@ -29,6 +30,12 @@ import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.Combinable;
|
||||
public class StackUnwindWarningSet implements Collection<StackUnwindWarning> {
|
||||
private final Collection<StackUnwindWarning> warnings = new LinkedHashSet<>();
|
||||
|
||||
public static StackUnwindWarningSet custom(String message) {
|
||||
StackUnwindWarningSet set = new StackUnwindWarningSet();
|
||||
set.add(new CustomStackUnwindWarning(message));
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new empty set
|
||||
*/
|
||||
@@ -130,7 +137,7 @@ public class StackUnwindWarningSet implements Collection<StackUnwindWarning> {
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public String summarize() {
|
||||
public List<String> summarize() {
|
||||
Set<Class<? extends StackUnwindWarning>> combined = new LinkedHashSet<>();
|
||||
List<String> lines = new ArrayList<>();
|
||||
for (StackUnwindWarning w : warnings) {
|
||||
@@ -155,6 +162,12 @@ public class StackUnwindWarningSet implements Collection<StackUnwindWarning> {
|
||||
lines.add(w.getMessage());
|
||||
}
|
||||
}
|
||||
return lines.stream().collect(Collectors.joining("\n"));
|
||||
return lines;
|
||||
}
|
||||
|
||||
public void reportDetails() {
|
||||
for (StackUnwindWarning w : warnings) {
|
||||
w.reportDetails();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +203,9 @@ public class StackUnwinder {
|
||||
return unwind(coordinates, level, pcVal, spVal, state, new SavedRegisterMap(), monitor);
|
||||
}
|
||||
|
||||
record StaticAndUnwind(Address staticPc, UnwindInfo info) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the unwind information for the given program counter and context
|
||||
*
|
||||
@@ -217,8 +220,8 @@ public class StackUnwinder {
|
||||
* @return the unwind info, possibly incomplete
|
||||
* @throws CancelledException if the monitor is cancelled
|
||||
*/
|
||||
public UnwindInfo computeUnwindInfo(long snap, int level, Address pcVal, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
public StaticAndUnwind computeUnwindInfo(long snap, int level, Address pcVal,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
// TODO: Try markup in trace first?
|
||||
ProgramLocation staticPcLoc = mappings == null ? null
|
||||
: mappings.getOpenMappedLocation(
|
||||
@@ -229,23 +232,27 @@ public class StackUnwinder {
|
||||
}
|
||||
Program program = staticPcLoc.getProgram();
|
||||
Address staticPc = staticPcLoc.getAddress();
|
||||
// TODO: Cache these?
|
||||
UnwindAnalysis ua = new UnwindAnalysis(program);
|
||||
return ua.computeUnwindInfo(staticPc, monitor);
|
||||
try {
|
||||
// TODO: Cache these?
|
||||
UnwindAnalysis ua = new UnwindAnalysis(program);
|
||||
return new StaticAndUnwind(staticPc, ua.computeUnwindInfo(staticPc, monitor));
|
||||
}
|
||||
catch (Exception e) {
|
||||
return new StaticAndUnwind(staticPc, UnwindInfo.errorOnly(e));
|
||||
}
|
||||
}
|
||||
|
||||
<T> AnalysisUnwoundFrame<T> unwind(DebuggerCoordinates coordinates, int level, Address pcVal,
|
||||
Address spVal, PcodeExecutorState<T> state, SavedRegisterMap registerMap,
|
||||
TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
try {
|
||||
UnwindInfo info = computeUnwindInfo(coordinates.getSnap(), level, pcVal, monitor);
|
||||
StaticAndUnwind sau = computeUnwindInfo(coordinates.getSnap(), level, pcVal, monitor);
|
||||
return new AnalysisUnwoundFrame<>(tool, coordinates, this, state, level, pcVal, spVal,
|
||||
info, null, registerMap);
|
||||
sau.staticPc, sau.info, registerMap);
|
||||
}
|
||||
catch (UnwindException e) {
|
||||
catch (Exception e) {
|
||||
return new AnalysisUnwoundFrame<>(tool, coordinates, this, state, level, pcVal, spVal,
|
||||
null, e, registerMap);
|
||||
null, UnwindInfo.errorOnly(e), registerMap);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,17 +67,16 @@ class SymPcodeExecutor extends PcodeExecutor<Sym> {
|
||||
* @return the executor
|
||||
*/
|
||||
public static SymPcodeExecutor forProgram(Program program, SymPcodeExecutorState state,
|
||||
Reason reason, Set<StackUnwindWarning> warnings, TaskMonitor monitor) {
|
||||
Reason reason, TaskMonitor monitor) {
|
||||
CompilerSpec cSpec = program.getCompilerSpec();
|
||||
SleighLanguage language = (SleighLanguage) cSpec.getLanguage();
|
||||
SymPcodeArithmetic arithmetic = new SymPcodeArithmetic(cSpec);
|
||||
return new SymPcodeExecutor(program, cSpec, language, arithmetic, state, reason, warnings,
|
||||
monitor);
|
||||
return new SymPcodeExecutor(program, cSpec, language, arithmetic, state, reason, monitor);
|
||||
}
|
||||
|
||||
private final Program program;
|
||||
private final Register sp;
|
||||
private final Set<StackUnwindWarning> warnings;
|
||||
final Set<StackUnwindWarning> warnings;
|
||||
private final TaskMonitor monitor;
|
||||
|
||||
private final DecompInterface decomp = new DecompInterface();
|
||||
@@ -87,11 +86,11 @@ class SymPcodeExecutor extends PcodeExecutor<Sym> {
|
||||
|
||||
public SymPcodeExecutor(Program program, CompilerSpec cSpec, SleighLanguage language,
|
||||
SymPcodeArithmetic arithmetic, SymPcodeExecutorState state, Reason reason,
|
||||
Set<StackUnwindWarning> warnings, TaskMonitor monitor) {
|
||||
TaskMonitor monitor) {
|
||||
super(language, arithmetic, state, reason);
|
||||
this.program = program;
|
||||
this.sp = cSpec.getStackPointer();
|
||||
this.warnings = warnings;
|
||||
this.warnings = state.warnings;
|
||||
this.monitor = monitor;
|
||||
}
|
||||
|
||||
@@ -217,7 +216,7 @@ class SymPcodeExecutor extends PcodeExecutor<Sym> {
|
||||
protected FunctionSignature getSignatureFromTargetPointerType(PcodeOpAST op) {
|
||||
VarnodeAST target = (VarnodeAST) op.getInput(0);
|
||||
HighVariable high = target.getHigh();
|
||||
|
||||
|
||||
if (high == null) {
|
||||
warnings.add(new NoHighVariableFromTargetPointerTypeUnwindWarning(target));
|
||||
return null;
|
||||
@@ -243,7 +242,13 @@ class SymPcodeExecutor extends PcodeExecutor<Sym> {
|
||||
*/
|
||||
protected FunctionSignature getSignatureFromContextAtCallSite(PcodeOpAST op) {
|
||||
FunctionDefinitionDataType sig = new FunctionDefinitionDataType("__indirect");
|
||||
sig.setReturnType(op.getOutput().getHigh().getDataType());
|
||||
Varnode output = op.getOutput();
|
||||
if (output == null) {
|
||||
sig.setReturnType(VoidDataType.dataType);
|
||||
}
|
||||
else {
|
||||
sig.setReturnType(output.getHigh().getDataType());
|
||||
}
|
||||
// input 0 is the target, so drop it.
|
||||
int numInputs = op.getNumInputs();
|
||||
Parameter[] params = new Parameter[numInputs - 1];
|
||||
@@ -251,19 +256,19 @@ class SymPcodeExecutor extends PcodeExecutor<Sym> {
|
||||
for (int i = 1; i < numInputs; i++) {
|
||||
Varnode input = op.getInput(i);
|
||||
HighVariable highVar = input.getHigh();
|
||||
DataType dataType = highVar.getDataType();
|
||||
try {
|
||||
/**
|
||||
* NOTE: Not specifying storage, since: 1) It's not germane to the function
|
||||
* signature, and 2) It may require chasing use-def chains through uniques.
|
||||
*/
|
||||
params[i - 1] = new ParameterImpl("param_" + i, highVar.getDataType(),
|
||||
params[i - 1] = new ParameterImpl("param_" + i, dataType,
|
||||
/*new VariableStorage(program, input),*/ program);
|
||||
}
|
||||
catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
arguments[i - 1] = new ParameterDefinitionImpl("param_" + i,
|
||||
input.getHigh().getDataType(), "generated");
|
||||
arguments[i - 1] = new ParameterDefinitionImpl("param_" + i, dataType, "generated");
|
||||
}
|
||||
sig.setArguments(arguments);
|
||||
sig.setComment("generated");
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.stack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.plugin.core.debug.stack.Sym.*;
|
||||
import ghidra.app.plugin.core.debug.stack.SymStateSpace.SymEntry;
|
||||
@@ -50,6 +49,8 @@ public class SymPcodeExecutorState implements PcodeExecutorState<Sym> {
|
||||
private final SymStateSpace registerSpace;
|
||||
private final SymStateSpace uniqueSpace;
|
||||
|
||||
final Set<StackUnwindWarning> warnings = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Construct a new state for the given program
|
||||
*/
|
||||
|
||||
@@ -142,7 +142,7 @@ public class UnwindAnalysis {
|
||||
private final BlockGraph graph;
|
||||
private final BlockVertex pcBlock;
|
||||
private final DijkstraShortestPathsAlgorithm<BlockVertex, BlockEdge> pathFinder;
|
||||
private final Set<StackUnwindWarning> warnings = new HashSet<>();
|
||||
private final Set<StackUnwindWarning> warnings = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Begin analysis for unwinding a frame, knowing only the program counter for that frame
|
||||
@@ -353,7 +353,7 @@ public class UnwindAnalysis {
|
||||
public SymPcodeExecutorState executeToPc(Deque<BlockEdge> to) throws CancelledException {
|
||||
SymPcodeExecutorState state = new SymPcodeExecutorState(program);
|
||||
SymPcodeExecutor exec =
|
||||
SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE_READ, warnings, monitor);
|
||||
SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE_READ, monitor);
|
||||
executePathTo(exec, to);
|
||||
executeBlockTo(exec, pcBlock.block, pc);
|
||||
return state;
|
||||
@@ -375,7 +375,7 @@ public class UnwindAnalysis {
|
||||
public SymPcodeExecutorState executeFromPc(SymPcodeExecutorState state,
|
||||
Deque<BlockEdge> from) throws CancelledException {
|
||||
SymPcodeExecutor exec =
|
||||
SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE_READ, warnings, monitor);
|
||||
SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE_READ, monitor);
|
||||
executeBlockFrom(exec, pcBlock.block, pc);
|
||||
executePathFrom(exec, from);
|
||||
return state;
|
||||
@@ -454,37 +454,76 @@ public class UnwindAnalysis {
|
||||
"Could not find a path from " + function + " entry to " + pc);
|
||||
}
|
||||
Collection<Deque<BlockEdge>> exitsPaths = getExitsPaths();
|
||||
if (exitsPaths.isEmpty()) {
|
||||
warnings.add(new NoReturnPathStackUnwindWarning(pc));
|
||||
}
|
||||
SymPcodeExecutorState lastSuccessfulEntryState = null;
|
||||
Exception lastError = null;
|
||||
// TODO: Proper exceptions for useless results
|
||||
for (Deque<BlockEdge> entryPath : entryPaths) {
|
||||
SymPcodeExecutorState entryState = executeToPc(entryPath);
|
||||
Long depth = entryState.computeStackDepth();
|
||||
if (depth == null) {
|
||||
SymPcodeExecutorState entryState;
|
||||
try {
|
||||
entryState = executeToPc(entryPath);
|
||||
}
|
||||
catch (Exception e) {
|
||||
lastError = e;
|
||||
continue;
|
||||
}
|
||||
if (exitsPaths.isEmpty()) {
|
||||
warnings.add(new NoReturnPathStackUnwindWarning(pc));
|
||||
Long depth = entryState.computeStackDepth();
|
||||
if (depth == null) {
|
||||
lastError = new UnwindException("Cannot determine stack depth");
|
||||
continue;
|
||||
}
|
||||
lastSuccessfulEntryState = entryState;
|
||||
|
||||
Map<Register, Address> mapByEntry = entryState.computeMapUsingStack();
|
||||
for (Deque<BlockEdge> exitPath : exitsPaths) {
|
||||
SymPcodeExecutorState exitState =
|
||||
executeFromPc(entryState.forkRegs(), exitPath);
|
||||
Address addressOfReturn = exitState.computeAddressOfReturn();
|
||||
Long adjust = exitState.computeStackDepth();
|
||||
if (addressOfReturn == null || adjust == null) {
|
||||
SymPcodeExecutorState exitState;
|
||||
try {
|
||||
exitState = executeFromPc(entryState.forkRegs(), exitPath);
|
||||
}
|
||||
catch (Exception e) {
|
||||
lastError = e;
|
||||
continue;
|
||||
}
|
||||
Address addressOfReturn = exitState.computeAddressOfReturn();
|
||||
if (addressOfReturn == null) {
|
||||
lastError =
|
||||
new UnwindException("Cannot determine address of return pointer");
|
||||
continue;
|
||||
}
|
||||
Long adjust = exitState.computeStackDepth();
|
||||
if (adjust == null) {
|
||||
lastError = new UnwindException("Cannot determine stack adjustment");
|
||||
continue;
|
||||
}
|
||||
warnings.addAll(entryState.warnings);
|
||||
warnings.addAll(exitState.warnings);
|
||||
Map<Register, Address> mapByExit = exitState.computeMapUsingRegisters();
|
||||
mapByExit.entrySet().retainAll(mapByEntry.entrySet());
|
||||
return new UnwindInfo(function, depth, adjust, addressOfReturn, mapByExit,
|
||||
new StackUnwindWarningSet(warnings));
|
||||
new StackUnwindWarningSet(warnings), null);
|
||||
}
|
||||
warnings.add(new OpaqueReturnPathStackUnwindWarning(pc));
|
||||
long adjust = SymPcodeExecutor.computeStackChange(function, warnings);
|
||||
return new UnwindInfo(function, depth, adjust, null, mapByEntry,
|
||||
new StackUnwindWarningSet(warnings));
|
||||
}
|
||||
throw new UnwindException(
|
||||
"Could not analyze any path from " + function + " entry to " + pc);
|
||||
if (lastSuccessfulEntryState != null) {
|
||||
warnings.add(new OpaqueReturnPathStackUnwindWarning(pc, lastError));
|
||||
try {
|
||||
long adjust = SymPcodeExecutor.computeStackChange(function, warnings);
|
||||
return new UnwindInfo(function, lastSuccessfulEntryState.computeStackDepth(),
|
||||
adjust, null, lastSuccessfulEntryState.computeMapUsingStack(),
|
||||
new StackUnwindWarningSet(warnings), lastError);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return new UnwindInfo(function, lastSuccessfulEntryState.computeStackDepth(),
|
||||
null, null, lastSuccessfulEntryState.computeMapUsingStack(),
|
||||
new StackUnwindWarningSet(warnings), e);
|
||||
}
|
||||
}
|
||||
return new UnwindInfo(function, null, null, null, null,
|
||||
new StackUnwindWarningSet(warnings), new UnwindException(
|
||||
"Could not analyze any path from %s entry to %s.\n%s".formatted(function, pc,
|
||||
lastError.getMessage()),
|
||||
lastError));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ public class UnwindException extends RuntimeException {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UnwindException(String message, UnwindException cause) {
|
||||
public UnwindException(String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,12 @@ import ghidra.util.task.TaskMonitor;
|
||||
/**
|
||||
* Information for interpreting the current stack frame and unwinding to the next
|
||||
*/
|
||||
public record UnwindInfo(Function function, long depth, long adjust, Address ofReturn,
|
||||
Map<Register, Address> saved, StackUnwindWarningSet warnings) {
|
||||
public record UnwindInfo(Function function, Long depth, Long adjust, Address ofReturn,
|
||||
Map<Register, Address> saved, StackUnwindWarningSet warnings, Exception error) {
|
||||
|
||||
public static UnwindInfo errorOnly(Exception error) {
|
||||
return new UnwindInfo(null, null, null, null, null, new StackUnwindWarningSet(), error);
|
||||
}
|
||||
|
||||
/**
|
||||
* The function that was analyzed
|
||||
@@ -55,7 +59,7 @@ public record UnwindInfo(Function function, long depth, long adjust, Address ofR
|
||||
*
|
||||
* @return the depth
|
||||
*/
|
||||
public long depth() {
|
||||
public Long depth() {
|
||||
return depth;
|
||||
}
|
||||
|
||||
@@ -67,7 +71,7 @@ public record UnwindInfo(Function function, long depth, long adjust, Address ofR
|
||||
*
|
||||
* @return the adjustment
|
||||
*/
|
||||
public long adjust() {
|
||||
public Long adjust() {
|
||||
return adjust;
|
||||
}
|
||||
|
||||
@@ -139,7 +143,7 @@ public record UnwindInfo(Function function, long depth, long adjust, Address ofR
|
||||
* @return the base address
|
||||
*/
|
||||
public Address computeBase(Address spVal) {
|
||||
return spVal.subtract(depth);
|
||||
return depth == null ? null : spVal.subtract(depth);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -124,12 +124,23 @@ public interface UnwoundFrame<T> {
|
||||
/**
|
||||
* Get the warnings generated during analysis
|
||||
*
|
||||
* <p>
|
||||
* Several warnings may be returned, each on its own line.
|
||||
*
|
||||
* @return the warnings
|
||||
*/
|
||||
String getWarnings();
|
||||
StackUnwindWarningSet getWarnings();
|
||||
|
||||
/**
|
||||
* If the unwind is in error or incomplete, get the error explaining why.
|
||||
*
|
||||
* <p>
|
||||
* When analysis is incomplete, the frame may still be partially unwound, meaning only certain
|
||||
* variables can be evaluated, and the return address may not be available. Typically, a
|
||||
* partially unwound frame is the last frame that can be recovered in the stack. If the base
|
||||
* pointer could not be recovered, then only register variables and static variables can be
|
||||
* evaluated.
|
||||
*
|
||||
* @return the error
|
||||
*/
|
||||
Exception getError();
|
||||
|
||||
/**
|
||||
* Get the value of the storage from the frame
|
||||
|
||||
@@ -602,14 +602,14 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
|
||||
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
||||
assertEquals(
|
||||
new UnwindInfo(function, 0, 4, stack(0), Map.of(), new StackUnwindWarningSet()),
|
||||
new UnwindInfo(function, 0L, 4L, stack(0), Map.of(), new StackUnwindWarningSet(), null),
|
||||
infoAtEntry);
|
||||
|
||||
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
||||
assertEquals(new UnwindInfo(function, -20, 4, stack(0),
|
||||
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0),
|
||||
Map.of(
|
||||
register("EBP"), stack(-4)),
|
||||
new StackUnwindWarningSet()),
|
||||
new StackUnwindWarningSet(), null),
|
||||
infoAtBody);
|
||||
}
|
||||
|
||||
@@ -629,16 +629,17 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
|
||||
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
||||
assertEquals(
|
||||
new UnwindInfo(function, 0, 4, stack(0), Map.of(), new StackUnwindWarningSet()),
|
||||
new UnwindInfo(function, 0L, 4L, stack(0), Map.of(), new StackUnwindWarningSet(), null),
|
||||
infoAtEntry);
|
||||
|
||||
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
||||
assertEquals(new UnwindInfo(function, -20, 4, stack(0),
|
||||
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0),
|
||||
Map.of(
|
||||
register("EBP"), stack(-4)),
|
||||
new StackUnwindWarningSet(
|
||||
new UnspecifiedConventionStackUnwindWarning(myExtern),
|
||||
new UnknownPurgeStackUnwindWarning(myExtern))),
|
||||
new UnknownPurgeStackUnwindWarning(myExtern)),
|
||||
null),
|
||||
infoAtBody);
|
||||
}
|
||||
|
||||
@@ -656,16 +657,17 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
|
||||
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
||||
assertEquals(
|
||||
new UnwindInfo(function, 0, 4, stack(0), Map.of(), new StackUnwindWarningSet()),
|
||||
new UnwindInfo(function, 0L, 4L, stack(0), Map.of(), new StackUnwindWarningSet(), null),
|
||||
infoAtEntry);
|
||||
|
||||
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
||||
DataType ptr2Undef = new PointerDataType(DataType.DEFAULT, program.getDataTypeManager());
|
||||
assertEquals(new UnwindInfo(function, -20, 4, stack(0),
|
||||
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0),
|
||||
Map.of(
|
||||
register("EBP"), stack(-4)),
|
||||
new StackUnwindWarningSet(
|
||||
new UnexpectedTargetTypeStackUnwindWarning(ptr2Undef))),
|
||||
new UnexpectedTargetTypeStackUnwindWarning(ptr2Undef)),
|
||||
null),
|
||||
infoAtBody);
|
||||
}
|
||||
|
||||
@@ -1163,7 +1165,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
ProgramLocation programLocation, DebuggerCoordinates current,
|
||||
FieldLocation fieldLocation, Field field) throws Throwable {
|
||||
VariableValueTable table = new VariableValueTable();
|
||||
List<String> warnings = new ArrayList<>();
|
||||
StackUnwindWarningSet warnings = new StackUnwindWarningSet();
|
||||
waitOn(valuesService.fillVariableValueTable(table, programLocation, current,
|
||||
fieldLocation, field, warnings));
|
||||
table.add(new WarningsRow(warnings));
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.util.bin.format.macho.dyld;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.app.util.bin.format.macho.MachConstants;
|
||||
import ghidra.app.util.bin.format.macho.dyld.DyldChainedPtr.DyldChainType;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
/**
|
||||
* Represents a dyld_chained_starts_offsets structure.
|
||||
*
|
||||
* @see <a href="https://github.com/apple-oss-distributions/dyld/blob/main/include/mach-o/fixup-chains.h">mach-o/fixup-chains.h</a>
|
||||
*/
|
||||
public class DyldChainedStartsOffsets implements StructConverter {
|
||||
|
||||
private int pointerFormat;
|
||||
private int startsCount;
|
||||
private int[] chainStarts;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DyldChainedStartsOffsets}
|
||||
*
|
||||
* @param reader A {@link BinaryReader} positioned at the start of the structure
|
||||
* @throws IOException if there was an IO-related problem creating the structure
|
||||
*/
|
||||
public DyldChainedStartsOffsets(BinaryReader reader) throws IOException {
|
||||
pointerFormat = reader.readNextInt();
|
||||
startsCount = reader.readNextInt();
|
||||
chainStarts = reader.readNextIntArray(startsCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pointer format
|
||||
*
|
||||
* @return The pointer format
|
||||
*/
|
||||
public DyldChainType getPointerFormat() {
|
||||
return DyldChainType.lookupChainPtr(pointerFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the starts count
|
||||
*
|
||||
* @return The starts count
|
||||
*/
|
||||
public int getStartsCount() {
|
||||
return startsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the chain start offsets
|
||||
*
|
||||
* @return The chain start offsets
|
||||
*/
|
||||
public int[] getChainStartOffsets() {
|
||||
return chainStarts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
StructureDataType struct = new StructureDataType("dyld_chained_starts_offset", 0);
|
||||
struct.add(DWORD, "pointer_format", "DYLD_CHAINED_PTR_*");
|
||||
struct.add(DWORD, "starts_count", "number of starts in array");
|
||||
struct.add(new ArrayDataType(IBO32, startsCount, 1), "chain_starts",
|
||||
"array chain start offsets");
|
||||
struct.setCategoryPath(new CategoryPath(MachConstants.DATA_TYPE_CATEGORY));
|
||||
return struct;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user