Merge remote-tracking branch 'origin/GP-6369_Dan_portEmulatorTests--SQUASHED'

This commit is contained in:
Ryan Kurtz
2026-02-24 13:27:11 -05:00
33 changed files with 884 additions and 588 deletions
@@ -230,7 +230,7 @@ public enum TraceEmulationIntegration {
* @param set the uninitialized portion required * @param set the uninitialized portion required
* @return the addresses in {@code set} that remain uninitialized * @return the addresses in {@code set} that remain uninitialized
* @see PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece, * @see PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece,
* AddressSetView) * AddressSetView, Reason)
*/ */
AddressSetView readUninitialized(PcodeTraceDataAccess acc, PcodeThread<?> thread, AddressSetView readUninitialized(PcodeTraceDataAccess acc, PcodeThread<?> thread,
PcodeExecutorStatePiece<A, T> piece, AddressSetView set); PcodeExecutorStatePiece<A, T> piece, AddressSetView set);
@@ -246,12 +246,14 @@ public enum TraceEmulationIntegration {
* @param space the address space * @param space the address space
* @param offset the offset at the start of the uninitialized portion * @param offset the offset at the start of the uninitialized portion
* @param length the size in bytes of the uninitialized portion * @param length the size in bytes of the uninitialized portion
* @param reason the reason for reading
* @return the number of bytes just initialized, typically 0 or {@code length} * @return the number of bytes just initialized, typically 0 or {@code length}
* @see PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece, * @see PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece,
* AddressSpace, Object, int) * AddressSpace, Object, int, Reason)
*/ */
default int abstractReadUninit(PcodeTraceDataAccess acc, PcodeThread<?> thread, default int abstractReadUninit(PcodeTraceDataAccess acc, PcodeThread<?> thread,
PcodeExecutorStatePiece<A, T> piece, AddressSpace space, A offset, int length) { PcodeExecutorStatePiece<A, T> piece, AddressSpace space, A offset, int length,
Reason reason) {
return 0; return 0;
} }
@@ -542,7 +544,8 @@ public enum TraceEmulationIntegration {
*/ */
@Override @Override
public int abstractReadUninit(PcodeTraceDataAccess acc, PcodeThread<?> thread, public int abstractReadUninit(PcodeTraceDataAccess acc, PcodeThread<?> thread,
PcodeExecutorStatePiece<A, T> piece, AddressSpace space, A offset, int length) { PcodeExecutorStatePiece<A, T> piece, AddressSpace space, A offset, int length,
Reason reason) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@@ -783,14 +786,16 @@ public enum TraceEmulationIntegration {
@Override @Override
public <B, U> int readUninitialized(PcodeThread<Object> thread, public <B, U> int readUninitialized(PcodeThread<Object> thread,
PcodeExecutorStatePiece<B, U> piece, AddressSpace space, B offset, int length) { PcodeExecutorStatePiece<B, U> piece, AddressSpace space, B offset, int length,
Reason reason) {
PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess; PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess;
return handlerFor(piece).abstractReadUninit(acc, thread, piece, space, offset, length); return handlerFor(piece).abstractReadUninit(acc, thread, piece, space, offset, length,
reason);
} }
@Override @Override
public <B, U> AddressSetView readUninitialized(PcodeThread<Object> thread, public <B, U> AddressSetView readUninitialized(PcodeThread<Object> thread,
PcodeExecutorStatePiece<B, U> piece, AddressSetView set) { PcodeExecutorStatePiece<B, U> piece, AddressSetView set, Reason reason) {
if (set.isEmpty()) { if (set.isEmpty()) {
return set; return set;
} }
@@ -118,7 +118,7 @@ public class TaintPcodeExecutorStatePiece
@Override @Override
protected TaintVec getFromSpace(TaintSpace space, long offset, int size, Reason reason, protected TaintVec getFromSpace(TaintSpace space, long offset, int size, Reason reason,
PcodeStateCallbacks cb) { PcodeStateCallbacks cb) {
return space.get(offset, size, cb); return space.get(offset, size, reason, cb);
} }
@Override @Override
@@ -18,6 +18,7 @@ package ghidra.pcode.emu.taint.state;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
@@ -85,14 +86,15 @@ public class TaintSpace {
* *
* @param offset the offset * @param offset the offset
* @param buf the vector to receive taint sets * @param buf the vector to receive taint sets
* @param reason the reason for reading
* @param cb callbacks to receive emulation events * @param cb callbacks to receive emulation events
*/ */
public void getInto(long offset, TaintVec buf, PcodeStateCallbacks cb) { public void getInto(long offset, TaintVec buf, Reason reason, PcodeStateCallbacks cb) {
for (int i = 0; i < buf.length; i++) { for (int i = 0; i < buf.length; i++) {
TaintSet s = taints.get(offset + i); TaintSet s = taints.get(offset + i);
if (s == null) { if (s == null) {
if (cb.readUninitialized(piece, PcodeStateCallbacks.rngSet(space, offset + i, 1)) if (cb.readUninitialized(piece, PcodeStateCallbacks.rngSet(space, offset + i, 1),
.isEmpty()) { reason).isEmpty()) {
s = taints.get(offset + i); s = taints.get(offset + i);
} }
} }
@@ -107,17 +109,18 @@ public class TaintSpace {
* Retrieve the taint sets for the variable at the given offset * Retrieve the taint sets for the variable at the given offset
* *
* <p> * <p>
* This works the same as {@link #getInto(long, TaintVec, PcodeStateCallbacks)}, but creates a * This works the same as {@link #getInto(long, TaintVec, Reason, PcodeStateCallbacks)}, but
* new vector of the given size, reads the taint sets, and returns the vector. * creates a new vector of the given size, reads the taint sets, and returns the vector.
* *
* @param offset the offset * @param offset the offset
* @param size the size of the variable * @param size the size of the variable
* @param reason the reason for reading
* @param cb callbacks to receive emulation events * @param cb callbacks to receive emulation events
* @return the taint vector for that variable * @return the taint vector for that variable
*/ */
public TaintVec get(long offset, int size, PcodeStateCallbacks cb) { public TaintVec get(long offset, int size, Reason reason, PcodeStateCallbacks cb) {
TaintVec vec = new TaintVec(size); TaintVec vec = new TaintVec(size);
getInto(offset, vec, cb); getInto(offset, vec, reason, cb);
return vec; return vec;
} }
@@ -153,7 +156,7 @@ public class TaintSpace {
} }
PcodeOp pcodeOp = ops.get(offset); // Needed here to generate the TaintVec PcodeOp pcodeOp = ops.get(offset); // Needed here to generate the TaintVec
TaintVec vec = new TaintVec(MathUtilities.unsignedMin(1024, end - offset), pcodeOp); TaintVec vec = new TaintVec(MathUtilities.unsignedMin(1024, end - offset), pcodeOp);
getInto(offset, vec, PcodeStateCallbacks.NONE); getInto(offset, vec, Reason.INSPECT, PcodeStateCallbacks.NONE);
return Map.entry(offset, vec); return Map.entry(offset, vec);
} }
} }
@@ -109,9 +109,10 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece<S>
* @param size the number of bytes to read (the size of the value) * @param size the number of bytes to read (the size of the value)
* @return the read value * @return the read value
*/ */
protected SymValueZ3 getUnique(SymValueZ3 offset, int size, PcodeStateCallbacks cb) { protected SymValueZ3 getUnique(SymValueZ3 offset, int size, Reason reason,
PcodeStateCallbacks cb) {
S s = getForSpace(uniqueSpace, false); S s = getForSpace(uniqueSpace, false);
return getFromSpace(s, offset, size, cb); return getFromSpace(s, offset, size, reason, cb);
} }
/** /**
@@ -143,10 +144,11 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece<S>
* @param space the address space * @param space the address space
* @param offset the offset within the space * @param offset the offset within the space
* @param size the number of bytes to read (the size of the value) * @param size the number of bytes to read (the size of the value)
* @param reason the reason for reading
* @param cb callbacks to receive emulation events * @param cb callbacks to receive emulation events
* @return the read value * @return the read value
*/ */
protected abstract SymValueZ3 getFromSpace(S space, SymValueZ3 offset, int size, protected abstract SymValueZ3 getFromSpace(S space, SymValueZ3 offset, int size, Reason reason,
PcodeStateCallbacks cb); PcodeStateCallbacks cb);
/** /**
@@ -229,7 +231,7 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece<S>
} }
} }
if (space.isUniqueSpace()) { if (space.isUniqueSpace()) {
return getUnique(offset, size, cb); return getUnique(offset, size, reason, cb);
} }
S s = getForSpace(space, false); S s = getForSpace(space, false);
//Msg.info(this, "Now we likely have a space to get from: " + s); //Msg.info(this, "Now we likely have a space to get from: " + s);
@@ -237,7 +239,7 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece<S>
return getFromNullSpace(size, cb); return getFromNullSpace(size, cb);
} }
//offset = quantizeOffset(space, offset); //offset = quantizeOffset(space, offset);
return getFromSpace(s, offset, size, cb); return getFromSpace(s, offset, size, reason, cb);
} }
@Override @Override
@@ -144,9 +144,9 @@ public class SymZ3PcodeExecutorStatePiece
* the storage space. * the storage space.
*/ */
@Override @Override
protected SymValueZ3 getFromSpace(SymZ3Space space, SymValueZ3 offset, int size, protected SymValueZ3 getFromSpace(SymZ3Space space, SymValueZ3 offset, int size, Reason reason,
PcodeStateCallbacks cb) { PcodeStateCallbacks cb) {
return space.get(offset, size, cb); return space.get(offset, size, reason, cb);
} }
@Override @Override
@@ -23,6 +23,7 @@ import com.microsoft.z3.Context;
import ghidra.pcode.emu.symz3.AbstractSymZ3OffsetPcodeExecutorStatePiece; import ghidra.pcode.emu.symz3.AbstractSymZ3OffsetPcodeExecutorStatePiece;
import ghidra.pcode.emu.symz3.SymZ3MemoryMap; import ghidra.pcode.emu.symz3.SymZ3MemoryMap;
import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
@@ -53,9 +54,9 @@ public class SymZ3MemorySpace extends SymZ3Space {
} }
@Override @Override
public SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb) { public SymValueZ3 get(SymValueZ3 offset, int size, Reason reason, PcodeStateCallbacks cb) {
if (!mmap.hasValueFor(offset, size)) { if (!mmap.hasValueFor(offset, size)) {
cb.readUninitialized(piece, space, offset, size); cb.readUninitialized(piece, space, offset, size, reason);
} }
return mmap.load(offset, size, true, cb); return mmap.load(offset, size, true, cb);
} }
@@ -84,7 +84,7 @@ public class SymZ3PieceHandler
@Override @Override
public int abstractReadUninit(PcodeTraceDataAccess acc, PcodeThread<?> thread, public int abstractReadUninit(PcodeTraceDataAccess acc, PcodeThread<?> thread,
PcodeExecutorStatePiece<SymValueZ3, SymValueZ3> piece, AddressSpace space, PcodeExecutorStatePiece<SymValueZ3, SymValueZ3> piece, AddressSpace space,
SymValueZ3 offset, int length) { SymValueZ3 offset, int length, Reason reason) {
String string = acc.getPropertyAccess(NAME, String.class).get(Address.NO_ADDRESS); String string = acc.getPropertyAccess(NAME, String.class).get(Address.NO_ADDRESS);
if (string == null) { if (string == null) {
return 0; return 0;
@@ -23,6 +23,7 @@ import com.microsoft.z3.Context;
import ghidra.pcode.emu.symz3.AbstractSymZ3OffsetPcodeExecutorStatePiece; import ghidra.pcode.emu.symz3.AbstractSymZ3OffsetPcodeExecutorStatePiece;
import ghidra.pcode.emu.symz3.SymZ3RegisterMap; import ghidra.pcode.emu.symz3.SymZ3RegisterMap;
import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
@@ -87,7 +88,7 @@ public class SymZ3RegisterSpace extends SymZ3Space {
} }
@Override @Override
public SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb) { public SymValueZ3 get(SymValueZ3 offset, int size, Reason reason, PcodeStateCallbacks cb) {
Register r = getRegister(offset, size); Register r = getRegister(offset, size);
if (r == null) { if (r == null) {
Msg.warn(this, "unable to get register with space: " + space.getSpaceID() + Msg.warn(this, "unable to get register with space: " + space.getSpaceID() +
@@ -95,7 +96,7 @@ public class SymZ3RegisterSpace extends SymZ3Space {
return null; return null;
} }
if (!rmap.hasValueForRegister(r)) { if (!rmap.hasValueForRegister(r)) {
cb.readUninitialized(piece, space, offset, size); cb.readUninitialized(piece, space, offset, size, reason);
} }
SymValueZ3 result = this.rmap.getRegister(r); SymValueZ3 result = this.rmap.getRegister(r);
return result; return result;
@@ -22,6 +22,7 @@ import java.util.stream.Stream;
import com.microsoft.z3.Context; import com.microsoft.z3.Context;
import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.symz3.model.SymValueZ3; import ghidra.symz3.model.SymValueZ3;
@@ -36,7 +37,8 @@ import ghidra.symz3.model.SymValueZ3;
public abstract class SymZ3Space { public abstract class SymZ3Space {
public abstract void set(SymValueZ3 offset, int size, SymValueZ3 val, PcodeStateCallbacks cb); public abstract void set(SymValueZ3 offset, int size, SymValueZ3 val, PcodeStateCallbacks cb);
public abstract SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb); public abstract SymValueZ3 get(SymValueZ3 offset, int size, Reason reason,
PcodeStateCallbacks cb);
public abstract String printableSummary(); public abstract String printableSummary();
@@ -23,6 +23,7 @@ import java.util.stream.Stream;
import com.microsoft.z3.*; import com.microsoft.z3.*;
import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.symz3.model.SymValueZ3; import ghidra.symz3.model.SymValueZ3;
@@ -86,7 +87,7 @@ public class SymZ3UniqueSpace extends SymZ3Space {
} }
@Override @Override
public SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb) { public SymValueZ3 get(SymValueZ3 offset, int size, Reason reason, PcodeStateCallbacks cb) {
assert offset != null; assert offset != null;
try (Context ctx = new Context()) { try (Context ctx = new Context()) {
BitVecExpr b = offset.getBitVecExpr(ctx); BitVecExpr b = offset.getBitVecExpr(ctx);
@@ -26,6 +26,7 @@ import ghidra.pcode.emu.symz3.SymZ3MemoryMap;
import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter;
import ghidra.pcode.emu.symz3.state.SymZ3PieceHandler; import ghidra.pcode.emu.symz3.state.SymZ3PieceHandler;
import ghidra.pcode.emu.symz3.state.SymZ3WriteDownHelper; import ghidra.pcode.emu.symz3.state.SymZ3WriteDownHelper;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
@@ -86,7 +87,7 @@ public class SymZ3TraceMemorySpace extends SymZ3TraceSpace {
} }
@Override @Override
public SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb) { public SymValueZ3 get(SymValueZ3 offset, int size, Reason reason, PcodeStateCallbacks cb) {
if (mmap.hasValueFor(offset, size)) { if (mmap.hasValueFor(offset, size)) {
return mmap.load(offset, size, true, cb); return mmap.load(offset, size, true, cb);
} }
@@ -23,6 +23,7 @@ import com.microsoft.z3.Context;
import ghidra.pcode.emu.symz3.SymZ3RegisterMap; import ghidra.pcode.emu.symz3.SymZ3RegisterMap;
import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter;
import ghidra.pcode.emu.symz3.state.SymZ3WriteDownHelper; import ghidra.pcode.emu.symz3.state.SymZ3WriteDownHelper;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
@@ -96,7 +97,7 @@ public class SymZ3TraceRegisterSpace extends SymZ3TraceSpace {
} }
@Override @Override
public SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb) { public SymValueZ3 get(SymValueZ3 offset, int size, Reason reason, PcodeStateCallbacks cb) {
assert offset != null; assert offset != null;
Register r = getRegister(offset, size); Register r = getRegister(offset, size);
if (r == null) { if (r == null) {
@@ -25,27 +25,34 @@
// to the function "deobfuscate" so that the various return values can be recorded with a comment // to the function "deobfuscate" so that the various return values can be recorded with a comment
// placed just after the call. // placed just after the call.
//@category Examples.Emulation //@category Examples.Emulation
import ghidra.app.emulator.EmulatorHelper; import java.util.NoSuchElementException;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.util.opinion.ElfLoader; import ghidra.app.util.opinion.ElfLoader;
import ghidra.pcode.emulate.EmulateExecutionState; import ghidra.pcode.emu.*;
import ghidra.pcode.exec.InterruptPcodeExecutionException;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Instruction; import ghidra.program.model.listing.Instruction;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.NotFoundException;
public class EmuX86DeobfuscateExampleScript extends GhidraScript { public class EmuX86DeobfuscateExampleScript extends GhidraScript {
private static String PROGRAM_NAME = "deobExample"; private static final String PROGRAM_NAME = "deobExample";
private EmulatorHelper emuHelper; private PcodeEmulator emu;
private PcodeThread<byte[]> emuThread;
private PcodeArithmetic<byte[]> arithmetic;
// Important breakpoint locations // Important breakpoint locations
private Address deobfuscateCall; private Address deobfuscateCall;
private Address deobfuscateReturn; private Address deobfuscateReturn;
// Function locations // Function locations
private Function mainFunction;
private Address mainFunctionEntry; // start of emulation address private Address mainFunctionEntry; // start of emulation address
// Address used as final return location // Address used as final return location
@@ -66,18 +73,21 @@ public class EmuX86DeobfuscateExampleScript extends GhidraScript {
!"x86:LE:64:default".equals(currentProgram.getLanguageID().toString()) || !"x86:LE:64:default".equals(currentProgram.getLanguageID().toString()) ||
!ElfLoader.ELF_NAME.equals(format)) { !ElfLoader.ELF_NAME.equals(format)) {
printerr( printerr("""
"This emulation example script is specifically intended to be executed against the\n" + This emulation example script is specifically intended to be executed against
PROGRAM_NAME + the %s program whose source is contained within the GhidraClass exercise files
" program whose source is contained within the GhidraClass exercise files\n" + (see docs/GhidraClass/ExerciseFiles/Emulation/%s.c). This program should be
"(see docs/GhidraClass/ExerciseFiles/Emulation/" + PROGRAM_NAME + ".c).\n" + compiled using gcc for x86 64-bit, imported into your project, analyzed and
"This program should be compiled using gcc for x86 64-bit, imported into your project, \n" + open as the active program before running ths script."""
"analyzed and open as the active program before running ths script."); .formatted(PROGRAM_NAME, PROGRAM_NAME));
return; return;
} }
// Identify function to be emulated // Identify function to be emulated
mainFunctionEntry = getSymbolAddress("main"); mainFunction = getGlobalFunctions("main").stream()
.findFirst()
.orElseThrow(() -> new NoSuchElementException("main"));
mainFunctionEntry = mainFunction.getEntryPoint();
// Obtain entry instruction in order to establish initial processor context // Obtain entry instruction in order to establish initial processor context
Instruction entryInstr = getInstructionAt(mainFunctionEntry); Instruction entryInstr = getInstructionAt(mainFunctionEntry);
@@ -100,53 +110,54 @@ public class EmuX86DeobfuscateExampleScript extends GhidraScript {
// Remove prior pre-comment // Remove prior pre-comment
setPreComment(deobfuscateReturn, null); setPreComment(deobfuscateReturn, null);
// Establish emulation helper // Establish emulator
emuHelper = new EmulatorHelper(currentProgram); emu = new PcodeEmulator(currentProgram.getLanguage());
try { monitor.addCancelledListener(() -> {
emu.setSuspended(true);
});
emuThread = emu.newThread();
arithmetic = emuThread.getArithmetic();
EmulatorUtilities.loadProgram(emu, currentProgram);
// Initialize stack pointer (not used by this example) // Initialize program counter, registers from context, and stack pointer
long stackOffset = EmulatorUtilities.initializeForFunction(emuThread, mainFunction);
(entryInstr.getAddress().getAddressSpace().getMaxAddress().getOffset() >>> 1) - Address stackBase =
0x7fff; EmulatorUtilities.inspectStackPointer(emuThread, currentProgram.getCompilerSpec());
emuHelper.writeRegister(emuHelper.getStackPointerRegister(), stackOffset);
// Setup breakpoints // Setup breakpoints
emuHelper.setBreakpoint(deobfuscateCall); emu.addBreakpoint(deobfuscateCall, "1:1");
emuHelper.setBreakpoint(deobfuscateReturn); emu.addBreakpoint(deobfuscateReturn, "1:1");
// Set controlled return location so we can identify return from emulated function // Set controlled return location so we can identify return from emulated function
controlledReturnAddr = getAddress(CONTROLLED_RETURN_OFFSET); controlledReturnAddr = getAddress(CONTROLLED_RETURN_OFFSET);
emuHelper.writeStackValue(0, 8, CONTROLLED_RETURN_OFFSET); emuThread.getState()
emuHelper.setBreakpoint(controlledReturnAddr); .setVar(stackBase.add(8), 8, false, arithmetic.fromConst(controlledReturnAddr));
emu.addBreakpoint(controlledReturnAddr, "1:1");
Msg.debug(this, "EMU starting at " + mainFunctionEntry); Msg.debug(this, "EMU starting at " + emuThread.getCounter());
// Execution loop until return from function or error occurs // Execution loop until return from function or error occurs
while (!monitor.isCancelled()) { while (!monitor.isCancelled()) {
boolean success = try {
(emuHelper.getEmulateExecutionState() == EmulateExecutionState.BREAKPOINT) emuThread.run();
? emuHelper.run(monitor)
: emuHelper.run(mainFunctionEntry, entryInstr, monitor);
Address executionAddress = emuHelper.getExecutionAddress();
if (monitor.isCancelled()) {
println("Emulation cancelled");
return;
}
if (executionAddress.equals(controlledReturnAddr)) {
println("Returned from function");
return;
}
if (!success) {
String lastError = emuHelper.getLastError();
printerr("Emulation Error: " + lastError);
return;
}
processBreakpoint(executionAddress);
} }
} catch (InterruptPcodeExecutionException e) {
finally { // Hit a breakpoint. Good.
// cleanup resources and release hold on currentProgram }
emuHelper.dispose(); catch (Throwable t) {
printerr("Emulation Error: " + t);
return;
}
if (monitor.isCancelled()) {
println("Emulation cancelled");
return;
}
Address executionAddress = emuThread.getCounter();
if (executionAddress.equals(controlledReturnAddr)) {
println("Returned from function");
return;
}
processBreakpoint(executionAddress);
} }
} }
@@ -156,19 +167,25 @@ public class EmuX86DeobfuscateExampleScript extends GhidraScript {
/** /**
* Perform processing for the various breakpoints. * Perform processing for the various breakpoints.
*
* @param addr current execute address where emulation has been suspended * @param addr current execute address where emulation has been suspended
* @throws Exception if an error occurs * @throws Exception if an error occurs
*/ */
private void processBreakpoint(Address addr) throws Exception { private void processBreakpoint(Address addr) throws Exception {
if (addr.equals(deobfuscateCall)) { if (addr.equals(deobfuscateCall)) {
lastDeobfuscateArg0 = emuHelper.readRegister("RDI").longValue(); Register rdi = currentProgram.getRegister("RDI");
lastDeobfuscateArg0 =
emuThread.getState().inspectRegisterValue(rdi).getUnsignedValue().longValue();
} }
else if (addr.equals(deobfuscateReturn)) { else if (addr.equals(deobfuscateReturn)) {
long deobfuscateReturnValue = emuHelper.readRegister("RAX").longValue(); Register rax = currentProgram.getRegister("RAX");
String str = "deobfuscate(src=0x" + Long.toHexString(lastDeobfuscateArg0) + ") -> \"" + long deobfuscateReturnValue =
emuHelper.readNullTerminatedString(getAddress(deobfuscateReturnValue), 32) + "\""; emuThread.getState().inspectRegisterValue(rax).getUnsignedValue().longValue();
String read = EmulatorUtilities.decodeNullTerminatedString(emuThread.getState(),
getAddress(deobfuscateReturnValue));
String str = """
deobfuscate(src=0x%x) -> "%s"\
""".formatted(lastDeobfuscateArg0, read);
String comment = getPreComment(deobfuscateReturn); String comment = getPreComment(deobfuscateReturn);
if (comment == null) { if (comment == null) {
comment = ""; comment = "";
@@ -192,14 +209,4 @@ public class EmuX86DeobfuscateExampleScript extends GhidraScript {
} }
return null; return null;
} }
private Address getSymbolAddress(String symbolName) throws NotFoundException {
Symbol symbol = SymbolUtilities.getLabelOrFunctionSymbol(currentProgram, symbolName,
err -> Msg.error(this, err));
if (symbol != null) {
return symbol.getAddress();
}
throw new NotFoundException("Failed to locate label: " + symbolName);
}
} }
@@ -23,23 +23,23 @@
// data. This script hooks the functions "malloc", "free" and "use_string" where the later // data. This script hooks the functions "malloc", "free" and "use_string" where the later
// simply prints the deobfuscated string passed as an argument. // simply prints the deobfuscated string passed as an argument.
//@category Examples.Emulation //@category Examples.Emulation
import java.util.HashMap; import java.util.*;
import java.util.Map;
import ghidra.app.emulator.EmulatorHelper;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.util.opinion.ElfLoader; import ghidra.app.util.opinion.ElfLoader;
import ghidra.pcode.emulate.EmulateExecutionState; import ghidra.pcode.emu.*;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.InsufficientBytesException; import ghidra.program.model.lang.InsufficientBytesException;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.NotFoundException; import ghidra.util.exception.NotFoundException;
public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript { public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
private static String PROGRAM_NAME = "deobHookExample"; private static final String PROGRAM_NAME = "deobHookExample";
// Heap allocation area // Heap allocation area
private static final int MALLOC_REGION_SIZE = 0x1000; private static final int MALLOC_REGION_SIZE = 0x1000;
@@ -47,7 +47,10 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
// Address used as final return location // Address used as final return location
private static final long CONTROLLED_RETURN_OFFSET = 0; private static final long CONTROLLED_RETURN_OFFSET = 0;
private EmulatorHelper emuHelper; private PcodeEmulator emu;
private PcodeThread<byte[]> emuThread;
private PcodeArithmetic<byte[]> arithmetic;
private SimpleMallocMgr mallocMgr; private SimpleMallocMgr mallocMgr;
// Important breakpoint locations for hooking behavior not contained with binary (e.g., dynamic library) // Important breakpoint locations for hooking behavior not contained with binary (e.g., dynamic library)
@@ -57,6 +60,7 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
private Address useStringEntry; private Address useStringEntry;
// Function locations // Function locations
private Function mainFunction;
private Address mainFunctionEntry; // start of emulation private Address mainFunctionEntry; // start of emulation
private Address controlledReturnAddr; // end of emulation private Address controlledReturnAddr; // end of emulation
@@ -69,18 +73,19 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
!"x86:LE:64:default".equals(currentProgram.getLanguageID().toString()) || !"x86:LE:64:default".equals(currentProgram.getLanguageID().toString()) ||
!ElfLoader.ELF_NAME.equals(format)) { !ElfLoader.ELF_NAME.equals(format)) {
printerr( printerr("""
"This emulation example script is specifically intended to be executed against the\n" + This emulation example script is specifically intended to be executed against
PROGRAM_NAME + the %s program whose source is contained within the GhidraClass exercise files
" program whose source is contained within the GhidraClass exercise files\n" + (see docs/GhidraClass/ExerciseFiles/Emulation/%s.c). This program should be
"(see docs/GhidraClass/ExerciseFiles/Emulation/" + PROGRAM_NAME + ".c).\n" + compiled using gcc for x86 64-bit, imported into your project, analyzed and
"This program should be compiled using gcc for x86 64-bit, imported into your project, \n" + open as the active program before running ths script."""
"analyzed and open as the active program before running ths script."); .formatted(PROGRAM_NAME, PROGRAM_NAME));
return; return;
} }
// Identify function be emulated // Identify function be emulated
mainFunctionEntry = getSymbolAddress("main"); mainFunctionEntry = getSymbolAddress("main");
mainFunction = currentProgram.getFunctionManager().getFunctionAt(mainFunctionEntry);
useStringEntry = getSymbolAddress("use_string"); useStringEntry = getSymbolAddress("use_string");
// Identify important symbol addresses // Identify important symbol addresses
@@ -88,59 +93,71 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
freeEntry = getExternalThunkAddress("free"); freeEntry = getExternalThunkAddress("free");
strlenEntry = getExternalThunkAddress("strlen"); strlenEntry = getExternalThunkAddress("strlen");
// Establish emulation helper // Establish emulator
emuHelper = new EmulatorHelper(currentProgram); emu = new PcodeEmulator(currentProgram.getLanguage()) {
try { @Override
// Initialize stack pointer (not used by this example) protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
long stackOffset = return super.createUseropLibrary().compose(new DeobfUseropLibrary<byte[]>());
(mainFunctionEntry.getAddressSpace().getMaxAddress().getOffset() >>> 1) - 0x7fff;
emuHelper.writeRegister(emuHelper.getStackPointerRegister(), stackOffset);
// Establish simple malloc memory manager with memory region spaced relative to stack pointer
mallocMgr = new SimpleMallocMgr(getAddress(stackOffset - 0x10000), MALLOC_REGION_SIZE);
// Setup hook breakpoints
emuHelper.setBreakpoint(mallocEntry);
emuHelper.setBreakpoint(freeEntry);
emuHelper.setBreakpoint(strlenEntry);
emuHelper.setBreakpoint(useStringEntry);
// Set controlled return location so we can identify return from emulated function
controlledReturnAddr = getAddress(CONTROLLED_RETURN_OFFSET);
emuHelper.writeStackValue(0, 8, CONTROLLED_RETURN_OFFSET);
emuHelper.setBreakpoint(controlledReturnAddr);
// This example directly manipulates the PC register to facilitate hooking
// which must alter the PC during a breakpoint, and optional stepping which does not
// permit an initial address to be specified.
emuHelper.writeRegister(emuHelper.getPCRegister(), mainFunctionEntry.getOffset());
Msg.debug(this, "EMU starting at " + emuHelper.getExecutionAddress());
// Execution loop until return from function or error occurs
while (!monitor.isCancelled()) {
// Use stepping if needed for troubleshooting - although it runs much slower
//boolean success = emuHelper.step();
boolean success = emuHelper.run(monitor);
Address executionAddress = emuHelper.getExecutionAddress();
if (monitor.isCancelled()) {
println("Emulation cancelled");
return;
}
if (executionAddress.equals(controlledReturnAddr)) {
println("Returned from function");
return;
}
if (!success) {
String lastError = emuHelper.getLastError();
printerr("Emulation Error: " + lastError);
return;
}
processBreakpoint(executionAddress);
} }
};
monitor.addCancelledListener(() -> {
emu.setSuspended(true);
});
emuThread = emu.newThread();
arithmetic = emuThread.getArithmetic();
EmulatorUtilities.loadProgram(emu, currentProgram);
// Initialize program counter, registers from context, and stack pointer
// Request more stack space that normal, so we can use the extra for the heap
EmulatorUtilities.initializeForFunction(emuThread, mainFunction,
EmulatorUtilities.DEFAULT_STACK_SIZE + MALLOC_REGION_SIZE);
Address stackBase =
EmulatorUtilities.inspectStackPointer(emuThread, currentProgram.getCompilerSpec());
// Establish simple malloc memory manager with memory region spaced relative to stack pointer
mallocMgr = new SimpleMallocMgr(
stackBase.subtract(EmulatorUtilities.DEFAULT_STACK_SIZE + MALLOC_REGION_SIZE),
MALLOC_REGION_SIZE);
// Setup hook breakpoints
emu.inject(mallocEntry, """
RAX = __libc_malloc(RDI);
__x86_64_RET();
""");
emu.inject(freeEntry, """
__libc_free(RDI);
__x86_64_RET();
""");
emu.inject(strlenEntry, """
RAX = __libc_strlen(RDI);
__x86_64_RET();
""");
emu.inject(useStringEntry, """
__hook_useString(RDI);
emu_exec_decoded();
""");
// Set controlled return location so we can identify return from emulated function
controlledReturnAddr = getAddress(CONTROLLED_RETURN_OFFSET);
emuThread.getState()
.setVar(stackBase.add(8), 8, false, arithmetic.fromConst(controlledReturnAddr));
emu.addBreakpoint(controlledReturnAddr, "1:1");
emuThread.overrideCounter(mainFunctionEntry);
emuThread.overrideContext(
currentProgram.getProgramContext().getDisassemblyContext(mainFunctionEntry));
Msg.debug(this, "EMU starting at " + emuThread.getCounter());
// First call to run should break after final return
try {
emuThread.run();
} }
finally { catch (InterruptPcodeExecutionException e) {
// cleanup resources and release hold on currentProgram // Hit the breakpoint. Good.
emuHelper.dispose(); }
catch (Throwable t) {
printerr("Emulation error: " + t);
return;
} }
} }
@@ -149,61 +166,9 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
} }
/** /**
* Perform processing for the various hook points where breakpoints have been set. * Get the thunk function corresponding to an external function. Such thunks should reside
* @param addr current execute address where emulation has been suspended * within the EXTERNAL block. (Note: this is specific to the ELF import)
* @throws Exception if an error occurs *
*/
private void processBreakpoint(Address addr) throws Exception {
// malloc hook
if (addr.equals(mallocEntry)) {
int size = emuHelper.readRegister("RDI").intValue();
Address memAddr = mallocMgr.malloc(size);
emuHelper.writeRegister("RAX", memAddr.getOffset());
}
// free hook
else if (addr.equals(freeEntry)) {
Address freeAddr = getAddress(emuHelper.readRegister("RDI").longValue());
mallocMgr.free(freeAddr);
}
// strlen hook
else if (addr.equals(strlenEntry)) {
Address ptr = getAddress(emuHelper.readRegister("RDI").longValue());
int len = 0;
while (emuHelper.readMemoryByte(ptr) != 0) {
++len;
ptr = ptr.next();
}
emuHelper.writeRegister("RAX", len);
}
// use_string hook - print string
else if (addr.equals(useStringEntry)) {
Address stringAddr = getAddress(emuHelper.readRegister("RDI").longValue());
String str = emuHelper.readNullTerminatedString(stringAddr, 32);
println("use_string: " + str); // output string argument to consoles
}
// unexpected
else {
if (emuHelper.getEmulateExecutionState() != EmulateExecutionState.BREAKPOINT) {
// assume we are stepping and simply return
return;
}
throw new NotFoundException("Unhandled breakpoint at " + addr);
}
// force early return
long returnOffset = emuHelper.readStackValue(0, 8, false).longValue();
emuHelper.writeRegister(emuHelper.getPCRegister(), returnOffset);
}
/**
* Get the thunk function corresponding to an external function. Such thunks
* should reside within the EXTERNAL block. (Note: this is specific to the ELF import)
* @param symbolName external function name * @param symbolName external function name
* @return address of thunk function which corresponds to an external function * @return address of thunk function which corresponds to an external function
* @throws NotFoundException if thunk not found * @throws NotFoundException if thunk not found
@@ -212,7 +177,7 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
Symbol externalSymbol = currentProgram.getSymbolTable().getExternalSymbol(symbolName); Symbol externalSymbol = currentProgram.getSymbolTable().getExternalSymbol(symbolName);
if (externalSymbol != null && externalSymbol.getSymbolType() == SymbolType.FUNCTION) { if (externalSymbol != null && externalSymbol.getSymbolType() == SymbolType.FUNCTION) {
Function f = (Function) externalSymbol.getObject(); Function f = (Function) externalSymbol.getObject();
Address[] thunkAddrs = f.getFunctionThunkAddresses(); Address[] thunkAddrs = f.getFunctionThunkAddresses(false);
if (thunkAddrs.length == 1) { if (thunkAddrs.length == 1) {
return thunkAddrs[0]; return thunkAddrs[0];
} }
@@ -222,6 +187,7 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
/** /**
* Get the global namespace symbol address which corresponds to the specified name. * Get the global namespace symbol address which corresponds to the specified name.
*
* @param symbolName global symbol name * @param symbolName global symbol name
* @return symbol address * @return symbol address
* @throws NotFoundException if symbol not found * @throws NotFoundException if symbol not found
@@ -235,6 +201,66 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
throw new NotFoundException("Failed to locate label: " + symbolName); throw new NotFoundException("Failed to locate label: " + symbolName);
} }
public class DeobfUseropLibrary<T> extends AnnotatedPcodeUseropLibrary<T> {
static final String SRC_X86_RET = """
RIP = *:8 RSP;
RSP = RSP + 8;
return [RIP];
""";
PcodeProgram progRet;
@PcodeUserop(canInline = true)
public void __x86_64_RET(@OpExecutor PcodeExecutor<T> executor,
@OpLibrary PcodeUseropLibrary<T> library) {
if (progRet == null) {
progRet = SleighProgramCompiler.compileUserop(executor.getLanguage(),
"__x86_64_RET", List.of(), SRC_X86_RET, PcodeUseropLibrary.nil(), List.of());
}
progRet.execute(executor, library);
}
@PcodeUserop(functional = true, hasSideEffects = true)
public long __libc_malloc(int size) throws InsufficientBytesException {
Address memAddr = mallocMgr.malloc(size);
return memAddr.getOffset();
}
@PcodeUserop(functional = true, hasSideEffects = true)
public void __libc_free(long ptr) {
mallocMgr.free(getAddress(ptr));
}
static final String SRC_STRLEN = """
__result = 0;
<loop>
if (*:1 (str+__result) == 0 || __result >= maxlen) goto <exit>;
__result = __result + 1;
goto <loop>;
<exit>""";
private PcodeProgram progStrlen;
@PcodeUserop(canInline = true)
public void __libc_strlen(@OpExecutor PcodeExecutor<T> executor,
@OpLibrary PcodeUseropLibrary<T> library, @OpOutput Varnode out, Varnode start) {
Varnode const128 =
new Varnode(executor.getLanguage().getAddressFactory().getConstantAddress(128), 4);
// NOTE: This assumes all calls to __libc_strlen have the same output and input varnodes
if (progStrlen == null) {
progStrlen = SleighProgramCompiler.compileUserop(executor.getLanguage(),
"__libc_strlen", List.of("__result", "str", "maxlen"),
SRC_STRLEN, PcodeUseropLibrary.nil(), List.of(out, start, const128));
}
progStrlen.execute(executor, library);
}
@PcodeUserop
public void __hook_useString(@OpState PcodeExecutorState<T> state, long ptr) {
Address addr = state.getLanguage().getDefaultDataSpace().getAddress(ptr);
String str = EmulatorUtilities.decodeNullTerminatedString(state, addr);
println("use_string: " + str); // output string argument to consoles
}
}
/** /**
* <code>SimpleMallocMgr</code> provides a simple malloc memory manager to be used by the * <code>SimpleMallocMgr</code> provides a simple malloc memory manager to be used by the
* malloc/free hooked implementations. * malloc/free hooked implementations.
@@ -246,8 +272,9 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
/** /**
* <code>SimpleMallocMgr</code> constructor. * <code>SimpleMallocMgr</code> constructor.
* @param rangeStart start of the free malloc region (i.e., Heap) which has been *
* deemed a safe * @param rangeStart start of the free malloc region (i.e., Heap) which has been deemed a
* safe
* @param byteSize * @param byteSize
* @throws AddressOverflowException * @throws AddressOverflowException
*/ */
@@ -1,13 +1,12 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,8 +19,6 @@ import ghidra.program.model.address.Address;
public interface ExecutionListener extends TestLogger { public interface ExecutionListener extends TestLogger {
public void stepCompleted(EmulatorTestRunner testRunner);
public void logWrite(EmulatorTestRunner testRunner, Address address, int size, byte[] values); public void logWrite(EmulatorTestRunner testRunner, Address address, int size, byte[] values);
public void logRead(EmulatorTestRunner testRunner, Address address, int size, byte[] values); public void logRead(EmulatorTestRunner testRunner, Address address, int size, byte[] values);
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,11 +15,14 @@
*/ */
package ghidra.test.processors.support; package ghidra.test.processors.support;
import java.nio.charset.Charset;
import java.util.*; import java.util.*;
import ghidra.app.emulator.EmulatorHelper;
import ghidra.app.util.PseudoDisassembler; import ghidra.app.util.PseudoDisassembler;
import ghidra.docking.settings.SettingsImpl; import ghidra.docking.settings.SettingsImpl;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.memstate.MemoryState; import ghidra.pcode.memstate.MemoryState;
import ghidra.pcode.utils.Utils; import ghidra.pcode.utils.Utils;
import ghidra.program.disassemble.Disassembler; import ghidra.program.disassemble.Disassembler;
@@ -36,9 +39,9 @@ import ghidra.util.exception.AssertException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** /**
* <code>PCodeTestAbstractControlBlock</code> data is models the general capabilities * <code>PCodeTestAbstractControlBlock</code> data is models the general capabilities of the
* of the TestInfo data structure which is used for different puposes as handled * TestInfo data structure which is used for different puposes as handled by extensions of this
* by extensions of this class. * class.
*/ */
public abstract class PCodeTestAbstractControlBlock { public abstract class PCodeTestAbstractControlBlock {
@@ -59,17 +62,18 @@ public abstract class PCodeTestAbstractControlBlock {
private HashMap<String, FunctionInfo> functionMap = new HashMap<>(); private HashMap<String, FunctionInfo> functionMap = new HashMap<>();
/** /**
* Construct test control block instance for the specified program. * Construct test control block instance for the specified program.
*
* @param program program containing control block structure * @param program program containing control block structure
* @param infoStructAddr program address where structure resides * @param infoStructAddr program address where structure resides
* @param infoStruct appropriate Info structure definition which will have array * @param infoStruct appropriate Info structure definition which will have array of FunctionInfo
* of FunctionInfo immediately following. * immediately following.
*/ */
PCodeTestAbstractControlBlock(Program program, Address infoStructAddr, Structure infoStruct) { PCodeTestAbstractControlBlock(Program program, Address infoStructAddr, Structure infoStruct) {
this.program = program; this.program = program;
this.pointerSize = program.getDataTypeManager().getDataOrganization().getPointerSize(); this.pointerSize = program.getDataTypeManager().getDataOrganization().getPointerSize();
this.infoStructAddr = infoStructAddr; this.infoStructAddr = infoStructAddr;
this.infoProgramStruct = (Structure) infoStruct.clone(program.getDataTypeManager()); this.infoProgramStruct = infoStruct.clone(program.getDataTypeManager());
codeSpace = program.getAddressFactory().getDefaultAddressSpace(); codeSpace = program.getAddressFactory().getDefaultAddressSpace();
dataSpace = program.getLanguage().getDefaultDataSpace(); dataSpace = program.getLanguage().getDefaultDataSpace();
@@ -95,10 +99,10 @@ public abstract class PCodeTestAbstractControlBlock {
} }
/** /**
* Force an existing reference to refer to the code space. Pointers * Force an existing reference to refer to the code space. Pointers created in the data space
* created in the data space refer to the data space by default, this method * refer to the data space by default, this method is used to change these pointers in the data
* is used to change these pointers in the data space to refer to * space to refer to code.
* code. *
* @param addr location with data space which contains code reference * @param addr location with data space which contains code reference
*/ */
void forceCodePointer(Address addr) { void forceCodePointer(Address addr) {
@@ -169,8 +173,8 @@ public abstract class PCodeTestAbstractControlBlock {
} }
/** /**
* Check for a Data pointer at the specified address and return the referenced * Check for a Data pointer at the specified address and return the referenced address.
* address. *
* @param addr address of stored pointer * @param addr address of stored pointer
* @return pointer referenced address or null if no pointer found * @return pointer referenced address or null if no pointer found
*/ */
@@ -182,7 +186,8 @@ public abstract class PCodeTestAbstractControlBlock {
return (Address) data.getValue(); return (Address) data.getValue();
} }
protected Address readCodePointer(MemBuffer buffer, int bufferOffset, boolean updateReference) throws MemoryAccessException { protected Address readCodePointer(MemBuffer buffer, int bufferOffset, boolean updateReference)
throws MemoryAccessException {
Address codePtr = readPointer(buffer, bufferOffset, codeSpace, updateReference); Address codePtr = readPointer(buffer, bufferOffset, codeSpace, updateReference);
// treat null pointer as special case - just return it // treat null pointer as special case - just return it
@@ -312,8 +317,9 @@ public abstract class PCodeTestAbstractControlBlock {
new TerminatedStringDataType(program.getDataTypeManager()); new TerminatedStringDataType(program.getDataTypeManager());
Structure functionInfoStruct = Structure functionInfoStruct =
(Structure) infoProgramStruct.getDataTypeManager().getDataType(CategoryPath.ROOT, (Structure) infoProgramStruct.getDataTypeManager()
"FunctionInfo"); .getDataType(CategoryPath.ROOT,
"FunctionInfo");
if (functionInfoStruct == null) { if (functionInfoStruct == null) {
throw new AssertException("FunctionInfo structure not yet resolved"); throw new AssertException("FunctionInfo structure not yet resolved");
} }
@@ -383,46 +389,62 @@ public abstract class PCodeTestAbstractControlBlock {
} }
protected String emuReadString(EmulatorHelper emu, Address strPtrAddr) { /**
* Read an ASCII-encoded string, perhaps with wide characters, from the emulator.
* <p>
* This is really a hack, but suffices for these tests. The character size is taken from the
* program's data organization. However, even if wide characters are used, this only reads the
* least-significant byte, effectively truncating each to the ASCII-2 range [0-255].
*
* @param emu the emulator
* @param strPtrAddr the pointer to the string
* @return the string
*/
protected String emuReadString(PcodeThread<byte[]> emu, Address strPtrAddr) {
DataOrganization dataOrganization = DataOrganization dataOrganization =
emu.getProgram().getDataTypeManager().getDataOrganization(); program.getDataTypeManager().getDataOrganization();
int charSize = dataOrganization.getCharSize(); int charSize = dataOrganization.getCharSize();
boolean isBigEndian = emu.getProgram().getMemory().isBigEndian(); boolean isBigEndian = emu.getLanguage().isBigEndian();
MemoryState memState = emu.getEmulator().getMemState(); PcodeExecutorState<byte[]> state = emu.getState();
long offset = strPtrAddr.getOffset();
if (isBigEndian) { if (isBigEndian) {
offset += (charSize - 1); strPtrAddr = strPtrAddr.add(charSize - 1);
} }
char[] buffer = new char[128]; MemBuffer memBuf = state.getConcreteBuffer(strPtrAddr, Purpose.INSPECT);
int offset = 0;
int index = 0; int index = 0;
byte[] buffer = new byte[128];
while (index < buffer.length) { while (index < buffer.length) {
buffer[index] = try {
(char) (memState.getValue(strPtrAddr.getAddressSpace(), offset, 1) & 0xff); buffer[index] = memBuf.getByte(offset);
}
catch (MemoryAccessException e) {
throw new AssertionError(e);
}
if (buffer[index] == 0) { if (buffer[index] == 0) {
break; break;
} }
offset += charSize; offset += charSize;
++index; ++index;
} }
return new String(buffer, 0, index); return new String(buffer, 0, index, Charset.forName("ASCII"));
} }
protected long emuRead(EmulatorHelper emu, Address addr, int size) { protected long emuRead(PcodeThread<byte[]> emu, Address addr, int size) {
if (size < 1 || size > 8) { if (size < 1 || size > 8) {
throw new IllegalArgumentException("Unsupported EMU read size: " + size); throw new IllegalArgumentException("Unsupported EMU read size: " + size);
} }
MemoryState memState = emu.getEmulator().getMemState(); return emu.getArithmetic()
return memState.getValue(addr.getAddressSpace(), addr.getOffset(), size); .toLong(emu.getState().inspectConcrete(addr, size), Purpose.INSPECT);
} }
protected void emuWrite(EmulatorHelper emu, Address addr, int size, long value) { protected void emuWrite(PcodeThread<byte[]> emu, Address addr, int size, long value) {
if (size < 1 || size > 8) { if (size < 1 || size > 8) {
throw new IllegalArgumentException("Unsupported EMU read size: " + size); throw new IllegalArgumentException("Unsupported EMU read size: " + size);
} }
MemoryState memState = emu.getEmulator().getMemState(); emu.getState().setVar(addr, size, false, emu.getArithmetic().fromConst(value, size));
memState.setValue(addr.getAddressSpace(), addr.getOffset(), size, value);
} }
protected Address getMirroredDataAddress(EmulatorTestRunner emuTestRunner, Address addr) { protected Address getMirroredDataAddress(EmulatorTestRunner emuTestRunner, Address addr) {
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -27,9 +27,9 @@ import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.Msg; import ghidra.util.Msg;
/** /**
* <code>PCodeTestControlBlock</code> data is read from each binary test file and * <code>PCodeTestControlBlock</code> data is read from each binary test file and identified by the
* identified by the MAIN_CONTROL_BLOCK_MAGIC 64-bit character field value at the start of the * MAIN_CONTROL_BLOCK_MAGIC 64-bit character field value at the start of the data structure. Only
* data structure. Only one instance of this should exist within the binary. * one instance of this should exist within the binary.
*/ */
public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock { public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
@@ -72,13 +72,14 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
private final PCodeTestResults testResults; private final PCodeTestResults testResults;
/** /**
* Construct test control block instance for the specified * Construct test control block instance for the specified program. Create TestInfo structure
* program. Create TestInfo structure data within program if requested. * data within program if requested.
*
* @param program program containing control block structure * @param program program containing control block structure
* @param restrictedSet the restricted memory area which should be searched * @param restrictedSet the restricted memory area which should be searched for control
* for control structures * structures
* @param testInfoStructAddr address of Main TestInfo structure * @param testInfoStructAddr address of Main TestInfo structure
* @param testFile original binary test file * @param testFile original binary test file
* @param cachedProgramPath program path within program file cache * @param cachedProgramPath program path within program file cache
* @param applyStruct create structure Data within program if true * @param applyStruct create structure Data within program if true
* @throws InvalidControlBlockException * @throws InvalidControlBlockException
@@ -109,6 +110,7 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
/** /**
* Find Main TestInfo structure within memory and return instance of PCodeTestControlBlock * Find Main TestInfo structure within memory and return instance of PCodeTestControlBlock
*
* @param program * @param program
* @param testFile original binary test file * @param testFile original binary test file
* @param restrictedSet a restricted set to be searched for control structures * @param restrictedSet a restricted set to be searched for control structures
@@ -294,33 +296,36 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
/** /**
* Enable/Diable sprintf use within P-Code test emulation. * Enable/Diable sprintf use within P-Code test emulation.
*
* @param emuTestRunner emulator test runner * @param emuTestRunner emulator test runner
* @param enable sprintf enablement * @param enable sprintf enablement
*/ */
void setSprintfEnabled(EmulatorTestRunner emuTestRunner, boolean enable) { void setSprintfEnabled(EmulatorTestRunner emuTestRunner, boolean enable) {
Address addr = Address addr =
getMirroredDataAddress(emuTestRunner, infoStructAddr.add(sprintfEnableOffset)); getMirroredDataAddress(emuTestRunner, infoStructAddr.add(sprintfEnableOffset));
emuWrite(emuTestRunner.getEmulatorHelper(), addr, SIZEOF_U4, enable ? 1 : 0); emuWrite(emuTestRunner.getEmulatorThread(), addr, SIZEOF_U4, enable ? 1 : 0);
} }
/** /**
* Get 'numpass' field value from emulation memory state * Get 'numpass' field value from emulation memory state
*
* @param emuTestRunner emulator test runner * @param emuTestRunner emulator test runner
* @return 'numpass' field value * @return 'numpass' field value
*/ */
int getNumberPassed(EmulatorTestRunner emuTestRunner) { int getNumberPassed(EmulatorTestRunner emuTestRunner) {
Address addr = getMirroredDataAddress(emuTestRunner, infoStructAddr.add(numPassOffset)); Address addr = getMirroredDataAddress(emuTestRunner, infoStructAddr.add(numPassOffset));
return (int) emuRead(emuTestRunner.getEmulatorHelper(), addr, SIZEOF_U4); return (int) emuRead(emuTestRunner.getEmulatorThread(), addr, SIZEOF_U4);
} }
/** /**
* Set 'numpass' field value within emulation memory state * Set 'numpass' field value within emulation memory state
*
* @param emuTestRunner emulator test runner * @param emuTestRunner emulator test runner
* @param value field value * @param value field value
*/ */
void setNumberPassed(EmulatorTestRunner emuTestRunner, int value) { void setNumberPassed(EmulatorTestRunner emuTestRunner, int value) {
Address addr = getMirroredDataAddress(emuTestRunner, infoStructAddr.add(numPassOffset)); Address addr = getMirroredDataAddress(emuTestRunner, infoStructAddr.add(numPassOffset));
emuWrite(emuTestRunner.getEmulatorHelper(), addr, SIZEOF_U4, value); emuWrite(emuTestRunner.getEmulatorThread(), addr, SIZEOF_U4, value);
} }
void resultIgnored(String testGroupName, String testName, boolean passed) { void resultIgnored(String testGroupName, String testName, boolean passed) {
@@ -335,6 +340,7 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
/** /**
* Get the number of passed tests which should be ignored * Get the number of passed tests which should be ignored
*
* @return number of passed tests which should be ignored * @return number of passed tests which should be ignored
*/ */
int getNumberPassedIgnored() { int getNumberPassedIgnored() {
@@ -343,6 +349,7 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
/** /**
* Get the number of failed tests which should be ignored * Get the number of failed tests which should be ignored
*
* @return number of failed tests which should be ignored * @return number of failed tests which should be ignored
*/ */
int getNumberFailedIgnored() { int getNumberFailedIgnored() {
@@ -359,62 +366,68 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
/** /**
* Get 'numfail' field value from emulation memory state * Get 'numfail' field value from emulation memory state
*
* @param emuTestRunner emulator test runner * @param emuTestRunner emulator test runner
* @return 'numfail' field value * @return 'numfail' field value
*/ */
int getNumberFailed(EmulatorTestRunner emuTestRunner) { int getNumberFailed(EmulatorTestRunner emuTestRunner) {
Address addr = getMirroredDataAddress(emuTestRunner, infoStructAddr.add(numFailOffset)); Address addr = getMirroredDataAddress(emuTestRunner, infoStructAddr.add(numFailOffset));
return (int) emuRead(emuTestRunner.getEmulatorHelper(), addr, SIZEOF_U4); return (int) emuRead(emuTestRunner.getEmulatorThread(), addr, SIZEOF_U4);
} }
/** /**
* Set 'numfail' field value within emulation memory state * Set 'numfail' field value within emulation memory state
*
* @param emuTestRunner emulator test runner * @param emuTestRunner emulator test runner
* @param value field value * @param value field value
*/ */
void setNumberFailed(EmulatorTestRunner emuTestRunner, int value) { void setNumberFailed(EmulatorTestRunner emuTestRunner, int value) {
Address addr = getMirroredDataAddress(emuTestRunner, infoStructAddr.add(numFailOffset)); Address addr = getMirroredDataAddress(emuTestRunner, infoStructAddr.add(numFailOffset));
emuWrite(emuTestRunner.getEmulatorHelper(), addr, SIZEOF_U4, value); emuWrite(emuTestRunner.getEmulatorThread(), addr, SIZEOF_U4, value);
} }
/** /**
* Get 'lastTestPos' field value from emulation memory state * Get 'lastTestPos' field value from emulation memory state
*
* @param emuTestRunner emulator test runner * @param emuTestRunner emulator test runner
* @return 'lastTestPos' field value * @return 'lastTestPos' field value
*/ */
int getLastTestIndex(EmulatorTestRunner emuTestRunner) { int getLastTestIndex(EmulatorTestRunner emuTestRunner) {
Address addr = getMirroredDataAddress(emuTestRunner, infoStructAddr.add(lastTestPosOffset)); Address addr = getMirroredDataAddress(emuTestRunner, infoStructAddr.add(lastTestPosOffset));
return (int) emuRead(emuTestRunner.getEmulatorHelper(), addr, SIZEOF_U4); return (int) emuRead(emuTestRunner.getEmulatorThread(), addr, SIZEOF_U4);
} }
/** /**
* Get 'lastErrorLine' field value from emulation memory state * Get 'lastErrorLine' field value from emulation memory state
*
* @param emuTestRunner emulator test runner * @param emuTestRunner emulator test runner
* @return 'lastErrorLine' field value * @return 'lastErrorLine' field value
*/ */
int getLastErrorLine(EmulatorTestRunner emuTestRunner) { int getLastErrorLine(EmulatorTestRunner emuTestRunner) {
Address addr = Address addr =
getMirroredDataAddress(emuTestRunner, infoStructAddr.add(lastErrorLineOffset)); getMirroredDataAddress(emuTestRunner, infoStructAddr.add(lastErrorLineOffset));
return (int) emuRead(emuTestRunner.getEmulatorHelper(), addr, SIZEOF_U4); return (int) emuRead(emuTestRunner.getEmulatorThread(), addr, SIZEOF_U4);
} }
/** /**
* Get 'lastErrorFile' string value from emulation memory state. Must follow string * Get 'lastErrorFile' string value from emulation memory state. Must follow string pointer
* pointer contained within lastErrorFile field. * contained within lastErrorFile field.
*
* @param emuTestRunner emulator test runner * @param emuTestRunner emulator test runner
* @return 'lastErrorLine' field value * @return 'lastErrorLine' field value
*/ */
String getLastErrorFile(EmulatorTestRunner emuTestRunner) { String getLastErrorFile(EmulatorTestRunner emuTestRunner) {
Address addr = Address addr =
getMirroredDataAddress(emuTestRunner, infoStructAddr.add(lastErrorFileOffset)); getMirroredDataAddress(emuTestRunner, infoStructAddr.add(lastErrorFileOffset));
long fileNameOffset = emuRead(emuTestRunner.getEmulatorHelper(), addr, pointerSize); long fileNameOffset = emuRead(emuTestRunner.getEmulatorThread(), addr, pointerSize);
addr = addr.getNewAddress(fileNameOffset, true); addr = addr.getNewAddress(fileNameOffset, true);
addr = getMirroredDataAddress(emuTestRunner, addr); addr = getMirroredDataAddress(emuTestRunner, addr);
return emuReadString(emuTestRunner.getEmulatorHelper(), addr); return emuReadString(emuTestRunner.getEmulatorThread(), addr);
} }
/** /**
* Get the name of the last test function to be run * Get the name of the last test function to be run
*
* @param emuTestRunner * @param emuTestRunner
* @return last test function name * @return last test function name
*/ */
@@ -422,10 +435,10 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
PCodeTestGroup activeGroup) { PCodeTestGroup activeGroup) {
Address ptrStorageAddr = infoStructAddr.add(lastFuncOffset); Address ptrStorageAddr = infoStructAddr.add(lastFuncOffset);
Address ptrAddr = getMirroredDataAddress(emuTestRunner, ptrStorageAddr); Address ptrAddr = getMirroredDataAddress(emuTestRunner, ptrStorageAddr);
long funcNameOffset = emuRead(emuTestRunner.getEmulatorHelper(), ptrAddr, pointerSize); long funcNameOffset = emuRead(emuTestRunner.getEmulatorThread(), ptrAddr, pointerSize);
Address strAddr = ptrAddr.getNewAddress(funcNameOffset, true); Address strAddr = ptrAddr.getNewAddress(funcNameOffset, true);
strAddr = getMirroredDataAddress(emuTestRunner, strAddr); strAddr = getMirroredDataAddress(emuTestRunner, strAddr);
String fnName = emuReadString(emuTestRunner.getEmulatorHelper(), strAddr); String fnName = emuReadString(emuTestRunner.getEmulatorThread(), strAddr);
if ("none".equals(fnName)) { if ("none".equals(fnName)) {
if (logger != null) { if (logger != null) {
logger.log(activeGroup, "ERROR last executed function name pointer stored at " + logger.log(activeGroup, "ERROR last executed function name pointer stored at " +
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -65,20 +65,21 @@ import utilities.util.FileUtilities;
import utility.application.ApplicationLayout; import utility.application.ApplicationLayout;
/** /**
* <code>ProcessorEmulatorTestAdapter</code> provides an abstract JUnit test implementation * <code>ProcessorEmulatorTestAdapter</code> provides an abstract JUnit test implementation for
* for processor-specific test cases. All test cases which extend this class must have a * processor-specific test cases. All test cases which extend this class must have a class name
* class name which ends with 'EmulatorTest' and starts with the processor designator which * which ends with 'EmulatorTest' and starts with the processor designator which will be used to
* will be used to identify associated test binaries within either the processor module's * identify associated test binaries within either the processor module's data/pcodetests/ directory
* data/pcodetests/ directory or the Ghidra/Test/TestResources/data/pcodetests/ directory generally * or the Ghidra/Test/TestResources/data/pcodetests/ directory generally contained within the binary
* contained within the binary repository (e.g., ghidra.bin). * repository (e.g., ghidra.bin).
* <p> * <p>
* Within the pcodetests directory all files and folders which start with the prefix * Within the pcodetests directory all files and folders which start with the prefix
* {@literal <processor-designator>_pcodetest*} will be processed. All files contained within a matching * {@literal <processor-designator>_pcodetest*} will be processed. All files contained within a
* subdirectory will be treated as related binaries and imported. Any *.gzf file will be * matching subdirectory will be treated as related binaries and imported. Any *.gzf file will be
* imported but assumed to be pre-analyzed. Binary files to be imported and analyzed must * imported but assumed to be pre-analyzed. Binary files to be imported and analyzed must utilize
* utilize the *.out file extension. * the *.out file extension.
* <p> * <p>
* JUnit X86EmulatorTest could utilize the following binary file naming strategy: * JUnit X86EmulatorTest could utilize the following binary file naming strategy:
*
* <pre> * <pre>
* pcodetests/X86_PCodeTests * pcodetests/X86_PCodeTests
* - binary1.o * - binary1.o
@@ -92,25 +93,22 @@ import utility.application.ApplicationLayout;
* - pcodetests/X86_PCodeTest.out * - pcodetests/X86_PCodeTest.out
* </pre> * </pre>
* *
* Any *.out binary found will be imported and analyzed. The resulting program will * Any *.out binary found will be imported and analyzed. The resulting program will be stored as a
* be stored as a gzf in the test-output cache directory. These cached files will be used * gzf in the test-output cache directory. These cached files will be used instead of a test
* instead of a test resource binary if that binary's md5 checksum has not changed since its cached * resource binary if that binary's md5 checksum has not changed since its cached gzf was created.
* gzf was created. This use of cache files will allow the tests to run quickly on subsequent * This use of cache files will allow the tests to run quickly on subsequent executions. If
* executions. If re-analysis is required, the cache will need to be cleared manually. * re-analysis is required, the cache will need to be cleared manually.
* *
* NOTES: * NOTES: 1. Dummy Test Methods must be added for all known test groups. See bottom of this file.
* 1. Dummy Test Methods must be added for all known test groups. See bottom of this file. This * This all allows for the single test trace mode execution to work within Eclipse. 2. Trace logging
* all allows for the single test trace mode execution to work within Eclipse. * disabled by default when all test groups are run (see buildEmulatorTestSuite method). Specific
* 2. Trace logging disabled by default when all test groups are run (see buildEmulatorTestSuite method). * traceLevel and traceLog file controlled via environment properties EmuTestTraceLevel and
* Specific traceLevel and traceLog file controlled via environment properties * EmuTestTraceFile. 3. The TestInfo structure must be properly maintained within the datatype
* EmuTestTraceLevel and EmuTestTraceFile. * archive EmuTesting.gdt and field naming consistent with use in PCodeTestControlBlock.java 4. The
* 3. The TestInfo structure must be properly maintained within the datatype archive EmuTesting.gdt * {@link #initializeState(EmulatorTestRunner, Program)} may be overriden to initialize the register
* and field naming consistent with use in PCodeTestControlBlock.java * values if needed. This should be based upon symbols or other program information if possible
* 4. The {@link #initializeState(EmulatorTestRunner, Program)} may be overriden to initialize the * since hardcoded constants may not track future builds of a test binaries. An attempt is made to
* register values if needed. This should be based upon symbols or other program information * initialize the stack pointer automatically based upon well known stack initialization symbols.
* if possible since hardcoded constants may not track future builds of a test binaries.
* An attempt is made to initialize the stack pointer automatically based upon well known
* stack initialization symbols.
*/ */
public abstract class ProcessorEmulatorTestAdapter extends TestCase implements ExecutionListener { public abstract class ProcessorEmulatorTestAdapter extends TestCase implements ExecutionListener {
@@ -495,10 +493,11 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
} }
/** /**
* Create TestSuite based upon available test groups contained within binary * Create TestSuite based upon available test groups contained within binary test files
* test files associated with target processor. * associated with target processor.
* @param emulatorTestClass test which extends <code>ProcessorEmulatorTestAdapter</code> *
* and whose name ends with "EmulatorTest". * @param emulatorTestClass test which extends <code>ProcessorEmulatorTestAdapter</code> and
* whose name ends with "EmulatorTest".
* @return test suite * @return test suite
*/ */
public static final Test buildEmulatorTestSuite(Class<?> emulatorTestClass) { public static final Test buildEmulatorTestSuite(Class<?> emulatorTestClass) {
@@ -825,7 +824,8 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
int byteCount = dumpSize * elementSize; int byteCount = dumpSize * elementSize;
byte[] bytes = emulatorTestRunner.getEmulatorHelper().readMemory(dumpAddr, byteCount); byte[] bytes =
emulatorTestRunner.getEmulatorThread().getState().inspectConcrete(dumpAddr, byteCount);
int index = 0; int index = 0;
log(null, "MEMORY DUMP (" + elementFormat + "): " + comment); log(null, "MEMORY DUMP (" + elementFormat + "): " + comment);
@@ -902,11 +902,6 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
log(testRunner.getTestGroup(), " Write " + formatAssignmentString(address, size, values)); log(testRunner.getTestGroup(), " Write " + formatAssignmentString(address, size, values));
} }
@Override
public void stepCompleted(EmulatorTestRunner testRunner) {
logState(testRunner);
}
/** /**
* Converts the stack trace into a string * Converts the stack trace into a string
*/ */
@@ -1088,8 +1083,7 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
} }
/** /**
* Single unit test which handles named test group specified during test * Single unit test which handles named test group specified during test instantiation.
* instantiation.
*/ */
@Override @Override
public final void runTest() { public final void runTest() {
@@ -1187,7 +1181,7 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
boolean done; boolean done;
if (traceDisabled) { if (traceDisabled) {
done = testRunner.execute(EXECUTION_TIMEOUT_MS, TaskMonitor.DUMMY); done = testRunner.execute(EXECUTION_TIMEOUT_MS);
} }
else { else {
done = testRunner.executeSingleStep(MAX_EXECUTION_STEPS); done = testRunner.executeSingleStep(MAX_EXECUTION_STEPS);
@@ -1327,6 +1321,7 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
/** /**
* Get symbol name which defines initial stack pointer offset * Get symbol name which defines initial stack pointer offset
*
* @return stack symbol or null * @return stack symbol or null
*/ */
protected String getPreferredStackSymbolName() { protected String getPreferredStackSymbolName() {
@@ -1350,8 +1345,7 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
Address currentAddr = testRunner.getCurrentAddress(); Address currentAddr = testRunner.getCurrentAddress();
// dump context register state if decode failure // dump context register state if decode failure
RegisterValue contextRegValue = RegisterValue contextRegValue = testRunner.getEmulatorThread().getContext();
testRunner.getEmulatorHelper().getEmulator().getContextRegisterValue();
if (contextRegValue == null) { if (contextRegValue == null) {
return; return;
} }
@@ -1383,7 +1377,8 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
} }
// check for memory modification // check for memory modification
byte[] emuBytes = testRunner.getEmulatorHelper().readMemory(currentAddr, len); byte[] emuBytes =
testRunner.getEmulatorThread().getState().inspectConcrete(currentAddr, len);
byte[] programBytes = new byte[emuBytes.length]; byte[] programBytes = new byte[emuBytes.length];
program.getMemory().getBytes(currentAddr, programBytes); program.getMemory().getBytes(currentAddr, programBytes);
if (!Arrays.equals(emuBytes, programBytes)) { if (!Arrays.equals(emuBytes, programBytes)) {
@@ -1425,6 +1420,7 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
/** /**
* Get the maximum defined memory address ignoring any overlays which have been defined. * Get the maximum defined memory address ignoring any overlays which have been defined.
*
* @return max defined physical address * @return max defined physical address
*/ */
protected static final Address getMaxDefinedMemoryAddress(Program program) { protected static final Address getMaxDefinedMemoryAddress(Program program) {
@@ -1441,10 +1437,10 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
} }
/** /**
* Add specified test names to the set of tests which should be ignored due to know issues * Add specified test names to the set of tests which should be ignored due to know issues or
* or limitations. The tests will still be executed, if present, however they will * limitations. The tests will still be executed, if present, however they will not be included
* not be included in pass/fail counts. They will appear in log as "(IGNORED)" test * in pass/fail counts. They will appear in log as "(IGNORED)" test result.
* result. *
* @param testNames one or more test names to be ignored * @param testNames one or more test names to be ignored
*/ */
protected void addIgnoredTests(String... testNames) { protected void addIgnoredTests(String... testNames) {
@@ -1456,10 +1452,10 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
// //
/** /**
* Get the processor designator used to identify test binary files/folder. * Get the processor designator used to identify test binary files/folder. The default
* The default implementation requires the JUnit test class name to end with * implementation requires the JUnit test class name to end with "EmulatorTest" where the
* "EmulatorTest" where the portion of the name proceeding this suffix will be * portion of the name proceeding this suffix will be used as the processor designator
* used as the processor designator *
* @return processor designator * @return processor designator
*/ */
protected String getProcessorDesignator() { protected String getProcessorDesignator() {
@@ -1473,6 +1469,7 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
/** /**
* Get CUint file designator if use of A, B, C... is not suitable. * Get CUint file designator if use of A, B, C... is not suitable.
*
* @param fileIndex file index within sorted list * @param fileIndex file index within sorted list
* @param filePath binary file path * @param filePath binary file path
* @return short file designator for use in qualified test name * @return short file designator for use in qualified test name
@@ -1482,12 +1479,12 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
} }
/** /**
* Invoked for each program immediately prior to executing a test group. * Invoked for each program immediately prior to executing a test group. By default this method
* By default this method will initialize the register states based upon the * will initialize the register states based upon the specific register values/context stored at
* specific register values/context stored at the test group function entry point. * the test group function entry point. Such register values may have been established via the
* Such register values may have been established via the processor specification, * processor specification, loader or analyzers. A specific test may override or extend this
* loader or analyzers. A specific test may override or extend * behavior for other registers as needed.
* this behavior for other registers as needed. *
* @param testRunner emulator group test runner/facilitator * @param testRunner emulator group test runner/facilitator
* @param program * @param program
* @throws Exception if initialization criteria has not been satisfied * @throws Exception if initialization criteria has not been satisfied
@@ -1513,11 +1510,11 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
} }
/** /**
* Invoked immediately following import allow byte processing prior to * Invoked immediately following import allow byte processing prior to control structure
* control structure identification. * identification. NOTE: This method will only be invoked once during the first test setup for
* NOTE: This method will only be invoked once during the first test setup * all test binaries found. This method will not be invoked during subsequent tests since the
* for all test binaries found. This method will not be invoked * analyzed program will be cached.
* during subsequent tests since the analyzed program will be cached. *
* @param program * @param program
* @throws Exception * @throws Exception
*/ */
@@ -1527,10 +1524,10 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
/** /**
* Invoked prior to analysis to allow analysis options or other pre-analysis * Invoked prior to analysis to allow analysis options or other pre-analysis
* inspection/modification to be performed. * inspection/modification to be performed. NOTE: This method will only be invoked once during
* NOTE: This method will only be invoked once during the first test setup * the first test setup for all test binaries found. This method will not be invoked during
* for all test binaries found. This method will not be invoked * subsequent tests since the analyzed program will be cached.
* during subsequent tests since the analyzed program will be cached. *
* @param program * @param program
* @throws Exception * @throws Exception
*/ */
@@ -1539,11 +1536,11 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
} }
/** /**
* Invoked for non-gzf files immediately after the analyze method to * Invoked for non-gzf files immediately after the analyze method to perform any follow-up
* perform any follow-up changes of inspection of the program. * changes of inspection of the program. NOTE: This method will only be invoked once during the
* NOTE: This method will only be invoked once during the first test setup * first test setup for all test binaries found. This method will not be invoked during
* for all test binaries found. This method will not be invoked * subsequent tests since the analyzed program will be cached.
* during subsequent tests since the analyzed program will be cached. *
* @param program * @param program
* @throws Exception * @throws Exception
*/ */
@@ -1552,10 +1549,10 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
} }
/** /**
* Invoked for non-gzf files to perform auto-analysis. * Invoked for non-gzf files to perform auto-analysis. NOTE: This method will only be invoked
* NOTE: This method will only be invoked once during the first test setup * once during the first test setup for all test binaries found. This method will not be invoked
* for all test binaries found. This method will not be invoked
* during subsequent tests since the analyzed program will be cached. * during subsequent tests since the analyzed program will be cached.
*
* @param program * @param program
* @throws Exception * @throws Exception
*/ */
@@ -1713,8 +1710,9 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
} }
/** /**
* Get the loader class which should be used. A null value should be * Get the loader class which should be used. A null value should be return to use the preferred
* return to use the preferred loader. * loader.
*
* @return loader class or null * @return loader class or null
*/ */
protected Class<? extends Loader> getLoaderClass() { protected Class<? extends Loader> getLoaderClass() {
@@ -2137,7 +2135,9 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
} }
/** /**
* Force proper code address alignment to compensate for address encoding schemes (e.g., Thumb mode) * Force proper code address alignment to compensate for address encoding schemes (e.g., Thumb
* mode)
*
* @param offset * @param offset
* @param alignment * @param alignment
* @return * @return
@@ -2147,7 +2147,9 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
} }
/** /**
* Force proper code address alignment to compensate for address encoding schemes (e.g., Thumb mode) * Force proper code address alignment to compensate for address encoding schemes (e.g., Thumb
* mode)
*
* @param addr * @param addr
* @param alignment * @param alignment
* @return * @return
@@ -159,7 +159,7 @@ public class AdaptedEmulator implements Emulator {
@Override @Override
public <A, T> AddressSetView readUninitialized(PcodeExecutorStatePiece<A, T> piece, public <A, T> AddressSetView readUninitialized(PcodeExecutorStatePiece<A, T> piece,
AddressSetView set) { AddressSetView set, Reason reason) {
PcodeExecutorStatePiece<A, byte[]> bytesPiece = PcodeExecutorStatePiece<A, byte[]> bytesPiece =
PcodeStateCallbacks.checkValueDomain(piece, byte[].class); PcodeStateCallbacks.checkValueDomain(piece, byte[].class);
if (bytesPiece == null) { if (bytesPiece == null) {
@@ -18,6 +18,7 @@ package ghidra.pcode.emu;
import java.util.List; import java.util.List;
import ghidra.pcode.exec.*; import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Instruction; import ghidra.program.model.listing.Instruction;
@@ -29,18 +30,18 @@ import ghidra.program.model.pcode.PcodeOp;
* <p> * <p>
* The two (currently) methods that do not implement a pure broadcast pattern are * The two (currently) methods that do not implement a pure broadcast pattern are
* {@link #handleMissingUserop(PcodeThread, PcodeOp, PcodeFrame, String, PcodeUseropLibrary)} and * {@link #handleMissingUserop(PcodeThread, PcodeOp, PcodeFrame, String, PcodeUseropLibrary)} and
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView)}. For a missing * {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView, Reason)}. For a
* userop, it will terminate after the first delegate returns {@code true}, in which case it also * missing userop, it will terminate after the first delegate returns {@code true}, in which case it
* returns {@code true}. If all delegates return {@code false}, then it returns {@code false}. For * also returns {@code true}. If all delegates return {@code false}, then it returns {@code false}.
* an uninitialized read, each delegate's returned "still-uninitialized" set is passed to the * For an uninitialized read, each delegate's returned "still-uninitialized" set is passed to the
* subsequent delegate. The first delegate gets the set as passed into the composition. The set * subsequent delegate. The first delegate gets the set as passed into the composition. The set
* returned by the composition is that returned by the last delegate. This terminates early when any * returned by the composition is that returned by the last delegate. This terminates early when any
* delegate returns the empty set. * delegate returns the empty set.
* *
* <p> * <p>
* One (currently) other method has a non-void return type: * One (currently) other method has a non-void return type:
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int)}. The * {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Reason)}.
* callback is broadcast as expected, and the return value is the max returned by the delegates. * The callback is broadcast as expected, and the return value is the max returned by the delegates.
* *
* @param <T> the emulator's value domain * @param <T> the emulator's value domain
*/ */
@@ -207,8 +208,8 @@ public class ComposedPcodeEmulationCallbacks<T> implements PcodeEmulationCallbac
} }
@Override @Override
public <A, U> int readUninitialized(PcodeThread<T> thread, public <A, U> int readUninitialized(PcodeThread<T> thread, PcodeExecutorStatePiece<A, U> piece,
PcodeExecutorStatePiece<A, U> piece, AddressSpace space, A offset, int length) { AddressSpace space, A offset, int length, Reason reason) {
/** /**
* NOTE: This could use some work. It's a bit onerous to specify arbitrary, possibly * NOTE: This could use some work. It's a bit onerous to specify arbitrary, possibly
* disjoint, sets of offsets of type A. Can only be guaranteed to mean something if A is * disjoint, sets of offsets of type A. Can only be guaranteed to mean something if A is
@@ -218,7 +219,8 @@ public class ComposedPcodeEmulationCallbacks<T> implements PcodeEmulationCallbac
*/ */
int maxL = 0; int maxL = 0;
for (PcodeEmulationCallbacks<T> d : delegates) { for (PcodeEmulationCallbacks<T> d : delegates) {
maxL = Math.max(maxL, d.readUninitialized(thread, piece, space, offset, length)); maxL =
Math.max(maxL, d.readUninitialized(thread, piece, space, offset, length, reason));
if (maxL == length) { if (maxL == length) {
return maxL; return maxL;
} }
@@ -228,10 +230,10 @@ public class ComposedPcodeEmulationCallbacks<T> implements PcodeEmulationCallbac
@Override @Override
public <A, U> AddressSetView readUninitialized(PcodeThread<T> thread, public <A, U> AddressSetView readUninitialized(PcodeThread<T> thread,
PcodeExecutorStatePiece<A, U> piece, AddressSetView set) { PcodeExecutorStatePiece<A, U> piece, AddressSetView set, Reason reason) {
AddressSetView remains = set; AddressSetView remains = set;
for (PcodeEmulationCallbacks<T> d : delegates) { for (PcodeEmulationCallbacks<T> d : delegates) {
remains = d.readUninitialized(thread, piece, remains); remains = d.readUninitialized(thread, piece, remains, reason);
if (remains.isEmpty()) { if (remains.isEmpty()) {
return remains; return remains;
} }
@@ -374,9 +374,13 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@Override @Override
public void assignContext(RegisterValue context) { public void assignContext(RegisterValue context) {
if (!context.getRegister().isProcessorContext()) { if (context.getRegister().getBaseRegister() != contextreg) {
throw new IllegalArgumentException("context must be the contextreg value"); throw new IllegalArgumentException("context must be the contextreg value");
} }
if (this.context == null) {
assert this.contextreg == Register.NO_CONTEXT;
return;
}
this.context = this.context.assign(context.getRegister(), context); this.context = this.context.assign(context.getRegister(), context);
} }
@@ -386,7 +390,14 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
} }
protected final void writeContext(RegisterValue context) { protected final void writeContext(RegisterValue context) {
if (contextreg == Register.NO_CONTEXT && context == null) {
return;
}
assignContext(context); assignContext(context);
if (this.context == null) {
assert this.contextreg == Register.NO_CONTEXT;
return;
}
state.setVar(contextreg, arithmetic.fromConst( state.setVar(contextreg, arithmetic.fromConst(
this.context.getUnsignedValueIgnoreMask(), this.context.getUnsignedValueIgnoreMask(),
contextreg.getMinimumByteSize(), true)); contextreg.getMinimumByteSize(), true));
@@ -16,16 +16,20 @@
package ghidra.pcode.emu; package ghidra.pcode.emu;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Arrays; import java.util.Arrays;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import ghidra.pcode.exec.PcodeArithmetic; import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorState; import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.*;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.DifferenceAddressSetView; import ghidra.util.DifferenceAddressSetView;
import ghidra.util.Msg; import ghidra.util.Msg;
@@ -232,6 +236,51 @@ public enum EmulatorUtilities {
return chooseStackRange(program, entry, DEFAULT_STACK_SIZE); return chooseStackRange(program, entry, DEFAULT_STACK_SIZE);
} }
/**
* Initialize the given thread's registers with the given program counter and (optionall)
* program register context.
*
* @param <T> the type of values in the emulator
* @param thread the thread to initialize
* @param program optionally, the program whose context to read
* @param pc the program counter, also used for register context
*/
public static <T> void initializeRegisters(PcodeThread<T> thread, Program program, Address pc) {
if (program != null) {
ThreadPcodeExecutorState<T> state = thread.getState();
ProgramProcessorContext ctx =
new ProgramProcessorContext(program.getProgramContext(), pc);
for (Register reg : ctx.getRegisters()) {
if (!reg.isBaseRegister() || reg.isProcessorContext()) {
continue;
}
RegisterValue rv = ctx.getRegisterValue(reg);
if (rv == null || !rv.hasAnyValue()) {
continue;
}
if (!rv.hasValue()) {
rv = new RegisterValue(reg, BigInteger.ZERO).combineValues(rv);
}
/**
* NOTE: In theory, there's no need to combine masked values, if this is a fresh
* emulator. If I had to guess, the client would want their values to take
* precedence, so they should overwrite the values after calling this method.
* Combining can be problematic, because the emulator could return some abstraction
* for the current value.
*/
state.setRegisterValue(rv);
}
}
thread.overrideCounter(pc);
if (program != null) {
thread.overrideContext(program.getProgramContext().getDisassemblyContext(pc));
}
else {
thread.overrideContextWithDefault();
}
}
/** /**
* Prepare a thread to emulate a given function * Prepare a thread to emulate a given function
* *
@@ -250,31 +299,88 @@ public enum EmulatorUtilities {
Register sp = cSpec.getStackPointer(); Register sp = cSpec.getStackPointer();
ThreadPcodeExecutorState<T> state = thread.getState(); ThreadPcodeExecutorState<T> state = thread.getState();
ProgramProcessorContext ctx = initializeRegisters(thread, program, entry);
new ProgramProcessorContext(program.getProgramContext(), entry);
for (Register reg : ctx.getRegisters()) {
if (!reg.isBaseRegister()) {
continue;
}
RegisterValue rv = ctx.getRegisterValue(reg);
if (rv == null || !rv.hasAnyValue()) {
continue;
}
/**
* NOTE: In theory, there's no need to combine masked values, if this is a fresh
* emulator. If I had to guess, the client would want their values to take precedence,
* so they should overwrite the values after calling this method. Combining can be
* problematic, because the emulator could return some abstraction for the current
* value.
*/
state.setRegisterValue(rv);
}
AddressRange stack = chooseStackRange(program, entry); AddressRange stack = chooseStackRange(program, entry, stackSize);
long stackOffset = cSpec.stackGrowsNegative() ? stack.getMaxAddress().getOffset() + 1 long stackOffset = cSpec.stackGrowsNegative() ? stack.getMaxAddress().getOffset() + 1
: stack.getMinAddress().getOffset(); : stack.getMinAddress().getOffset();
state.setVar(sp, arithmetic.fromConst(stackOffset, sp.getMinimumByteSize())); state.setVar(sp, arithmetic.fromConst(stackOffset, sp.getMinimumByteSize()));
}
thread.overrideCounter(entry); /**
* Prepare a thread to emulate a given function, using the default stack size
*
* @param <T> the type of values in the emulator
* @param thread the thread whose state to initialize
* @param function the function to prepare to enter
*/
public static <T> void initializeForFunction(PcodeThread<T> thread, Function function) {
initializeForFunction(thread, function, DEFAULT_STACK_SIZE);
}
/**
* Inspect the value of the stack pointer, and return it as an address
*
* @param <T> the type of values in the emulator
* @param thread the thread whose stack pointer to inspect
* @param cSpec the compiler spec defining the stack register and base space
* @return the address pointed to by the stack pointer
*/
public static <T> Address inspectStackPointer(PcodeThread<T> thread, CompilerSpec cSpec) {
Register sp = cSpec.getStackPointer();
long stackOffset =
thread.getState().inspectRegisterValue(sp).getUnsignedValue().longValueExact();
return cSpec.getStackBaseSpace().getAddress(stackOffset);
}
/**
* Decode a null-terminated string of the given charset from memory
* <p>
* This retrieves bytes one at a time from the given state and feeds them to a decoder until
* that decoder emits a null character.
*
* @param state the memory
* @param ptr the starting address
* @param cs the character set
* @param maxBytes the maximum number of bytes to read
* @return the decoded string
*/
public static String decodeNullTerminatedString(PcodeExecutorState<?> state, Address ptr,
Charset cs, int maxBytes) {
CharsetDecoder decoder = cs.newDecoder();
ByteBuffer in = ByteBuffer.allocate(1);
CharBuffer out = CharBuffer.allocate(maxBytes); // should never be more chars than bytes
MemBuffer buf = state.getConcreteBuffer(ptr, Purpose.INSPECT);
for (int i = 0; i < maxBytes; i++) {
in.position(0);
int len = buf.getBytes(in.array(), i);
in.limit(len);
if (len == 0) {
// Flush out any partials and be done
decoder.decode(in, out, true);
return out.flip().toString();
}
decoder.decode(in, out, false);
int nullPos = out.position() - 1;
if (nullPos >= 0 && out.get(nullPos) == 0) {
return out.position(nullPos).flip().toString();
}
}
in.position(0);
in.limit(0);
// Flush out any partials and be done
decoder.decode(in, out, true);
return out.flip().toString();
}
/**
* Decode an ASCII null-terminated string of at most 128 bytes from memory
*
* @param state the memory
* @param ptr the starting address
* @return the decoded string
*/
public static String decodeNullTerminatedString(PcodeExecutorState<?> state, Address ptr) {
return decodeNullTerminatedString(state, ptr, Charset.forName("ASCII"), 128);
} }
} }
@@ -17,6 +17,7 @@ package ghidra.pcode.emu;
import ghidra.pcode.exec.*; import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Instruction; import ghidra.program.model.listing.Instruction;
@@ -378,18 +379,19 @@ public interface PcodeEmulationCallbacks<T> {
* @param space the address space of the operand * @param space the address space of the operand
* @param offset the offset of the operand * @param offset the offset of the operand
* @param length the size of the operand * @param length the size of the operand
* @param reason the reason for reading
* @return the length of the operand just initialized, typically 0 or {@code length} * @return the length of the operand just initialized, typically 0 or {@code length}
*/ */
default <A, U> int readUninitialized(PcodeThread<T> thread, default <A, U> int readUninitialized(PcodeThread<T> thread, PcodeExecutorStatePiece<A, U> piece,
PcodeExecutorStatePiece<A, U> piece, AddressSpace space, A offset, int length) { AddressSpace space, A offset, int length, Reason reason) {
return 0; return 0;
} }
/** /**
* Typically used from within * Typically used from within
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int)} * {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Reason)}
* to forward to the callback for concrete addressing * to forward to the callback for concrete addressing
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView)}. * {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView, Reason)}.
* *
* @param <A> the piece's address domain * @param <A> the piece's address domain
* @param <U> the piece's value domain * @param <U> the piece's value domain
@@ -399,13 +401,15 @@ public interface PcodeEmulationCallbacks<T> {
* @param space the address space of the operand * @param space the address space of the operand
* @param offset the offset of the operand * @param offset the offset of the operand
* @param length the size of the operand * @param length the size of the operand
* @param reason the reason for reading
* @return the length of the operand just initialized, typically 0 or {@code length} * @return the length of the operand just initialized, typically 0 or {@code length}
*/ */
default <A, U> int delegateReadUninitialized(PcodeThread<T> thread, default <A, U> int delegateReadUninitialized(PcodeThread<T> thread,
PcodeExecutorStatePiece<A, U> piece, AddressSpace space, A offset, int length) { PcodeExecutorStatePiece<A, U> piece, AddressSpace space, A offset, int length,
Reason reason) {
long lOffset = piece.getAddressArithmetic().toLong(offset, Purpose.LOAD); long lOffset = piece.getAddressArithmetic().toLong(offset, Purpose.LOAD);
AddressSet set = PcodeStateCallbacks.rngSet(space, lOffset, length); AddressSet set = PcodeStateCallbacks.rngSet(space, lOffset, length);
AddressSetView remains = readUninitialized(thread, piece, set); AddressSetView remains = readUninitialized(thread, piece, set, reason);
if (set == remains) { if (set == remains) {
return 0; return 0;
} }
@@ -432,18 +436,19 @@ public interface PcodeEmulationCallbacks<T> {
* {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)} * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)}
* @param piece the state piece * @param piece the state piece
* @param set the uninitialized portion required * @param set the uninitialized portion required
* @param reason the reason for reading
* @return the addresses in {@code set} that remain uninitialized * @return the addresses in {@code set} that remain uninitialized
*/ */
default <A, U> AddressSetView readUninitialized(PcodeThread<T> thread, default <A, U> AddressSetView readUninitialized(PcodeThread<T> thread,
PcodeExecutorStatePiece<A, U> piece, AddressSetView set) { PcodeExecutorStatePiece<A, U> piece, AddressSetView set, Reason reason) {
return set; return set;
} }
/** /**
* Typically used from within * Typically used from within
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView)} to forward * {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView, Reason)} to
* to the callback for abstract addressing * forward to the callback for abstract addressing
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int)}. * {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Reason)}.
* *
* @param <A> the piece's address domain * @param <A> the piece's address domain
* @param <U> the piece's value domain * @param <U> the piece's value domain
@@ -451,10 +456,11 @@ public interface PcodeEmulationCallbacks<T> {
* {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)} * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)}
* @param piece the state piece * @param piece the state piece
* @param set the uninitialized portion required * @param set the uninitialized portion required
* @param reason the reason for reading
* @return the addresses in {@code set} that remain uninitialized * @return the addresses in {@code set} that remain uninitialized
*/ */
default <A, U> AddressSetView delegateReadUninitialized(PcodeThread<T> thread, default <A, U> AddressSetView delegateReadUninitialized(PcodeThread<T> thread,
PcodeExecutorStatePiece<A, U> piece, AddressSetView set) { PcodeExecutorStatePiece<A, U> piece, AddressSetView set, Reason reason) {
if (set.isEmpty()) { if (set.isEmpty()) {
return set; return set;
} }
@@ -463,7 +469,7 @@ public interface PcodeEmulationCallbacks<T> {
for (AddressRange range : set) { for (AddressRange range : set) {
int l = readUninitialized(thread, piece, range.getAddressSpace(), int l = readUninitialized(thread, piece, range.getAddressSpace(),
piece.getAddressArithmetic().fromConst(range.getMinAddress()), piece.getAddressArithmetic().fromConst(range.getMinAddress()),
(int) range.getLength()); (int) range.getLength(), reason);
if (l == 0) { if (l == 0) {
continue; continue;
} }
@@ -497,14 +503,14 @@ public interface PcodeEmulationCallbacks<T> {
@Override @Override
public <A, U> int readUninitialized(PcodeExecutorStatePiece<A, U> piece, public <A, U> int readUninitialized(PcodeExecutorStatePiece<A, U> piece,
AddressSpace space, A offset, int length) { AddressSpace space, A offset, int length, Reason reason) {
return cb.readUninitialized(thread, piece, space, offset, length); return cb.readUninitialized(thread, piece, space, offset, length, reason);
} }
@Override @Override
public <A, U> AddressSetView readUninitialized(PcodeExecutorStatePiece<A, U> piece, public <A, U> AddressSetView readUninitialized(PcodeExecutorStatePiece<A, U> piece,
AddressSetView set) { AddressSetView set, Reason reason) {
return cb.readUninitialized(thread, piece, set); return cb.readUninitialized(thread, piece, set, reason);
} }
} }
@@ -75,7 +75,7 @@ public abstract class AbstractBytesPcodeExecutorStatePiece<S extends BytesPcodeE
Address min = address.add(addressOffset); Address min = address.add(addressOffset);
AddressSet set = new AddressSet(min, min.add(buffer.remaining() - 1)); AddressSet set = new AddressSet(min, min.add(buffer.remaining() - 1));
if (set.equals( if (set.equals(
cb.readUninitialized(AbstractBytesPcodeExecutorStatePiece.this, set))) { cb.readUninitialized(AbstractBytesPcodeExecutorStatePiece.this, set, reason))) {
return 0; return 0;
} }
source = getForSpace(address.getAddressSpace(), false); source = getForSpace(address.getAddressSpace(), false);
@@ -259,7 +259,7 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
S s = getForSpace(space, false); S s = getForSpace(space, false);
if (s == null) { if (s == null) {
AddressSet set = PcodeStateCallbacks.rngSet(space, offset, size); AddressSet set = PcodeStateCallbacks.rngSet(space, offset, size);
if (set.equals(cb.readUninitialized(this, set))) { if (set.equals(cb.readUninitialized(this, set, reason))) {
return getFromNullSpace(size, reason, cb); return getFromNullSpace(size, reason, cb);
} }
s = getForSpace(space, false); s = getForSpace(space, false);
@@ -211,7 +211,7 @@ public class BytesPcodeExecutorStateSpace {
if (uninitialized.isEmpty()) { if (uninitialized.isEmpty()) {
return readBytes(offset, size, reason); return readBytes(offset, size, reason);
} }
uninitialized = cb.readUninitialized(piece, uninitialized); uninitialized = cb.readUninitialized(piece, uninitialized, reason);
if (uninitialized.isEmpty()) { if (uninitialized.isEmpty()) {
return readBytes(offset, size, reason); return readBytes(offset, size, reason);
} }
@@ -17,6 +17,7 @@ package ghidra.pcode.exec;
import ghidra.pcode.emu.*; import ghidra.pcode.emu.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
/** /**
@@ -29,8 +30,8 @@ import ghidra.program.model.address.*;
* {@link PcodeExecutorState}s and/or {@link PcodeExecutorStatePiece}s just to introduce * {@link PcodeExecutorState}s and/or {@link PcodeExecutorStatePiece}s just to introduce
* integration-driven behaviors. E.g., to lazily load state from an external machine-state snapshot, * integration-driven behaviors. E.g., to lazily load state from an external machine-state snapshot,
* the client should implement the * the client should implement the
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView)} or * {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView, Reason)} or
* {@link PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView)} * {@link PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView, Reason)}
* callback rather than extending {@link BytesPcodeExecutorStatePiece}. * callback rather than extending {@link BytesPcodeExecutorStatePiece}.
*/ */
public interface PcodeStateCallbacks { public interface PcodeStateCallbacks {
@@ -164,18 +165,19 @@ public interface PcodeStateCallbacks {
* @param space the address space of the operand * @param space the address space of the operand
* @param offset the offset of the operand * @param offset the offset of the operand
* @param length the size of the operand * @param length the size of the operand
* @param reason the reason for reading
* @return the length of the operand just initialized, typically 0 or {@code length} * @return the length of the operand just initialized, typically 0 or {@code length}
*/ */
default <A, T> int readUninitialized(PcodeExecutorStatePiece<A, T> piece, default <A, T> int readUninitialized(PcodeExecutorStatePiece<A, T> piece,
AddressSpace space, A offset, int length) { AddressSpace space, A offset, int length, Reason reason) {
return 0; return 0;
} }
/** /**
* Typically used from within * Typically used from within
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSpace, Object, int)} to forward to * {@link #readUninitialized(PcodeExecutorStatePiece, AddressSpace, Object, int, Reason)} to
* the callback for concrete addressing * forward to the callback for concrete addressing
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView)}. * {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView, Reason)}.
* *
* @param <A> the piece's address domain * @param <A> the piece's address domain
* @param <T> the piece's value domain * @param <T> the piece's value domain
@@ -183,13 +185,14 @@ public interface PcodeStateCallbacks {
* @param space the address space of the operand * @param space the address space of the operand
* @param offset the offset of the operand * @param offset the offset of the operand
* @param length the size of the operand * @param length the size of the operand
* @param reason the reason for reading
* @return the length of the operand just initialized, typically 0 or {@code length} * @return the length of the operand just initialized, typically 0 or {@code length}
*/ */
default <A, T> int delegateReadUninitialized(PcodeExecutorStatePiece<A, T> piece, default <A, T> int delegateReadUninitialized(PcodeExecutorStatePiece<A, T> piece,
AddressSpace space, A offset, int length) { AddressSpace space, A offset, int length, Reason reason) {
long lOffset = piece.getAddressArithmetic().toLong(offset, Purpose.LOAD); long lOffset = piece.getAddressArithmetic().toLong(offset, Purpose.LOAD);
AddressSet set = PcodeStateCallbacks.rngSet(space, lOffset, length); AddressSet set = PcodeStateCallbacks.rngSet(space, lOffset, length);
AddressSetView remains = readUninitialized(piece, set); AddressSetView remains = readUninitialized(piece, set, reason);
if (set == remains) { if (set == remains) {
return 0; return 0;
} }
@@ -214,27 +217,29 @@ public interface PcodeStateCallbacks {
* @param <T> the piece's value domain * @param <T> the piece's value domain
* @param piece the state piece * @param piece the state piece
* @param set the uninitialized portion required * @param set the uninitialized portion required
* @param reason the reason for reading
* @return the addresses in {@code set} that remain uninitialized * @return the addresses in {@code set} that remain uninitialized
*/ */
default <A, T> AddressSetView readUninitialized(PcodeExecutorStatePiece<A, T> piece, default <A, T> AddressSetView readUninitialized(PcodeExecutorStatePiece<A, T> piece,
AddressSetView set) { AddressSetView set, Reason reason) {
return set; return set;
} }
/** /**
* Typically used from within * Typically used from within
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView)} to forward to the * {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView, Reason)} to forward to the
* callback for abstract addressing * callback for abstract addressing
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSpace, Object, int)}. * {@link #readUninitialized(PcodeExecutorStatePiece, AddressSpace, Object, int, Reason)}.
* *
* @param <A> the piece's address domain * @param <A> the piece's address domain
* @param <T> the piece's value domain * @param <T> the piece's value domain
* @param piece the state piece * @param piece the state piece
* @param set the uninitialized portion required * @param set the uninitialized portion required
* @param reason the reason for reading
* @return the addresses in {@code set} that remain uninitialized * @return the addresses in {@code set} that remain uninitialized
*/ */
default <A, T> AddressSetView delegateReadUninitialized(PcodeExecutorStatePiece<A, T> piece, default <A, T> AddressSetView delegateReadUninitialized(PcodeExecutorStatePiece<A, T> piece,
AddressSetView set) { AddressSetView set, Reason reason) {
if (set.isEmpty()) { if (set.isEmpty()) {
return set; return set;
} }
@@ -242,7 +247,7 @@ public interface PcodeStateCallbacks {
for (AddressRange range : set) { for (AddressRange range : set) {
int l = readUninitialized(piece, range.getAddressSpace(), int l = readUninitialized(piece, range.getAddressSpace(),
piece.getAddressArithmetic().fromConst(range.getMinAddress()), piece.getAddressArithmetic().fromConst(range.getMinAddress()),
(int) range.getLength()); (int) range.getLength(), reason);
if (l == 0) { if (l == 0) {
continue; continue;
} }
@@ -287,9 +287,9 @@ the Python source over and add it to your <code>PYTHONPATH</code>.</p>
<p>Double-check that you have installed all the required packages and <p>Double-check that you have installed all the required packages and
their dependencies. A common forgotten or incorrectly-versioned their dependencies. A common forgotten or incorrectly-versioned
dependency is <code>protobuf</code>. We developed using dependency is <code>protobuf</code>. We developed using
<code>protobuf==3.20.3</code>, newer versions generally work fine. Its “sdist” package is distributed with <code>protobuf==3.20.3</code>, newer versions generally work fine. Its
Ghidra under <code>Debugger-rmi-trace/pypkg/dist</code> for your “sdist” package is distributed with Ghidra under
convenience.</p> <code>Debugger-rmi-trace/pypkg/dist</code> for your convenience.</p>
<p>It is also possible that <code>gdb</code> has embedded a different <p>It is also possible that <code>gdb</code> has embedded a different
version of the interpreter than the one that <code>python3</code> version of the interpreter than the one that <code>python3</code>
provides. This can happen if you built GDB or Python yourself, or you provides. This can happen if you built GDB or Python yourself, or you
@@ -780,13 +780,13 @@ class="sourceCode numberSource java numberLines"><code class="sourceCode java"><
<span id="cb8-18"><a href="#cb8-18"></a> cb<span class="op">.</span><span class="fu">dataWritten</span><span class="op">(</span>piece<span class="op">,</span> space<span class="op">.</span><span class="fu">getAddress</span><span class="op">(</span>offset<span class="op">),</span> size<span class="op">,</span> val<span class="op">);</span></span> <span id="cb8-18"><a href="#cb8-18"></a> cb<span class="op">.</span><span class="fu">dataWritten</span><span class="op">(</span>piece<span class="op">,</span> space<span class="op">.</span><span class="fu">getAddress</span><span class="op">(</span>offset<span class="op">),</span> size<span class="op">,</span> val<span class="op">);</span></span>
<span id="cb8-19"><a href="#cb8-19"></a> <span class="op">}</span></span> <span id="cb8-19"><a href="#cb8-19"></a> <span class="op">}</span></span>
<span id="cb8-20"><a href="#cb8-20"></a></span> <span id="cb8-20"><a href="#cb8-20"></a></span>
<span id="cb8-21"><a href="#cb8-21"></a> <span class="kw">public</span> Expr <span class="fu">get</span><span class="op">(</span><span class="dt">long</span> offset<span class="op">,</span> <span class="dt">int</span> size<span class="op">,</span> PcodeStateCallbacks cb<span class="op">)</span> <span class="op">{</span></span> <span id="cb8-21"><a href="#cb8-21"></a> <span class="kw">public</span> Expr <span class="fu">get</span><span class="op">(</span><span class="dt">long</span> offset<span class="op">,</span> <span class="dt">int</span> size<span class="op">,</span> Reason reason<span class="op">,</span> PcodeStateCallbacks cb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-22"><a href="#cb8-22"></a> <span class="co">// </span><span class="al">TODO</span><span class="co">: Handle overlaps / offcut gets and sets</span></span> <span id="cb8-22"><a href="#cb8-22"></a> <span class="co">// </span><span class="al">TODO</span><span class="co">: Handle overlaps / offcut gets and sets</span></span>
<span id="cb8-23"><a href="#cb8-23"></a> Expr expr <span class="op">=</span> map<span class="op">.</span><span class="fu">get</span><span class="op">(</span>offset<span class="op">);</span></span> <span id="cb8-23"><a href="#cb8-23"></a> Expr expr <span class="op">=</span> map<span class="op">.</span><span class="fu">get</span><span class="op">(</span>offset<span class="op">);</span></span>
<span id="cb8-24"><a href="#cb8-24"></a> <span class="cf">if</span> <span class="op">(</span>expr <span class="op">==</span> <span class="kw">null</span><span class="op">)</span> <span class="op">{</span></span> <span id="cb8-24"><a href="#cb8-24"></a> <span class="cf">if</span> <span class="op">(</span>expr <span class="op">==</span> <span class="kw">null</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-25"><a href="#cb8-25"></a> <span class="dt">byte</span><span class="op">[]</span> aOffset <span class="op">=</span></span> <span id="cb8-25"><a href="#cb8-25"></a> <span class="dt">byte</span><span class="op">[]</span> aOffset <span class="op">=</span></span>
<span id="cb8-26"><a href="#cb8-26"></a> piece<span class="op">.</span><span class="fu">getAddressArithmetic</span><span class="op">().</span><span class="fu">fromConst</span><span class="op">(</span>offset<span class="op">,</span> space<span class="op">.</span><span class="fu">getPointerSize</span><span class="op">());</span></span> <span id="cb8-26"><a href="#cb8-26"></a> piece<span class="op">.</span><span class="fu">getAddressArithmetic</span><span class="op">().</span><span class="fu">fromConst</span><span class="op">(</span>offset<span class="op">,</span> space<span class="op">.</span><span class="fu">getPointerSize</span><span class="op">());</span></span>
<span id="cb8-27"><a href="#cb8-27"></a> <span class="cf">if</span> <span class="op">(</span>cb<span class="op">.</span><span class="fu">readUninitialized</span><span class="op">(</span>piece<span class="op">,</span> space<span class="op">,</span> aOffset<span class="op">,</span> size<span class="op">)</span> <span class="op">!=</span> <span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span> <span id="cb8-27"><a href="#cb8-27"></a> <span class="cf">if</span> <span class="op">(</span>cb<span class="op">.</span><span class="fu">readUninitialized</span><span class="op">(</span>piece<span class="op">,</span> space<span class="op">,</span> aOffset<span class="op">,</span> size<span class="op">,</span> reason<span class="op">)</span> <span class="op">!=</span> <span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-28"><a href="#cb8-28"></a> <span class="cf">return</span> map<span class="op">.</span><span class="fu">get</span><span class="op">(</span>offset<span class="op">);</span></span> <span id="cb8-28"><a href="#cb8-28"></a> <span class="cf">return</span> map<span class="op">.</span><span class="fu">get</span><span class="op">(</span>offset<span class="op">);</span></span>
<span id="cb8-29"><a href="#cb8-29"></a> <span class="op">}</span></span> <span id="cb8-29"><a href="#cb8-29"></a> <span class="op">}</span></span>
<span id="cb8-30"><a href="#cb8-30"></a> <span class="op">}</span></span> <span id="cb8-30"><a href="#cb8-30"></a> <span class="op">}</span></span>
@@ -846,7 +846,7 @@ class="sourceCode numberSource java numberLines"><code class="sourceCode java"><
<span id="cb8-84"><a href="#cb8-84"></a> <span class="at">@Override</span></span> <span id="cb8-84"><a href="#cb8-84"></a> <span class="at">@Override</span></span>
<span id="cb8-85"><a href="#cb8-85"></a> <span class="kw">protected</span> Expr <span class="fu">getFromSpace</span><span class="op">(</span>ExprSpace space<span class="op">,</span> <span class="dt">long</span> offset<span class="op">,</span> <span class="dt">int</span> size<span class="op">,</span> Reason reason<span class="op">,</span></span> <span id="cb8-85"><a href="#cb8-85"></a> <span class="kw">protected</span> Expr <span class="fu">getFromSpace</span><span class="op">(</span>ExprSpace space<span class="op">,</span> <span class="dt">long</span> offset<span class="op">,</span> <span class="dt">int</span> size<span class="op">,</span> Reason reason<span class="op">,</span></span>
<span id="cb8-86"><a href="#cb8-86"></a> PcodeStateCallbacks cb<span class="op">)</span> <span class="op">{</span></span> <span id="cb8-86"><a href="#cb8-86"></a> PcodeStateCallbacks cb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-87"><a href="#cb8-87"></a> <span class="cf">return</span> space<span class="op">.</span><span class="fu">get</span><span class="op">(</span>offset<span class="op">,</span> size<span class="op">,</span> cb<span class="op">);</span></span> <span id="cb8-87"><a href="#cb8-87"></a> <span class="cf">return</span> space<span class="op">.</span><span class="fu">get</span><span class="op">(</span>offset<span class="op">,</span> size<span class="op">,</span> reason<span class="op">,</span> cb<span class="op">);</span></span>
<span id="cb8-88"><a href="#cb8-88"></a> <span class="op">}</span></span> <span id="cb8-88"><a href="#cb8-88"></a> <span class="op">}</span></span>
<span id="cb8-89"><a href="#cb8-89"></a></span> <span id="cb8-89"><a href="#cb8-89"></a></span>
<span id="cb8-90"><a href="#cb8-90"></a> <span class="at">@Override</span></span> <span id="cb8-90"><a href="#cb8-90"></a> <span class="at">@Override</span></span>
@@ -555,13 +555,13 @@ public static class ExprSpace {
cb.dataWritten(piece, space.getAddress(offset), size, val); cb.dataWritten(piece, space.getAddress(offset), size, val);
} }
public Expr get(long offset, int size, PcodeStateCallbacks cb) { public Expr get(long offset, int size, Reason reason, PcodeStateCallbacks cb) {
// TODO: Handle overlaps / offcut gets and sets // TODO: Handle overlaps / offcut gets and sets
Expr expr = map.get(offset); Expr expr = map.get(offset);
if (expr == null) { if (expr == null) {
byte[] aOffset = byte[] aOffset =
piece.getAddressArithmetic().fromConst(offset, space.getPointerSize()); piece.getAddressArithmetic().fromConst(offset, space.getPointerSize());
if (cb.readUninitialized(piece, space, aOffset, size) != 0) { if (cb.readUninitialized(piece, space, aOffset, size, reason) != 0) {
return map.get(offset); return map.get(offset);
} }
} }
@@ -621,7 +621,7 @@ public static class ExprPcodeExecutorStatePiece
@Override @Override
protected Expr getFromSpace(ExprSpace space, long offset, int size, Reason reason, protected Expr getFromSpace(ExprSpace space, long offset, int size, Reason reason,
PcodeStateCallbacks cb) { PcodeStateCallbacks cb) {
return space.get(offset, size, cb); return space.get(offset, size, reason, cb);
} }
@Override @Override
@@ -336,13 +336,13 @@ public class ModelingScript extends GhidraScript {
cb.dataWritten(piece, space.getAddress(offset), size, val); cb.dataWritten(piece, space.getAddress(offset), size, val);
} }
public Expr get(long offset, int size, PcodeStateCallbacks cb) { public Expr get(long offset, int size, Reason reason, PcodeStateCallbacks cb) {
// TODO: Handle overlaps / offcut gets and sets // TODO: Handle overlaps / offcut gets and sets
Expr expr = map.get(offset); Expr expr = map.get(offset);
if (expr == null) { if (expr == null) {
byte[] aOffset = byte[] aOffset =
piece.getAddressArithmetic().fromConst(offset, space.getPointerSize()); piece.getAddressArithmetic().fromConst(offset, space.getPointerSize());
if (cb.readUninitialized(piece, space, aOffset, size) != 0) { if (cb.readUninitialized(piece, space, aOffset, size, reason) != 0) {
return map.get(offset); return map.get(offset);
} }
} }
@@ -402,7 +402,7 @@ public class ModelingScript extends GhidraScript {
@Override @Override
protected Expr getFromSpace(ExprSpace space, long offset, int size, Reason reason, protected Expr getFromSpace(ExprSpace space, long offset, int size, Reason reason,
PcodeStateCallbacks cb) { PcodeStateCallbacks cb) {
return space.get(offset, size, cb); return space.get(offset, size, reason, cb);
} }
@Override @Override
@@ -7,4 +7,4 @@ Sample scripts deobExampleX86 and deobHookExampleX86 may be built under Linux.
Once these examples have been compiled they may be imported into a Ghidra project and the Once these examples have been compiled they may be imported into a Ghidra project and the
corresponding Ghidra Scripts (EmuX86DeobfuscateExampleScript and EmuX86GccDeobfuscateHookExampleScript) corresponding Ghidra Scripts (EmuX86DeobfuscateExampleScript and EmuX86GccDeobfuscateHookExampleScript)
used to demonstrate the use of the EmulatorHelper class. used to demonstrate the use of the PcodeEmulator class.