From 05ffbfc4880a641e8a10ba00d4ef56de4367350d Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Fri, 27 Feb 2026 16:36:05 +0000 Subject: [PATCH] GP-6397: Fix JIT bugs, esp., found by converting deob scripts. --- ...EmuX86GccDeobfuscateHookExampleScript.java | 16 +++++- .../pcode/emu/jit/JitConfiguration.java | 57 ++++++++++++++++--- .../java/ghidra/pcode/emu/jit/JitPassage.java | 22 +++++++ .../ghidra/pcode/emu/jit/JitPcodeThread.java | 4 +- .../pcode/emu/jit/decode/DecoderExecutor.java | 24 +++----- .../pcode/emu/jit/gen/ExceptionHandler.java | 13 ++++- .../ghidra/pcode/emu/jit/gen/GenConsts.java | 2 + .../pcode/emu/jit/gen/op/IntPredBinOpGen.java | 4 +- .../java/ghidra/pcode/exec/PcodeProgram.java | 8 ++- .../ghidra/app/util/pcode/PcodeFormatter.java | 2 +- 10 files changed, 118 insertions(+), 34 deletions(-) diff --git a/Ghidra/Features/Base/ghidra_scripts/EmuX86GccDeobfuscateHookExampleScript.java b/Ghidra/Features/Base/ghidra_scripts/EmuX86GccDeobfuscateHookExampleScript.java index 7c9e2732c2..846ae08eee 100644 --- a/Ghidra/Features/Base/ghidra_scripts/EmuX86GccDeobfuscateHookExampleScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/EmuX86GccDeobfuscateHookExampleScript.java @@ -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 createUseropLibrary() { return super.createUseropLibrary().compose(new DeobfUseropLibrary()); } + + @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(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitConfiguration.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitConfiguration.java index 02fca050b5..1b38236d08 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitConfiguration.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitConfiguration.java @@ -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 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)); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPassage.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPassage.java index 77d790fbbe..ba3088843f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPassage.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPassage.java @@ -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. * diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPcodeThread.java index 8c998a70f5..1767980c5e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPcodeThread.java @@ -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); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderExecutor.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderExecutor.java index 43eca5bc3e..b9ecbd6734 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderExecutor.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderExecutor.java @@ -74,7 +74,7 @@ class DecoderExecutor extends PcodeExecutor final AddrCtx at; private PseudoInstruction instruction; - private NopPcodeOp termNop; + private final Map termNopsPerFrame = new HashMap<>(); private RegisterValue flow; private final Map futCtx = new HashMap<>(); @@ -225,6 +225,7 @@ class DecoderExecutor extends PcodeExecutor @Override public void finish(PcodeFrame frame, PcodeUseropLibrary library) { super.finish(frame, library); + NopPcodeOp termNop = termNopsPerFrame.remove(frame); if (termNop != null) { opsForThisStep.add(termNop); } @@ -319,9 +320,9 @@ class DecoderExecutor extends PcodeExecutor * {@inheritDoc} * *

- * 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 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 * @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; } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/ExceptionHandler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/ExceptionHandler.java index 6a7de75cd3..59a6334074 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/ExceptionHandler.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/ExceptionHandler.java @@ -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 Emitter genRun(Emitter em, Local> localThis, JitCodeGenerator 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); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java index d707210ee9..708981acd8 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java @@ -272,6 +272,8 @@ public interface GenConsts { .param(Types.T_INT_ARR) .param(Types.T_INT) .build(); + public static final MthDesc 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 diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntPredBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntPredBinOpGen.java index ab3d792941..e8461e40c0 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntPredBinOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntPredBinOpGen.java @@ -171,8 +171,8 @@ public interface IntPredBinOpGen extends BinOpGen { JitCodeGenerator 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()) diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeProgram.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeProgram.java index a444b34cf7..9ef11c988b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeProgram.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeProgram.java @@ -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 { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/pcode/PcodeFormatter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/pcode/PcodeFormatter.java index 31af8eb9ff..39e25d1d44 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/pcode/PcodeFormatter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/pcode/PcodeFormatter.java @@ -144,7 +144,7 @@ public interface PcodeFormatter { 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; }