GP-6397: Fix JIT bugs, esp., found by converting deob scripts.

This commit is contained in:
Dan
2026-02-27 16:36:05 +00:00
parent 52f44893d5
commit 05ffbfc488
10 changed files with 118 additions and 34 deletions

View File

@@ -23,11 +23,15 @@
// 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.lang.invoke.MethodHandles;
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.app.util.opinion.ElfLoader;
import ghidra.pcode.emu.*;
import ghidra.pcode.emu.jit.JitConfiguration;
import ghidra.pcode.emu.jit.JitConfiguration.Opt;
import ghidra.pcode.emu.jit.JitPcodeEmulator;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.InsufficientBytesException;
@@ -94,13 +98,23 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
strlenEntry = getExternalThunkAddress("strlen");
// Establish emulator
emu = new PcodeEmulator(currentProgram.getLanguage()) {
emu = new JitPcodeEmulator(currentProgram.getLanguage(), new JitConfiguration(
Opt.REMOVE_UNUSED_OPERATIONS, Opt.EMIT_COUNTERS/*, Opt.LOG_STACK_TRACES*/),
MethodHandles.lookup()) {
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return super.createUseropLibrary().compose(new DeobfUseropLibrary<byte[]>());
}
@Override
public boolean isSuspended() {
// Because the monitor-cancelled listener isn't reliable
return super.isSuspended() || monitor.isCancelled();
}
};
monitor.addCancelledListener(() -> {
// Why isn't this reliable?
emu.setSuspended(true);
});
emuThread = emu.newThread();

View File

@@ -15,6 +15,8 @@
*/
package ghidra.pcode.emu.jit;
import java.util.Set;
/**
* The configuration for a JIT-accelerated emulator.
*
@@ -30,23 +32,64 @@ package ghidra.pcode.emu.jit;
* limit is exceeded, the ASM library throws an exception. When this happens, the
* compiler will retry the whole process, but with this configuration parameter halved.
* @param maxPassageStrides The maximum number of strides to include.
* @param removeUnusedOperations Some p-code ops produce outputs that are never used later. One
* common case is flags computed from arithmetic operations. If this option is enabled,
* the JIT compiler will remove those p-code ops.
* @param emitCounters Causes the translator to emit a call to
* {@link JitPcodeThread#count(int, int)} at the start of each basic block.
* @param removeUnusedOperations See {@link Opt#REMOVE_UNUSED_OPERATIONS}
* @param emitCounters See {@link Opt#EMIT_COUNTERS}
* @param logStackTraces See {@link Opt#LOG_STACK_TRACES}
*/
public record JitConfiguration(
int maxPassageInstructions,
int maxPassageOps,
int maxPassageStrides,
boolean removeUnusedOperations,
boolean emitCounters) {
boolean emitCounters,
boolean logStackTraces) {
/**
* Fluent specifiers for the boolean options of {@link JitConfiguration}
*/
public enum Opt {
/**
* Some p-code ops produce outputs that are never used later. One common case is flags
* computed from arithmetic operations. If this option is enabled, the JIT compiler will
* remove those p-code ops.
*/
REMOVE_UNUSED_OPERATIONS,
/**
* Causes the translator to emit a call to {@link JitPcodeThread#count(int, int)} at the
* start of each basic block.
*/
EMIT_COUNTERS,
/**
* Causes the translator to emit code to print a stack trace in its exception handlers.
*/
LOG_STACK_TRACES,
}
/**
* Construct a default configuration
*/
public JitConfiguration() {
this(1000, 5000, 10, true, true);
this(1000, 5000, 10, true, true, false);
}
/**
* Construct a configuration with default maxes and the given boolean options
*
* @param opts the options
*/
public JitConfiguration(Set<Opt> opts) {
this(1000, 5000, 10,
opts.contains(Opt.REMOVE_UNUSED_OPERATIONS),
opts.contains(Opt.EMIT_COUNTERS),
opts.contains(Opt.LOG_STACK_TRACES));
}
/**
* Construct a configuration with default maxes and the given boolean options
*
* @param opts the options
*/
public JitConfiguration(Opt... opts) {
this(Set.of(opts));
}
}

View File

@@ -20,6 +20,7 @@ import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.processors.sleigh.template.OpTpl;
import ghidra.app.util.PseudoInstruction;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeThread;
@@ -1063,6 +1064,27 @@ public class JitPassage extends PcodeProgram {
}).collect(Collectors.joining("\n ")) + "\n>\n" + format(true);
}
@Override
public String format(boolean numberOps) {
return new MyFormatter(this, numberOps) {
@Override
protected FormatResult formatOpTemplate(MyAppender appender, OpTpl tpl) {
if (tpl.getOpcode() != PcodeOp.UNIMPLEMENTED) {
return super.formatOpTemplate(appender, tpl);
}
return switch (code.get(appender.getOpIdx())) {
case NopPcodeOp nop -> {
appender.appendIndent();
appender.appendString("NOP(%d)".formatted(System.identityHashCode(nop)));
appender.endLine();
yield FormatResult.CONTINUE;
}
default -> super.formatOpTemplate(appender, tpl);
};
}
}.formatOps(language, code);
}
/**
* Check if a given p-code op is the first of an instruction.
*

View File

@@ -243,7 +243,7 @@ public class JitPcodeThread extends BytesPcodeThread {
finishInstruction();
}
EntryPoint next = null;
while (!isSuspended()) {
while (!isSuspended() && !getMachine().isSuspended()) {
if (next == null) {
next = getEntry(new AddrCtx(getContext(), getCounter()));
}
@@ -272,7 +272,7 @@ public class JitPcodeThread extends BytesPcodeThread {
* in the current (partial) instruction.
*/
public void count(int instructions, int trailingOps) {
if (isSuspended()) {
if (isSuspended() || getMachine().isSuspended()) {
throw new SuspendedPcodeExecutionException(null, null);
}
}

View File

@@ -74,7 +74,7 @@ class DecoderExecutor extends PcodeExecutor<Object>
final AddrCtx at;
private PseudoInstruction instruction;
private NopPcodeOp termNop;
private final Map<PcodeFrame, NopPcodeOp> termNopsPerFrame = new HashMap<>();
private RegisterValue flow;
private final Map<Address, RegisterValue> futCtx = new HashMap<>();
@@ -225,6 +225,7 @@ class DecoderExecutor extends PcodeExecutor<Object>
@Override
public void finish(PcodeFrame frame, PcodeUseropLibrary<Object> library) {
super.finish(frame, library);
NopPcodeOp termNop = termNopsPerFrame.remove(frame);
if (termNop != null) {
opsForThisStep.add(termNop);
}
@@ -319,9 +320,9 @@ class DecoderExecutor extends PcodeExecutor<Object>
* {@inheritDoc}
*
* <p>
* This create an {@link IntBranch} record and collects it for this instruction step. The record
* will first be used to check for fall through. Then, the passage decoder is notified, which
* collects the records to later passage-wide control flow analysis.
* This creates an {@link IntBranch} record and collects it for this instruction step. The
* record will first be used to check for fall through. Then, the passage decoder is notified,
* which collects the records for later passage-wide control flow analysis.
*
* @see #checkFallthroughAndAccumulate(PcodeProgram)
*/
@@ -329,9 +330,8 @@ class DecoderExecutor extends PcodeExecutor<Object>
protected void branchInternal(PcodeOp op, PcodeFrame frame, int relative) {
int tgtSeq = op.getSeqnum().getTime() + relative;
if (tgtSeq == frame.getCode().size()) {
if (termNop == null) {
termNop = new NopPcodeOp(at, tgtSeq);
}
NopPcodeOp termNop =
termNopsPerFrame.computeIfAbsent(frame, f -> new NopPcodeOp(at, tgtSeq));
branchesForThisStep.add(new SIntBranch(op, termNop, false));
}
else {
@@ -454,16 +454,6 @@ class DecoderExecutor extends PcodeExecutor<Object>
* @return the reachability of the fall-through flow
*/
public Reachability checkFallthroughAndAccumulate(PcodeProgram from) {
if (instruction instanceof DecodeErrorInstruction) {
stride.opsForStride.addAll(opsForThisStep);
for (Branch branch : branchesForThisStep) {
switch (branch) {
case ErrBranch eb -> stride.passage.otherBranches.put(eb.from(), eb);
default -> throw new AssertionError();
}
}
return null;
}
if (opsForThisStep.isEmpty()) {
return Reachability.WITHOUT_CTXMOD;
}

View File

@@ -21,6 +21,7 @@ import ghidra.pcode.emu.jit.gen.JitCodeGenerator.PcGen;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
import ghidra.pcode.emu.jit.gen.util.*;
import ghidra.pcode.emu.jit.gen.util.Emitter.*;
import ghidra.pcode.emu.jit.gen.util.Methods.Inv;
import ghidra.pcode.emu.jit.gen.util.Types.TRef;
import ghidra.program.model.pcode.PcodeOp;
@@ -65,8 +66,16 @@ public record ExceptionHandler(PcodeOp op, JitBlock block, Lbl<Ent<Bot, TRef<Thr
*/
public <THIS extends JitCompiledPassage> Emitter<Dead> genRun(Emitter<Dead> em,
Local<TRef<THIS>> localThis, JitCodeGenerator<THIS> gen) {
return em
.emit(Lbl::placeDead, lbl)
var emLive = em.emit(Lbl::placeDead, lbl);
if (gen.context.getConfiguration().logStackTraces()) {
emLive = emLive
.emit(Op::dup)
.emit(Op::invokevirtual, GenConsts.T_THROWABLE, "printStackTrace",
GenConsts.MDESC_THROWABLE__PRINT_STACK_TRACE, false)
.step(Inv::takeObjRef)
.step(Inv::retVoid);
}
return emLive
.emit(gen::genExit, localThis, block, PcGen.loadOffset(gen.getAddressForOp(op)),
gen.getExitContext(op))
.emit(Op::athrow);

View File

@@ -272,6 +272,8 @@ public interface GenConsts {
.param(Types.T_INT_ARR)
.param(Types.T_INT)
.build();
public static final MthDesc<TVoid, Bot> MDESC_THROWABLE__PRINT_STACK_TRACE =
MthDesc.returns(Types.T_VOID).build();
/**
* This is just to assure all the methods referred to below have the same signature. The fields

View File

@@ -171,8 +171,8 @@ public interface IntPredBinOpGen<T extends JitBinOp> extends BinOpGen<T> {
JitCodeGenerator<THIS> gen, T op, JitBlock block, Scope scope) {
JitType lType = gen.resolveType(op.l(), op.lType());
JitType rType = gen.resolveType(op.r(), op.rType());
assert rType == lType;
return new LiveOpResult(switch (lType) {
JitType uType = JitType.unify(lType, rType);
return new LiveOpResult(switch (uType) {
case IntJitType t -> em
.emit(gen::genReadToStack, localThis, op.l(), t, ext())
.emit(gen::genReadToStack, localThis, op.r(), t, rExt())

View File

@@ -54,11 +54,11 @@ public class PcodeProgram {
}
@Override
protected void appendString(String string) {
public void appendString(String string) {
buf.append(string);
}
protected void endLine() {
public void endLine() {
buf.append("\n");
}
@@ -90,6 +90,10 @@ public class PcodeProgram {
buf.append(": ");
}
}
public int getOpIdx() {
return opIdx;
}
}
protected static class MyFormatter extends AbstractPcodeFormatter<String, MyAppender> {

View File

@@ -144,7 +144,7 @@ public interface PcodeFormatter<T> {
Collections.sort(offsetList);
for (int i = offsetList.size() - 1; i >= 0; i--) {
int labelOffset = offsetList.get(i);
if (labelOffset > pcodeOps.size()) {
if (labelOffset > pcodeOps.size() || labelOffset < 0) {
// Skip jumps out of this block/program
continue;
}