mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GP-0: Fix tests and clean up.
This commit is contained in:
@@ -420,11 +420,10 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
}
|
||||
|
||||
public CompletableFuture<VariableValueTable> fillStorage(Function function, String name,
|
||||
DataType type, Program program, VariableStorage storage,
|
||||
AddressSetView symbolStorage) {
|
||||
DataType type, Program program, VariableStorage storage) {
|
||||
return executeBackground(monitor -> {
|
||||
UnwoundFrame<WatchValue> frame =
|
||||
VariableValueUtils.requiresFrame(program, storage, symbolStorage)
|
||||
VariableValueUtils.requiresFrame(program, storage)
|
||||
? eval.getStackFrame(function, warnings, monitor, true)
|
||||
: eval.getGlobalsFakeFrame();
|
||||
return fillFrameStorage(frame, name, type, program, storage);
|
||||
@@ -432,15 +431,15 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
}
|
||||
|
||||
public CompletableFuture<VariableValueTable> fillPcodeOp(Function function, String name,
|
||||
DataType type, PcodeOp op, AddressSetView symbolStorage) {
|
||||
DataType type, PcodeOp op) {
|
||||
Program program = function.getProgram();
|
||||
boolean requiresFrame = applyCopyHeuristic(program, op, symbolStorage,
|
||||
boolean requiresFrame = applyCopyHeuristic(program, op,
|
||||
VariableValueUtils::requiresFrame, VariableValueUtils::requiresFrame);
|
||||
return executeBackground(monitor -> {
|
||||
UnwoundFrame<WatchValue> frame = requiresFrame
|
||||
? eval.getStackFrame(function, warnings, monitor, true)
|
||||
: eval.getGlobalsFakeFrame();
|
||||
return fillFrameOp(frame, program, name, type, op, symbolStorage);
|
||||
return fillFrameOp(frame, program, name, type, op);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -473,11 +472,11 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
}
|
||||
|
||||
interface CopyCase<T> {
|
||||
T evaluate(Program program, Varnode varnode, AddressSetView symbolStorage);
|
||||
T evaluate(Program program, Varnode varnode);
|
||||
}
|
||||
|
||||
interface DefaultCase<T> {
|
||||
T evaluate(Program program, PcodeOp op, AddressSetView symbolStorage);
|
||||
T evaluate(Program program, PcodeOp op);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -486,29 +485,25 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
* the LHS of an assignment operator, but that could be difficult and complex.... In any
|
||||
* case, if it's an assignment, I'm going to evaluate the output of the output operand
|
||||
* instead. Trouble is, that may just traverse back over the copy, as the copy is the
|
||||
* defining operator. It might only work if I can guarantee the output is part of the symbol
|
||||
* storage.
|
||||
*
|
||||
* defining operator.
|
||||
*/
|
||||
protected <T> T applyCopyHeuristic(Program program, PcodeOp op,
|
||||
AddressSetView symbolStorage,
|
||||
CopyCase<T> copyCase, DefaultCase<T> defaultCase) {
|
||||
protected <T> T applyCopyHeuristic(Program program, PcodeOp op, CopyCase<T> copyCase,
|
||||
DefaultCase<T> defaultCase) {
|
||||
return switch (op.getOpcode()) {
|
||||
case PcodeOp.COPY -> copyCase.evaluate(program, op.getOutput(), symbolStorage);
|
||||
default -> defaultCase.evaluate(program, op, symbolStorage);
|
||||
case PcodeOp.COPY -> copyCase.evaluate(program, op.getOutput());
|
||||
default -> defaultCase.evaluate(program, op);
|
||||
};
|
||||
}
|
||||
|
||||
public VariableValueTable fillFrameOp(UnwoundFrame<WatchValue> frame, Program program,
|
||||
String name, DataType type, PcodeOp op, AddressSetView symbolStorage) {
|
||||
String name, DataType type, PcodeOp op) {
|
||||
table.add(new NameRow(name));
|
||||
if (!frame.isFake()) {
|
||||
table.add(new FrameRow(frame));
|
||||
}
|
||||
table.add(new TypeRow(type));
|
||||
|
||||
WatchValue value =
|
||||
applyCopyHeuristic(program, op, symbolStorage, frame::evaluate, frame::evaluate);
|
||||
WatchValue value = applyCopyHeuristic(program, op, frame::evaluate, frame::evaluate);
|
||||
|
||||
// TODO: What if the type is dynamic with non-fixed size?
|
||||
if (type.getLength() != value.length()) {
|
||||
@@ -518,7 +513,7 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
}
|
||||
|
||||
public CompletableFuture<VariableValueTable> fillHighVariable(HighVariable hVar,
|
||||
String name, AddressSetView symbolStorage) {
|
||||
String name) {
|
||||
Function function = hVar.getHighFunction().getFunction();
|
||||
VariableStorage storage = VariableValueUtils.fabricateStorage(hVar);
|
||||
if (storage.isUniqueStorage()) {
|
||||
@@ -527,17 +522,14 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
table.add(new ValueRow("(Unique)", TraceMemoryState.KNOWN));
|
||||
return CompletableFuture.completedFuture(table);
|
||||
}
|
||||
return fillStorage(function, name, hVar.getDataType(), function.getProgram(), storage,
|
||||
symbolStorage);
|
||||
return fillStorage(function, name, hVar.getDataType(), function.getProgram(), storage);
|
||||
}
|
||||
|
||||
public CompletableFuture<VariableValueTable> fillHighVariable(HighVariable hVar,
|
||||
AddressSetView symbolStorage) {
|
||||
return fillHighVariable(hVar, hVar.getName(), symbolStorage);
|
||||
public CompletableFuture<VariableValueTable> fillHighVariable(HighVariable hVar) {
|
||||
return fillHighVariable(hVar, hVar.getName());
|
||||
}
|
||||
|
||||
public CompletableFuture<VariableValueTable> fillComponent(ClangFieldToken token,
|
||||
AddressSetView symbolStorage) {
|
||||
public CompletableFuture<VariableValueTable> fillComponent(ClangFieldToken token) {
|
||||
Function function = token.getClangFunction().getHighFunction().getFunction();
|
||||
Program program = function.getProgram();
|
||||
PcodeOp op = token.getPcodeOp();
|
||||
@@ -547,13 +539,13 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
if (hVar.getDataType().isEquivalent(new PointerDataType(type))) {
|
||||
op = VariableValueUtils.findDeref(program.getAddressFactory(), vn);
|
||||
}
|
||||
return fillPcodeOp(function, token.getText(), type, op, symbolStorage);
|
||||
return fillPcodeOp(function, token.getText(), type, op);
|
||||
}
|
||||
|
||||
public CompletableFuture<VariableValueTable> fillComposite(HighSymbol hSym,
|
||||
HighVariable hVar, AddressSetView symbolStorage) {
|
||||
HighVariable hVar) {
|
||||
return fillStorage(hVar.getHighFunction().getFunction(), hSym.getName(),
|
||||
hSym.getDataType(), hSym.getProgram(), hSym.getStorage(), symbolStorage);
|
||||
hSym.getDataType(), hSym.getProgram(), hSym.getStorage());
|
||||
}
|
||||
|
||||
public CompletableFuture<VariableValueTable> fillToken(ClangToken token) {
|
||||
@@ -561,17 +553,8 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* I can't get just the expression tree here, except as p-code AST, which doesn't seem
|
||||
* to include token info. A line should contain the full expression, though. I'll grab
|
||||
* the symbols' storage from it and ensure my evaluation recurses until it hits those
|
||||
* symbols.
|
||||
*/
|
||||
AddressSet symbolStorage =
|
||||
VariableValueUtils.collectSymbolStorage(token.getLineParent());
|
||||
|
||||
if (token instanceof ClangFieldToken fieldToken) {
|
||||
return fillComponent(fieldToken, symbolStorage);
|
||||
return fillComponent(fieldToken);
|
||||
}
|
||||
|
||||
HighVariable hVar = token.getHighVariable();
|
||||
@@ -594,16 +577,16 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
||||
Varnode representative = hVar.getRepresentative();
|
||||
if (!storage.contains(representative.getAddress())) {
|
||||
// I'm not sure this can ever happen....
|
||||
return fillHighVariable(hVar, symbolStorage);
|
||||
return fillHighVariable(hVar);
|
||||
}
|
||||
|
||||
if (Arrays.asList(storage.getVarnodes()).equals(List.of(representative))) {
|
||||
// The var is the symbol
|
||||
return fillHighVariable(hVar, symbolStorage);
|
||||
return fillHighVariable(hVar);
|
||||
}
|
||||
|
||||
// Presumably, there's some component path from symbol to high var
|
||||
return fillComposite(hSym, hVar, symbolStorage);
|
||||
return fillComposite(hSym, hVar);
|
||||
}
|
||||
|
||||
public CompletableFuture<VariableValueTable> fillVariable(Variable variable) {
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
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.stream.Collectors;
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -33,7 +33,7 @@ public class VariableValueTable {
|
||||
* At most one of each row type can be present. Adding a row whose type already exists will
|
||||
* remove the old row of the same type.
|
||||
*
|
||||
* @param row
|
||||
* @param row the row to add
|
||||
*/
|
||||
public void add(VariableValueRow row) {
|
||||
synchronized (rows) {
|
||||
|
||||
@@ -19,8 +19,6 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.decompiler.ClangLine;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.app.plugin.core.debug.stack.*;
|
||||
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.CustomStackUnwindWarning;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
@@ -65,21 +63,6 @@ public enum VariableValueUtils {
|
||||
* context
|
||||
*/
|
||||
private static final class RequiresFrameEvaluator extends AbstractVarnodeEvaluator<Boolean> {
|
||||
private final AddressSetView symbolStorage;
|
||||
|
||||
private RequiresFrameEvaluator(AddressSetView symbolStorage) {
|
||||
this.symbolStorage = symbolStorage;
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Address applyBase(long offset) {
|
||||
throw new AssertionError();
|
||||
@@ -359,13 +342,10 @@ public enum VariableValueUtils {
|
||||
*
|
||||
* @param program the program containing the variable storage
|
||||
* @param storage the storage to evaluate
|
||||
* @param symbolStorage the leaves of evaluation, usually storage used by symbols in scope. See
|
||||
* {@link #collectSymbolStorage(ClangLine)}
|
||||
* @return true if a frame is required, false otherwise
|
||||
*/
|
||||
public static boolean requiresFrame(Program program, VariableStorage storage,
|
||||
AddressSetView symbolStorage) {
|
||||
return new RequiresFrameEvaluator(symbolStorage).evaluateStorage(program, storage);
|
||||
public static boolean requiresFrame(Program program, VariableStorage storage) {
|
||||
return new RequiresFrameEvaluator().evaluateStorage(program, storage);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -373,13 +353,10 @@ public enum VariableValueUtils {
|
||||
*
|
||||
* @param program the program containing the variable storage
|
||||
* @param varnode the varnode to evaluate
|
||||
* @param symbolStorage the leaves of evaluation, usually storage used by symbols in scope. See
|
||||
* {@link #collectSymbolStorage(ClangLine)}
|
||||
* @return true if a frame is required, false otherwise
|
||||
*/
|
||||
public static boolean requiresFrame(Program program, Varnode varnode,
|
||||
AddressSetView symbolStorage) {
|
||||
return new RequiresFrameEvaluator(symbolStorage).evaluateVarnode(program, varnode);
|
||||
public static boolean requiresFrame(Program program, Varnode varnode) {
|
||||
return new RequiresFrameEvaluator().evaluateVarnode(program, varnode);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,12 +364,10 @@ public enum VariableValueUtils {
|
||||
*
|
||||
* @param program the program containing the variable storage
|
||||
* @param op the op whose output to evaluation
|
||||
* @param symbolStorage the leaves of evaluation, usually storage used by symbols in scope. See
|
||||
* {@link #collectSymbolStorage(ClangLine)}
|
||||
* @return true if a frame is required, false otherwise
|
||||
*/
|
||||
public static boolean requiresFrame(Program program, PcodeOp op, AddressSetView symbolStorage) {
|
||||
return new RequiresFrameEvaluator(symbolStorage).evaluateOp(program, op);
|
||||
public static boolean requiresFrame(Program program, PcodeOp op) {
|
||||
return new RequiresFrameEvaluator().evaluateOp(program, op);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -470,8 +445,8 @@ public enum VariableValueUtils {
|
||||
*
|
||||
* <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.
|
||||
* 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
|
||||
@@ -520,8 +495,8 @@ public enum VariableValueUtils {
|
||||
*
|
||||
* <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.
|
||||
* 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
|
||||
@@ -607,45 +582,6 @@ public enum VariableValueUtils {
|
||||
return set.contains(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the addresses used for storage by any symbol in the given line of decompiled C code
|
||||
*
|
||||
* <p>
|
||||
* 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(Program, PcodeOp, AddressSetView)}, and
|
||||
* {@link UnwoundFrame#evaluate(Program, PcodeOp, AddressSetView)}.
|
||||
*
|
||||
* @param line the line
|
||||
* @return the address set
|
||||
*/
|
||||
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 stVn : hSym.getStorage().getVarnodes()) {
|
||||
storage.add(rangeFromVarnode(stVn));
|
||||
}
|
||||
}
|
||||
return storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the descendant that dereferences this given varnode
|
||||
*
|
||||
|
||||
@@ -34,7 +34,8 @@ import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.pcode.opbehavior.BinaryOpBehavior;
|
||||
import ghidra.pcode.opbehavior.UnaryOpBehavior;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.listing.Program;
|
||||
@@ -117,31 +118,13 @@ public abstract class AbstractUnwoundFrame<T> implements UnwoundFrame<T> {
|
||||
* @param <U> the evaluation result type
|
||||
*/
|
||||
protected abstract class FrameVarnodeEvaluator<U> extends ArithmeticFrameVarnodeEvaluator<U> {
|
||||
private final AddressSetView symbolStorage;
|
||||
|
||||
/**
|
||||
* Construct an evaluator with the given arithmetic and symbol storage
|
||||
*
|
||||
* <p>
|
||||
* Varnodes contained completely in symbol storage are presumed to be the inputs of the
|
||||
* evaluation. All other varnodes are evaluated by examining their defining p-code op. It is
|
||||
* an error to include any unique space in symbol storage.
|
||||
* Construct an evaluator with the given arithmetic
|
||||
*
|
||||
* @param arithmetic the arithmetic for evaluating p-code ops
|
||||
* @param symbolStorage the address ranges to regard as input, i.e., the leaves of evalution
|
||||
*/
|
||||
public FrameVarnodeEvaluator(PcodeArithmetic<U> arithmetic, AddressSetView symbolStorage) {
|
||||
public FrameVarnodeEvaluator(PcodeArithmetic<U> arithmetic) {
|
||||
super(arithmetic);
|
||||
this.symbolStorage = symbolStorage;
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,9 +280,9 @@ public abstract class AbstractUnwoundFrame<T> implements UnwoundFrame<T> {
|
||||
Reason.INSPECT);
|
||||
}
|
||||
|
||||
protected FrameVarnodeEvaluator<T> newEvaluator(AddressSetView symbolStorage) {
|
||||
protected FrameVarnodeEvaluator<T> newEvaluator() {
|
||||
SavedRegisterMap registerMap = computeRegisterMap();
|
||||
return new FrameVarnodeEvaluator<>(state.getArithmetic(), symbolStorage) {
|
||||
return new FrameVarnodeEvaluator<>(state.getArithmetic()) {
|
||||
@Override
|
||||
protected T evaluateMemory(Address address, int size) {
|
||||
return registerMap.getVar(state, address, size, Reason.INSPECT);
|
||||
@@ -308,18 +291,18 @@ public abstract class AbstractUnwoundFrame<T> implements UnwoundFrame<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public T evaluate(Program program, VariableStorage storage, AddressSetView symbolStorage) {
|
||||
return newEvaluator(symbolStorage).evaluateStorage(program, storage);
|
||||
public T evaluate(Program program, VariableStorage storage) {
|
||||
return newEvaluator().evaluateStorage(program, storage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T evaluate(Program program, Varnode varnode, AddressSetView symbolStorage) {
|
||||
return newEvaluator(symbolStorage).evaluateVarnode(program, varnode);
|
||||
public T evaluate(Program program, Varnode varnode) {
|
||||
return newEvaluator().evaluateVarnode(program, varnode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T evaluate(Program program, PcodeOp op, AddressSetView symbolStorage) {
|
||||
return newEvaluator(symbolStorage).evaluateOp(program, op);
|
||||
public T evaluate(Program program, PcodeOp op) {
|
||||
return newEvaluator().evaluateOp(program, op);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,10 +21,10 @@ import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.plugin.core.bookmark.BookmarkNavigator;
|
||||
import ghidra.app.services.DebuggerControlService.StateEditor;
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.exec.BytesPcodeArithmetic;
|
||||
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRangeImpl;
|
||||
@@ -47,6 +47,8 @@ import ghidra.util.task.TaskMonitor;
|
||||
* <p>
|
||||
* The typical pattern for invoking analysis to unwind an entire stack is to use
|
||||
* {@link StackUnwinder#getFrames(DebuggerCoordinates, TaskMonitor)}.
|
||||
*
|
||||
* @param <T> the type of values retrievable from the unwound frame
|
||||
*/
|
||||
public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
|
||||
@@ -105,16 +107,13 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
|
||||
/**
|
||||
* Unwind the next frame up
|
||||
*
|
||||
* <p>
|
||||
* Unwind the frame that would become current if the function that allocated this frame were to
|
||||
* return. For example, if this frame is at level 3, {@code unwindNext} will attempt to unwind
|
||||
* the frame at level 4.
|
||||
*
|
||||
* <p>
|
||||
* The program counter and stack pointer for the next frame are computed using the state
|
||||
* originally given in
|
||||
* {@link StackUnwinder#start(DebuggerCoordinates, PcodeExecutorState, TaskMonitor)} and this
|
||||
* originally given in {@link StackUnwinder#start(DebuggerCoordinates, TaskMonitor)} and this
|
||||
* frame's unwind information. The state is usually the watch-value state bound to the starting
|
||||
* coordinates. The program counter is evaluated like any other variable. The stack pointer is
|
||||
* computed by removing the depth of this frame. Then registers are restored and unwinding
|
||||
@@ -125,13 +124,12 @@ 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)
|
||||
public AnalysisUnwoundFrame<WatchValue> unwindNext(TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
if (info == null || info.ofReturn() == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return (AnalysisUnwoundFrame<T>) unwinder.getFrame(coordinates, state, level + 1, null,
|
||||
monitor);
|
||||
return unwinder.getFrame(coordinates, state, level + 1, null, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,6 +190,11 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
return pcVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getStaticCounter() {
|
||||
return staticPcVal;
|
||||
}
|
||||
|
||||
public Address getStackPointer() {
|
||||
return spVal;
|
||||
}
|
||||
@@ -280,18 +283,15 @@ public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
* may be placed a little after the derived stack pointer to accommodate the parameters of an
|
||||
* inner stack frame. The structure data type will have the category path
|
||||
* {@link StackUnwinder#FRAMES_PATH}. This allows follow-on analysis to identify data units
|
||||
* representing unwound frames. See {@link #isFrame(TraceData)}.</li>
|
||||
* representing unwound frames.</li>
|
||||
* <li>Places a comment at the start of the frame. This is meant for human consumption, so
|
||||
* follow-on analysis should not attempt to parse or otherwise interpret it. It will indicate
|
||||
* the frame level (0 being the innermost), the function name, the program counter, the stack
|
||||
* pointer, and the frame base pointer.</li>
|
||||
* <li>Places a {@link RefType#DATA} reference from the frame start to its own base address.
|
||||
* This permits follow-on analysis to derive variable values stored on the stack. See
|
||||
* {@link #getBase(TraceData)} and {@link #getValue(TraceData, VariableStorage)}.</li>
|
||||
* This permits follow-on analysis to derive variable values stored on the stack.</li>
|
||||
* <li>Places a {@link RefType#DATA} reference from the program counter to the frame start. This
|
||||
* allows follow-on analysis to determine the function for the frame. See
|
||||
* {@link #getProgramCounter(TraceData)} and
|
||||
* {@link #getFunction(TraceData, DebuggerStaticMappingService)}.</li>
|
||||
* allows follow-on analysis to determine the function for the frame.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
|
||||
@@ -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.
|
||||
@@ -32,6 +32,8 @@ import ghidra.program.model.listing.Function;
|
||||
* of a given stack frame. Based on an inspection of a variable's storage, it may not be necessary
|
||||
* to attempt a stack unwind to evaluate it. If that is the case, this "frame" may be used to
|
||||
* evaluate it where a frame interface is expected or convenient.
|
||||
*
|
||||
* @param <T> the type of values retrievable from the unwound frame
|
||||
*/
|
||||
public class FakeUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
private static final SavedRegisterMap IDENTITY_MAP = new SavedRegisterMap();
|
||||
@@ -71,6 +73,11 @@ public class FakeUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getStaticCounter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function getFunction() {
|
||||
return null;
|
||||
|
||||
@@ -115,6 +115,7 @@ public class ListingUnwoundFrame extends AbstractUnwoundFrame<WatchValue> {
|
||||
|
||||
private final int level;
|
||||
private final Address pcVal;
|
||||
private final Address staticPcVal;
|
||||
private final Function function;
|
||||
private final Address base;
|
||||
|
||||
@@ -139,6 +140,7 @@ public class ListingUnwoundFrame extends AbstractUnwoundFrame<WatchValue> {
|
||||
this.level = loadLevel();
|
||||
this.pcVal = loadProgramCounter();
|
||||
this.function = loadFunction();
|
||||
this.staticPcVal = mapProgramCounter();
|
||||
this.base = loadBasePointer();
|
||||
}
|
||||
|
||||
@@ -182,6 +184,15 @@ public class ListingUnwoundFrame extends AbstractUnwoundFrame<WatchValue> {
|
||||
throw new UnwindException("The program counter reference is missing for the frame!");
|
||||
}
|
||||
|
||||
private Address mapProgramCounter() {
|
||||
ProgramLocation location = mappingService.getOpenMappedLocation(
|
||||
new DefaultTraceLocation(trace, null, Lifespan.at(snap), pcVal));
|
||||
if (location.getProgram() != function.getProgram()) {
|
||||
return null;
|
||||
}
|
||||
return location.getByteAddress();
|
||||
}
|
||||
|
||||
private Function loadFunction() {
|
||||
ProgramLocation staticLoc =
|
||||
mappingService.getOpenMappedLocation(new DefaultTraceLocation(frame.getTrace(), null,
|
||||
@@ -226,6 +237,11 @@ public class ListingUnwoundFrame extends AbstractUnwoundFrame<WatchValue> {
|
||||
return pcVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getStaticCounter() {
|
||||
return staticPcVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function getFunction() {
|
||||
return function;
|
||||
|
||||
@@ -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.
|
||||
@@ -74,6 +74,8 @@ public interface StackUnwindWarning {
|
||||
|
||||
/**
|
||||
* The unwind analyzer could not find an exit path from the frame's program counter.
|
||||
*
|
||||
* @param pc the program counter
|
||||
*/
|
||||
public record NoReturnPathStackUnwindWarning(Address pc) implements StackUnwindWarning {
|
||||
@Override
|
||||
@@ -88,7 +90,10 @@ public interface StackUnwindWarning {
|
||||
}
|
||||
|
||||
/**
|
||||
* The unwind analyzer discovered at last one exit path, but none could be analyzed.
|
||||
* The unwind analyzer discovered at least one exit path, but none could be analyzed.
|
||||
*
|
||||
* @param pc the program counter
|
||||
* @param last the error from the last attempt
|
||||
*/
|
||||
public record OpaqueReturnPathStackUnwindWarning(Address pc, Exception last)
|
||||
implements StackUnwindWarning {
|
||||
@@ -107,11 +112,12 @@ public interface StackUnwindWarning {
|
||||
/**
|
||||
* While analyzing instructions, the unwind analyzer encountered a call to a function whose
|
||||
* effect on the stack is unknown.
|
||||
*
|
||||
* <p>
|
||||
* The analyzer does not descend into calls or otherwise implement inter-procedural analysis.
|
||||
* Instead, it relies on analysis already performed by Ghidra's other analyzers and/or the human
|
||||
* user. The analyzer will assume a reasonable default.
|
||||
*
|
||||
* @param function the target function of the encountered call
|
||||
*/
|
||||
public record UnknownPurgeStackUnwindWarning(Function function)
|
||||
implements StackUnwindWarning, Combinable<UnknownPurgeStackUnwindWarning> {
|
||||
@@ -137,9 +143,10 @@ public interface StackUnwindWarning {
|
||||
/**
|
||||
* While analyzing instructions, the unwind analyzer encountered a call to a function whose
|
||||
* convention is not known.
|
||||
*
|
||||
* <p>
|
||||
* The analyzer will assume the default convention for the program's compiler.
|
||||
*
|
||||
* @param function the target function of the encountered call
|
||||
*/
|
||||
public record UnspecifiedConventionStackUnwindWarning(Function function)
|
||||
implements StackUnwindWarning, Combinable<UnspecifiedConventionStackUnwindWarning> {
|
||||
@@ -165,10 +172,11 @@ public interface StackUnwindWarning {
|
||||
/**
|
||||
* While analyzing an indirect call, using the decompiler, the unwind analyzer obtained multiple
|
||||
* high {@link PcodeOp#CALL} or {@link PcodeOp#CALLIND} p-code ops.
|
||||
*
|
||||
* <p>
|
||||
* Perhaps this should be replaced by an assertion, but failing fast may not be a good approach
|
||||
* for this case.
|
||||
*
|
||||
* @param found the list of candidate call[ind] ops
|
||||
*/
|
||||
public record MultipleHighCallsStackUnwindWarning(List<PcodeOpAST> found)
|
||||
implements StackUnwindWarning {
|
||||
@@ -180,6 +188,8 @@ public interface StackUnwindWarning {
|
||||
|
||||
/**
|
||||
* Similar to {@link MultipleHighCallsStackUnwindWarning}, except no high call p-code ops.
|
||||
*
|
||||
* @param op the op which had no corresponding high call[ind] after decompilation
|
||||
*/
|
||||
public record NoHighCallsStackUnwindWarning(PcodeOp op) implements StackUnwindWarning {
|
||||
@Override
|
||||
@@ -190,6 +200,8 @@ public interface StackUnwindWarning {
|
||||
|
||||
/**
|
||||
* While analyzing an indirect call, the target's type was not a function pointer.
|
||||
*
|
||||
* @param type the actual type found
|
||||
*/
|
||||
public record UnexpectedTargetTypeStackUnwindWarning(DataType type)
|
||||
implements StackUnwindWarning {
|
||||
@@ -202,6 +214,8 @@ public interface StackUnwindWarning {
|
||||
/**
|
||||
* While analyzing an indirect call, couldn't get the function signature because its input
|
||||
* doesn't have a high variable.
|
||||
*
|
||||
* @param vn the varnode expected to identify the callee, but that had no high variable
|
||||
*/
|
||||
public record NoHighVariableFromTargetPointerTypeUnwindWarning(VarnodeAST vn)
|
||||
implements StackUnwindWarning {
|
||||
@@ -213,6 +227,8 @@ public interface StackUnwindWarning {
|
||||
|
||||
/**
|
||||
* While analyzing an indirect call, the signature could not be derived from call-site context.
|
||||
*
|
||||
* @param op the indirect call op
|
||||
*/
|
||||
public record CouldNotRecoverSignatureStackUnwindWarning(PcodeOpAST op)
|
||||
implements StackUnwindWarning {
|
||||
@@ -225,6 +241,8 @@ public interface StackUnwindWarning {
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
public record CustomStackUnwindWarning(String message) implements StackUnwindWarning {
|
||||
@Override
|
||||
|
||||
@@ -22,14 +22,12 @@ import ghidra.program.model.lang.Register;
|
||||
|
||||
/**
|
||||
* A symbolic value tailored for stack unwind analysis
|
||||
*
|
||||
* <p>
|
||||
* The goals of stack unwind analysis are 1) to figure the stack depth at a particular instruction,
|
||||
* 2) to figure the locations of saved registers on the stack, 3) to figure the location of the
|
||||
* return address, whether in a register or on the stack, and 4) to figure the change in stack depth
|
||||
* from calling the function. Not surprisingly, these are the fields of {@link UnwindInfo}. To these
|
||||
* ends, symbols may have only one of the following forms:
|
||||
*
|
||||
* <ul>
|
||||
* <li>An opaque value: {@link OpaqueSym}, to represent expressions too complex.</li>
|
||||
* <li>A constant: {@link ConstSym}, to fold constants and use as offsets.</li>
|
||||
@@ -39,10 +37,8 @@ import ghidra.program.model.lang.Register;
|
||||
* <li>A dereference of a stack offset, i.e., *(SP + c): {@link StackDerefSym}, to detect restored
|
||||
* registers and return address location</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* The rules are fairly straightforward:
|
||||
*
|
||||
* <ul>
|
||||
* <li>a:Opaque + b:Any => Opaque()</li>
|
||||
* <li>a:Const + b:Const => Const(val=a.val + b.val)</li>
|
||||
@@ -51,7 +47,6 @@ import ghidra.program.model.lang.Register;
|
||||
* <li>*a:Offset => Deref(offset=a.offset)</li>
|
||||
* <li>*a:Register(reg==SP) => Deref(offset=0)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Some minute operations are omitted for clarity. Any other operation results in Opaque(). There is
|
||||
* a small fault in that Register(reg=SP) and Offset(offset=0) represent the same thing, but with
|
||||
@@ -126,7 +121,6 @@ sealed interface Sym {
|
||||
/**
|
||||
* When this symbol is used as the offset in a given address space, translate it to the address
|
||||
* if possible
|
||||
*
|
||||
* <p>
|
||||
* The address will be used by the state to retrieve the appropriate (symbolic) value, possibly
|
||||
* generating a fresh symbol. If the address is {@link Address#NO_ADDRESS}, then the state will
|
||||
@@ -176,6 +170,9 @@ sealed interface Sym {
|
||||
|
||||
/**
|
||||
* A constant symbol
|
||||
*
|
||||
* @param value the constant value
|
||||
* @param size the size in bytes
|
||||
*/
|
||||
public record ConstSym(long value, int size) implements Sym {
|
||||
@Override
|
||||
@@ -220,6 +217,9 @@ sealed interface Sym {
|
||||
|
||||
/**
|
||||
* A register symbol
|
||||
*
|
||||
* @param register the register
|
||||
* @param mask a mask that has been applied (bitwise and) to the register
|
||||
*/
|
||||
public record RegisterSym(Register register, long mask) implements Sym {
|
||||
@Override
|
||||
@@ -266,10 +266,11 @@ sealed interface Sym {
|
||||
|
||||
/**
|
||||
* A stack offset symbol
|
||||
*
|
||||
* <p>
|
||||
* This represents a value in the form SP + c, where SP is the stack pointer register and c is a
|
||||
* constant.
|
||||
*
|
||||
* @param offset the offset from SP (at entry)
|
||||
*/
|
||||
public record StackOffsetSym(long offset) implements Sym {
|
||||
@Override
|
||||
@@ -309,10 +310,13 @@ sealed interface Sym {
|
||||
|
||||
/**
|
||||
* A stack dereference symbol
|
||||
*
|
||||
* <p>
|
||||
* This represents a dereferenced {@link StackOffsetSym} (or the dereferenced stack pointer
|
||||
* register, in which is treated as a stack offset of 0).
|
||||
*
|
||||
* @param offset the offset from SP (at entry)
|
||||
* @param mask a mask that has been applied (bitwise and) to the stack variable
|
||||
* @param size the size of the variable in bytes
|
||||
*/
|
||||
public record StackDerefSym(long offset, long mask, int size) implements Sym {
|
||||
@Override
|
||||
|
||||
@@ -62,7 +62,6 @@ public class SymPcodeExecutor extends PcodeExecutor<Sym> {
|
||||
* @param program the program to analyze
|
||||
* @param state the symbolic state
|
||||
* @param reason a reason to give when reading state
|
||||
* @param warnings a place to emit warnings
|
||||
* @param monitor a monitor for analysis, usually decompilation
|
||||
* @return the executor
|
||||
*/
|
||||
|
||||
@@ -18,11 +18,8 @@ package ghidra.app.plugin.core.debug.stack;
|
||||
import java.math.BigInteger;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.decompiler.ClangLine;
|
||||
import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueUtils;
|
||||
import ghidra.app.services.DebuggerControlService.StateEditor;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
@@ -74,6 +71,16 @@ public interface UnwoundFrame<T> {
|
||||
*/
|
||||
Address getProgramCounter();
|
||||
|
||||
/**
|
||||
* Get the frame's program counter in the static program
|
||||
* <p>
|
||||
* To get the corresponding static program, use {@link #getFunction()}.
|
||||
*
|
||||
* @see #getProgramCounter()
|
||||
* @return the frame's static program counter
|
||||
*/
|
||||
Address getStaticCounter();
|
||||
|
||||
/**
|
||||
* Get the function that allocated this frame
|
||||
*
|
||||
@@ -154,8 +161,7 @@ public interface UnwoundFrame<T> {
|
||||
*
|
||||
* <p>
|
||||
* Each varnode's value is simply retrieved from the state, in contrast to
|
||||
* {@link #evaluate(Program, VariableStorage, AddressSetView)}, which ascends to varnodes'
|
||||
* defining p-code ops.
|
||||
* {@link #evaluate(Program, VariableStorage)}, which ascends to varnodes' defining p-code ops.
|
||||
*
|
||||
* <p>
|
||||
* <b>WARNING:</b> Never invoke this method from the Swing thread. The state could be associated
|
||||
@@ -195,57 +201,44 @@ public interface UnwoundFrame<T> {
|
||||
T getValue(Register register);
|
||||
|
||||
/**
|
||||
* Evaluate the given storage, following defining p-code ops until symbol storage is reached
|
||||
*
|
||||
* Evaluate the given storage, following defining p-code ops of unique varnodes
|
||||
* <p>
|
||||
* This behaves similarly to {@link #getValue(Program, VariableStorage)}, except this one will
|
||||
* ascend recursively to each varnode's defining p-code op. The recursion terminates when a
|
||||
* varnode is contained in the given symbol storage. The symbol storage is usually collected by
|
||||
* examining the tokens on the same line, searching for ones that represent "high symbols." This
|
||||
* ensures that any temporary storage used by the original program in the evaluation of, e.g., a
|
||||
* field access, are not read from the current state but re-evaluated in terms of the symbols'
|
||||
* current values.
|
||||
*
|
||||
* ascend recursively to each unique varnode's defining p-code op.
|
||||
* <p>
|
||||
* <b>WARNING:</b> Never invoke this method from the Swing thread. The state could be associated
|
||||
* with a live session, and this may block to retrieve live state.
|
||||
*
|
||||
* @see VariableValueUtils#collectSymbolStorage(ClangLine)
|
||||
* @param program the program containing the variable storage
|
||||
* @param storage the storage to evaluate
|
||||
* @param symbolStorage the terminal storage, usually that of symbols
|
||||
* @return the value
|
||||
*/
|
||||
T evaluate(Program program, VariableStorage storage, AddressSetView symbolStorage);
|
||||
T evaluate(Program program, VariableStorage storage);
|
||||
|
||||
/**
|
||||
* Evaluate the given varnode, following defining p-code ops until symbol storage is reached
|
||||
*
|
||||
* Evaluate the given varnode, following defining p-code ops of unique varnodes
|
||||
* <p>
|
||||
* <b>WARNING:</b> Never invoke this method from the Swing thread. The state could be associated
|
||||
* with a live session, and this may block to retrieve live state.
|
||||
*
|
||||
* @param program the program containing the varnode
|
||||
* @param varnode the varnode
|
||||
* @param symbolStorage the terminal storage, usually that of symbols
|
||||
* @return the value
|
||||
*/
|
||||
T evaluate(Program program, Varnode varnode, AddressSetView symbolStorage);
|
||||
T evaluate(Program program, Varnode varnode);
|
||||
|
||||
/**
|
||||
* Evaluate the output for the given p-code op, ascending until symbol storage is reached
|
||||
*
|
||||
* Evaluate the output for the given p-code op, following defining p-code ops of unique varnodes
|
||||
* <p>
|
||||
* <b>WARNING:</b> Never invoke this method from the Swing thread. The state could be associated
|
||||
* with a live session, and this may block to retrieve live state.
|
||||
*
|
||||
* @see #evaluate(Program, VariableStorage, AddressSetView)
|
||||
* @see #evaluate(Program, VariableStorage)
|
||||
* @param program the program containing the op
|
||||
* @param op the op
|
||||
* @param symbolStorage the terminal storage, usually that of symbols
|
||||
* @return the value
|
||||
*/
|
||||
T evaluate(Program program, PcodeOp op, AddressSetView symbolStorage);
|
||||
T evaluate(Program program, PcodeOp op);
|
||||
|
||||
/**
|
||||
* Set the value of the given storage
|
||||
|
||||
@@ -68,7 +68,9 @@ public abstract class AbstractVarnodeEvaluator<T> implements VarnodeEvaluator<T>
|
||||
* @return true to treat the varnode as a base case, or false to ascend to its defining p-code
|
||||
* op
|
||||
*/
|
||||
protected abstract boolean isLeaf(Varnode vn);
|
||||
protected boolean isLeaf(Varnode vn) {
|
||||
return !vn.isUnique();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a (static) stack offset to its physical (dynamic) address in the frame
|
||||
@@ -163,7 +165,7 @@ public abstract class AbstractVarnodeEvaluator<T> implements VarnodeEvaluator<T>
|
||||
* @return the value
|
||||
*/
|
||||
protected T evaluateVarnode(Program program, Varnode vn, Map<Varnode, T> already) {
|
||||
// computeIfAbsent does nto work because of the recursion. Will get CME.
|
||||
// computeIfAbsent does not work because of the recursion. Will get CME.
|
||||
if (already.containsKey(vn)) {
|
||||
return already.get(vn);
|
||||
}
|
||||
@@ -223,7 +225,7 @@ public abstract class AbstractVarnodeEvaluator<T> implements VarnodeEvaluator<T>
|
||||
*
|
||||
* @param value the constant value
|
||||
* @param size the size of the value in bytes
|
||||
* @return the value as a {@link T}
|
||||
* @return the value
|
||||
*/
|
||||
protected abstract T evaluateConstant(long value, int size);
|
||||
|
||||
@@ -281,7 +283,7 @@ public abstract class AbstractVarnodeEvaluator<T> implements VarnodeEvaluator<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a variable whose offset is of type {@link T}
|
||||
* Evaluate a variable with an abstract offset
|
||||
*
|
||||
* <p>
|
||||
* The three parameters {@code space}, {@code offset}, and {@code size} imitate the varnode
|
||||
|
||||
Reference in New Issue
Block a user