mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 02:09:44 +02:00
GP-3771: Add mask to the unwind analyzer (Fixes unwind with ARM/THUMB)
This commit is contained in:
parent
d23e67a088
commit
655082ecb5
10 changed files with 387 additions and 89 deletions
|
@ -52,6 +52,7 @@ public class ComputeUnwindInfoScript extends GhidraScript {
|
|||
}
|
||||
println("Stack depth at " + currentAddress + ": " + info.depth());
|
||||
println("Return address address: " + addressToString(info.ofReturn()));
|
||||
println("Return address mask: 0x" + Long.toHexString(info.maskOfReturn()));
|
||||
println("Saved registers:");
|
||||
for (Entry<Register, Address> entry : info.saved().entrySet()) {
|
||||
println(" " + entry);
|
||||
|
|
|
@ -261,8 +261,7 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
|||
}
|
||||
}
|
||||
|
||||
record MappedLocation(Program stProg, Address stAddr, Address dynAddr) {
|
||||
}
|
||||
record MappedLocation(Program stProg, Address stAddr, Address dynAddr) {}
|
||||
|
||||
protected MappedLocation mapLocation(Program programOrView, Address address) {
|
||||
if (programOrView instanceof TraceProgramView view) {
|
||||
|
@ -400,7 +399,7 @@ public class VariableValueHoverService extends AbstractConfigurableHover
|
|||
public CompletableFuture<VariableValueTable> fillOperand(OperandFieldLocation opLoc,
|
||||
Instruction ins) {
|
||||
RefType refType = ins.getOperandRefType(opLoc.getOperandIndex());
|
||||
if (refType.isFlow()) {
|
||||
if (refType != null && refType.isFlow()) {
|
||||
return null;
|
||||
}
|
||||
Object operand = ins.getDefaultOperandRepresentationList(opLoc.getOperandIndex())
|
||||
|
|
|
@ -71,6 +71,11 @@ public class StackUnwindWarningSet implements Collection<StackUnwindWarning> {
|
|||
return this.warnings.equals(that.warnings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.warnings.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return warnings.size();
|
||||
|
|
|
@ -96,6 +96,15 @@ sealed interface Sym {
|
|||
*/
|
||||
Sym twosComp();
|
||||
|
||||
/**
|
||||
* Logical bitwise and this and another symbol with the given compiler context
|
||||
*
|
||||
* @param cSpec the compiler specification
|
||||
* @param in2 the second symbol
|
||||
* @return the resulting symbol
|
||||
*/
|
||||
Sym and(CompilerSpec cSpec, Sym in2);
|
||||
|
||||
/**
|
||||
* Get the size of this symbol with the given compiler for context
|
||||
*
|
||||
|
@ -144,6 +153,11 @@ sealed interface Sym {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sym and(CompilerSpec cSpec, Sym in2) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sym twosComp() {
|
||||
return this;
|
||||
|
@ -166,19 +180,23 @@ sealed interface Sym {
|
|||
public record ConstSym(long value, int size) implements Sym {
|
||||
@Override
|
||||
public Sym add(CompilerSpec cSpec, Sym in2) {
|
||||
if (in2 instanceof ConstSym const2) {
|
||||
return new ConstSym(value + const2.value, size);
|
||||
return switch (in2) {
|
||||
case ConstSym const2 -> new ConstSym(value + const2.value, size);
|
||||
case RegisterSym reg2 when reg2.register() == cSpec
|
||||
.getStackPointer() -> new StackOffsetSym(value);
|
||||
case StackOffsetSym off2 -> new StackOffsetSym(value + off2.offset);
|
||||
default -> Sym.opaque();
|
||||
};
|
||||
}
|
||||
if (in2 instanceof RegisterSym reg2) {
|
||||
if (reg2.register() == cSpec.getStackPointer()) {
|
||||
return new StackOffsetSym(value);
|
||||
}
|
||||
return Sym.opaque();
|
||||
}
|
||||
if (in2 instanceof StackOffsetSym off2) {
|
||||
return new StackOffsetSym(value + off2.offset);
|
||||
}
|
||||
return Sym.opaque();
|
||||
|
||||
@Override
|
||||
public Sym and(CompilerSpec cSpec, Sym in2) {
|
||||
return switch (in2) {
|
||||
case ConstSym const2 -> new ConstSym(value & const2.value, size);
|
||||
case RegisterSym reg2 -> reg2.withAppliedMask(value);
|
||||
case StackDerefSym deref2 -> deref2.withAppliedMask(value);
|
||||
default -> Sym.opaque();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -203,13 +221,25 @@ sealed interface Sym {
|
|||
/**
|
||||
* A register symbol
|
||||
*/
|
||||
public record RegisterSym(Register register) implements Sym {
|
||||
public record RegisterSym(Register register, long mask) implements Sym {
|
||||
@Override
|
||||
public Sym add(CompilerSpec cSpec, Sym in2) {
|
||||
if (in2 instanceof ConstSym const2) {
|
||||
return const2.add(cSpec, this);
|
||||
return switch (in2) {
|
||||
case ConstSym const2 -> const2.add(cSpec, this);
|
||||
default -> Sym.opaque();
|
||||
};
|
||||
}
|
||||
return Sym.opaque();
|
||||
|
||||
@Override
|
||||
public Sym and(CompilerSpec cSpec, Sym in2) {
|
||||
return switch (in2) {
|
||||
case ConstSym const2 -> const2.and(cSpec, this);
|
||||
default -> Sym.opaque();
|
||||
};
|
||||
}
|
||||
|
||||
public RegisterSym withAppliedMask(long mask) {
|
||||
return new RegisterSym(register, this.mask & mask);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -244,10 +274,18 @@ sealed interface Sym {
|
|||
public record StackOffsetSym(long offset) implements Sym {
|
||||
@Override
|
||||
public Sym add(CompilerSpec cSpec, Sym in2) {
|
||||
if (in2 instanceof ConstSym const2) {
|
||||
return new StackOffsetSym(offset + const2.value());
|
||||
return switch (in2) {
|
||||
case ConstSym const2 -> const2.add(cSpec, this);
|
||||
default -> Sym.opaque();
|
||||
};
|
||||
}
|
||||
return Sym.opaque();
|
||||
|
||||
@Override
|
||||
public Sym and(CompilerSpec cSpec, Sym in2) {
|
||||
return switch (in2) {
|
||||
case ConstSym const2 -> const2.and(cSpec, this);
|
||||
default -> Sym.opaque();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -276,10 +314,25 @@ sealed interface Sym {
|
|||
* This represents a dereferenced {@link StackOffsetSym} (or the dereferenced stack pointer
|
||||
* register, in which is treated as a stack offset of 0).
|
||||
*/
|
||||
public record StackDerefSym(long offset, int size) implements Sym {
|
||||
public record StackDerefSym(long offset, long mask, int size) implements Sym {
|
||||
@Override
|
||||
public Sym add(CompilerSpec cSpec, Sym in2) {
|
||||
return Sym.opaque();
|
||||
return switch (in2) {
|
||||
case ConstSym const2 -> const2.add(cSpec, this);
|
||||
default -> Sym.opaque();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sym and(CompilerSpec cSpec, Sym in2) {
|
||||
return switch (in2) {
|
||||
case ConstSym const2 -> const2.and(cSpec, this);
|
||||
default -> Sym.opaque();
|
||||
};
|
||||
}
|
||||
|
||||
public StackDerefSym withAppliedMask(long mask) {
|
||||
return new StackDerefSym(offset, this.mask & mask, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -65,6 +65,8 @@ class SymPcodeArithmetic implements PcodeArithmetic<Sym> {
|
|||
return in1.add(cSpec, in2);
|
||||
case PcodeOp.INT_SUB:
|
||||
return in1.sub(cSpec, in2);
|
||||
case PcodeOp.INT_AND:
|
||||
return in1.and(cSpec, in2);
|
||||
default:
|
||||
return Sym.opaque();
|
||||
}
|
||||
|
|
|
@ -218,17 +218,37 @@ public class SymPcodeExecutorState implements PcodeExecutorState<Sym> {
|
|||
* <li>PC:Deref => location is [Stack]:PC.offset
|
||||
* </ul>
|
||||
*
|
||||
* @return
|
||||
* @return the address (stack offset or register) of the return address
|
||||
*/
|
||||
public Address computeAddressOfReturn() {
|
||||
Sym expr = getVar(language.getProgramCounter(), Reason.INSPECT);
|
||||
if (expr instanceof StackDerefSym stackVar) {
|
||||
return cSpec.getStackSpace().getAddress(stackVar.offset());
|
||||
return switch (getVar(language.getProgramCounter(), Reason.INSPECT)) {
|
||||
case StackDerefSym stackVar -> cSpec.getStackSpace().getAddress(stackVar.offset());
|
||||
case RegisterSym regVar -> regVar.register().getAddress();
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
if (expr instanceof RegisterSym regVar) {
|
||||
return regVar.register().getAddress();
|
||||
}
|
||||
return null;
|
||||
|
||||
/**
|
||||
* Examine this state's PC to determine how the return address is masked
|
||||
*
|
||||
* <p>
|
||||
* This is only applicable in cases where {@link #computeAddressOfReturn()} returns a non-null
|
||||
* address. This is to handle architectures where the low bits indicate an ISA mode, and the
|
||||
* higher bits form the actual address. Often, the sleigh specifications for these processors
|
||||
* will mask off those low bits when setting the PC. If that has happened, and the symbolic
|
||||
* expression stored in the PC is otherwise understood to come from the stack or a register,
|
||||
* this will return that mask. Most often, this will return -1, indicating that all bits are
|
||||
* relevant to the actual address. If the symbolic expression does not indicate the stack or a
|
||||
* register, this still returns -1.
|
||||
*
|
||||
* @return the mask, often -1
|
||||
*/
|
||||
public long computeMaskOfReturn() {
|
||||
return switch (getVar(language.getProgramCounter(), Reason.INSPECT)) {
|
||||
case StackDerefSym stackVar -> stackVar.mask();
|
||||
case RegisterSym regVar -> regVar.mask();
|
||||
default -> -1;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -211,7 +211,10 @@ public class SymStateSpace {
|
|||
return map.values()
|
||||
.stream()
|
||||
.map(se -> se.toString(language))
|
||||
.collect(Collectors.joining("\n" + indent, indent + "{", "\n" + indent + "}"));
|
||||
.collect(Collectors.joining(
|
||||
"\n" + indent + indent,
|
||||
"{\n" + indent + indent,
|
||||
"\n" + indent + "}"));
|
||||
}
|
||||
|
||||
private NavigableMap<Address, SymEntry> subMap(Address lower, Address upper) {
|
||||
|
@ -281,10 +284,10 @@ public class SymStateSpace {
|
|||
Msg.warn(this, "Could not figure register: address=" + address + ",size=" + size);
|
||||
return Sym.opaque();
|
||||
}
|
||||
return new RegisterSym(register);
|
||||
return new RegisterSym(register, -1);
|
||||
}
|
||||
if (address.isStackAddress()) {
|
||||
return new StackDerefSym(address.getOffset(), size);
|
||||
return new StackDerefSym(address.getOffset(), -1, size);
|
||||
}
|
||||
return Sym.opaque();
|
||||
}
|
||||
|
|
|
@ -98,8 +98,7 @@ public class UnwindAnalysis {
|
|||
/**
|
||||
* Wrap a {@link CodeBlock}
|
||||
*/
|
||||
record BlockVertex(CodeBlock block) {
|
||||
}
|
||||
record BlockVertex(CodeBlock block) {}
|
||||
|
||||
/**
|
||||
* Wrap a {@link CodeBlockReference}
|
||||
|
@ -492,6 +491,7 @@ public class UnwindAnalysis {
|
|||
new UnwindException("Cannot determine address of return pointer");
|
||||
continue;
|
||||
}
|
||||
long maskOfReturn = exitState.computeMaskOfReturn();
|
||||
Long adjust = exitState.computeStackDepth();
|
||||
if (adjust == null) {
|
||||
lastError = new UnwindException("Cannot determine stack adjustment");
|
||||
|
@ -501,8 +501,8 @@ public class UnwindAnalysis {
|
|||
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), null);
|
||||
return new UnwindInfo(function, depth, adjust, addressOfReturn, maskOfReturn,
|
||||
mapByExit, new StackUnwindWarningSet(warnings), null);
|
||||
}
|
||||
}
|
||||
if (lastSuccessfulEntryState != null) {
|
||||
|
@ -510,16 +510,16 @@ public class UnwindAnalysis {
|
|||
try {
|
||||
long adjust = SymPcodeExecutor.computeStackChange(function, warnings);
|
||||
return new UnwindInfo(function, lastSuccessfulEntryState.computeStackDepth(),
|
||||
adjust, null, lastSuccessfulEntryState.computeMapUsingStack(),
|
||||
adjust, null, -1, lastSuccessfulEntryState.computeMapUsingStack(),
|
||||
new StackUnwindWarningSet(warnings), lastError);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return new UnwindInfo(function, lastSuccessfulEntryState.computeStackDepth(),
|
||||
null, null, lastSuccessfulEntryState.computeMapUsingStack(),
|
||||
null, null, -1, lastSuccessfulEntryState.computeMapUsingStack(),
|
||||
new StackUnwindWarningSet(warnings), e);
|
||||
}
|
||||
}
|
||||
return new UnwindInfo(function, null, null, null, null,
|
||||
return new UnwindInfo(function, null, null, null, -1, null,
|
||||
new StackUnwindWarningSet(warnings), new UnwindException(
|
||||
"Could not analyze any path from %s entry to %s.\n%s".formatted(function, pc,
|
||||
lastError.getMessage()),
|
||||
|
|
|
@ -31,12 +31,28 @@ import ghidra.util.task.TaskMonitor;
|
|||
|
||||
/**
|
||||
* Information for interpreting the current stack frame and unwinding to the next
|
||||
*
|
||||
* @param function see {@link #function()}
|
||||
* @param depth see {@link #depth()}
|
||||
* @param adjust see {@link #adjust()}
|
||||
* @param ofReturn see {@link #ofReturn()}
|
||||
* @param maskOfReturn see {@link #maskOfReturn()}
|
||||
* @param saved see {@link #saved()}
|
||||
* @param warnings see {@link #warnings()}
|
||||
* @param error see {@link #error()}
|
||||
*/
|
||||
public record UnwindInfo(Function function, Long depth, Long adjust, Address ofReturn,
|
||||
Map<Register, Address> saved, StackUnwindWarningSet warnings, Exception error) {
|
||||
long maskOfReturn, Map<Register, Address> saved, StackUnwindWarningSet warnings,
|
||||
Exception error) {
|
||||
|
||||
/**
|
||||
* Construct an error-only info
|
||||
*
|
||||
* @param error the error
|
||||
* @return the info containing only the error
|
||||
*/
|
||||
public static UnwindInfo errorOnly(Exception error) {
|
||||
return new UnwindInfo(null, null, null, null, null, new StackUnwindWarningSet(), error);
|
||||
return new UnwindInfo(null, null, null, null, -1, null, new StackUnwindWarningSet(), error);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,6 +104,29 @@ public record UnwindInfo(Function function, Long depth, Long adjust, Address ofR
|
|||
return ofReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* The mask applied to the return address
|
||||
*
|
||||
* <p>
|
||||
* This is to handle ISAs that use the low bits of addresses in jumps to indicate an ISA switch.
|
||||
* Often, the code that returns from a function will apply a mask. If that is the case, this
|
||||
* returns that mask. In most cases, this returns -1, which when applied as a mask has no
|
||||
* effect.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE</b>: There is currently no tracking of the ISA mode by the stack unwinder. First, the
|
||||
* conventions for tracking that in the Sleigh specification varies from processor to processor.
|
||||
* There is often custom-made handling of that bit programmed in Java for the emulator, but it's
|
||||
* not generally accessible for static analysis. Second, for stack unwinding purposes, we use
|
||||
* the statically disassembled code at the return address, anyway. That should already be of the
|
||||
* correct ISA; if not, then we are already lost.
|
||||
*
|
||||
* @return the mask, often -1
|
||||
*/
|
||||
public long maskOfReturn() {
|
||||
return maskOfReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* The <em>address of</em> the return address, given a stack base
|
||||
*
|
||||
|
@ -172,7 +211,7 @@ public record UnwindInfo(Function function, Long depth, Long adjust, Address ofR
|
|||
* Add register map entries for the saved registers in this frame
|
||||
*
|
||||
* @param base the current frame's base pointer, as in {@link #computeBase(Address)}
|
||||
* @param registerMap the register map of the stack to this point, to be modified
|
||||
* @param map the register map of the stack to this point, to be modified
|
||||
*/
|
||||
public void mapSavedRegisters(Address base, SavedRegisterMap map) {
|
||||
for (Entry<Register, Address> ent : saved.entrySet()) {
|
||||
|
@ -221,7 +260,8 @@ public record UnwindInfo(Function function, Long depth, Long adjust, Address ofR
|
|||
AddressSpace codeSpace, Register pc) {
|
||||
T value = computeNextPc(base, state, pc);
|
||||
long concrete = state.getArithmetic().toLong(value, Purpose.INSPECT);
|
||||
return codeSpace.getAddress(concrete);
|
||||
long masked = concrete & maskOfReturn;
|
||||
return codeSpace.getAddress(masked);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,6 +38,7 @@ import ghidra.app.decompiler.*;
|
|||
import ghidra.app.decompiler.component.*;
|
||||
import ghidra.app.plugin.assembler.*;
|
||||
import ghidra.app.plugin.assembler.sleigh.sem.*;
|
||||
import ghidra.app.plugin.core.analysis.*;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||
import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
|
@ -81,6 +82,7 @@ import ghidra.trace.model.TraceLocation;
|
|||
import ghidra.trace.model.breakpoint.TraceBreakpoint;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||
import ghidra.trace.model.listing.TraceData;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.schedule.Scheduler;
|
||||
import ghidra.util.Msg;
|
||||
|
@ -587,6 +589,59 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest {
|
|||
}
|
||||
}
|
||||
|
||||
protected Function createInfiniteRecursionProgramArm() throws Throwable {
|
||||
createProgram("ARM:LE:32:v8", "default");
|
||||
intoProject(program);
|
||||
try (Transaction tx = program.openTransaction("Assemble")) {
|
||||
Address entry = addr(program, 0x00400000);
|
||||
program.getMemory()
|
||||
.createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false);
|
||||
|
||||
Assembler asm = Assemblers.getAssembler(program.getLanguage());
|
||||
AssemblyBuffer buf = new AssemblyBuffer(asm, entry);
|
||||
|
||||
Language language = asm.getLanguage();
|
||||
Register regCtx = language.getContextBaseRegister();
|
||||
Register regT = language.getRegister("T");
|
||||
RegisterValue rvDefault = new RegisterValue(regCtx,
|
||||
asm.getContextAt(entry).toBigInteger(regCtx.getNumBytes()));
|
||||
RegisterValue rvThumb = rvDefault.assign(regT, BigInteger.ONE);
|
||||
AssemblyPatternBlock ctxThumb = AssemblyPatternBlock.fromRegisterValue(rvThumb);
|
||||
|
||||
buf.emit(NumericUtilities.convertStringToBytes("00 b5")); // push {lr}
|
||||
buf.emit(NumericUtilities.convertStringToBytes("00 04")); // lsl r0,r0,#0x10
|
||||
buf.emit(NumericUtilities.convertStringToBytes("00 0c")); // lsr r0,r0,#0x10
|
||||
buf.assemble("bl 0x%s".formatted(entry), ctxThumb);
|
||||
bodyInstr = buf.getNext();
|
||||
buf.assemble("pop {r0}", ctxThumb);
|
||||
buf.assemble("bx r0", ctxThumb);
|
||||
|
||||
byte[] bytes = buf.getBytes();
|
||||
program.getMemory().setBytes(entry, bytes);
|
||||
|
||||
Disassembler dis = Disassembler.getDisassembler(program, monitor, null);
|
||||
dis.disassemble(entry, null, rvThumb, false);
|
||||
|
||||
Function func = program.getFunctionManager()
|
||||
.createFunction("recurseInfinitely", entry,
|
||||
new AddressSet(entry, entry.add(bytes.length - 1)),
|
||||
SourceType.USER_DEFINED);
|
||||
return func;
|
||||
}
|
||||
}
|
||||
|
||||
protected void openAndAnalyze(Function function) {
|
||||
GhidraProgramUtilities.markProgramNotToAskToAnalyze(program);
|
||||
programManager.openProgram(program);
|
||||
waitForSwing();
|
||||
|
||||
AutoAnalysisManager analysisManager = AutoAnalysisManager.getAnalysisManager(program);
|
||||
tool.executeBackgroundCommand(new AnalysisBackgroundCommand(analysisManager, true),
|
||||
program);
|
||||
analysisManager.reAnalyzeAll(function.getBody());
|
||||
waitForTasks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComputeUnwindInfoX86_32() throws Throwable {
|
||||
addPlugin(tool, CodeBrowserPlugin.class);
|
||||
|
@ -599,13 +654,14 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest {
|
|||
UnwindAnalysis ua = new UnwindAnalysis(program);
|
||||
|
||||
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
||||
assertEquals(
|
||||
new UnwindInfo(function, 0L, 4L, stack(0), Map.of(), new StackUnwindWarningSet(), null),
|
||||
assertEquals(new UnwindInfo(function, 0L, 4L, stack(0), -1,
|
||||
Map.of(), new StackUnwindWarningSet(), null),
|
||||
infoAtEntry);
|
||||
|
||||
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
||||
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0),
|
||||
Map.of(register("EBP"), stack(-4)), new StackUnwindWarningSet(), null), infoAtBody);
|
||||
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0), -1,
|
||||
Map.of(register("EBP"), stack(-4)), new StackUnwindWarningSet(), null),
|
||||
infoAtBody);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -623,14 +679,15 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest {
|
|||
UnwindAnalysis ua = new UnwindAnalysis(program);
|
||||
|
||||
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
||||
assertEquals(
|
||||
new UnwindInfo(function, 0L, 4L, stack(0), Map.of(), new StackUnwindWarningSet(), null),
|
||||
assertEquals(new UnwindInfo(function, 0L, 4L, stack(0), -1,
|
||||
Map.of(), new StackUnwindWarningSet(), null),
|
||||
infoAtEntry);
|
||||
|
||||
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
||||
assertEquals(
|
||||
new UnwindInfo(function, -20L, 4L, stack(0), Map.of(register("EBP"), stack(-4)),
|
||||
new StackUnwindWarningSet(new UnspecifiedConventionStackUnwindWarning(myExtern),
|
||||
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0), -1,
|
||||
Map.of(register("EBP"), stack(-4)),
|
||||
new StackUnwindWarningSet(
|
||||
new UnspecifiedConventionStackUnwindWarning(myExtern),
|
||||
new UnknownPurgeStackUnwindWarning(myExtern)),
|
||||
null),
|
||||
infoAtBody);
|
||||
|
@ -649,18 +706,47 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest {
|
|||
UnwindAnalysis ua = new UnwindAnalysis(program);
|
||||
|
||||
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
||||
assertEquals(
|
||||
new UnwindInfo(function, 0L, 4L, stack(0), Map.of(), new StackUnwindWarningSet(), null),
|
||||
assertEquals(new UnwindInfo(function, 0L, 4L, stack(0), -1,
|
||||
Map.of(), new StackUnwindWarningSet(), null),
|
||||
infoAtEntry);
|
||||
|
||||
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
||||
DataType ptr2Undef = new PointerDataType(DataType.DEFAULT, program.getDataTypeManager());
|
||||
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0),
|
||||
assertEquals(new UnwindInfo(function, -20L, 4L, stack(0), -1,
|
||||
Map.of(register("EBP"), stack(-4)),
|
||||
new StackUnwindWarningSet(new UnexpectedTargetTypeStackUnwindWarning(ptr2Undef)), null),
|
||||
infoAtBody);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComputeUnwindInfoWithArmBx() throws Throwable {
|
||||
addPlugin(tool, CodeBrowserPlugin.class);
|
||||
addPlugin(tool, AutoAnalysisPlugin.class);
|
||||
addPlugin(tool, DecompilePlugin.class);
|
||||
|
||||
Function function = createInfiniteRecursionProgramArm();
|
||||
openAndAnalyze(function);
|
||||
Address entry = function.getEntryPoint();
|
||||
|
||||
UnwindAnalysis ua = new UnwindAnalysis(program);
|
||||
|
||||
UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor);
|
||||
assertEquals(new UnwindInfo(function, 0L, 0L, register("lr").getAddress(), 0xfffffffeL,
|
||||
Map.of(), new StackUnwindWarningSet(
|
||||
new UnspecifiedConventionStackUnwindWarning(function),
|
||||
new UnknownPurgeStackUnwindWarning(function)),
|
||||
null),
|
||||
infoAtEntry);
|
||||
|
||||
UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor);
|
||||
assertEquals(new UnwindInfo(function, -4L, 0L, stack(-4), 0xfffffffeL,
|
||||
Map.of(), new StackUnwindWarningSet(
|
||||
new UnspecifiedConventionStackUnwindWarning(function),
|
||||
new UnknownPurgeStackUnwindWarning(function)),
|
||||
null),
|
||||
infoAtBody);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnwindTopFrameX86_32() throws Throwable {
|
||||
addPlugin(tool, CodeBrowserPlugin.class);
|
||||
|
@ -814,6 +900,95 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest {
|
|||
assertEquals(BigInteger.valueOf(34), retVal.toBigInteger(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnwindRecursiveArmThumb() throws Throwable {
|
||||
addPlugin(tool, CodeBrowserPlugin.class);
|
||||
addPlugin(tool, DebuggerListingPlugin.class);
|
||||
addPlugin(tool, DisassemblerPlugin.class);
|
||||
addPlugin(tool, AutoAnalysisPlugin.class);
|
||||
addPlugin(tool, DecompilePlugin.class);
|
||||
DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class);
|
||||
|
||||
Function function = createInfiniteRecursionProgramArm();
|
||||
openAndAnalyze(function);
|
||||
Address entry = function.getEntryPoint();
|
||||
|
||||
useTrace(ProgramEmulationUtils.launchEmulationTrace(program, entry, this));
|
||||
tb.trace.release(this);
|
||||
TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads());
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
DebuggerCoordinates atSetup = traceManager.getCurrent();
|
||||
StackUnwinder unwinder = new StackUnwinder(tool, atSetup.getPlatform());
|
||||
|
||||
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||
Register sp = program.getCompilerSpec().getStackPointer();
|
||||
long spAtSetup = regs.getValue(0, sp).getUnsignedValue().longValueExact();
|
||||
|
||||
TraceBreakpoint bptUnwind;
|
||||
try (Transaction tx = tb.startTransaction()) {
|
||||
bptUnwind = tb.trace.getBreakpointManager()
|
||||
.addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), entry, Set.of(),
|
||||
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "unwind stack");
|
||||
bptUnwind.setEmuSleigh("""
|
||||
if (%s >= 0x%x) goto <skip>;
|
||||
emu_swi();
|
||||
<skip>
|
||||
emu_exec_decoded();
|
||||
""".formatted(sp, spAtSetup - 0xc));
|
||||
}
|
||||
|
||||
EmulationResult result = emuService.run(atSetup.getPlatform(), atSetup.getTime(), monitor,
|
||||
Scheduler.oneThread(thread));
|
||||
Msg.debug(this, "Broke after " + result.schedule());
|
||||
|
||||
traceManager.activateTime(result.schedule());
|
||||
waitForTasks();
|
||||
DebuggerCoordinates broke = traceManager.getCurrent();
|
||||
|
||||
AnalysisUnwoundFrame<WatchValue> frameBroke = unwinder.start(broke, monitor);
|
||||
assertEquals(tb.addr(0x00004ff0), frameBroke.getStackPointer());
|
||||
assertEquals(tb.addr(0x00004ff0), frameBroke.getBasePointer());
|
||||
assertEquals(tb.addr(0x00400000), frameBroke.getProgramCounter());
|
||||
|
||||
frameBroke = frameBroke.unwindNext(monitor); // Innermost hadn't pushed anything, yet
|
||||
assertEquals(tb.addr(0x00004ff0), frameBroke.getStackPointer());
|
||||
assertEquals(tb.addr(0x00004ff4), frameBroke.getBasePointer());
|
||||
assertEquals(tb.addr(0x0040000a), frameBroke.getProgramCounter());
|
||||
|
||||
frameBroke = frameBroke.unwindNext(monitor);
|
||||
assertEquals(tb.addr(0x00004ff4), frameBroke.getStackPointer());
|
||||
assertEquals(tb.addr(0x00004ff8), frameBroke.getBasePointer());
|
||||
assertEquals(tb.addr(0x0040000a), frameBroke.getProgramCounter());
|
||||
|
||||
frameBroke = frameBroke.unwindNext(monitor);
|
||||
assertEquals(tb.addr(0x00004ff8), frameBroke.getStackPointer());
|
||||
assertEquals(tb.addr(0x00004ffc), frameBroke.getBasePointer());
|
||||
assertEquals(tb.addr(0x0040000a), frameBroke.getProgramCounter());
|
||||
|
||||
frameBroke = frameBroke.unwindNext(monitor);
|
||||
assertEquals(tb.addr(0x00004ffc), frameBroke.getStackPointer());
|
||||
assertEquals(tb.addr(0x00005000), frameBroke.getBasePointer());
|
||||
assertEquals(tb.addr(0x0040000a), frameBroke.getProgramCounter());
|
||||
|
||||
frameBroke = frameBroke.unwindNext(monitor);
|
||||
assertEquals(tb.addr(0x00005000), frameBroke.getStackPointer());
|
||||
assertNull(frameBroke.getBasePointer());
|
||||
assertEquals(tb.addr(0x00000000), frameBroke.getProgramCounter());
|
||||
|
||||
assertEquals(5, frameBroke.getLevel());
|
||||
|
||||
try {
|
||||
frameBroke.unwindNext(monitor);
|
||||
fail();
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateFramesAtEntryX86_32() throws Throwable {
|
||||
addPlugin(tool, CodeBrowserPlugin.class);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue