GP-6369: Convert remaining uses of EmulatorHelper to PcodeEmulator.

This commit is contained in:
Dan
2026-02-24 18:25:50 +00:00
parent f8eada2b7c
commit 66c9edc9bf
33 changed files with 884 additions and 588 deletions
@@ -230,7 +230,7 @@ public enum TraceEmulationIntegration {
* @param set the uninitialized portion required
* @return the addresses in {@code set} that remain uninitialized
* @see PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece,
* AddressSetView)
* AddressSetView, Reason)
*/
AddressSetView readUninitialized(PcodeTraceDataAccess acc, PcodeThread<?> thread,
PcodeExecutorStatePiece<A, T> piece, AddressSetView set);
@@ -246,12 +246,14 @@ public enum TraceEmulationIntegration {
* @param space the address space
* @param offset the offset at the start 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}
* @see PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece,
* AddressSpace, Object, int)
* AddressSpace, Object, int, Reason)
*/
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;
}
@@ -542,7 +544,8 @@ public enum TraceEmulationIntegration {
*/
@Override
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();
}
@@ -783,14 +786,16 @@ public enum TraceEmulationIntegration {
@Override
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;
return handlerFor(piece).abstractReadUninit(acc, thread, piece, space, offset, length);
return handlerFor(piece).abstractReadUninit(acc, thread, piece, space, offset, length,
reason);
}
@Override
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()) {
return set;
}
@@ -118,7 +118,7 @@ public class TaintPcodeExecutorStatePiece
@Override
protected TaintVec getFromSpace(TaintSpace space, long offset, int size, Reason reason,
PcodeStateCallbacks cb) {
return space.get(offset, size, cb);
return space.get(offset, size, reason, cb);
}
@Override
@@ -18,6 +18,7 @@ package ghidra.pcode.emu.taint.state;
import java.util.*;
import java.util.Map.Entry;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Register;
@@ -85,14 +86,15 @@ public class TaintSpace {
*
* @param offset the offset
* @param buf the vector to receive taint sets
* @param reason the reason for reading
* @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++) {
TaintSet s = taints.get(offset + i);
if (s == null) {
if (cb.readUninitialized(piece, PcodeStateCallbacks.rngSet(space, offset + i, 1))
.isEmpty()) {
if (cb.readUninitialized(piece, PcodeStateCallbacks.rngSet(space, offset + i, 1),
reason).isEmpty()) {
s = taints.get(offset + i);
}
}
@@ -107,17 +109,18 @@ public class TaintSpace {
* Retrieve the taint sets for the variable at the given offset
*
* <p>
* This works the same as {@link #getInto(long, TaintVec, PcodeStateCallbacks)}, but creates a
* new vector of the given size, reads the taint sets, and returns the vector.
* This works the same as {@link #getInto(long, TaintVec, Reason, PcodeStateCallbacks)}, but
* creates a new vector of the given size, reads the taint sets, and returns the vector.
*
* @param offset the offset
* @param size the size of the variable
* @param reason the reason for reading
* @param cb callbacks to receive emulation events
* @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);
getInto(offset, vec, cb);
getInto(offset, vec, reason, cb);
return vec;
}
@@ -153,7 +156,7 @@ public class TaintSpace {
}
PcodeOp pcodeOp = ops.get(offset); // Needed here to generate the TaintVec
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);
}
}
@@ -109,9 +109,10 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece<S>
* @param size the number of bytes to read (the size of the 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);
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 offset the offset within the space
* @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
* @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);
/**
@@ -229,7 +231,7 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece<S>
}
}
if (space.isUniqueSpace()) {
return getUnique(offset, size, cb);
return getUnique(offset, size, reason, cb);
}
S s = getForSpace(space, false);
//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);
}
//offset = quantizeOffset(space, offset);
return getFromSpace(s, offset, size, cb);
return getFromSpace(s, offset, size, reason, cb);
}
@Override
@@ -144,9 +144,9 @@ public class SymZ3PcodeExecutorStatePiece
* the storage space.
*/
@Override
protected SymValueZ3 getFromSpace(SymZ3Space space, SymValueZ3 offset, int size,
protected SymValueZ3 getFromSpace(SymZ3Space space, SymValueZ3 offset, int size, Reason reason,
PcodeStateCallbacks cb) {
return space.get(offset, size, cb);
return space.get(offset, size, reason, cb);
}
@Override
@@ -23,6 +23,7 @@ import com.microsoft.z3.Context;
import ghidra.pcode.emu.symz3.AbstractSymZ3OffsetPcodeExecutorStatePiece;
import ghidra.pcode.emu.symz3.SymZ3MemoryMap;
import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
@@ -53,9 +54,9 @@ public class SymZ3MemorySpace extends SymZ3Space {
}
@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)) {
cb.readUninitialized(piece, space, offset, size);
cb.readUninitialized(piece, space, offset, size, reason);
}
return mmap.load(offset, size, true, cb);
}
@@ -84,7 +84,7 @@ public class SymZ3PieceHandler
@Override
public int abstractReadUninit(PcodeTraceDataAccess acc, PcodeThread<?> thread,
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);
if (string == null) {
return 0;
@@ -23,6 +23,7 @@ import com.microsoft.z3.Context;
import ghidra.pcode.emu.symz3.AbstractSymZ3OffsetPcodeExecutorStatePiece;
import ghidra.pcode.emu.symz3.SymZ3RegisterMap;
import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
@@ -87,7 +88,7 @@ public class SymZ3RegisterSpace extends SymZ3Space {
}
@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);
if (r == null) {
Msg.warn(this, "unable to get register with space: " + space.getSpaceID() +
@@ -95,7 +96,7 @@ public class SymZ3RegisterSpace extends SymZ3Space {
return null;
}
if (!rmap.hasValueForRegister(r)) {
cb.readUninitialized(piece, space, offset, size);
cb.readUninitialized(piece, space, offset, size, reason);
}
SymValueZ3 result = this.rmap.getRegister(r);
return result;
@@ -22,6 +22,7 @@ import java.util.stream.Stream;
import com.microsoft.z3.Context;
import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.symz3.model.SymValueZ3;
@@ -36,7 +37,8 @@ import ghidra.symz3.model.SymValueZ3;
public abstract class SymZ3Space {
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();
@@ -23,6 +23,7 @@ import java.util.stream.Stream;
import com.microsoft.z3.*;
import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.symz3.model.SymValueZ3;
@@ -86,7 +87,7 @@ public class SymZ3UniqueSpace extends SymZ3Space {
}
@Override
public SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb) {
public SymValueZ3 get(SymValueZ3 offset, int size, Reason reason, PcodeStateCallbacks cb) {
assert offset != null;
try (Context ctx = new Context()) {
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.state.SymZ3PieceHandler;
import ghidra.pcode.emu.symz3.state.SymZ3WriteDownHelper;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess;
import ghidra.program.model.address.*;
@@ -86,7 +87,7 @@ public class SymZ3TraceMemorySpace extends SymZ3TraceSpace {
}
@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)) {
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.lib.Z3InfixPrinter;
import ghidra.pcode.emu.symz3.state.SymZ3WriteDownHelper;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess;
import ghidra.program.model.address.AddressSpace;
@@ -96,7 +97,7 @@ public class SymZ3TraceRegisterSpace extends SymZ3TraceSpace {
}
@Override
public SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb) {
public SymValueZ3 get(SymValueZ3 offset, int size, Reason reason, PcodeStateCallbacks cb) {
assert offset != null;
Register r = getRegister(offset, size);
if (r == null) {
@@ -25,27 +25,34 @@
// to the function "deobfuscate" so that the various return values can be recorded with a comment
// placed just after the call.
//@category Examples.Emulation
import ghidra.app.emulator.EmulatorHelper;
import java.util.NoSuchElementException;
import ghidra.app.script.GhidraScript;
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.lang.Register;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.symbol.*;
import ghidra.util.Msg;
import ghidra.util.exception.NotFoundException;
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
private Address deobfuscateCall;
private Address deobfuscateReturn;
// Function locations
private Function mainFunction;
private Address mainFunctionEntry; // start of emulation address
// Address used as final return location
@@ -66,18 +73,21 @@ public class EmuX86DeobfuscateExampleScript extends GhidraScript {
!"x86:LE:64:default".equals(currentProgram.getLanguageID().toString()) ||
!ElfLoader.ELF_NAME.equals(format)) {
printerr(
"This emulation example script is specifically intended to be executed against the\n" +
PROGRAM_NAME +
" program whose source is contained within the GhidraClass exercise files\n" +
"(see docs/GhidraClass/ExerciseFiles/Emulation/" + PROGRAM_NAME + ".c).\n" +
"This program should be compiled using gcc for x86 64-bit, imported into your project, \n" +
"analyzed and open as the active program before running ths script.");
printerr("""
This emulation example script is specifically intended to be executed against
the %s program whose source is contained within the GhidraClass exercise files
(see docs/GhidraClass/ExerciseFiles/Emulation/%s.c). This program should be
compiled using gcc for x86 64-bit, imported into your project, analyzed and
open as the active program before running ths script."""
.formatted(PROGRAM_NAME, PROGRAM_NAME));
return;
}
// 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
Instruction entryInstr = getInstructionAt(mainFunctionEntry);
@@ -100,53 +110,54 @@ public class EmuX86DeobfuscateExampleScript extends GhidraScript {
// Remove prior pre-comment
setPreComment(deobfuscateReturn, null);
// Establish emulation helper
emuHelper = new EmulatorHelper(currentProgram);
try {
// Establish emulator
emu = new PcodeEmulator(currentProgram.getLanguage());
monitor.addCancelledListener(() -> {
emu.setSuspended(true);
});
emuThread = emu.newThread();
arithmetic = emuThread.getArithmetic();
EmulatorUtilities.loadProgram(emu, currentProgram);
// Initialize stack pointer (not used by this example)
long stackOffset =
(entryInstr.getAddress().getAddressSpace().getMaxAddress().getOffset() >>> 1) -
0x7fff;
emuHelper.writeRegister(emuHelper.getStackPointerRegister(), stackOffset);
// Initialize program counter, registers from context, and stack pointer
EmulatorUtilities.initializeForFunction(emuThread, mainFunction);
Address stackBase =
EmulatorUtilities.inspectStackPointer(emuThread, currentProgram.getCompilerSpec());
// Setup breakpoints
emuHelper.setBreakpoint(deobfuscateCall);
emuHelper.setBreakpoint(deobfuscateReturn);
// Setup breakpoints
emu.addBreakpoint(deobfuscateCall, "1:1");
emu.addBreakpoint(deobfuscateReturn, "1:1");
// 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);
// 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");
Msg.debug(this, "EMU starting at " + mainFunctionEntry);
Msg.debug(this, "EMU starting at " + emuThread.getCounter());
// Execution loop until return from function or error occurs
while (!monitor.isCancelled()) {
boolean success =
(emuHelper.getEmulateExecutionState() == EmulateExecutionState.BREAKPOINT)
? 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);
// Execution loop until return from function or error occurs
while (!monitor.isCancelled()) {
try {
emuThread.run();
}
}
finally {
// cleanup resources and release hold on currentProgram
emuHelper.dispose();
catch (InterruptPcodeExecutionException e) {
// Hit a breakpoint. Good.
}
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.
*
* @param addr current execute address where emulation has been suspended
* @throws Exception if an error occurs
*/
private void processBreakpoint(Address addr) throws Exception {
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)) {
long deobfuscateReturnValue = emuHelper.readRegister("RAX").longValue();
String str = "deobfuscate(src=0x" + Long.toHexString(lastDeobfuscateArg0) + ") -> \"" +
emuHelper.readNullTerminatedString(getAddress(deobfuscateReturnValue), 32) + "\"";
Register rax = currentProgram.getRegister("RAX");
long deobfuscateReturnValue =
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);
if (comment == null) {
comment = "";
@@ -192,14 +209,4 @@ public class EmuX86DeobfuscateExampleScript extends GhidraScript {
}
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
// simply prints the deobfuscated string passed as an argument.
//@category Examples.Emulation
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import ghidra.app.emulator.EmulatorHelper;
import ghidra.app.script.GhidraScript;
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.lang.InsufficientBytesException;
import ghidra.program.model.listing.Function;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.symbol.*;
import ghidra.util.Msg;
import ghidra.util.exception.NotFoundException;
public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
private static String PROGRAM_NAME = "deobHookExample";
private static final String PROGRAM_NAME = "deobHookExample";
// Heap allocation area
private static final int MALLOC_REGION_SIZE = 0x1000;
@@ -47,7 +47,10 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
// Address used as final return location
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;
// 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;
// Function locations
private Function mainFunction;
private Address mainFunctionEntry; // start 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()) ||
!ElfLoader.ELF_NAME.equals(format)) {
printerr(
"This emulation example script is specifically intended to be executed against the\n" +
PROGRAM_NAME +
" program whose source is contained within the GhidraClass exercise files\n" +
"(see docs/GhidraClass/ExerciseFiles/Emulation/" + PROGRAM_NAME + ".c).\n" +
"This program should be compiled using gcc for x86 64-bit, imported into your project, \n" +
"analyzed and open as the active program before running ths script.");
printerr("""
This emulation example script is specifically intended to be executed against
the %s program whose source is contained within the GhidraClass exercise files
(see docs/GhidraClass/ExerciseFiles/Emulation/%s.c). This program should be
compiled using gcc for x86 64-bit, imported into your project, analyzed and
open as the active program before running ths script."""
.formatted(PROGRAM_NAME, PROGRAM_NAME));
return;
}
// Identify function be emulated
mainFunctionEntry = getSymbolAddress("main");
mainFunction = currentProgram.getFunctionManager().getFunctionAt(mainFunctionEntry);
useStringEntry = getSymbolAddress("use_string");
// Identify important symbol addresses
@@ -88,59 +93,71 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
freeEntry = getExternalThunkAddress("free");
strlenEntry = getExternalThunkAddress("strlen");
// Establish emulation helper
emuHelper = new EmulatorHelper(currentProgram);
try {
// Initialize stack pointer (not used by this example)
long stackOffset =
(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);
// Establish emulator
emu = new PcodeEmulator(currentProgram.getLanguage()) {
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return super.createUseropLibrary().compose(new DeobfUseropLibrary<byte[]>());
}
};
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 {
// cleanup resources and release hold on currentProgram
emuHelper.dispose();
catch (InterruptPcodeExecutionException e) {
// Hit the breakpoint. Good.
}
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.
* @param addr current execute address where emulation has been suspended
* @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)
* 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
* @return address of thunk function which corresponds to an external function
* @throws NotFoundException if thunk not found
@@ -212,7 +177,7 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
Symbol externalSymbol = currentProgram.getSymbolTable().getExternalSymbol(symbolName);
if (externalSymbol != null && externalSymbol.getSymbolType() == SymbolType.FUNCTION) {
Function f = (Function) externalSymbol.getObject();
Address[] thunkAddrs = f.getFunctionThunkAddresses();
Address[] thunkAddrs = f.getFunctionThunkAddresses(false);
if (thunkAddrs.length == 1) {
return thunkAddrs[0];
}
@@ -222,6 +187,7 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
/**
* Get the global namespace symbol address which corresponds to the specified name.
*
* @param symbolName global symbol name
* @return symbol address
* @throws NotFoundException if symbol not found
@@ -235,6 +201,66 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
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
* malloc/free hooked implementations.
@@ -246,8 +272,9 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
/**
* <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
* @throws AddressOverflowException
*/
@@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,8 +19,6 @@ import ghidra.program.model.address.Address;
public interface ExecutionListener extends TestLogger {
public void stepCompleted(EmulatorTestRunner testRunner);
public void logWrite(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");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,11 +15,14 @@
*/
package ghidra.test.processors.support;
import java.nio.charset.Charset;
import java.util.*;
import ghidra.app.emulator.EmulatorHelper;
import ghidra.app.util.PseudoDisassembler;
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.utils.Utils;
import ghidra.program.disassemble.Disassembler;
@@ -36,9 +39,9 @@ import ghidra.util.exception.AssertException;
import ghidra.util.task.TaskMonitor;
/**
* <code>PCodeTestAbstractControlBlock</code> data is models the general capabilities
* of the TestInfo data structure which is used for different puposes as handled
* by extensions of this class.
* <code>PCodeTestAbstractControlBlock</code> data is models the general capabilities of the
* TestInfo data structure which is used for different puposes as handled by extensions of this
* class.
*/
public abstract class PCodeTestAbstractControlBlock {
@@ -59,17 +62,18 @@ public abstract class PCodeTestAbstractControlBlock {
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 infoStructAddr program address where structure resides
* @param infoStruct appropriate Info structure definition which will have array
* of FunctionInfo immediately following.
* @param infoStruct appropriate Info structure definition which will have array of FunctionInfo
* immediately following.
*/
PCodeTestAbstractControlBlock(Program program, Address infoStructAddr, Structure infoStruct) {
this.program = program;
this.pointerSize = program.getDataTypeManager().getDataOrganization().getPointerSize();
this.infoStructAddr = infoStructAddr;
this.infoProgramStruct = (Structure) infoStruct.clone(program.getDataTypeManager());
this.infoProgramStruct = infoStruct.clone(program.getDataTypeManager());
codeSpace = program.getAddressFactory().getDefaultAddressSpace();
dataSpace = program.getLanguage().getDefaultDataSpace();
@@ -95,10 +99,10 @@ public abstract class PCodeTestAbstractControlBlock {
}
/**
* Force an existing reference to refer to the code space. Pointers
* created in the data space refer to the data space by default, this method
* is used to change these pointers in the data space to refer to
* code.
* Force an existing reference to refer to the code space. Pointers created in the data space
* refer to the data space by default, this method is used to change these pointers in the data
* space to refer to code.
*
* @param addr location with data space which contains code reference
*/
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
* address.
* Check for a Data pointer at the specified address and return the referenced address.
*
* @param addr address of stored pointer
* @return pointer referenced address or null if no pointer found
*/
@@ -182,7 +186,8 @@ public abstract class PCodeTestAbstractControlBlock {
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);
// treat null pointer as special case - just return it
@@ -312,8 +317,9 @@ public abstract class PCodeTestAbstractControlBlock {
new TerminatedStringDataType(program.getDataTypeManager());
Structure functionInfoStruct =
(Structure) infoProgramStruct.getDataTypeManager().getDataType(CategoryPath.ROOT,
"FunctionInfo");
(Structure) infoProgramStruct.getDataTypeManager()
.getDataType(CategoryPath.ROOT,
"FunctionInfo");
if (functionInfoStruct == null) {
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 =
emu.getProgram().getDataTypeManager().getDataOrganization();
program.getDataTypeManager().getDataOrganization();
int charSize = dataOrganization.getCharSize();
boolean isBigEndian = emu.getProgram().getMemory().isBigEndian();
boolean isBigEndian = emu.getLanguage().isBigEndian();
MemoryState memState = emu.getEmulator().getMemState();
long offset = strPtrAddr.getOffset();
PcodeExecutorState<byte[]> state = emu.getState();
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;
byte[] buffer = new byte[128];
while (index < buffer.length) {
buffer[index] =
(char) (memState.getValue(strPtrAddr.getAddressSpace(), offset, 1) & 0xff);
try {
buffer[index] = memBuf.getByte(offset);
}
catch (MemoryAccessException e) {
throw new AssertionError(e);
}
if (buffer[index] == 0) {
break;
}
offset += charSize;
++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) {
throw new IllegalArgumentException("Unsupported EMU read size: " + size);
}
MemoryState memState = emu.getEmulator().getMemState();
return memState.getValue(addr.getAddressSpace(), addr.getOffset(), size);
return emu.getArithmetic()
.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) {
throw new IllegalArgumentException("Unsupported EMU read size: " + size);
}
MemoryState memState = emu.getEmulator().getMemState();
memState.setValue(addr.getAddressSpace(), addr.getOffset(), size, value);
emu.getState().setVar(addr, size, false, emu.getArithmetic().fromConst(value, size));
}
protected Address getMirroredDataAddress(EmulatorTestRunner emuTestRunner, Address addr) {
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -27,9 +27,9 @@ import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.Msg;
/**
* <code>PCodeTestControlBlock</code> data is read from each binary test file and
* identified by the MAIN_CONTROL_BLOCK_MAGIC 64-bit character field value at the start of the
* data structure. Only one instance of this should exist within the binary.
* <code>PCodeTestControlBlock</code> data is read from each binary test file and identified by the
* MAIN_CONTROL_BLOCK_MAGIC 64-bit character field value at the start of the data structure. Only
* one instance of this should exist within the binary.
*/
public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
@@ -72,13 +72,14 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
private final PCodeTestResults testResults;
/**
* Construct test control block instance for the specified
* program. Create TestInfo structure data within program if requested.
* Construct test control block instance for the specified program. Create TestInfo structure
* data within program if requested.
*
* @param program program containing control block structure
* @param restrictedSet the restricted memory area which should be searched
* for control structures
* @param restrictedSet the restricted memory area which should be searched for control
* structures
* @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 applyStruct create structure Data within program if true
* @throws InvalidControlBlockException
@@ -109,6 +110,7 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
/**
* Find Main TestInfo structure within memory and return instance of PCodeTestControlBlock
*
* @param program
* @param testFile original binary test file
* @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.
*
* @param emuTestRunner emulator test runner
* @param enable sprintf enablement
*/
void setSprintfEnabled(EmulatorTestRunner emuTestRunner, boolean enable) {
Address addr =
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
*
* @param emuTestRunner emulator test runner
* @return 'numpass' field value
*/
int getNumberPassed(EmulatorTestRunner emuTestRunner) {
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
*
* @param emuTestRunner emulator test runner
* @param value field value
*/
void setNumberPassed(EmulatorTestRunner emuTestRunner, int value) {
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) {
@@ -335,6 +340,7 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
/**
* Get the number of passed tests which should be ignored
*
* @return number of passed tests which should be ignored
*/
int getNumberPassedIgnored() {
@@ -343,6 +349,7 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
/**
* Get the number of failed tests which should be ignored
*
* @return number of failed tests which should be ignored
*/
int getNumberFailedIgnored() {
@@ -359,62 +366,68 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
/**
* Get 'numfail' field value from emulation memory state
*
* @param emuTestRunner emulator test runner
* @return 'numfail' field value
*/
int getNumberFailed(EmulatorTestRunner emuTestRunner) {
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
*
* @param emuTestRunner emulator test runner
* @param value field value
*/
void setNumberFailed(EmulatorTestRunner emuTestRunner, int value) {
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
*
* @param emuTestRunner emulator test runner
* @return 'lastTestPos' field value
*/
int getLastTestIndex(EmulatorTestRunner emuTestRunner) {
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
*
* @param emuTestRunner emulator test runner
* @return 'lastErrorLine' field value
*/
int getLastErrorLine(EmulatorTestRunner emuTestRunner) {
Address addr =
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
* pointer contained within lastErrorFile field.
* Get 'lastErrorFile' string value from emulation memory state. Must follow string pointer
* contained within lastErrorFile field.
*
* @param emuTestRunner emulator test runner
* @return 'lastErrorLine' field value
*/
String getLastErrorFile(EmulatorTestRunner emuTestRunner) {
Address addr =
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 = getMirroredDataAddress(emuTestRunner, addr);
return emuReadString(emuTestRunner.getEmulatorHelper(), addr);
return emuReadString(emuTestRunner.getEmulatorThread(), addr);
}
/**
* Get the name of the last test function to be run
*
* @param emuTestRunner
* @return last test function name
*/
@@ -422,10 +435,10 @@ public class PCodeTestControlBlock extends PCodeTestAbstractControlBlock {
PCodeTestGroup activeGroup) {
Address ptrStorageAddr = infoStructAddr.add(lastFuncOffset);
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);
strAddr = getMirroredDataAddress(emuTestRunner, strAddr);
String fnName = emuReadString(emuTestRunner.getEmulatorHelper(), strAddr);
String fnName = emuReadString(emuTestRunner.getEmulatorThread(), strAddr);
if ("none".equals(fnName)) {
if (logger != null) {
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");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -65,20 +65,21 @@ import utilities.util.FileUtilities;
import utility.application.ApplicationLayout;
/**
* <code>ProcessorEmulatorTestAdapter</code> provides an abstract JUnit test implementation
* for processor-specific test cases. All test cases which extend this class must have a
* class name which ends with 'EmulatorTest' and starts with the processor designator which
* will be used to identify associated test binaries within either the processor module's
* data/pcodetests/ directory or the Ghidra/Test/TestResources/data/pcodetests/ directory generally
* contained within the binary repository (e.g., ghidra.bin).
* <code>ProcessorEmulatorTestAdapter</code> provides an abstract JUnit test implementation for
* processor-specific test cases. All test cases which extend this class must have a class name
* which ends with 'EmulatorTest' and starts with the processor designator which will be used to
* identify associated test binaries within either the processor module's data/pcodetests/ directory
* or the Ghidra/Test/TestResources/data/pcodetests/ directory generally contained within the binary
* repository (e.g., ghidra.bin).
* <p>
* 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
* 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
* utilize the *.out file extension.
* {@literal <processor-designator>_pcodetest*} will be processed. All files contained within a
* 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 utilize
* the *.out file extension.
* <p>
* JUnit X86EmulatorTest could utilize the following binary file naming strategy:
*
* <pre>
* pcodetests/X86_PCodeTests
* - binary1.o
@@ -92,25 +93,22 @@ import utility.application.ApplicationLayout;
* - pcodetests/X86_PCodeTest.out
* </pre>
*
* Any *.out binary found will be imported and analyzed. The resulting program will
* be stored as a gzf in the test-output cache directory. These cached files will be used
* instead of a test resource binary if that binary's md5 checksum has not changed since its cached
* gzf was created. This use of cache files will allow the tests to run quickly on subsequent
* executions. If re-analysis is required, the cache will need to be cleared manually.
* Any *.out binary found will be imported and analyzed. The resulting program will be stored as a
* gzf in the test-output cache directory. These cached files will be used instead of a test
* resource binary if that binary's md5 checksum has not changed since its cached gzf was created.
* This use of cache files will allow the tests to run quickly on subsequent executions. If
* re-analysis is required, the cache will need to be cleared manually.
*
* NOTES:
* 1. Dummy Test Methods must be added for all known test groups. See bottom of this file. This
* all allows for the single test trace mode execution to work within Eclipse.
* 2. Trace logging disabled by default when all test groups are run (see buildEmulatorTestSuite method).
* Specific traceLevel and traceLog file controlled via environment properties
* EmuTestTraceLevel and EmuTestTraceFile.
* 3. The TestInfo structure must be properly maintained within the datatype archive EmuTesting.gdt
* and field naming consistent with use in PCodeTestControlBlock.java
* 4. The {@link #initializeState(EmulatorTestRunner, Program)} may be overriden to initialize the
* register values if needed. This should be based upon symbols or other program information
* 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.
* NOTES: 1. Dummy Test Methods must be added for all known test groups. See bottom of this file.
* This all allows for the single test trace mode execution to work within Eclipse. 2. Trace logging
* disabled by default when all test groups are run (see buildEmulatorTestSuite method). Specific
* traceLevel and traceLog file controlled via environment properties EmuTestTraceLevel and
* EmuTestTraceFile. 3. The TestInfo structure must be properly maintained within the datatype
* archive EmuTesting.gdt and field naming consistent with use in PCodeTestControlBlock.java 4. The
* {@link #initializeState(EmulatorTestRunner, Program)} may be overriden to initialize the register
* values if needed. This should be based upon symbols or other program information 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 {
@@ -495,10 +493,11 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
}
/**
* Create TestSuite based upon available test groups contained within binary
* test files associated with target processor.
* @param emulatorTestClass test which extends <code>ProcessorEmulatorTestAdapter</code>
* and whose name ends with "EmulatorTest".
* Create TestSuite based upon available test groups contained within binary test files
* associated with target processor.
*
* @param emulatorTestClass test which extends <code>ProcessorEmulatorTestAdapter</code> and
* whose name ends with "EmulatorTest".
* @return test suite
*/
public static final Test buildEmulatorTestSuite(Class<?> emulatorTestClass) {
@@ -825,7 +824,8 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
int byteCount = dumpSize * elementSize;
byte[] bytes = emulatorTestRunner.getEmulatorHelper().readMemory(dumpAddr, byteCount);
byte[] bytes =
emulatorTestRunner.getEmulatorThread().getState().inspectConcrete(dumpAddr, byteCount);
int index = 0;
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));
}
@Override
public void stepCompleted(EmulatorTestRunner testRunner) {
logState(testRunner);
}
/**
* 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
* instantiation.
* Single unit test which handles named test group specified during test instantiation.
*/
@Override
public final void runTest() {
@@ -1187,7 +1181,7 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
boolean done;
if (traceDisabled) {
done = testRunner.execute(EXECUTION_TIMEOUT_MS, TaskMonitor.DUMMY);
done = testRunner.execute(EXECUTION_TIMEOUT_MS);
}
else {
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
*
* @return stack symbol or null
*/
protected String getPreferredStackSymbolName() {
@@ -1350,8 +1345,7 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
Address currentAddr = testRunner.getCurrentAddress();
// dump context register state if decode failure
RegisterValue contextRegValue =
testRunner.getEmulatorHelper().getEmulator().getContextRegisterValue();
RegisterValue contextRegValue = testRunner.getEmulatorThread().getContext();
if (contextRegValue == null) {
return;
}
@@ -1383,7 +1377,8 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
}
// 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];
program.getMemory().getBytes(currentAddr, 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.
*
* @return max defined physical address
*/
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
* or limitations. The tests will still be executed, if present, however they will
* not be included in pass/fail counts. They will appear in log as "(IGNORED)" test
* result.
* Add specified test names to the set of tests which should be ignored due to know issues or
* limitations. The tests will still be executed, if present, however they will not be included
* in pass/fail counts. They will appear in log as "(IGNORED)" test result.
*
* @param testNames one or more test names to be ignored
*/
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.
* The default implementation requires the JUnit test class name to end with
* "EmulatorTest" where the portion of the name proceeding this suffix will be
* used as the processor designator
* Get the processor designator used to identify test binary files/folder. The default
* implementation requires the JUnit test class name to end with "EmulatorTest" where the
* portion of the name proceeding this suffix will be used as the processor designator
*
* @return processor designator
*/
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.
*
* @param fileIndex file index within sorted list
* @param filePath binary file path
* @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.
* By default this method will initialize the register states based upon the
* specific register values/context stored at the test group function entry point.
* Such register values may have been established via the processor specification,
* loader or analyzers. A specific test may override or extend
* this behavior for other registers as needed.
* Invoked for each program immediately prior to executing a test group. By default this method
* will initialize the register states based upon the specific register values/context stored at
* the test group function entry point. Such register values may have been established via the
* processor specification, loader or analyzers. A specific test may override or extend this
* behavior for other registers as needed.
*
* @param testRunner emulator group test runner/facilitator
* @param program
* @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
* control structure identification.
* NOTE: This method will only be invoked once during the first test setup
* for all test binaries found. This method will not be invoked
* during subsequent tests since the analyzed program will be cached.
* Invoked immediately following import allow byte processing prior to control structure
* identification. NOTE: This method will only be invoked once during the first test setup for
* all test binaries found. This method will not be invoked during subsequent tests since the
* analyzed program will be cached.
*
* @param program
* @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
* inspection/modification to be performed.
* NOTE: This method will only be invoked once during the first test setup
* for all test binaries found. This method will not be invoked
* during subsequent tests since the analyzed program will be cached.
* inspection/modification to be performed. NOTE: This method will only be invoked once during
* the first test setup for all test binaries found. This method will not be invoked during
* subsequent tests since the analyzed program will be cached.
*
* @param program
* @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
* perform any follow-up changes of inspection of the program.
* NOTE: This method will only be invoked once during the first test setup
* for all test binaries found. This method will not be invoked
* during subsequent tests since the analyzed program will be cached.
* Invoked for non-gzf files immediately after the analyze method to perform any follow-up
* changes of inspection of the program. NOTE: This method will only be invoked once during the
* first test setup for all test binaries found. This method will not be invoked during
* subsequent tests since the analyzed program will be cached.
*
* @param program
* @throws Exception
*/
@@ -1552,10 +1549,10 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
}
/**
* Invoked for non-gzf files to perform auto-analysis.
* NOTE: This method will only be invoked once during the first test setup
* for all test binaries found. This method will not be invoked
* Invoked for non-gzf files to perform auto-analysis. NOTE: This method will only be invoked
* once during the first test setup for all test binaries found. This method will not be invoked
* during subsequent tests since the analyzed program will be cached.
*
* @param program
* @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
* return to use the preferred loader.
* Get the loader class which should be used. A null value should be return to use the preferred
* loader.
*
* @return loader class or null
*/
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 alignment
* @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 alignment
* @return
@@ -159,7 +159,7 @@ public class AdaptedEmulator implements Emulator {
@Override
public <A, T> AddressSetView readUninitialized(PcodeExecutorStatePiece<A, T> piece,
AddressSetView set) {
AddressSetView set, Reason reason) {
PcodeExecutorStatePiece<A, byte[]> bytesPiece =
PcodeStateCallbacks.checkValueDomain(piece, byte[].class);
if (bytesPiece == null) {
@@ -18,6 +18,7 @@ package ghidra.pcode.emu;
import java.util.List;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Instruction;
@@ -29,18 +30,18 @@ import ghidra.program.model.pcode.PcodeOp;
* <p>
* The two (currently) methods that do not implement a pure broadcast pattern are
* {@link #handleMissingUserop(PcodeThread, PcodeOp, PcodeFrame, String, PcodeUseropLibrary)} and
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView)}. For a missing
* userop, it will terminate after the first delegate returns {@code true}, in which case it also
* returns {@code true}. If all delegates return {@code false}, then it returns {@code false}. For
* an uninitialized read, each delegate's returned "still-uninitialized" set is passed to the
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView, Reason)}. For a
* missing userop, it will terminate after the first delegate returns {@code true}, in which case it
* also returns {@code true}. If all delegates return {@code false}, then it returns {@code false}.
* 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
* returned by the composition is that returned by the last delegate. This terminates early when any
* delegate returns the empty set.
*
* <p>
* One (currently) other method has a non-void return type:
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int)}. The
* callback is broadcast as expected, and the return value is the max returned by the delegates.
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Reason)}.
* The callback is broadcast as expected, and the return value is the max returned by the delegates.
*
* @param <T> the emulator's value domain
*/
@@ -207,8 +208,8 @@ public class ComposedPcodeEmulationCallbacks<T> implements PcodeEmulationCallbac
}
@Override
public <A, U> int readUninitialized(PcodeThread<T> thread,
PcodeExecutorStatePiece<A, U> piece, AddressSpace space, A offset, int length) {
public <A, U> int readUninitialized(PcodeThread<T> thread, PcodeExecutorStatePiece<A, U> piece,
AddressSpace space, A offset, int length, Reason reason) {
/**
* 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
@@ -218,7 +219,8 @@ public class ComposedPcodeEmulationCallbacks<T> implements PcodeEmulationCallbac
*/
int maxL = 0;
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) {
return maxL;
}
@@ -228,10 +230,10 @@ public class ComposedPcodeEmulationCallbacks<T> implements PcodeEmulationCallbac
@Override
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;
for (PcodeEmulationCallbacks<T> d : delegates) {
remains = d.readUninitialized(thread, piece, remains);
remains = d.readUninitialized(thread, piece, remains, reason);
if (remains.isEmpty()) {
return remains;
}
@@ -374,9 +374,13 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@Override
public void assignContext(RegisterValue context) {
if (!context.getRegister().isProcessorContext()) {
if (context.getRegister().getBaseRegister() != contextreg) {
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);
}
@@ -386,7 +390,14 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
}
protected final void writeContext(RegisterValue context) {
if (contextreg == Register.NO_CONTEXT && context == null) {
return;
}
assignContext(context);
if (this.context == null) {
assert this.contextreg == Register.NO_CONTEXT;
return;
}
state.setVar(contextreg, arithmetic.fromConst(
this.context.getUnsignedValueIgnoreMask(),
contextreg.getMinimumByteSize(), true));
@@ -16,16 +16,20 @@
package ghidra.pcode.emu;
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.NoSuchElementException;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.mem.*;
import ghidra.util.DifferenceAddressSetView;
import ghidra.util.Msg;
@@ -232,6 +236,51 @@ public enum EmulatorUtilities {
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
*
@@ -250,31 +299,88 @@ public enum EmulatorUtilities {
Register sp = cSpec.getStackPointer();
ThreadPcodeExecutorState<T> state = thread.getState();
ProgramProcessorContext ctx =
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);
}
initializeRegisters(thread, program, entry);
AddressRange stack = chooseStackRange(program, entry);
AddressRange stack = chooseStackRange(program, entry, stackSize);
long stackOffset = cSpec.stackGrowsNegative() ? stack.getMaxAddress().getOffset() + 1
: stack.getMinAddress().getOffset();
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.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Instruction;
@@ -378,18 +379,19 @@ public interface PcodeEmulationCallbacks<T> {
* @param space the address space of the operand
* @param offset the offset 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}
*/
default <A, U> int readUninitialized(PcodeThread<T> thread,
PcodeExecutorStatePiece<A, U> piece, AddressSpace space, A offset, int length) {
default <A, U> int readUninitialized(PcodeThread<T> thread, PcodeExecutorStatePiece<A, U> piece,
AddressSpace space, A offset, int length, Reason reason) {
return 0;
}
/**
* 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
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView)}.
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView, Reason)}.
*
* @param <A> the piece's address 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 offset the offset 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}
*/
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);
AddressSet set = PcodeStateCallbacks.rngSet(space, lOffset, length);
AddressSetView remains = readUninitialized(thread, piece, set);
AddressSetView remains = readUninitialized(thread, piece, set, reason);
if (set == remains) {
return 0;
}
@@ -432,18 +436,19 @@ public interface PcodeEmulationCallbacks<T> {
* {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)}
* @param piece the state piece
* @param set the uninitialized portion required
* @param reason the reason for reading
* @return the addresses in {@code set} that remain uninitialized
*/
default <A, U> AddressSetView readUninitialized(PcodeThread<T> thread,
PcodeExecutorStatePiece<A, U> piece, AddressSetView set) {
PcodeExecutorStatePiece<A, U> piece, AddressSetView set, Reason reason) {
return set;
}
/**
* Typically used from within
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView)} to forward
* to the callback for abstract addressing
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int)}.
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView, Reason)} to
* forward to the callback for abstract addressing
* {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Reason)}.
*
* @param <A> the piece's address domain
* @param <U> the piece's value domain
@@ -451,10 +456,11 @@ public interface PcodeEmulationCallbacks<T> {
* {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)}
* @param piece the state piece
* @param set the uninitialized portion required
* @param reason the reason for reading
* @return the addresses in {@code set} that remain uninitialized
*/
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()) {
return set;
}
@@ -463,7 +469,7 @@ public interface PcodeEmulationCallbacks<T> {
for (AddressRange range : set) {
int l = readUninitialized(thread, piece, range.getAddressSpace(),
piece.getAddressArithmetic().fromConst(range.getMinAddress()),
(int) range.getLength());
(int) range.getLength(), reason);
if (l == 0) {
continue;
}
@@ -497,14 +503,14 @@ public interface PcodeEmulationCallbacks<T> {
@Override
public <A, U> int readUninitialized(PcodeExecutorStatePiece<A, U> piece,
AddressSpace space, A offset, int length) {
return cb.readUninitialized(thread, piece, space, offset, length);
AddressSpace space, A offset, int length, Reason reason) {
return cb.readUninitialized(thread, piece, space, offset, length, reason);
}
@Override
public <A, U> AddressSetView readUninitialized(PcodeExecutorStatePiece<A, U> piece,
AddressSetView set) {
return cb.readUninitialized(thread, piece, set);
AddressSetView set, Reason reason) {
return cb.readUninitialized(thread, piece, set, reason);
}
}
@@ -75,7 +75,7 @@ public abstract class AbstractBytesPcodeExecutorStatePiece<S extends BytesPcodeE
Address min = address.add(addressOffset);
AddressSet set = new AddressSet(min, min.add(buffer.remaining() - 1));
if (set.equals(
cb.readUninitialized(AbstractBytesPcodeExecutorStatePiece.this, set))) {
cb.readUninitialized(AbstractBytesPcodeExecutorStatePiece.this, set, reason))) {
return 0;
}
source = getForSpace(address.getAddressSpace(), false);
@@ -259,7 +259,7 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
S s = getForSpace(space, false);
if (s == null) {
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);
}
s = getForSpace(space, false);
@@ -211,7 +211,7 @@ public class BytesPcodeExecutorStateSpace {
if (uninitialized.isEmpty()) {
return readBytes(offset, size, reason);
}
uninitialized = cb.readUninitialized(piece, uninitialized);
uninitialized = cb.readUninitialized(piece, uninitialized, reason);
if (uninitialized.isEmpty()) {
return readBytes(offset, size, reason);
}
@@ -17,6 +17,7 @@ package ghidra.pcode.exec;
import ghidra.pcode.emu.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
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
* integration-driven behaviors. E.g., to lazily load state from an external machine-state snapshot,
* the client should implement the
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView)} or
* {@link PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView)}
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView, Reason)} or
* {@link PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView, Reason)}
* callback rather than extending {@link BytesPcodeExecutorStatePiece}.
*/
public interface PcodeStateCallbacks {
@@ -164,18 +165,19 @@ public interface PcodeStateCallbacks {
* @param space the address space of the operand
* @param offset the offset 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}
*/
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;
}
/**
* Typically used from within
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSpace, Object, int)} to forward to
* the callback for concrete addressing
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView)}.
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSpace, Object, int, Reason)} to
* forward to the callback for concrete addressing
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView, Reason)}.
*
* @param <A> the piece's address domain
* @param <T> the piece's value domain
@@ -183,13 +185,14 @@ public interface PcodeStateCallbacks {
* @param space the address space of the operand
* @param offset the offset 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}
*/
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);
AddressSet set = PcodeStateCallbacks.rngSet(space, lOffset, length);
AddressSetView remains = readUninitialized(piece, set);
AddressSetView remains = readUninitialized(piece, set, reason);
if (set == remains) {
return 0;
}
@@ -214,27 +217,29 @@ public interface PcodeStateCallbacks {
* @param <T> the piece's value domain
* @param piece the state piece
* @param set the uninitialized portion required
* @param reason the reason for reading
* @return the addresses in {@code set} that remain uninitialized
*/
default <A, T> AddressSetView readUninitialized(PcodeExecutorStatePiece<A, T> piece,
AddressSetView set) {
AddressSetView set, Reason reason) {
return set;
}
/**
* 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
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSpace, Object, int)}.
* {@link #readUninitialized(PcodeExecutorStatePiece, AddressSpace, Object, int, Reason)}.
*
* @param <A> the piece's address domain
* @param <T> the piece's value domain
* @param piece the state piece
* @param set the uninitialized portion required
* @param reason the reason for reading
* @return the addresses in {@code set} that remain uninitialized
*/
default <A, T> AddressSetView delegateReadUninitialized(PcodeExecutorStatePiece<A, T> piece,
AddressSetView set) {
AddressSetView set, Reason reason) {
if (set.isEmpty()) {
return set;
}
@@ -242,7 +247,7 @@ public interface PcodeStateCallbacks {
for (AddressRange range : set) {
int l = readUninitialized(piece, range.getAddressSpace(),
piece.getAddressArithmetic().fromConst(range.getMinAddress()),
(int) range.getLength());
(int) range.getLength(), reason);
if (l == 0) {
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
their dependencies. A common forgotten or incorrectly-versioned
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
Ghidra under <code>Debugger-rmi-trace/pypkg/dist</code> for your
convenience.</p>
<code>protobuf==3.20.3</code>, newer versions generally work fine. Its
“sdist” package is distributed with Ghidra under
<code>Debugger-rmi-trace/pypkg/dist</code> for your convenience.</p>
<p>It is also possible that <code>gdb</code> has embedded a different
version of the interpreter than the one that <code>python3</code>
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-19"><a href="#cb8-19"></a> <span class="op">}</span></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-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-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-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-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>
@@ -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-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-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-89"><a href="#cb8-89"></a></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);
}
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
Expr expr = map.get(offset);
if (expr == null) {
byte[] aOffset =
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);
}
}
@@ -621,7 +621,7 @@ public static class ExprPcodeExecutorStatePiece
@Override
protected Expr getFromSpace(ExprSpace space, long offset, int size, Reason reason,
PcodeStateCallbacks cb) {
return space.get(offset, size, cb);
return space.get(offset, size, reason, cb);
}
@Override
@@ -336,13 +336,13 @@ public class ModelingScript extends GhidraScript {
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
Expr expr = map.get(offset);
if (expr == null) {
byte[] aOffset =
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);
}
}
@@ -402,7 +402,7 @@ public class ModelingScript extends GhidraScript {
@Override
protected Expr getFromSpace(ExprSpace space, long offset, int size, Reason reason,
PcodeStateCallbacks cb) {
return space.get(offset, size, cb);
return space.get(offset, size, reason, cb);
}
@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
corresponding Ghidra Scripts (EmuX86DeobfuscateExampleScript and EmuX86GccDeobfuscateHookExampleScript)
used to demonstrate the use of the EmulatorHelper class.
used to demonstrate the use of the PcodeEmulator class.