GP-0: Fix tests and clean up.

This commit is contained in:
Dan
2026-06-17 17:47:58 +00:00
parent 3a8dc77d40
commit 4f6ecbf983
13 changed files with 151 additions and 212 deletions

View File

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

View File

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

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -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) {

View File

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

View File

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

View File

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

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -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;

View File

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

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -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

View File

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

View File

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

View File

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

View File

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