diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java index 396f641a18..3f582677c0 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java @@ -181,6 +181,11 @@ public interface EmuSyscallLibrary extends PcodeUseropLibrary { return true; } + @Override + public boolean modifiesContext() { + return false; + } + @Override public boolean canInlinePcode() { return false; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java index 11eda8abf2..ba553c5398 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java @@ -171,9 +171,10 @@ public class DefaultPcodeThread implements PcodeThread { @Override public void executeSleigh(String source) { + PcodeUseropLibrary library = thread.getUseropLibrary(); PcodeProgram program = - SleighProgramCompiler.compileProgram(language, "exec", source, thread.library); - execute(program, thread.library); + SleighProgramCompiler.compileProgram(language, "exec", source, library); + execute(program, library); } @Override @@ -224,7 +225,9 @@ public class DefaultPcodeThread implements PcodeThread { protected final PcodeArithmetic arithmetic; protected final ThreadPcodeExecutorState state; protected final InstructionDecoder decoder; - protected final PcodeUseropLibrary library; + + // Delay, and compute lazily + private PcodeUseropLibrary library; protected final PcodeThreadExecutor executor; protected final Register pc; @@ -255,7 +258,6 @@ public class DefaultPcodeThread implements PcodeThread { PcodeExecutorState localState = machine.createLocalState(this); this.state = createThreadState(sharedState, localState); this.decoder = createInstructionDecoder(sharedState); - this.library = createUseropLibrary(); this.executor = createExecutor(); this.pc = @@ -342,7 +344,7 @@ public class DefaultPcodeThread implements PcodeThread { decoder.branched(counter); } - protected void writeCounter(Address counter) { + protected final void writeCounter(Address counter) { setCounter(counter); state.setVar(pc, arithmetic.fromConst(counter.getAddressableWordOffset(), pc.getMinimumByteSize())); @@ -366,14 +368,18 @@ public class DefaultPcodeThread implements PcodeThread { return context; } - @Override - public void overrideContext(RegisterValue context) { + protected final void writeContext(RegisterValue context) { assignContext(context); state.setVar(contextreg, arithmetic.fromConst( this.context.getUnsignedValueIgnoreMask(), contextreg.getMinimumByteSize(), true)); } + @Override + public void overrideContext(RegisterValue context) { + writeContext(context); + } + @Override public void overrideContextWithDefault() { if (contextreg != Register.NO_CONTEXT) { @@ -421,7 +427,7 @@ public class DefaultPcodeThread implements PcodeThread { if (inj != null) { instruction = null; try { - executor.execute(inj, library); + executor.execute(inj, getUseropLibrary()); } catch (PcodeExecutionException e) { frame = e.getFrame(); @@ -439,7 +445,7 @@ public class DefaultPcodeThread implements PcodeThread { beginInstructionOrInject(); } else if (!frame.isFinished()) { - executor.step(frame, library); + executor.step(frame, getUseropLibrary()); } else { advanceAfterFinished(); @@ -462,7 +468,7 @@ public class DefaultPcodeThread implements PcodeThread { @Override public void stepPatch(String sleigh) { PcodeProgram prog = getMachine().compileSleigh("patch", sleigh + ";"); - executor.execute(prog, library); + executor.execute(prog, getUseropLibrary()); } /** @@ -523,14 +529,14 @@ public class DefaultPcodeThread implements PcodeThread { return; } if (frame.isFallThrough()) { - overrideCounter(counter.addWrap(decoder.getLastLengthWithDelays())); + writeCounter(counter.addWrap(decoder.getLastLengthWithDelays())); } if (contextreg != Register.NO_CONTEXT) { RegisterValue ctx = new RegisterValue(contextreg, BigInteger.ZERO) .combineValues(defaultContext.getDefaultValue(contextreg, counter)) .combineValues(defaultContext.getFlowValue(context)) .combineValues(getContextAfterCommits()); - overrideContext(ctx); + writeContext(ctx); } postExecuteInstruction(); frame = null; @@ -603,7 +609,7 @@ public class DefaultPcodeThread implements PcodeThread { PcodeProgram insProg = PcodeProgram.fromInstruction(instruction); preExecuteInstruction(); try { - frame = executor.execute(insProg, library); + frame = executor.execute(insProg, getUseropLibrary()); } catch (PcodeExecutionException e) { frame = e.getFrame(); @@ -615,7 +621,7 @@ public class DefaultPcodeThread implements PcodeThread { @Override public void finishInstruction() { assertMidInstruction(); - executor.finish(frame, library); + executor.finish(frame, getUseropLibrary()); advanceAfterFinished(); } @@ -669,6 +675,9 @@ public class DefaultPcodeThread implements PcodeThread { @Override public PcodeUseropLibrary getUseropLibrary() { + if (library == null) { + library = createUseropLibrary(); + } return library; } @@ -697,7 +706,7 @@ public class DefaultPcodeThread implements PcodeThread { @Override public void inject(Address address, String source) { PcodeProgram pcode = SleighProgramCompiler.compileProgram( - language, "thread_inject:" + address, source, library); + language, "thread_inject:" + address, source, getUseropLibrary()); injects.put(address, pcode); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java index d706d13489..bd75907bfa 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java @@ -16,18 +16,22 @@ package ghidra.pcode.emu; import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.*; +import java.util.Map.Entry; import ghidra.app.emulator.AdaptedMemoryState; import ghidra.app.emulator.Emulator; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.emulate.*; -import ghidra.pcode.exec.ConcretionError; +import ghidra.pcode.emulate.callother.OpBehaviorOther; +import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.pcode.memstate.MemoryBank; import ghidra.pcode.memstate.MemoryState; import ghidra.program.model.address.Address; import ghidra.program.model.lang.*; -import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; import ghidra.util.Msg; /** @@ -88,6 +92,93 @@ public class ModifiedPcodeThread extends DefaultPcodeThread { } } + /** + * For incorporating the state modifier's userop behaviors + */ + protected class ModifierUseropLibrary implements PcodeUseropLibrary { + + /** + * A wrapper around {@link OpBehaviorOther} + */ + protected class ModifierUseropDefinition implements PcodeUseropDefinition { + private final String name; + private final OpBehaviorOther behavior; + + public ModifierUseropDefinition(String name, OpBehaviorOther behavior) { + this.name = name; + this.behavior = behavior; + } + + @Override + public String getName() { + return name; + } + + @Override + public int getInputCount() { + return -1; + } + + @Override + public void execute(PcodeExecutor executor, PcodeUseropLibrary library, + Varnode outVar, List inVars) { + behavior.evaluate(emulate, outVar, inVars.toArray(Varnode[]::new)); + } + + @Override + public boolean isFunctional() { + return false; + } + + @Override + public boolean hasSideEffects() { + return true; + } + + @Override + public boolean modifiesContext() { + return true; + } + + @Override + public boolean canInlinePcode() { + return false; + } + + @Override + public Method getJavaMethod() { + return null; + } + + @Override + public PcodeUseropLibrary getDefiningLibrary() { + return ModifierUseropLibrary.this; + } + } + + Map> userops; + + private Map> computeUserops() { + if (modifier == null) { + return Map.of(); + } + Map> userops = new HashMap<>(); + for (Entry entry : modifier.getPcodeOpMap().entrySet()) { + String name = language.getUserDefinedOpName(entry.getKey()); + userops.put(name, new ModifierUseropDefinition(name, entry.getValue())); + } + return userops; + } + + @Override + public Map> getUserops() { + if (userops == null) { + userops = computeUserops(); + } + return userops; + } + } + /** * Part of the glue that makes existing state modifiers work in new emulation framework * @@ -155,6 +246,11 @@ public class ModifiedPcodeThread extends DefaultPcodeThread { } } + @Override + protected PcodeUseropLibrary createUseropLibrary() { + return super.createUseropLibrary().compose(new ModifierUseropLibrary()); + } + @Override public void overrideCounter(Address counter) { super.overrideCounter(counter); @@ -171,12 +267,4 @@ public class ModifiedPcodeThread extends DefaultPcodeThread { frame.getBranched(), getCounter()); } } - - @Override - protected boolean onMissingUseropDef(PcodeOp op, String opName) { - if (modifier != null) { - return modifier.executeCallOther(op); - } - return super.onMissingUseropDef(op, opName); - } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitCompiler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitCompiler.java index 185fd8bd58..d6d99d2ef6 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitCompiler.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitCompiler.java @@ -180,6 +180,7 @@ public class JitCompiler { * In production, this should be empty. */ public static final EnumSet ENABLE_DIAGNOSTICS = EnumSet.noneOf(Diag.class); + /** * Exclude a given address offset from ASM's {@link ClassWriter#COMPUTE_MAXS} and * {@link ClassWriter#COMPUTE_FRAMES}. 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 502359c701..77d790fbbe 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 @@ -31,6 +31,7 @@ import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.op.OpGen; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.exec.*; +import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.PcodeUserop; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressOverflowException; import ghidra.program.model.lang.*; @@ -252,6 +253,122 @@ public class JitPassage extends PcodeProgram { } } + /** + * A branch as analyzed within an instruction step + * + *

+ * After intra-instruction reachability is determined and this branch is to be added to the + * whole passage, it will be "upgraded" to a {@link PBranch}. + */ + public interface SBranch extends Branch { + } + + /** + * A branch as analyzed within a passage + * + *

+ * Many implement this via {@link RBranch}. + */ + public interface PBranch extends Branch { + } + + /** + * A branch with known intra-instruction reachability + */ + public interface RBranch extends PBranch { + /** + * The intra-instruction reachability + * + * @return the reachability + */ + Reachability reach(); + } + + /** + * Describes the manner in which something is reachable, wrt. dynamic context changes within + * an instruction step. + * + *

+ * At the moment, the only way context can be changed dynamically is via a p-code userop. Such + * ops must have the {@link PcodeUserop#modifiesContext()} attribute set. If such an op is known + * to have been executed when finishing an instruction (either by branch or fall-through), we + * must exit the compiled passage. + */ + public enum Reachability { + /** + * There is at least one path to reach it. None of them modify the context dynamically. + */ + WITHOUT_CTXMOD { + @Override + public Reachability combine(Reachability that) { + return switch (that) { + case null -> this; + case WITHOUT_CTXMOD -> WITHOUT_CTXMOD; + case MAYBE_CTXMOD -> MAYBE_CTXMOD; + case WITH_CTXMOD -> MAYBE_CTXMOD; + }; + } + + @Override + public boolean canReachWithoutCtxMod() { + return true; + } + }, + /** + * There are at least two paths to reach it. Some modify the context dynamically, and some + * do not. + */ + MAYBE_CTXMOD { + @Override + public Reachability combine(Reachability that) { + return MAYBE_CTXMOD; + } + + @Override + public boolean canReachWithoutCtxMod() { + return true; + } + }, + /** + * There is at least one path to reach it. All of them modify the context dynamically. + */ + WITH_CTXMOD { + @Override + public Reachability combine(Reachability that) { + return switch (that) { + case null -> this; + case WITHOUT_CTXMOD -> MAYBE_CTXMOD; + case MAYBE_CTXMOD -> MAYBE_CTXMOD; + case WITH_CTXMOD -> WITH_CTXMOD; + }; + } + + @Override + public boolean canReachWithoutCtxMod() { + return false; + } + }; + + /** + * Consider this and another reachability as "or" + * + * @param that the other reachability + * @return the "or" of both + */ + public abstract Reachability combine(Reachability that); + + /** + * Check if it is possible for this block to be reached without a context modification. + * + *

+ * This is true if there exists any path to this block that doesn't include a + * possible context modification. + * + * @return true if reachable without context modification, false otherwise. + */ + public abstract boolean canReachWithoutCtxMod(); + } + /** * A branch to another p-code op in the same passage * @@ -260,12 +377,46 @@ public class JitPassage extends PcodeProgram { * equivalent branch to the translation of the target p-code op. Thus, we remain executing * inside the {@link JitCompiledPassage#run(int) run} method. This branch type incurs the least * run-time cost. - * - * @param from see {@link #from()} - * @param to the target p-code op - * @param isFall see {@link #isFall()} */ - public record IntBranch(PcodeOp from, PcodeOp to, boolean isFall) implements Branch {} + public interface IntBranch extends Branch { + /** + * The target pcode op + * + * @return the op + */ + PcodeOp to(); + } + + /** + * An {@link IntBranch} as analyzed during one instruction step + * + * @param from see {@link IntBranch#from()} + * @param to see {@link IntBranch#to()} + * @param isFall see {@link IntBranch#isFall()} + */ + public record SIntBranch(PcodeOp from, PcodeOp to, boolean isFall) + implements IntBranch, SBranch { + /** + * Upgrade this branch to an {@link RIntBranch} for inclusion in the passage. + * + * @param reach see {@link RBranch#reach()} + * @return the branch + */ + public RIntBranch withReach(Reachability reach) { + return new RIntBranch(from, to, isFall, reach); + } + } + + /** + * A {@link IntBranch} as added to the passage + * + * @param from see {@link IntBranch#from()} + * @param to see {@link IntBranch#to()} + * @param isFall see {@link IntBranch#isFall()} + * @param reach see {@link RBranch#reach()} + */ + public record RIntBranch(PcodeOp from, PcodeOp to, boolean isFall, Reachability reach) + implements IntBranch, RBranch {} /** * A branch to an address (and context value) not in the same passage @@ -275,13 +426,62 @@ public class JitPassage extends PcodeProgram { * sets the emulator's program counter and context to the {@link #to() branch target} and * returns the appropriate entry point for further execution. * + *

* Note that this branch type is used by the decoder to track queued decode seeds as well. * External branches that get decoded are changed into internal branches. - * - * @param from see {@link #from()} - * @param to the target address-context pair */ - public record ExtBranch(PcodeOp from, AddrCtx to) implements Branch {} + public interface ExtBranch extends Branch { + /** + * The target address-context pair + * + * @return the target + */ + AddrCtx to(); + } + + /** + * An {@link ExtBranch} as analyzed during one instruction step + * + * @param from see {@link ExtBranch#from()} + * @param to see {@link ExtBranch#to()} + */ + public record SExtBranch(PcodeOp from, AddrCtx to) implements ExtBranch, SBranch { + /** + * Upgrade this branch to an {@link RExtBranch} for inclusion in the passage. + * + * @param reach see {@link RBranch#reach()} + * @return the branch + */ + public RExtBranch withReach(Reachability reach) { + return new RExtBranch(from, to, reach); + } + } + + /** + * A {@link ExtBranch} as added to the passage + * + * @param from see {@link ExtBranch#from()} + * @param to see {@link ExtBranch#to()} + * @param reach see {@link RBranch#reach()} + */ + public record RExtBranch(PcodeOp from, AddrCtx to, Reachability reach) + implements ExtBranch, RBranch { + /** + * Convert this external branch into an internal one + * + *

+ * This is called whenever it becomes the case that an external target is decoded an added + * to the passage, making it an internal branch. Notably, this happens when selecting a seed + * from the queue of externals, when flowing to a target that is already decoded, and when + * finishing up a passage where all remaining seeds must be examined. + * + * @param to the target p-code op + * @return the resulting internal branch + */ + public RIntBranch toIntBranch(PcodeOp to) { + return new RIntBranch(from, to, false, reach); + } + } /** * A branch to a dynamic address @@ -295,11 +495,43 @@ public class JitPassage extends PcodeProgram { * TODO: Some analysis may be possible to narrow the possible addresses to a known few and then * treat this as several {@link IntBranch}es; however, I worry this is too expensive for what it * gets us. This will be necessary if we are to JIT, e.g., a switch table. - * - * @param from see {@link #from()} - * @param flowCtx the decode context after the branch is taken */ - public record IndBranch(PcodeOp from, RegisterValue flowCtx) implements Branch {} + public interface IndBranch extends Branch { + /** + * The decode context after the branch is taken + * + * @return the context + */ + RegisterValue flowCtx(); + } + + /** + * An {@link IndBranch} as analyzed during one instruction step + * + * @param from see {@link IndBranch#from()} + * @param flowCtx see {@link IndBranch#flowCtx()} + */ + public record SIndBranch(PcodeOp from, RegisterValue flowCtx) implements IndBranch, SBranch { + /** + * Upgrade this branch to an {@link RIndBranch} for inclusion in the passage. + * + * @param reach see {@link RBranch#reach()} + * @return the branch + */ + public RIndBranch withReach(Reachability reach) { + return new RIndBranch(from, flowCtx, reach); + } + } + + /** + * A {@link IndBranch} as added to the passage + * + * @param from see {@link IndBranch#from()} + * @param flowCtx see {@link IndBranch#flowCtx()} + * @param reach see {@link RBranch#reach()} + */ + public record RIndBranch(PcodeOp from, RegisterValue flowCtx, Reachability reach) + implements IndBranch, RBranch {} /** * A "branch" representing an error @@ -327,7 +559,7 @@ public class JitPassage extends PcodeProgram { * @param from see {@link #from()} * @param message the error message for the exception */ - public record ErrBranch(PcodeOp from, String message) implements Branch {} + public record ErrBranch(PcodeOp from, String message) implements SBranch, PBranch {} /** * An extension of {@link PcodeOp} that carries along with it the address and decode context @@ -462,10 +694,26 @@ public class JitPassage extends PcodeProgram { * * @param at the address and context value to set on the emulator when exiting the * {@link JitCompiledPassage#run(int)} method + * @return the op */ - public ExitPcodeOp(AddrCtx at) { - super(new SequenceNumber(at.address, 0), PcodeOp.BRANCH, new Varnode[] { - new Varnode(at.address, 0) }, null); + public static ExitPcodeOp exit(AddrCtx at) { + return new ExitPcodeOp(PcodeOp.BRANCH, at); + } + + /** + * Construct a synthetic conditional exit op + * + * @param at the address and context value to set on the emulator when exiting the + * {@link JitCompiledPassage#run(int)} method + * @return the op + */ + public static ExitPcodeOp cond(AddrCtx at) { + return new ExitPcodeOp(PcodeOp.CBRANCH, at); + } + + private ExitPcodeOp(int opcode, AddrCtx at) { + super(new SequenceNumber(at.address, 0), opcode, + new Varnode[] { new Varnode(at.address, 0) }, null); } } @@ -703,7 +951,7 @@ public class JitPassage extends PcodeProgram { private final List instructions; private final AddrCtx entry; private final PcodeUseropLibrary decodeLibrary; - private final Map branches; + private final Map branches; private final Map entries; private final Register contextreg; private final ProgramContextImpl defaultContext; @@ -725,7 +973,7 @@ public class JitPassage extends PcodeProgram { */ public JitPassage(SleighLanguage language, AddrCtx entry, List code, PcodeUseropLibrary decodeLibrary, List instructions, - Map branches, Map entries) { + Map branches, Map entries) { super(language, code, decodeLibrary.getSymbols(language)); this.entry = entry; this.decodeLibrary = decodeLibrary; @@ -804,7 +1052,7 @@ public class JitPassage extends PcodeProgram { * * @return the branches, keyed by {@link Branch#from()}. */ - public Map getBranches() { + public Map getBranches() { return branches; } 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 5bad5458bf..8c998a70f5 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 @@ -27,6 +27,7 @@ import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPointPrototype; import ghidra.pcode.exec.*; import ghidra.program.model.address.Address; +import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.listing.ProgramContext; /** @@ -275,4 +276,40 @@ public class JitPcodeThread extends BytesPcodeThread { throw new SuspendedPcodeExecutionException(null, null); } } + + /** + * Write the given counter and context to the emulator and its machine state + * + * @param counter the counter + * @param context the context + */ + public void writeCounterAndContext(Address counter, RegisterValue context) { + // Not overrideCounter/Context. Things can override those. + writeCounter(counter); + if (context != null) { + writeContext(context); + } + } + + /** + * Set the emulator's counter and context without affecting its machine state + * + * @param counter the counter + * @param context the context + * @implNote the reasons for doing this are a bit nuanced and they deal in the setting of the pc + * by p-code ops whilst it also makes hazardous userop invocations. The intended value + * of the pc may not survive if it gets clobbered with the current counter before + * execution reaches the "goto pc". + */ + public void setCounterAndContext(Address counter, RegisterValue context) { + setCounter(counter); + if (context != null) { + /** + * TODO: Later this might become assignContext, but only after we establish conventions + * for accessing context in userops. For now, state modifiers expect the contextreg to + * be in the machine state. + */ + writeContext(context); + } + } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitAllocationModel.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitAllocationModel.java index 370a13aeb3..aa4080681e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitAllocationModel.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitAllocationModel.java @@ -29,14 +29,14 @@ import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; import ghidra.pcode.emu.jit.JitCompiler; import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.GenConsts; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.var.*; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressFactory; -import ghidra.program.model.lang.Endian; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; import ghidra.program.model.pcode.Varnode; /** @@ -615,16 +615,17 @@ public class JitAllocationModel { *

* This is just a logical grouping of a varnode and its assigned p-code type. */ - private record VarDesc(int spaceId, long offset, int size, JitType type) { + private record VarDesc(int spaceId, long offset, int size, JitType type, Language language) { /** * Create a descriptor from the given varnode and type * * @param vn the varnode * @param type the p-code type + * @param langauge the language * @return the descriptor */ - static VarDesc fromVarnode(Varnode vn, JitType type) { - return new VarDesc(vn.getSpace(), vn.getOffset(), vn.getSize(), type); + static VarDesc fromVarnode(Varnode vn, JitType type, Language language) { + return new VarDesc(vn.getSpace(), vn.getOffset(), vn.getSize(), type, language); } /** @@ -633,20 +634,232 @@ public class JitAllocationModel { * @return the name */ public String name() { + AddressFactory factory = language.getAddressFactory(); + AddressSpace space = factory.getAddressSpace(spaceId); + Register reg = language.getRegister(space, offset, size); + if (reg != null) { + return "%s_%d_%s".formatted(reg.getName(), size, type.nm()); + } return "s%d_%x_%d_%s".formatted(spaceId, offset, size, type.nm()); } /** * Convert this descriptor back to a varnode * - * @param factory the address factory for the emulation target language * @return the varnode */ - public Varnode toVarnode(AddressFactory factory) { + public Varnode toVarnode() { + AddressFactory factory = language.getAddressFactory(); return new Varnode(factory.getAddressSpace(spaceId).getAddress(offset), size); } } + /** + * A local that is always allocated in its respective method + */ + public interface FixedLocal { + /** + * The JVM index of the local + * + * @return the index + */ + int index(); + + /** + * The name of the local + * + * @return the name + */ + String varName(); + + /** + * A JVM type descriptor for the local + * + * @param nameThis the name of this class, in case it's the this pointer. + * @return the type descriptor + */ + String typeDesc(String nameThis); + + /** + * The JVM opcode used to load the variable + * + * @return the load opcode + */ + int opcodeLoad(); + + /** + * The JVM opcode used to store the variable + * + * @return the store opcode + */ + int opcodeStore(); + + /** + * Generate the declaration of this variable. + * + *

+ * This is not required, but is nice to have when debugging generated code. + * + * @param mv the method visitor + * @param nameThis the name of the class defining the containing method + * @param startLocals the start label which should be placed at the top of the method + * @param endLocals the end label which should be placed at the bottom of the method + */ + default void generateDeclCode(MethodVisitor mv, String nameThis, Label startLocals, + Label endLocals) { + mv.visitLocalVariable(varName(), typeDesc(nameThis), null, startLocals, endLocals, + index()); + } + + /** + * Generate a load of this variable onto the JVM stack. + * + * @param mv the method visitor + */ + default void generateLoadCode(MethodVisitor mv) { + mv.visitVarInsn(opcodeLoad(), index()); + } + + /** + * Generate a store to this variable from the JVM stack. + * + * @param mv the method visitor + */ + default void generateStoreCode(MethodVisitor mv) { + mv.visitVarInsn(opcodeStore(), index()); + } + } + + /** + * Locals that exist in every compiled passage's constructor. + */ + public enum InitFixedLocal implements FixedLocal { + /** + * Because we're compiling a non-static method, the JVM reserves index 0 for {@code this}. + */ + THIS("this", ALOAD, ASTORE) { + @Override + public String typeDesc(String nameThis) { + return "L" + nameThis + ";"; + } + }, + /** + * The parameter {@code thread} is reserved by the JVM into index 1. + */ + THREAD("thread", ALOAD, ASTORE) { + @Override + public String typeDesc(String nameThis) { + return GenConsts.TDESC_JIT_PCODE_THREAD; + } + }; + + private final String varName; + private final int opcodeLoad; + private final int opcodeStore; + + private InitFixedLocal(String varName, int opcodeLoad, int opcodeStore) { + this.varName = varName; + this.opcodeLoad = opcodeLoad; + this.opcodeStore = opcodeStore; + } + + @Override + public int index() { + return ordinal(); + } + + @Override + public String varName() { + return varName; + } + + @Override + public int opcodeLoad() { + return opcodeLoad; + } + + @Override + public int opcodeStore() { + return opcodeStore; + } + } + + /** + * Locals that exist in every compiled passage's {@link JitCompiledPassage#run(int) run} method. + */ + public enum RunFixedLocal implements FixedLocal { + /** + * Because we're compiling a non-static method, the JVM reserves index 0 for {@code this}. + */ + THIS("this", ALOAD, ASTORE) { + @Override + public String typeDesc(String nameThis) { + return "L" + nameThis + ";"; + } + }, + /** + * The parameter {@code blockId} is reserved by the JVM into index 1. + */ + BLOCK_ID("blockId", ILOAD, ISTORE) { + @Override + public String typeDesc(String nameThis) { + return Type.getDescriptor(int.class); + } + }, + /** + * We declare a local variable to indicate that a context-modifying userop has been invoked. + */ + CTXMOD("ctxmod", ILOAD, ISTORE) { + @Override + public String typeDesc(String nameThis) { + return Type.getDescriptor(boolean.class); + } + + @Override + public void generateDeclCode(MethodVisitor mv, String nameThis, Label startLocals, + Label endLocals) { + super.generateDeclCode(mv, nameThis, startLocals, endLocals); + mv.visitLdcInsn(0); + mv.visitVarInsn(ISTORE, index()); + } + }; + + private final String varName; + private final int opcodeLoad; + private final int opcodeStore; + + private RunFixedLocal(String varName, int opcodeLoad, int opcodeStore) { + this.varName = varName; + this.opcodeLoad = opcodeLoad; + this.opcodeStore = opcodeStore; + } + + /** + * All of the runtime locals + */ + public static final List ALL = List.of(values()); + + @Override + public int index() { + return ordinal(); + } + + @Override + public String varName() { + return varName; + } + + @Override + public int opcodeLoad() { + return opcodeLoad; + } + + @Override + public int opcodeStore() { + return opcodeStore; + } + } + private final JitDataFlowModel dfm; private final JitVarScopeModel vsm; private final JitTypeModel tm; @@ -654,7 +867,7 @@ public class JitAllocationModel { private final SleighLanguage language; private final Endian endian; - private int nextLocal = 2; // 0:this, 1:blockId in run(int blockId) + private int nextLocal = RunFixedLocal.ALL.size(); private final Map handlers = new HashMap<>(); private final Map handlersPerVarnode = new HashMap<>(); private final NavigableMap locals = new TreeMap<>(); @@ -695,7 +908,7 @@ public class JitAllocationModel { else { nextLocal += 1; } - return new JvmLocal(i, name, type, desc.toVarnode(language.getAddressFactory())); + return new JvmLocal(i, name, type, desc.toVarnode()); } /** @@ -730,7 +943,7 @@ public class JitAllocationModel { long offset = desc.offset; int i = 0; for (SimpleJitType t : it) { - VarDesc d = new VarDesc(desc.spaceId, offset, t.size(), t); + VarDesc d = new VarDesc(desc.spaceId, offset, t.size(), t, language); result[i] = genFreeLocal(name + "_" + i, t, d); offset += t.size(); i++; @@ -852,6 +1065,9 @@ public class JitAllocationModel { if (v instanceof JitConstVal) { return NoHandler.INSTANCE; } + if (v instanceof JitFailVal) { + return NoHandler.INSTANCE; + } if (v instanceof JitMemoryVar) { return NoHandler.INSTANCE; } @@ -883,7 +1099,7 @@ public class JitAllocationModel { .stream() .sorted(Comparator.comparing(e -> e.getKey().getAddress())) .toList()) { - VarDesc desc = VarDesc.fromVarnode(entry.getKey(), entry.getValue().winner()); + VarDesc desc = VarDesc.fromVarnode(entry.getKey(), entry.getValue().winner(), language); switch (desc.type()) { case SimpleJitType t -> { locals.put(entry.getKey().getAddress(), genFreeLocal(desc.name(), t, desc)); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitControlFlowModel.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitControlFlowModel.java index fd7d7b738b..8ff97b932b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitControlFlowModel.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitControlFlowModel.java @@ -350,7 +350,7 @@ public class JitControlFlowModel { * external) that leave each block. We then compute all the branches (internal) that enter each * block and the associated flows in both directions. */ - public static class BlockSplitter { + public abstract static class BlockSplitter { private final PcodeProgram program; private final Map branches = new HashMap<>(); @@ -373,6 +373,8 @@ public class JitControlFlowModel { this.program = program; } + protected abstract IntBranch newFallthroughIntBranch(PcodeOp from, PcodeOp to); + /** * Notify the splitter of the given branches before analysis * @@ -418,7 +420,8 @@ public class JitControlFlowModel { return; } if (needsFallthrough(lastBlock)) { - lastBlock.branchesFrom.add(new IntBranch(lastBlock.getCode().getLast(), op, true)); + lastBlock.branchesFrom + .add(newFallthroughIntBranch(lastBlock.getCode().getLast(), op)); } lastBlock = null; } @@ -536,7 +539,13 @@ public class JitControlFlowModel { * @return the resulting blocks, keyed by {@link JitBlock#first()} */ protected SequencedMap analyze() { - BlockSplitter splitter = new BlockSplitter(passage); + BlockSplitter splitter = new BlockSplitter(passage) { + @Override + protected IntBranch newFallthroughIntBranch(PcodeOp from, PcodeOp to) { + // Decoder should already have inserted fall-through protectors + return new RIntBranch(from, to, true, Reachability.WITHOUT_CTXMOD); + } + }; splitter.addBranches(passage.getBranches().values()); return splitter.splitBlocks(); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowExecutor.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowExecutor.java index df7c8c1f6a..4db95aa010 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowExecutor.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowExecutor.java @@ -18,9 +18,9 @@ package ghidra.pcode.emu.jit.analysis; import java.util.Map; import java.util.Objects; -import ghidra.pcode.emu.jit.JitPassage.Branch; -import ghidra.pcode.emu.jit.JitPassage.IndBranch; +import ghidra.pcode.emu.jit.JitPassage.*; import ghidra.pcode.emu.jit.op.*; +import ghidra.pcode.emu.jit.var.JitFailVal; import ghidra.pcode.emu.jit.var.JitVal; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; @@ -55,7 +55,7 @@ import ghidra.program.model.pcode.Varnode; */ class JitDataFlowExecutor extends PcodeExecutor { private final JitDataFlowModel dfm; - private final Map branches; + private final Map branches; /** * Construct an executor from the given context @@ -85,7 +85,7 @@ class JitDataFlowExecutor extends PcodeExecutor { * @param op the op */ protected void recordBranch(PcodeOp op) { - Branch branch = Objects.requireNonNull(branches.get(op)); + RBranch branch = (RBranch) Objects.requireNonNull(branches.get(op)); dfm.notifyOp(new JitBranchOp(op, branch)); } @@ -100,9 +100,17 @@ class JitDataFlowExecutor extends PcodeExecutor { * @param op the op */ protected void recordConditionalBranch(PcodeOp op) { - Branch branch = Objects.requireNonNull(branches.get(op)); - Varnode condVar = getConditionalBranchPredicate(op); - JitVal cond = state.getVar(condVar, reason); + RBranch branch = (RBranch) Objects.requireNonNull(branches.get(op)); + + final JitVal cond; + if (op instanceof ExitPcodeOp) { + cond = JitFailVal.INSTANCE; + } + else { + Varnode condVar = getConditionalBranchPredicate(op); + cond = state.getVar(condVar, reason); + } + dfm.notifyOp(new JitCBranchOp(op, branch, cond)); } @@ -118,7 +126,7 @@ class JitDataFlowExecutor extends PcodeExecutor { protected void recordIndirectBranch(PcodeOp op) { Varnode offVar = getIndirectBranchTarget(op); JitVal offset = state.getVar(offVar, reason); - IndBranch branch = (IndBranch) Objects.requireNonNull(branches.get(op)); + RIndBranch branch = (RIndBranch) Objects.requireNonNull(branches.get(op)); dfm.notifyOp(new JitBranchIndOp(op, offset, branch)); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowUseropLibrary.java index 858ed53eb9..44f0fae094 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowUseropLibrary.java @@ -226,6 +226,11 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary { return decOp.hasSideEffects(); } + @Override + public boolean modifiesContext() { + return decOp.modifiesContext(); + } + @Override public boolean canInlinePcode() { return decOp.canInlinePcode(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitOpVisitor.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitOpVisitor.java index 3633dc54e3..edb4e13270 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitOpVisitor.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitOpVisitor.java @@ -190,6 +190,7 @@ public interface JitOpVisitor { default void visitVal(JitVal v) { switch (v) { case JitConstVal constVal -> visitConstVal(constVal); + case JitFailVal failVal -> visitFailVal(failVal); case JitVar jVar -> visitVar(jVar); default -> throw new AssertionError(); } @@ -217,11 +218,19 @@ public interface JitOpVisitor { /** * Visit a {@link JitConstVal} * - * @param constVal the variable visited + * @param constVal the value visited */ default void visitConstVal(JitConstVal constVal) { } + /** + * Visit a {@link JitFailVal} + * + * @param failVal the value visited + */ + default void visitFailVal(JitFailVal failVal) { + } + /** * Visit a {@link JitDirectMemoryVar} * 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 a48660ae4d..43eca5bc3e 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 @@ -27,6 +27,7 @@ import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.*; import ghidra.pcode.emu.jit.analysis.JitDataFlowState; import ghidra.pcode.emu.jit.op.JitNopOp; import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; import ghidra.program.disassemble.Disassembler; import ghidra.program.model.address.Address; import ghidra.program.model.lang.*; @@ -79,7 +80,7 @@ class DecoderExecutor extends PcodeExecutor private final Map futCtx = new HashMap<>(); final List opsForThisStep = new ArrayList<>(); - private final List branchesForThisStep = new ArrayList<>(); + private final List branchesForThisStep = new ArrayList<>(); private final Map rewrites = new HashMap<>(); @@ -311,7 +312,7 @@ class DecoderExecutor extends PcodeExecutor */ @Override protected void branchToAddress(PcodeOp op, Address target) { - branchesForThisStep.add(new ExtBranch(op, takeTargetContext(target))); + branchesForThisStep.add(new SExtBranch(op, takeTargetContext(target))); } /** @@ -331,11 +332,11 @@ class DecoderExecutor extends PcodeExecutor if (termNop == null) { termNop = new NopPcodeOp(at, tgtSeq); } - branchesForThisStep.add(new IntBranch(op, termNop, false)); + branchesForThisStep.add(new SIntBranch(op, termNop, false)); } else { PcodeOp to = frame.getCode().get(op.getSeqnum().getTime() + relative); - branchesForThisStep.add(new IntBranch(op, rewrite(to), false)); + branchesForThisStep.add(new SIntBranch(op, rewrite(to), false)); } } @@ -351,7 +352,7 @@ class DecoderExecutor extends PcodeExecutor */ @Override protected void doExecuteIndirectBranch(PcodeOp op, PcodeFrame frame) { - branchesForThisStep.add(new IndBranch(op, flow)); + branchesForThisStep.add(new SIndBranch(op, flow)); } /** @@ -450,9 +451,9 @@ class DecoderExecutor extends PcodeExecutor * reachability test between the two. The step has fall through if and only if a path is found. * * @param from the instruction's or inject's p-code - * @return true if the step falls through. + * @return the reachability of the fall-through flow */ - public boolean checkFallthroughAndAccumulate(PcodeProgram from) { + public Reachability checkFallthroughAndAccumulate(PcodeProgram from) { if (instruction instanceof DecodeErrorInstruction) { stride.opsForStride.addAll(opsForThisStep); for (Branch branch : branchesForThisStep) { @@ -461,28 +462,37 @@ class DecoderExecutor extends PcodeExecutor default -> throw new AssertionError(); } } - return false; + return null; } if (opsForThisStep.isEmpty()) { - return true; + return Reachability.WITHOUT_CTXMOD; } - ExitPcodeOp probeOp = new ExitPcodeOp(AddrCtx.NOWHERE); + ExitPcodeOp probeOp = ExitPcodeOp.exit(AddrCtx.NOWHERE); opsForThisStep.add(probeOp); - ExtBranch probeBranch = new ExtBranch(probeOp, AddrCtx.NOWHERE); + SExtBranch probeBranch = new SExtBranch(probeOp, AddrCtx.NOWHERE); branchesForThisStep.add(probeBranch); PcodeProgram program = new PcodeProgram(from, opsForThisStep); - BlockSplitter splitter = new BlockSplitter(program); + BlockSplitter splitter = new BlockSplitter(program) { + @Override + protected IntBranch newFallthroughIntBranch(PcodeOp from, PcodeOp to) { + return new SIntBranch(from, to, true); + } + }; splitter.addBranches(branchesForThisStep); SequencedMap blocks = splitter.splitBlocks(); JitBlock entry = blocks.firstEntry().getValue(); JitBlock exit = blocks.lastEntry().getValue(); - Set reachable = new HashSet<>(); - collectReachable(reachable, entry); + Map reachable = new HashMap<>(); + collectReachable(reachable, entry, Reachability.WITHOUT_CTXMOD); for (JitBlock block : blocks.values()) { + Reachability reach = reachable.get(block); + if (reach == null) { + continue; + } for (PcodeOp op : block.getCode()) { if (op != probeOp) { stride.opsForStride.add(op); @@ -490,37 +500,95 @@ class DecoderExecutor extends PcodeExecutor } for (IntBranch branch : block.branchesFrom()) { if (!branch.isFall()) { - stride.passage.internalBranches.put(branch.from(), branch); + switch (branch) { + case SIntBranch ib -> stride.passage.internalBranches.put(ib.from(), + ib.withReach(reach)); + default -> throw new AssertionError(); + } } } for (Branch branch : block.branchesOut()) { if (branch != probeBranch) { switch (branch) { - case ExtBranch eb -> stride.passage.flowTo(eb); - default -> stride.passage.otherBranches.put(branch.from(), branch); + case SExtBranch eb -> stride.passage.flowTo(eb.withReach(reach)); + case SIndBranch ib -> stride.passage.otherBranches.put(ib.from(), + ib.withReach(reach)); + case PBranch pb -> stride.passage.otherBranches.put(pb.from(), pb); + default -> throw new AssertionError(); } } } } - return reachable.contains(exit); + return reachable.get(exit); + } + + private boolean blockModifiesContext(JitBlock block) { + for (PcodeOp op : block.getCode()) { + if (op.getOpcode() != PcodeOp.CALLOTHER) { + continue; + } + String name = block.getUseropName(getCallotherOpNumber(op)); + if (name == null) { + continue; + } + PcodeUseropDefinition userop = stride.passage.library().getUserops().get(name); + if (userop == null) { + continue; + } + if (userop.modifiesContext()) { + return true; + } + } + return false; } /** * The reachability test mentioned in {@link #checkFallthroughAndAccumulate(PcodeProgram)} * *

- * Collects the set of blocks reachable from {@code cur} into the given mutable set. + * Collects the reachability of blocks reachable from {@code cur} into the given mutable map. + * The value indicates whether or not context modifications can occur along the paths to the + * block (key). If a block is not in the map, it is not reachable. * - * @param into a mutable set for collecting reachable blocks + * @param into a mutable map for collecting reachable blocks * @param cur the source block, or an intermediate during recursion + * @param the computed reachability of the source block. Use {@link Reachability#WITHOUT_CTXMOD} + * for the seed. */ - private void collectReachable(Set into, JitBlock cur) { - if (!into.add(cur)) { + private void collectReachable(Map into, JitBlock cur, + Reachability how) { + Reachability curHow = into.get(cur); + + /** + * Context-modifying userops are all considered hazards, but we shouldn't abort until after + * the instruction. If the exit is reachable without passing through a context modification, + * then we're good to proceed. Otherwise, no. Additionally, we're going to check all + * branches, direct or indirect, to see if they are reachable without context modification. + * If they are, then we treat them as usual. If not, then they will be treated as indirect, + * and we'll neglect to "retire" the context, because presumably, the userop will already + * have caused that retirement and modified it in place. + * + * If one branch is reachable by multiple paths where some require context modification and + * some do not, we'll keep a local variable at runtime to track whether a context-modifying + * userop has actually been executed. We'll generate code to check this variable at the + * branch site and treat it as a hazard if it is set. + */ + if (blockModifiesContext(cur)) { + // Not combine. If we're MAYBE here, we still become WITH_CTX. + how = Reachability.WITH_CTXMOD; + } + else { + how = how.combine(curHow); + } + + if (how == curHow) { return; } + into.put(cur, how); + for (BlockFlow flow : cur.flowsFrom().values()) { - collectReachable(into, flow.to()); + collectReachable(into, flow.to(), how); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderForOnePassage.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderForOnePassage.java index 21a4fda003..de44fe132a 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderForOnePassage.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderForOnePassage.java @@ -42,9 +42,10 @@ class DecoderForOnePassage { private final int maxInstrs; private final int maxStrides; - final Map internalBranches = new HashMap<>(); - final SequencedMap externalBranches = new LinkedHashMap<>(); - final Map otherBranches = new HashMap<>(); + final Map internalBranches = new HashMap<>(); + // Sequenced, because this is also the seed queue + final SequencedMap externalBranches = new LinkedHashMap<>(); + final Map otherBranches = new HashMap<>(); final Map firstOps = new HashMap<>(); final List strides = new ArrayList<>(); @@ -66,7 +67,7 @@ class DecoderForOnePassage { this.maxInstrs = config.maxPassageInstructions(); this.maxStrides = config.maxPassageStrides(); EntryPcodeOp entryOp = new EntryPcodeOp(seed); - externalBranches.put(entryOp, new ExtBranch(entryOp, seed)); + externalBranches.put(entryOp, new RExtBranch(entryOp, seed, Reachability.WITHOUT_CTXMOD)); } /** @@ -75,20 +76,23 @@ class DecoderForOnePassage { void decodePassage() { while (opCount < maxOps && instructionCount < maxInstrs && strides.size() < maxStrides) { - Entry nextEnt = externalBranches.pollFirstEntry(); + Entry nextEnt = externalBranches.pollFirstEntry(); if (nextEnt == null) { break; } - ExtBranch next = nextEnt.getValue(); + RExtBranch next = nextEnt.getValue(); AddrCtx start = next.to(); if (decoder.thread.hasEntry(start)) { otherBranches.put(next.from(), next); } + else if (!next.reach().canReachWithoutCtxMod()) { + otherBranches.put(next.from(), next); + } else { decodeStride(start); PcodeOp to = Objects.requireNonNull(firstOps.get(start)); - internalBranches.put(next.from(), new IntBranch(next.from(), to, false)); + internalBranches.put(next.from(), next.toIntBranch(to)); } } } @@ -107,10 +111,14 @@ class DecoderForOnePassage { * @param from the op representing or causing the control flow * @param to the target of the branch */ - void flowTo(ExtBranch eb) { - if (firstOps.containsKey(eb.to())) { - IntBranch ib = new IntBranch(eb.from(), firstOps.get(eb.to()), false); - internalBranches.put(ib.from(), ib); + void flowTo(RExtBranch eb) { + if (!eb.reach().canReachWithoutCtxMod()) { + otherBranches.put(eb.from(), eb); + return; + } + PcodeOp to = firstOps.get(eb.to()); + if (to != null) { + internalBranches.put(eb.from(), eb.toIntBranch(to)); return; } externalBranches.put(eb.from(), eb); @@ -144,11 +152,15 @@ class DecoderForOnePassage { List code = strides.stream().flatMap(b -> b.ops().stream()).toList(); List instructions = strides.stream().flatMap(b -> b.instructions().stream()).toList(); - Map branches = otherBranches; + Map branches = otherBranches; branches.putAll(internalBranches); - for (ExtBranch eb : externalBranches.values()) { - if (firstOps.containsKey(eb.to())) { - branches.put(eb.from(), new IntBranch(eb.from(), firstOps.get(eb.to()), false)); + for (RExtBranch eb : externalBranches.values()) { + if (!eb.reach().canReachWithoutCtxMod()) { + branches.put(eb.from(), eb); + } + PcodeOp to = firstOps.get(eb.to()); + if (to != null) { + branches.put(eb.from(), eb.toIntBranch(to)); } else { branches.put(eb.from(), eb); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderForOneStride.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderForOneStride.java index 4ef0dd049b..2bc192d6cc 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderForOneStride.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderForOneStride.java @@ -50,10 +50,10 @@ public class DecoderForOneStride { * Check whether the result falls through, accumulate its instructions and ops, and apply * any control-flow effects. * - * @return true if the result falls through. + * @return the reachability of the fall-through flow * @see DecoderExecutor#checkFallthroughAndAccumulate(PcodeProgram) */ - boolean checkFallthroughAndAccumulate() { + Reachability checkFallthroughAndAccumulate() { return executor.checkFallthroughAndAccumulate(program); } @@ -123,9 +123,10 @@ public class DecoderForOneStride { * exit branch. */ if (decoder.thread.hasEntry(at)) { - ExitPcodeOp exitOp = new ExitPcodeOp(at); + ExitPcodeOp exitOp = ExitPcodeOp.exit(at); opsForStride.add(exitOp); - passage.otherBranches.put(exitOp, new ExtBranch(exitOp, at)); + passage.otherBranches.put(exitOp, + new RExtBranch(exitOp, at, Reachability.WITHOUT_CTXMOD)); return null; } @@ -163,19 +164,43 @@ public class DecoderForOneStride { StepResult result = stepAddrCtx(at); - if (result == null || !result.checkFallthroughAndAccumulate()) { + if (result == null) { + return toStride(); + } + + Reachability reach = result.checkFallthroughAndAccumulate(); + if (reach == null) { return toStride(); } AddrCtx next = result.next(); if (at.equals(next)) { // Would happen because of inject without control flow - ExitPcodeOp exitOp = new ExitPcodeOp(at); + ExitPcodeOp exitOp = ExitPcodeOp.exit(at); opsForStride.add(exitOp); - passage.otherBranches.put(exitOp, new ExtBranch(exitOp, at)); + passage.otherBranches.put(exitOp, new RExtBranch(exitOp, at, reach)); return toStride(); } at = next; + + switch (reach) { + case WITHOUT_CTXMOD -> { + continue; + } + case WITH_CTXMOD -> { + // Looks like the without-control-flow case, but at has advanced + ExitPcodeOp exitOp = ExitPcodeOp.exit(at); + opsForStride.add(exitOp); + passage.otherBranches.put(exitOp, new RExtBranch(exitOp, at, reach)); + return toStride(); + } + case MAYBE_CTXMOD -> { + ExitPcodeOp exitOp = ExitPcodeOp.cond(at); + opsForStride.add(exitOp); + passage.otherBranches.put(exitOp, new RExtBranch(exitOp, at, reach)); + continue; + } + } } /** diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderUseropLibrary.java index a66ed0e4fe..75b5c7d8b7 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderUseropLibrary.java @@ -118,6 +118,11 @@ public class DecoderUseropLibrary extends AnnotatedPcodeUseropLibrary { return rtOp.hasSideEffects(); } + @Override + public boolean modifiesContext() { + return rtOp.modifiesContext(); + } + @Override public boolean canInlinePcode() { return rtOp.canInlinePcode(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForArrDirect.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForArrDirect.java index 40a9c12c8f..8b7624968d 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForArrDirect.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForArrDirect.java @@ -22,6 +22,8 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorStatePiece.JitBytesPcodeExecutorStateSpace; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.InitFixedLocal; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.program.model.address.Address; /** @@ -63,7 +65,7 @@ public record FieldForArrDirect(Address address) implements InstanceFieldReq { cv.visitField(ACC_PRIVATE | ACC_FINAL, name(), TDESC_BYTE_ARR, null, null); // [...] - iv.visitVarInsn(ALOAD, 0); + InitFixedLocal.THIS.generateLoadCode(iv); // [...,this] gen.generateLoadJitStateSpace(address.getAddressSpace(), iv); // [...,jitspace] @@ -78,7 +80,7 @@ public record FieldForArrDirect(Address address) implements InstanceFieldReq { @Override public void generateLoadCode(JitCodeGenerator gen, MethodVisitor rv) { // [...] - rv.visitVarInsn(ALOAD, 0); + RunFixedLocal.THIS.generateLoadCode(rv); // [...,this] rv.visitFieldInsn(GETFIELD, gen.nameThis, name(), TDESC_BYTE_ARR); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForExitSlot.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForExitSlot.java index 20136db252..1ca157b891 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForExitSlot.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForExitSlot.java @@ -24,6 +24,8 @@ import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.JitPassage.AddrCtx; import ghidra.pcode.emu.jit.JitPassage.ExtBranch; import ghidra.pcode.emu.jit.JitPcodeThread; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.InitFixedLocal; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.ExitSlot; @@ -75,7 +77,7 @@ public record FieldForExitSlot(AddrCtx target) implements InstanceFieldReq { cv.visitField(ACC_PRIVATE | ACC_FINAL, name(), TDESC_EXIT_SLOT, null, null); // [] - iv.visitVarInsn(ALOAD, 0); + InitFixedLocal.THIS.generateLoadCode(iv); // [this] iv.visitInsn(DUP); // [this,this] @@ -93,7 +95,7 @@ public record FieldForExitSlot(AddrCtx target) implements InstanceFieldReq { @Override public void generateLoadCode(JitCodeGenerator gen, MethodVisitor rv) { // [] - rv.visitVarInsn(ALOAD, 0); + RunFixedLocal.THIS.generateLoadCode(rv); // [this] rv.visitFieldInsn(GETFIELD, gen.nameThis, name(), TDESC_EXIT_SLOT); // [slot] diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForSpaceIndirect.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForSpaceIndirect.java index d9067d46be..a8776ac593 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForSpaceIndirect.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForSpaceIndirect.java @@ -22,6 +22,8 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorStatePiece.JitBytesPcodeExecutorStateSpace; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.InitFixedLocal; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.program.model.address.AddressSpace; /** @@ -63,7 +65,7 @@ public record FieldForSpaceIndirect(AddressSpace space) implements InstanceField TDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE, null, null); // [...] - iv.visitVarInsn(ALOAD, 0); + InitFixedLocal.THIS.generateLoadCode(iv); // [...,this] gen.generateLoadJitStateSpace(space, iv); // [...,this,jitspace] @@ -75,7 +77,7 @@ public record FieldForSpaceIndirect(AddressSpace space) implements InstanceField @Override public void generateLoadCode(JitCodeGenerator gen, MethodVisitor rv) { // [...] - rv.visitVarInsn(ALOAD, 0); + RunFixedLocal.THIS.generateLoadCode(rv); // [...,this] rv.visitFieldInsn(GETFIELD, gen.nameThis, name(), TDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForUserop.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForUserop.java index be4e34ac1b..97413715d4 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForUserop.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForUserop.java @@ -21,6 +21,8 @@ import static org.objectweb.asm.Opcodes.*; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.InitFixedLocal; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.analysis.JitDataFlowUseropLibrary; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; @@ -63,7 +65,7 @@ public record FieldForUserop(PcodeUseropDefinition userop) implements Instanc null); // [] - iv.visitVarInsn(ALOAD, 0); + InitFixedLocal.THIS.generateLoadCode(iv); // [this] iv.visitInsn(DUP); // [this,this] @@ -79,7 +81,7 @@ public record FieldForUserop(PcodeUseropDefinition userop) implements Instanc @Override public void generateLoadCode(JitCodeGenerator gen, MethodVisitor rv) { // [] - rv.visitVarInsn(ALOAD, 0); + RunFixedLocal.THIS.generateLoadCode(rv); // [this] rv.visitFieldInsn(GETFIELD, gen.nameThis, name(), TDESC_PCODE_USEROP_DEFINITION); // [userop] 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 4c722a138c..b8dbd1160c 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 @@ -139,7 +139,9 @@ public interface GenConsts { Type.getMethodDescriptor(Type.INT_TYPE, Type.getType(byte[].class), Type.INT_TYPE); public static final String MDESC_JIT_COMPILED_PASSAGE__READ_LONGX = Type.getMethodDescriptor(Type.LONG_TYPE, Type.getType(byte[].class), Type.INT_TYPE); - public static final String MDESC_JIT_COMPILED_PASSAGE__RETIRE_COUNTER_AND_CONTEXT = + public static final String MDESC_JIT_COMPILED_PASSAGE__WRITE_COUNTER_AND_CONTEXT = + Type.getMethodDescriptor(Type.VOID_TYPE, Type.LONG_TYPE, Type.getType(RegisterValue.class)); + public static final String MDESC_JIT_COMPILED_PASSAGE__SET_COUNTER_AND_CONTEXT = Type.getMethodDescriptor(Type.VOID_TYPE, Type.LONG_TYPE, Type.getType(RegisterValue.class)); public static final String MDESC_JIT_COMPILED_PASSAGE__S_CARRY_INT_RAW = Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java index 7fee8c10af..d09dd3cda5 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java @@ -32,8 +32,7 @@ import ghidra.pcode.emu.jit.JitCompiler.Diag; import ghidra.pcode.emu.jit.JitPassage.AddrCtx; import ghidra.pcode.emu.jit.JitPassage.DecodedPcodeOp; import ghidra.pcode.emu.jit.analysis.*; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmLocal; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.VarHandler; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.*; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.gen.op.OpGen; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; @@ -303,7 +302,7 @@ public class JitCodeGenerator { Type.getMethodDescriptor(Type.getType(JitPcodeThread.class)), null, null); gtMv.visitCode(); // [] - gtMv.visitVarInsn(ALOAD, 0); + RunFixedLocal.THIS.generateLoadCode(gtMv); // [this] gtMv.visitFieldInsn(GETFIELD, nameThis, "thread", TDESC_JIT_PCODE_THREAD); // [thread] @@ -408,7 +407,7 @@ public class JitCodeGenerator { initMv.visitCode(); // Object.super() // [] - initMv.visitVarInsn(ALOAD, 0); + InitFixedLocal.THIS.generateLoadCode(initMv); // [this] initMv.visitMethodInsn(INVOKESPECIAL, NAME_OBJECT, "", Type.getMethodDescriptor(Type.VOID_TYPE), false); @@ -416,18 +415,18 @@ public class JitCodeGenerator { // this.thread = thread // [] - initMv.visitVarInsn(ALOAD, 0); + InitFixedLocal.THIS.generateLoadCode(initMv); // [this] - initMv.visitVarInsn(ALOAD, 1); - // [this,state] + InitFixedLocal.THREAD.generateLoadCode(initMv); + // [this,thread] initMv.visitFieldInsn(PUTFIELD, nameThis, "thread", TDESC_JIT_PCODE_THREAD); // [] // this.state = thread.getState() // [] - initMv.visitVarInsn(ALOAD, 0); + InitFixedLocal.THIS.generateLoadCode(initMv); // [this] - initMv.visitVarInsn(ALOAD, 1); + InitFixedLocal.THREAD.generateLoadCode(initMv); // [this,thread] initMv.visitMethodInsn(INVOKEVIRTUAL, NAME_JIT_PCODE_THREAD, "getState", MDESC_JIT_PCODE_THREAD__GET_STATE, false); @@ -453,8 +452,7 @@ public class JitCodeGenerator { * this.spaceInd_`space` = * this.state.getForSpace(ADDRESS_FACTORY.getAddressSpace(`space.getSpaceID()`); */ - - iv.visitVarInsn(ALOAD, 0); + InitFixedLocal.THIS.generateLoadCode(initMv); // [...,this] iv.visitFieldInsn(GETFIELD, nameThis, "state", TDESC_JIT_BYTES_PCODE_EXECUTOR_STATE); @@ -726,7 +724,7 @@ public class JitCodeGenerator { requestExceptionHandler(first, block).label(), NAME_THROWABLE); runMv.visitLabel(tryStart); - runMv.visitVarInsn(ALOAD, 0); + RunFixedLocal.THIS.generateLoadCode(runMv); runMv.visitLdcInsn(block.instructionCount()); runMv.visitLdcInsn(block.trailingOpCount()); runMv.visitMethodInsn(INVOKEINTERFACE, NAME_JIT_COMPILED_PASSAGE, "count", @@ -862,9 +860,9 @@ public class JitCodeGenerator { runMv.visitCode(); runMv.visitLabel(startLocals); - runMv.visitLocalVariable("this", "L" + nameThis + ";", null, startLocals, endLocals, 0); - runMv.visitLocalVariable("blockId", Type.getDescriptor(int.class), null, startLocals, - endLocals, 1); + for (FixedLocal fixed : RunFixedLocal.ALL) { + fixed.generateDeclCode(runMv, nameThis, startLocals, endLocals); + } for (JvmLocal local : am.allLocals()) { local.generateDeclCode(this, startLocals, endLocals, runMv); @@ -889,7 +887,7 @@ public class JitCodeGenerator { } // [] - runMv.visitVarInsn(ILOAD, 1); + RunFixedLocal.BLOCK_ID.generateLoadCode(runMv); // [blockId] Label lblBadEntry = new Label(); runMv.visitTableSwitchInsn(0, entries.size() - 1, lblBadEntry, @@ -1041,6 +1039,32 @@ public class JitCodeGenerator { throw new AssertionError("Couldn't figure exit context for " + op); } + /** + * The manners in which the program counter and decode context can be "retired." + */ + public enum RetireMode { + /** + * Retire into the emulator's counter/context and its machine state + * + * @see JitCompiledPassage#writeCounterAndContext(long, RegisterValue) + */ + WRITE(MDESC_JIT_COMPILED_PASSAGE__WRITE_COUNTER_AND_CONTEXT, "writeCounterAndContext"), + /** + * Retire into the emulator's counter/context, but not its machine state + * + * @see JitCompiledPassage#setCounterAndContext(long, RegisterValue) + */ + SET(MDESC_JIT_COMPILED_PASSAGE__SET_COUNTER_AND_CONTEXT, "setCounterAndContext"); + + private String mdesc; + private String mname; + + private RetireMode(String mdesc, String mname) { + this.mdesc = mdesc; + this.mname = mname; + } + } + /** * Emit bytecode to set the emulator's counter and contextreg. * @@ -1057,12 +1081,13 @@ public class JitCodeGenerator { * branch target, which may be loaded from a varnode for an indirect branch. * @param ctx the contextreg value. For errors, this is the decode context of the op causing the * error. For branches, this is the decode context at the target. + * @param mode whether to set the machine state, too * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method */ - public void generateRetirePcCtx(Runnable pcGen, RegisterValue ctx, + public void generateRetirePcCtx(Runnable pcGen, RegisterValue ctx, RetireMode mode, MethodVisitor rv) { // [] - rv.visitVarInsn(ALOAD, 0); + RunFixedLocal.THIS.generateLoadCode(rv); // [this] pcGen.run(); // [this,pc:LONG] @@ -1073,8 +1098,8 @@ public class JitCodeGenerator { requestStaticFieldForContext(ctx).generateLoadCode(this, rv); } // [this,pc:LONG,ctx:RV] - rv.visitMethodInsn(INVOKEINTERFACE, NAME_JIT_COMPILED_PASSAGE, "retireCounterAndContext", - MDESC_JIT_COMPILED_PASSAGE__RETIRE_COUNTER_AND_CONTEXT, true); + rv.visitMethodInsn(INVOKEINTERFACE, NAME_JIT_COMPILED_PASSAGE, mode.mname, + mode.mdesc, true); } /** @@ -1082,18 +1107,20 @@ public class JitCodeGenerator { * *

* This retires all the variables of the current block as well as the program counter and decode - * coontext. It does not generate the actual {@link Opcodes#ARETURN areturn} or + * context. It does not generate the actual {@link Opcodes#ARETURN areturn} or * {@link Opcodes#ATHROW athrow}, but everything required up to that point. * * @param block the block containing the op at which we are exiting - * @param pcGen as in {@link #generateRetirePcCtx(Runnable, RegisterValue, MethodVisitor)} - * @param ctx as in {@link #generateRetirePcCtx(Runnable, RegisterValue, MethodVisitor)} + * @param pcGen as in + * {@link #generateRetirePcCtx(Runnable, RegisterValue, RetireMode, MethodVisitor)} + * @param ctx as in + * {@link #generateRetirePcCtx(Runnable, RegisterValue, RetireMode, MethodVisitor)} * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method */ public void generatePassageExit(JitBlock block, Runnable pcGen, RegisterValue ctx, MethodVisitor rv) { VarGen.computeBlockTransition(this, block, null).generate(rv); - generateRetirePcCtx(pcGen, ctx, rv); + generateRetirePcCtx(pcGen, ctx, RetireMode.WRITE, rv); } /** diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchIndOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchIndOpGen.java index 913e52c6e6..abd1652197 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchIndOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchIndOpGen.java @@ -17,13 +17,17 @@ package ghidra.pcode.emu.jit.gen.op; import org.objectweb.asm.MethodVisitor; +import ghidra.pcode.emu.jit.JitPassage.RIndBranch; import ghidra.pcode.emu.jit.JitPcodeThread; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.op.BranchOpGen.BranchGen; import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.op.JitBranchIndOp; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.RegisterValue; /** * The generator for a {@link JitBranchIndOp branchind}. @@ -37,18 +41,58 @@ public enum BranchIndOpGen implements OpGen { /** The generator singleton */ GEN; - @Override - public void generateRunCode(JitCodeGenerator gen, JitBranchIndOp op, JitBlock block, - MethodVisitor rv) { + /** + * Generate code to retire the variables, write the dynamic pc value, and return from the + * passage + * + * @param gen the code generator + * @param op the op + * @param ctx the context to write at exit, or null to not write the context + * @param block the block containing the op + * @param rv the run method visitor + */ + static void generateExitCode(JitCodeGenerator gen, JitBranchIndOp op, RegisterValue ctx, + JitBlock block, MethodVisitor rv) { gen.generatePassageExit(block, () -> { // [...] JitType targetType = gen.generateValReadCode(op.target(), op.targetType()); // [...,target:?] TypeConversions.generateToLong(targetType, LongJitType.I8, rv); // [...,target:LONG] - }, op.branch().flowCtx(), rv); + }, ctx, rv); rv.visitInsn(ACONST_NULL); rv.visitInsn(ARETURN); } + + /** + * A branch code generator for indirect branches + */ + static class IndBranchGen extends BranchGen { + /** Singleton */ + static final IndBranchGen IND = new IndBranchGen(); + + @Override + Address exit(JitCodeGenerator gen, RIndBranch branch) { + return null; + } + + @Override + void generateCodeWithoutCtxmod(JitCodeGenerator gen, JitBranchIndOp op, RIndBranch branch, + JitBlock block, MethodVisitor rv) { + generateExitCode(gen, op, branch.flowCtx(), block, rv); + } + + @Override + void generateCodeWithCtxmod(JitCodeGenerator gen, JitBranchIndOp op, Address exit, + JitBlock block, MethodVisitor rv) { + generateExitCode(gen, op, null, block, rv); + } + } + + @Override + public void generateRunCode(JitCodeGenerator gen, JitBranchIndOp op, JitBlock block, + MethodVisitor rv) { + IndBranchGen.IND.generateCode(gen, op, op.branch(), block, rv); + } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchOpGen.java index bcc5569ed7..6a082c9104 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchOpGen.java @@ -15,20 +15,20 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.pcode.emu.jit.gen.GenConsts.MDESC_JIT_COMPILED_PASSAGE__GET_CHAINED; -import static ghidra.pcode.emu.jit.gen.GenConsts.NAME_JIT_COMPILED_PASSAGE; - import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.JitPassage.*; import ghidra.pcode.emu.jit.JitPcodeThread; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.gen.FieldForExitSlot; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.*; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.op.JitBranchOp; +import ghidra.pcode.emu.jit.op.JitOp; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.RegisterValue; /** * The generator for a {@link JitBranchOp branch}. @@ -48,49 +48,185 @@ public enum BranchOpGen implements OpGen { GEN; /** - * Emit code that exits via a direct branch - * - *

- * This emits the {@link ExtBranch} record case. + * Generate code to retire the variables and write a given pc value. * * @param gen the code generator - * @param exit the target causing us to exit + * @param exit the pc value to write + * @param ctx the context to write at exit, or null to not write the context * @param block the block containing the op - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + * @param rv the run method visitor */ - static void generateExtBranchCode(JitCodeGenerator gen, AddrCtx exit, JitBlock block, - MethodVisitor rv) { - FieldForExitSlot slotField = gen.requestFieldForExitSlot(exit); - + static void generateRetireCode(JitCodeGenerator gen, Address exit, RegisterValue ctx, + JitBlock block, MethodVisitor rv) { gen.generatePassageExit(block, () -> { // [...] - rv.visitLdcInsn(exit.address.getOffset()); + rv.visitLdcInsn(exit.getOffset()); // [...,target:LONG] - }, exit.rvCtx, rv); + }, ctx, rv); + } - // [] - slotField.generateLoadCode(gen, rv); - // [slot] - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "getChained", - MDESC_JIT_COMPILED_PASSAGE__GET_CHAINED, true); - // [chained:ENTRY] + /** + * Generate code to retire the variables, write a given pc value, and return from the passage. + * + *

+ * This will not write any decode context. + * + * @param gen the code generator + * @param exit the pc value to write + * @param block the block containing the op + * @param rv the run method visitor + */ + static void generateExitCode(JitCodeGenerator gen, Address exit, JitBlock block, + MethodVisitor rv) { + generateRetireCode(gen, exit, null, block, rv); + rv.visitInsn(ACONST_NULL); rv.visitInsn(ARETURN); } + /** + * A branch code generator + * + * @param the type of branch + * @param the type of op + */ + static abstract class BranchGen { + /** + * Get the target address of the branch + * + * @param gen the code generator + * @param branch the branch + * @return the target address + */ + abstract Address exit(JitCodeGenerator gen, TB branch); + + /** + * Generate code for the branch in the case a context modification has not occurred. + * + *

+ * This means no context-modifying userop has been invoked. + * + * @param gen the code generator + * @param op the branch op + * @param branch the branch from the op + * @param block the block containing the op + * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + */ + abstract void generateCodeWithoutCtxmod(JitCodeGenerator gen, TO op, TB branch, + JitBlock block, MethodVisitor rv); + + /** + * Generate code for the branch in the case a context modification may have occurred. + * + *

+ * This means a context-modifying userop has certainly been invoked, but not + * necessarily that the context has actually changed. + * + * @param gen the code generator + * @param op the branch op + * @param branch the branch from the op + * @param block the block containing the op + * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + */ + abstract void generateCodeWithCtxmod(JitCodeGenerator gen, TO op, Address exit, + JitBlock block, MethodVisitor rv); + + /** + * Emit code that jumps or exits via a direct branch + * + * @param gen the code generator + * @param op the branch op + * @param branch the branch from the op + * @param block the block containing the op + * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + */ + void generateCode(JitCodeGenerator gen, TO op, TB branch, JitBlock block, + MethodVisitor rv) { + switch (branch.reach()) { + case WITH_CTXMOD -> generateCodeWithCtxmod(gen, op, exit(gen, branch), block, rv); + case WITHOUT_CTXMOD -> generateCodeWithoutCtxmod(gen, op, branch, block, rv); + case MAYBE_CTXMOD -> { + Label withModctx = new Label(); + RunFixedLocal.CTXMOD.generateLoadCode(rv); + rv.visitJumpInsn(IFNE, withModctx); + generateCodeWithoutCtxmod(gen, op, branch, block, rv); + rv.visitLabel(withModctx); + generateCodeWithCtxmod(gen, op, exit(gen, branch), block, rv); + } + default -> throw new AssertionError(); + } + } + } + + /** + * A branch code generator for internal branches + */ + static class IntBranchGen extends BranchGen { + /** Singleton */ + static final IntBranchGen INT = new IntBranchGen(); + + @Override + Address exit(JitCodeGenerator gen, RIntBranch branch) { + return gen.getAddressForOp(branch.to()); + } + + @Override + void generateCodeWithoutCtxmod(JitCodeGenerator gen, JitOp op, RIntBranch branch, + JitBlock block, MethodVisitor rv) { + JitBlock target = block.getTargetBlock(branch); + Label label = gen.labelForBlock(target); + VarGen.computeBlockTransition(gen, block, target).generate(rv); + rv.visitJumpInsn(GOTO, label); + } + + @Override + void generateCodeWithCtxmod(JitCodeGenerator gen, JitOp op, Address exit, JitBlock block, + MethodVisitor rv) { + generateExitCode(gen, exit, block, rv); + } + } + + /** + * A branch code generator for external branches + */ + static class ExtBranchGen extends BranchGen { + /** Singleton */ + static final ExtBranchGen EXT = new ExtBranchGen(); + + @Override + Address exit(JitCodeGenerator gen, RExtBranch branch) { + return branch.to().address; + } + + @Override + void generateCodeWithoutCtxmod(JitCodeGenerator gen, JitOp op, RExtBranch branch, + JitBlock block, MethodVisitor rv) { + AddrCtx exit = branch.to(); + FieldForExitSlot slotField = gen.requestFieldForExitSlot(exit); + + generateRetireCode(gen, exit.address, exit.rvCtx, block, rv); + + // [] + slotField.generateLoadCode(gen, rv); + // [slot] + rv.visitMethodInsn(INVOKESTATIC, GenConsts.NAME_JIT_COMPILED_PASSAGE, "getChained", + GenConsts.MDESC_JIT_COMPILED_PASSAGE__GET_CHAINED, true); + // [chained:ENTRY] + rv.visitInsn(ARETURN); + } + + @Override + void generateCodeWithCtxmod(JitCodeGenerator gen, JitOp op, Address exit, JitBlock block, + MethodVisitor rv) { + generateExitCode(gen, exit, block, rv); + } + } + @Override public void generateRunCode(JitCodeGenerator gen, JitBranchOp op, JitBlock block, MethodVisitor rv) { - switch (op.branch()) { - case IntBranch ib -> { - JitBlock target = block.getTargetBlock(ib); - Label label = gen.labelForBlock(target); - VarGen.computeBlockTransition(gen, block, target).generate(rv); - rv.visitJumpInsn(GOTO, label); - } - case ExtBranch eb -> { - generateExtBranchCode(gen, eb.to(), block, rv); - } + case RIntBranch ib -> IntBranchGen.INT.generateCode(gen, op, ib, block, rv); + case RExtBranch eb -> ExtBranchGen.EXT.generateCode(gen, op, eb, block, rv); default -> throw new AssertionError("Branch type confusion"); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CBranchOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CBranchOpGen.java index 13490ce187..7813d10e41 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CBranchOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CBranchOpGen.java @@ -18,15 +18,22 @@ package ghidra.pcode.emu.jit.gen.op; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; -import ghidra.pcode.emu.jit.JitPassage.ExtBranch; -import ghidra.pcode.emu.jit.JitPassage.IntBranch; +import ghidra.pcode.emu.jit.JitPassage.*; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitDataFlowModel; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.op.BranchOpGen.ExtBranchGen; +import ghidra.pcode.emu.jit.gen.op.BranchOpGen.IntBranchGen; import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition; import ghidra.pcode.emu.jit.op.JitCBranchOp; +import ghidra.pcode.emu.jit.op.JitOp; +import ghidra.pcode.emu.jit.var.JitFailVal; +import ghidra.program.model.address.Address; +import ghidra.program.model.pcode.PcodeOp; /** * The generator for a {@link JitCBranchOp cbranch}. @@ -48,35 +55,102 @@ public enum CBranchOpGen implements OpGen { /** The generator singleton */ GEN; + /** + * A branch code generator for internal conditional branches + */ + static class IntCBranchGen extends IntBranchGen { + /** Singleton */ + static final IntCBranchGen C_INT = new IntCBranchGen(); + + @Override + void generateCodeWithoutCtxmod(JitCodeGenerator gen, JitOp op, RIntBranch branch, + JitBlock block, MethodVisitor rv) { + JitBlock target = block.getTargetBlock(branch); + Label label = gen.labelForBlock(target); + BlockTransition transition = VarGen.computeBlockTransition(gen, block, target); + if (transition.needed()) { + Label fall = new Label(); + rv.visitJumpInsn(IFEQ, fall); + transition.generate(rv); + rv.visitJumpInsn(GOTO, label); + rv.visitLabel(fall); + } + else { + rv.visitJumpInsn(IFNE, label); + } + } + + @Override + void generateCodeWithCtxmod(JitCodeGenerator gen, JitOp op, Address exit, JitBlock block, + MethodVisitor rv) { + Label fall = new Label(); + rv.visitJumpInsn(IFEQ, fall); + super.generateCodeWithCtxmod(gen, op, exit, block, rv); + rv.visitLabel(fall); + } + } + + /** + * A branch code generator for external conditional branches + */ + static class ExtCBranchGen extends ExtBranchGen { + /** Singleton */ + static final ExtCBranchGen C_EXT = new ExtCBranchGen(); + + @Override + void generateCodeWithoutCtxmod(JitCodeGenerator gen, JitOp op, RExtBranch branch, + JitBlock block, MethodVisitor rv) { + Label fall = new Label(); + rv.visitJumpInsn(IFEQ, fall); + super.generateCodeWithoutCtxmod(gen, op, branch, block, rv); + rv.visitLabel(fall); + } + + @Override + void generateCodeWithCtxmod(JitCodeGenerator gen, JitOp op, Address exit, JitBlock block, + MethodVisitor rv) { + Label fall = new Label(); + rv.visitJumpInsn(IFEQ, fall); + super.generateCodeWithCtxmod(gen, op, exit, block, rv); + rv.visitLabel(fall); + } + } + + /** + * {@inheritDoc} + * + * @implNote In addition to implementing the proper logic for a conditional branch, this + * contains a special case for synthetic branches created using + * {@link ExitPcodeOp#cond(AddrCtx)}. Such synthetic ops are employed to check for + * context modification at instruction fall through. It's rare, but if there are + * multiple paths in an instruction's p-code or an injection, where one causes context + * modification and the other does not, then we must check for context modification at + * run time. + *

+ * Conventionally, all {@link PcodeOp#CBRANCH} ops should have the condition as its + * second operand. Our special "conditional exit" does not. The + * {@link JitDataFlowModel} recognizes this and uses {@link JitFailVal} for + * {@link JitCBranchOp#cond()}. The "fail" value asserts that it never gets generated, + * which will ensure we apply special handling here. + */ @Override public void generateRunCode(JitCodeGenerator gen, JitCBranchOp op, JitBlock block, MethodVisitor rv) { + if (op.op() instanceof ExitPcodeOp && op.branch() instanceof RExtBranch eb) { + assert eb.reach() == Reachability.MAYBE_CTXMOD; + Label fall = new Label(); + RunFixedLocal.CTXMOD.generateLoadCode(rv); + rv.visitJumpInsn(IFEQ, fall); + BranchOpGen.generateExitCode(gen, eb.to().address, block, rv); + rv.visitLabel(fall); + return; + } JitType cType = gen.generateValReadCode(op.cond(), op.condType()); TypeConversions.generateIntToBool(cType, rv); - switch (op.branch()) { - case IntBranch ib -> { - JitBlock target = block.getTargetBlock(ib); - Label label = gen.labelForBlock(target); - BlockTransition transition = VarGen.computeBlockTransition(gen, block, target); - if (transition.needed()) { - Label fall = new Label(); - rv.visitJumpInsn(IFEQ, fall); - transition.generate(rv); - rv.visitJumpInsn(GOTO, label); - rv.visitLabel(fall); - } - else { - rv.visitJumpInsn(IFNE, label); - } - } - case ExtBranch eb -> { - Label fall = new Label(); - rv.visitJumpInsn(IFEQ, fall); - BranchOpGen.generateExtBranchCode(gen, eb.to(), block, rv); - rv.visitLabel(fall); - } + case RIntBranch ib -> IntCBranchGen.C_INT.generateCode(gen, op, ib, block, rv); + case RExtBranch eb -> ExtCBranchGen.C_EXT.generateCode(gen, op, eb, block, rv); default -> throw new AssertionError("Branch type confusion"); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java index c92460acab..6c07775ccc 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java @@ -25,8 +25,10 @@ import org.objectweb.asm.*; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; import ghidra.pcode.emu.jit.JitPassage.DecodedPcodeOp; import ghidra.pcode.emu.jit.analysis.*; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.gen.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator.RetireMode; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.gen.var.VarGen; @@ -111,10 +113,10 @@ public enum CallOtherOpGen implements OpGen { gen.generateRetirePcCtx(() -> { rv.visitLdcInsn(gen.getAddressForOp(op).getOffset()); - }, gen.getExitContext(op), rv); + }, gen.getExitContext(op), RetireMode.SET, rv); // [] - rv.visitVarInsn(ALOAD, 0); + RunFixedLocal.THIS.generateLoadCode(rv); // [this] gen.requestFieldForUserop(userop).generateLoadCode(gen, rv); // [this,userop] @@ -217,7 +219,7 @@ public enum CallOtherOpGen implements OpGen { * @return true if applicable */ public static boolean canDoDirectInvocation(JitCallOtherOpIf op) { - if (!op.userop().isFunctional()) { + if (!op.userop().isFunctional() || op.userop().modifiesContext()) { return false; } @@ -238,6 +240,10 @@ public enum CallOtherOpGen implements OpGen { @Override public void generateRunCode(JitCodeGenerator gen, JitCallOtherOpIf op, JitBlock block, MethodVisitor rv) { + if (op.userop().modifiesContext()) { + rv.visitLdcInsn(1); + RunFixedLocal.CTXMOD.generateStoreCode(rv); + } if (canDoDirectInvocation(op)) { generateRunCodeUsingDirectStrategy(gen, op, block, rv); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnimplementedOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnimplementedOpGen.java index 5ae789490d..34761c09a6 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnimplementedOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnimplementedOpGen.java @@ -20,6 +20,7 @@ import static ghidra.pcode.emu.jit.gen.GenConsts.*; import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.JitPassage.DecodeErrorPcodeOp; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; @@ -50,7 +51,7 @@ public enum UnimplementedOpGen implements OpGen { String message = gen.getErrorMessage(op.op()); if (op.op() instanceof DecodeErrorPcodeOp) { - rv.visitVarInsn(ALOAD, 0); + RunFixedLocal.THIS.generateLoadCode(rv); rv.visitLdcInsn(message); rv.visitLdcInsn(counter); rv.visitMethodInsn(INVOKEINTERFACE, NAME_JIT_COMPILED_PASSAGE, "createDecodeError", diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java index 39197d5bd0..774bc04916 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java @@ -20,7 +20,6 @@ import java.util.*; import org.objectweb.asm.Opcodes; -import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.jit.JitCompiler; import ghidra.pcode.emu.jit.JitPassage.*; import ghidra.pcode.emu.jit.JitPcodeThread; @@ -1380,21 +1379,34 @@ public interface JitCompiledPassage { * Set the bound thread's program counter and decode context. * *

- * This is called during retirement, i.e., upon exiting a passage or entering a hazard. This - * just converts things to the right type and invokes - * {@link PcodeThread#overrideCounter(Address)} and - * {@link PcodeThread#overrideContext(RegisterValue)}. + * This is called during retirement, i.e., upon exiting a passage. This just converts things to + * the right type and invokes + * {@link JitPcodeThread#writeCounterAndContext(Address, RegisterValue)}. * * @param counter the offset of the next instruction to execute * @param context the decode context for the next instruction */ - default void retireCounterAndContext(long counter, RegisterValue context) { + default void writeCounterAndContext(long counter, RegisterValue context) { JitPcodeThread thread = thread(); Address pc = thread.getLanguage().getDefaultSpace().getAddress(counter); - thread.overrideCounter(pc); - if (context != null) { - thread.overrideContext(context); - } + thread.writeCounterAndContext(pc, context); + } + + /** + * Set the bound thread's program counter and decode context, without writing it to the machine + * state. + * + *

+ * This is called during retirement upon entering a hazard. This just converts things to the + * right type and invokes {@link JitPcodeThread#setCounterAndContext(Address, RegisterValue)}. + * + * @param counter the offset of the next instruction to execute + * @param context the decode context for the next instruction + */ + default void setCounterAndContext(long counter, RegisterValue context) { + JitPcodeThread thread = thread(); + Address pc = thread.getLanguage().getDefaultSpace().getAddress(counter); + thread.setCounterAndContext(pc, context); } /** diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/FailValGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/FailValGen.java new file mode 100644 index 0000000000..59ef446a35 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/FailValGen.java @@ -0,0 +1,41 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.jit.gen.var; + +import org.objectweb.asm.MethodVisitor; + +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.var.JitFailVal; + +/** + * The generator that is forbidden from actually generating. + */ +public enum FailValGen implements ValGen { + /** Singleton */ + GEN; + + @Override + public void generateValInitCode(JitCodeGenerator gen, JitFailVal v, MethodVisitor iv) { + } + + @Override + public JitType generateValReadCode(JitCodeGenerator gen, JitFailVal v, + JitTypeBehavior typeReq, MethodVisitor rv) { + throw new AssertionError(); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ValGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ValGen.java index 8dc4d19774..e83ac01118 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ValGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ValGen.java @@ -118,6 +118,7 @@ public interface ValGen { static ValGen lookup(V v) { return (ValGen) switch (v) { case JitConstVal c -> ConstValGen.GEN; + case JitFailVal m -> FailValGen.GEN; case JitVar vv -> VarGen.lookup(vv); default -> throw new AssertionError(); }; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitBranchIndOp.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitBranchIndOp.java index 124624345e..f4fba64db7 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitBranchIndOp.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitBranchIndOp.java @@ -17,7 +17,7 @@ package ghidra.pcode.emu.jit.op; import java.util.List; -import ghidra.pcode.emu.jit.JitPassage.IndBranch; +import ghidra.pcode.emu.jit.JitPassage.RIndBranch; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.var.JitVal; import ghidra.program.model.pcode.PcodeOp; @@ -29,7 +29,7 @@ import ghidra.program.model.pcode.PcodeOp; * @param target the use-def node for the target offset * @param branch the branch record created for the p-code op */ -public record JitBranchIndOp(PcodeOp op, JitVal target, IndBranch branch) implements JitOp { +public record JitBranchIndOp(PcodeOp op, JitVal target, RIndBranch branch) implements JitOp { @Override public boolean canBeRemoved() { diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitBranchOp.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitBranchOp.java index 4be0a42c40..14175631f5 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitBranchOp.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitBranchOp.java @@ -17,7 +17,7 @@ package ghidra.pcode.emu.jit.op; import java.util.List; -import ghidra.pcode.emu.jit.JitPassage.Branch; +import ghidra.pcode.emu.jit.JitPassage.RBranch; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.var.JitVal; import ghidra.program.model.pcode.PcodeOp; @@ -28,7 +28,7 @@ import ghidra.program.model.pcode.PcodeOp; * @param op the p-code op * @param branch the branch record created for the p-code op */ -public record JitBranchOp(PcodeOp op, Branch branch) implements JitOp { +public record JitBranchOp(PcodeOp op, RBranch branch) implements JitOp { @Override public boolean canBeRemoved() { diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitCBranchOp.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitCBranchOp.java index 48f66e32ef..4055133672 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitCBranchOp.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitCBranchOp.java @@ -17,7 +17,7 @@ package ghidra.pcode.emu.jit.op; import java.util.List; -import ghidra.pcode.emu.jit.JitPassage.Branch; +import ghidra.pcode.emu.jit.JitPassage.RBranch; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.var.JitVal; import ghidra.program.model.pcode.PcodeOp; @@ -29,7 +29,7 @@ import ghidra.program.model.pcode.PcodeOp; * @param branch the branch record created for the p-code op * @param cond the use-def node for the branch condition */ -public record JitCBranchOp(PcodeOp op, Branch branch, JitVal cond) +public record JitCBranchOp(PcodeOp op, RBranch branch, JitVal cond) implements JitOp { @Override diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/var/JitFailVal.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/var/JitFailVal.java new file mode 100644 index 0000000000..6cc78f7a74 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/var/JitFailVal.java @@ -0,0 +1,46 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.jit.var; + +import java.util.List; + +import ghidra.pcode.emu.jit.op.JitOp; + +/** + * A value that is forbidden from being translated + */ +public enum JitFailVal implements JitVal { + /** Singleton */ + INSTANCE; + + @Override + public int size() { + return 1; + } + + @Override + public List uses() { + return List.of(); + } + + @Override + public void addUse(JitOp op, int position) { + } + + @Override + public void removeUse(JitOp op, int position) { + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emulate/EmulateInstructionStateModifier.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emulate/EmulateInstructionStateModifier.java index ff8adcf19c..18dd3f470f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emulate/EmulateInstructionStateModifier.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emulate/EmulateInstructionStateModifier.java @@ -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,6 +15,8 @@ */ package ghidra.pcode.emulate; +import java.util.*; + import ghidra.pcode.emulate.callother.OpBehaviorOther; import ghidra.pcode.error.LowlevelError; import ghidra.program.model.address.Address; @@ -23,14 +25,10 @@ import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; -import java.util.HashMap; -import java.util.Map; - /** - * EmulateInstructionStateModifier defines a language specific - * handler to assist emulation with adjusting the current execution state, - * providing support for custom pcodeop's (i.e., CALLOTHER). - * The implementation of this interface must provide a public constructor which + * EmulateInstructionStateModifier defines a language specific handler to assist + * emulation with adjusting the current execution state, providing support for custom pcodeop's + * (i.e., CALLOTHER). The implementation of this interface must provide a public constructor which * takes a single Emulate argument. */ public abstract class EmulateInstructionStateModifier { @@ -47,6 +45,7 @@ public abstract class EmulateInstructionStateModifier { /** * Register a pcodeop behavior corresponding to a CALLOTHER opcode. + * * @param opName name as defined within language via "define pcodeop" * @param pcodeOpBehavior */ @@ -66,9 +65,10 @@ public abstract class EmulateInstructionStateModifier { /** * Execute a CALLOTHER op + * * @param op - * @return true if corresponding pcodeop was registered and emulation support is - * performed, or false if corresponding pcodeop is not supported by this class. + * @return true if corresponding pcodeop was registered and emulation support is performed, or + * false if corresponding pcodeop is not supported by this class. * @throws LowlevelError */ public final boolean executeCallOther(PcodeOp op) throws LowlevelError { @@ -85,20 +85,23 @@ public abstract class EmulateInstructionStateModifier { } /** - * Emulation callback immediately before the first instruction is executed. - * This callback permits any language specific initializations to be performed. + * Emulation callback immediately before the first instruction is executed. This callback + * permits any language specific initializations to be performed. + * * @param emulate * @param current_address intial execute address * @param contextRegisterValue initial context value or null if not applicable or unknown * @throws LowlevelError */ - public void initialExecuteCallback(Emulate emulate, Address current_address, RegisterValue contextRegisterValue) throws LowlevelError { + public void initialExecuteCallback(Emulate emulate, Address current_address, + RegisterValue contextRegisterValue) throws LowlevelError { // no default implementation } - + /** - * Emulation callback immediately following execution of the lastExecuteAddress. - * One use of this callback is to modify the flowing/future context state. + * Emulation callback immediately following execution of the lastExecuteAddress. One use of this + * callback is to modify the flowing/future context state. + * * @param emulate * @param lastExecuteAddress * @param lastExecutePcode @@ -111,4 +114,16 @@ public abstract class EmulateInstructionStateModifier { throws LowlevelError { // no default implementation } + + /** + * Get the map of registered pcode userop behaviors + * + * @return the map, by userop index. + */ + public Map getPcodeOpMap() { + if (pcodeOpMap == null) { + return Map.of(); + } + return Collections.unmodifiableMap(pcodeOpMap); + } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java index 55a0f1422e..5814f08d94 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java @@ -214,6 +214,7 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra private final AnnotatedPcodeUseropLibrary library; private final boolean isFunctional; private final boolean hasSideEffects; + private final boolean modifiesContext; private final boolean canInline; private final MethodHandle handle; @@ -256,8 +257,9 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra } initFinished(); this.isFunctional = annot.functional(); - this.canInline = annot.canInline(); this.hasSideEffects = annot.hasSideEffects(); + this.modifiesContext = annot.modifiesContext(); + this.canInline = annot.canInline(); } @Override @@ -312,6 +314,11 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra return hasSideEffects; } + @Override + public boolean modifiesContext() { + return modifiesContext; + } + @Override public boolean canInlinePcode() { return canInline; @@ -662,6 +669,17 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra */ boolean hasSideEffects() default true; + /** + * Set to true to indicate the userop can modify the decode context. + * + *

+ * Failure to indicate context modifications can lead to erroneous decodes and thus + * incorrect execution results. + * + * @see PcodeUseropLibrary.PcodeUseropDefinition#modifiesContext() + */ + boolean modifiesContext() default false; + /** * Set to true to suggest inlining. * 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 b6adc5f17b..e5089e4162 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 @@ -247,4 +247,11 @@ public class PcodeProgram { public String format() { return format(false); } + + public String getUseropName(int opNo) { + if (opNo < language.getNumberOfUserDefinedOpNames()) { + return language.getUserDefinedOpName(opNo); + } + return useropNames.get(opNo); + } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java index 1563226019..cf299100af 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java @@ -187,6 +187,19 @@ public interface PcodeUseropLibrary { */ boolean hasSideEffects(); + /** + * Indicates that this userop may modify the decode context. + * + *

+ * This means that the userop may set a field in {@code contextreg}, which could thus affect + * how subsequent instructions are decoded. Executors which decode ahead will have to + * consider this effect. + * + * @return true if this can modify the context. + * @see PcodeUserop#modifiesContext() + */ + boolean modifiesContext(); + /** * Indicates whether or not this userop definition produces p-code suitable for inlining in * place of its invocation. diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java index c749f65b0d..5245b6fb47 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java @@ -203,6 +203,19 @@ public class SleighPcodeUseropDefinition implements PcodeUseropDefinition return true; } + /** + * {@inheritDoc} + * + * @implNote We could scan the p-code ops for any that write to the contextreg; however, at the + * moment, that is highly unconventional and perhaps even considered an error. If that + * becomes more common, or even recommended, then we can detect it and behave + * accordingly during interpretation (whether for execution or translation). + */ + @Override + public boolean modifiesContext() { + return false; + } + @Override public boolean canInlinePcode() { return true; diff --git a/Ghidra/Processors/ARM/data/languages/ARM.cspec b/Ghidra/Processors/ARM/data/languages/ARM.cspec index 2a8c24b9d1..a7fcff8138 100644 --- a/Ghidra/Processors/ARM/data/languages/ARM.cspec +++ b/Ghidra/Processors/ARM/data/languages/ARM.cspec @@ -136,6 +136,15 @@ + + + + + + + diff --git a/Ghidra/Processors/ARM/data/languages/ARM.sinc b/Ghidra/Processors/ARM/data/languages/ARM.sinc index 2a8cd18502..756faecbbb 100644 --- a/Ghidra/Processors/ARM/data/languages/ARM.sinc +++ b/Ghidra/Processors/ARM/data/languages/ARM.sinc @@ -189,6 +189,9 @@ define pcodeop ReverseBitOrder; define pcodeop SendEvent; define pcodeop setEndianState; +# Copies ISAModeSwitch to TMode +define pcodeop setISAMode; + macro affectflags() { CY = tmpCY; ZR = tmpZR; NG = tmpNG; OV = tmpOV; } @@ -197,11 +200,16 @@ macro affect_resflags() { ZR = tmpZR; NG = tmpNG; } -macro SetThumbMode(value) { +macro SetISAModeSwitch(value) { ISAModeSwitch = value; TB = ISAModeSwitch; } +macro SetThumbMode(value) { + SetISAModeSwitch(value); + setISAMode(); +} + # # simple branch, not inter-working macro BranchWritePC(addr) { diff --git a/Ghidra/Processors/ARM/data/languages/ARM_v45.cspec b/Ghidra/Processors/ARM/data/languages/ARM_v45.cspec index 5b6a05aaa1..a6b7a3bda9 100644 --- a/Ghidra/Processors/ARM/data/languages/ARM_v45.cspec +++ b/Ghidra/Processors/ARM/data/languages/ARM_v45.cspec @@ -80,6 +80,15 @@ + + + + + + + diff --git a/Ghidra/Processors/ARM/data/languages/ARM_win.cspec b/Ghidra/Processors/ARM/data/languages/ARM_win.cspec index f4e1dc2d1c..79e4532da5 100644 --- a/Ghidra/Processors/ARM/data/languages/ARM_win.cspec +++ b/Ghidra/Processors/ARM/data/languages/ARM_win.cspec @@ -128,4 +128,12 @@ + + + + + + diff --git a/Ghidra/Processors/ARM/data/languages/ARMinstructions.sinc b/Ghidra/Processors/ARM/data/languages/ARMinstructions.sinc index bf815c6a62..ddd0fa648d 100644 --- a/Ghidra/Processors/ARM/data/languages/ARMinstructions.sinc +++ b/Ghidra/Processors/ARM/data/languages/ARMinstructions.sinc @@ -2317,7 +2317,7 @@ ArmPCRelImmed12: reloff is U23=0 & immed & rotate :blx HAddr24 is $(AMODE) & CALLoverride=0 & ARMcond=0 & cond=15 & c2527=5 & H24=0 & HAddr24 { lr = inst_next; - SetThumbMode(1); + SetISAModeSwitch(1); # TMode done by HAddr24's globalset call HAddr24; # don't do causes decompiler trouble TB = 0; } # Always changes to THUMB mode @@ -2325,7 +2325,7 @@ ArmPCRelImmed12: reloff is U23=0 & immed & rotate :blx HAddr24 is $(AMODE) & CALLoverride=1 & ARMcond=0 & cond=15 & c2527=5 & H24=0 & HAddr24 { lr = inst_next; - SetThumbMode(1); + SetISAModeSwitch(1); # TMode done by HAddr24's globalset goto HAddr24; } # Always changes to THUMB mode @@ -2333,7 +2333,7 @@ ArmPCRelImmed12: reloff is U23=0 & immed & rotate :blx HAddr24 is $(AMODE) & ARMcond=0 & CALLoverride=0 & cond=15 & c2527=5 & H24=1 & HAddr24 { lr = inst_next; - SetThumbMode(1); + SetISAModeSwitch(1); # TMode done by HAddr24's globalset call HAddr24; # don't do causes decompiler trouble TB = 0; } # Always changes to THUMB mode @@ -2341,7 +2341,7 @@ ArmPCRelImmed12: reloff is U23=0 & immed & rotate :blx HAddr24 is $(AMODE) & ARMcond=0 & CALLoverride=1 & cond=15 & c2527=5 & H24=1 & HAddr24 { lr = inst_next; - SetThumbMode(1); + SetISAModeSwitch(1); # TMode done by HAddr24's globalset goto HAddr24; } # Always changes to THUMB mode diff --git a/Ghidra/Processors/ARM/src/main/java/ghidra/program/emulation/ARMEmulateInstructionStateModifier.java b/Ghidra/Processors/ARM/src/main/java/ghidra/program/emulation/ARMEmulateInstructionStateModifier.java index eaeb514c90..4ebb1e150a 100644 --- a/Ghidra/Processors/ARM/src/main/java/ghidra/program/emulation/ARMEmulateInstructionStateModifier.java +++ b/Ghidra/Processors/ARM/src/main/java/ghidra/program/emulation/ARMEmulateInstructionStateModifier.java @@ -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. @@ -19,11 +19,12 @@ import java.math.BigInteger; import ghidra.pcode.emulate.Emulate; import ghidra.pcode.emulate.EmulateInstructionStateModifier; +import ghidra.pcode.emulate.callother.OpBehaviorOther; import ghidra.pcode.error.LowlevelError; import ghidra.program.model.address.Address; import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; -import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; public class ARMEmulateInstructionStateModifier extends EmulateInstructionStateModifier { @@ -45,74 +46,77 @@ public class ARMEmulateInstructionStateModifier extends EmulateInstructionStateM aMode = new RegisterValue(TModeReg, BigInteger.ZERO); } + registerPcodeOpBehavior("setISAMode", new SetISAModeOpBehavior()); + /** * We could registerPcodeOpBehavior for one or more of the following pcodeop's: - * - Absolute - ClearExclusiveLocal - DataMemoryBarrier - DataSynchronizationBarrier - ExclusiveAccess - HintDebug - HintPreloadData - HintPreloadDataForWrite - HintPreloadInstruction - HintYield - IndexCheck - InstructionSynchronizationBarrier - ReverseBitOrder - SendEvent - SignedDoesSaturate - SignedSaturate - UnsignedDoesSaturate - UnsignedSaturate - WaitForEvent - WaitForInterrupt - coprocessor_function - coprocessor_function2 - coprocessor_load - coprocessor_load2 - coprocessor_loadlong - coprocessor_loadlong2 - coprocessor_movefrom - coprocessor_movefrom2 - coprocessor_moveto - coprocessor_moveto2 - coprocessor_store - coprocessor_store2 - coprocessor_storelong - coprocessor_storelong2 - disableDataAbortInterrupts - disableFIQinterrupts - disableIRQinterrupts - enableDataAbortInterrupts - enableFIQinterrupts - enableIRQinterrupts - hasExclusiveAccess - isCurrentModePrivileged - isFIQinterruptsEnabled - isIRQinterruptsEnabled - isThreadMode - jazelle_branch - setAbortMode - setFIQMode - setIRQMode - setSupervisorMode - setSystemMode - setThreadModePrivileged - setUndefinedMode - setUserMode - software_breakpoint - software_interrupt - * + *

+ * Absolute
+ * ClearExclusiveLocal
+ * DataMemoryBarrier
+ * DataSynchronizationBarrier
+ * ExclusiveAccess
+ * HintDebug
+ * HintPreloadData
+ * HintPreloadDataForWrite
+ * HintPreloadInstruction
+ * HintYield
+ * IndexCheck
+ * InstructionSynchronizationBarrier
+ * ReverseBitOrder
+ * SendEvent
+ * SignedDoesSaturate
+ * SignedSaturate
+ * UnsignedDoesSaturate
+ * UnsignedSaturate
+ * WaitForEvent
+ * WaitForInterrupt
+ * coprocessor_function
+ * coprocessor_function2
+ * coprocessor_load
+ * coprocessor_load2
+ * coprocessor_loadlong
+ * coprocessor_loadlong2
+ * coprocessor_movefrom
+ * coprocessor_movefrom2
+ * coprocessor_moveto
+ * coprocessor_moveto2
+ * coprocessor_store
+ * coprocessor_store2
+ * coprocessor_storelong
+ * coprocessor_storelong2
+ * disableDataAbortInterrupts
+ * disableFIQinterrupts
+ * disableIRQinterrupts
+ * enableDataAbortInterrupts
+ * enableFIQinterrupts
+ * enableIRQinterrupts
+ * hasExclusiveAccess
+ * isCurrentModePrivileged
+ * isFIQinterruptsEnabled
+ * isIRQinterruptsEnabled
+ * isThreadMode
+ * jazelle_branch
+ * setAbortMode
+ * setFIQMode
+ * setIRQMode
+ * setSupervisorMode
+ * setSystemMode
+ * setThreadModePrivileged
+ * setUndefinedMode
+ * setUserMode
+ * software_breakpoint
+ * software_interrupt
*/ } /** - * Initialize TB register based upon context-register state before first instruction is executed. + * Initialize TB register based upon context-register state before first instruction is + * executed. */ @Override - public void initialExecuteCallback(Emulate emulate, Address current_address, RegisterValue contextRegisterValue) throws LowlevelError { + public void initialExecuteCallback(Emulate emulate, Address current_address, + RegisterValue contextRegisterValue) throws LowlevelError { if (TModeReg == null) { return; // Thumb mode not supported } @@ -127,46 +131,28 @@ public class ARMEmulateInstructionStateModifier extends EmulateInstructionStateM emu.getMemoryState().setValue(TBreg, tModeValue); } - /** - * Handle odd addresses which may occur when jumping/returning indirectly - * to Thumb mode. It is assumed that language will properly handle - * context changes during the flow of execution, we need only fix - * the current program counter. - */ - @Override - public void postExecuteCallback(Emulate emulate, Address lastExecuteAddress, - PcodeOp[] lastExecutePcode, int lastPcodeIndex, Address currentAddress) - throws LowlevelError { - if (TModeReg == null) { - return; // Thumb mode not supported - } - if (lastPcodeIndex < 0) { - // ignore fall-through condition - return; - } - int lastOp = lastExecutePcode[lastPcodeIndex].getOpcode(); - if (lastOp != PcodeOp.BRANCH && lastOp != PcodeOp.CBRANCH && lastOp != PcodeOp.BRANCHIND && - lastOp != PcodeOp.CALL && lastOp != PcodeOp.CALLIND && lastOp != PcodeOp.RETURN) { - // only concerned with Branch, Call or Return ops - return; - } - long tbValue = emu.getMemoryState().getValue(TBreg); - if (tbValue == 1) { - // Thumb mode - emu.setContextRegisterValue(tMode); // change context to be consistent with TB value - if ((currentAddress.getOffset() & 0x1) == 1) { - emulate.setExecuteAddress(currentAddress.previous()); + class SetISAModeOpBehavior implements OpBehaviorOther { + @Override + public void evaluate(Emulate emu, Varnode out, Varnode[] inputs) { + Address currentAddress = emu.getExecuteAddress(); + long tbValue = emu.getMemoryState().getValue(TBreg); + if (tbValue == 1) { + // Thumb mode + emu.setContextRegisterValue(tMode); // change context to be consistent with TB value + if ((currentAddress.getOffset() & 0x1) == 1) { + emu.setExecuteAddress(currentAddress.previous()); + } } - } - else if (tbValue == 0) { + else if (tbValue == 0) { - if ((currentAddress.getOffset() & 0x1) == 1) { - throw new LowlevelError( - "Flow to odd address occurred without setting TB register (Thumb mode)"); + if ((currentAddress.getOffset() & 0x1) == 1) { + throw new LowlevelError( + "Flow to odd address occurred without setting TB register (Thumb mode)"); + } + + // ARM mode + emu.setContextRegisterValue(aMode); // change context to be consistent with TB value } - - // ARM mode - emu.setContextRegisterValue(aMode); // change context to be consistent with TB value } } } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/AbstractPcodeEmulatorTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/AbstractPcodeEmulatorTest.java index 9d9d53aec9..07a62e851b 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/AbstractPcodeEmulatorTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/AbstractPcodeEmulatorTest.java @@ -26,8 +26,7 @@ import org.junit.Test; import generic.test.AbstractGTest; import ghidra.GhidraTestApplicationLayout; -import ghidra.app.plugin.assembler.Assemblers; -import ghidra.app.plugin.assembler.AssemblyBuffer; +import ghidra.app.plugin.assembler.*; import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock; import ghidra.framework.Application; import ghidra.framework.ApplicationConfiguration; @@ -303,11 +302,11 @@ public abstract class AbstractPcodeEmulatorTest extends AbstractGTest { public void testSkipThumbStaysThumb() throws Exception { PcodeEmulator emu = createEmulator(getLanguage(LANGID_ARMV8)); PcodeArithmetic arithmetic = emu.getArithmetic(); - AddressSpace space = emu.getLanguage().getDefaultSpace(); + Language language = emu.getLanguage(); + AddressSpace space = language.getDefaultSpace(); Address entry = space.getAddress(0x00400000); AssemblyBuffer asm = new AssemblyBuffer(Assemblers.getAssembler(emu.getLanguage()), entry); - Language language = asm.getAssembler().getLanguage(); Register regCtx = language.getContextBaseRegister(); Register regT = language.getRegister("T"); RegisterValue rvDefault = new RegisterValue(regCtx, @@ -429,4 +428,60 @@ public abstract class AbstractPcodeEmulatorTest extends AbstractGTest { arithmetic.toLong(thread.getState().getVar(r1, Reason.INSPECT), Purpose.INSPECT)); assertEquals(target, thread.getCounter()); } + + public void testArmPltIntoThumbFunction() throws Exception { + PcodeEmulator emu = createEmulator(getLanguage(LANGID_ARMV8)); + PcodeArithmetic arithmetic = emu.getArithmetic(); + Language language = emu.getLanguage(); + AddressSpace space = language.getDefaultSpace(); + Address pltEntry = space.getAddress(0x00500000); + Assembler asm = Assemblers.getAssembler(language); + AssemblyBuffer pltAsm = new AssemblyBuffer(asm, pltEntry); + + Register regCtx = language.getContextBaseRegister(); + Register regT = language.getRegister("T"); + RegisterValue rvDefault = new RegisterValue(regCtx, pltAsm.getAssembler() + .getContextAt(pltAsm.getNext()) + .toBigInteger(regCtx.getNumBytes())); + RegisterValue rvThumb = rvDefault.assign(regT, BigInteger.ONE); + AssemblyPatternBlock ctxThumb = AssemblyPatternBlock.fromRegisterValue(rvThumb); + + Address gotThumbFunc = space.getAddress(0x00510234); + + long gotOffset = gotThumbFunc.getOffset() - pltEntry.getOffset() - 0x10000 - 8; + + pltAsm.assemble("adr r12, 0x%s".formatted(pltEntry.add(8))); //("add r12, pc, #0, 12"); ? + pltAsm.assemble("add r12, r12, #0x10000"); // #16, 20"); + // Assembler bug doesn't allow space in , # in this case + pltAsm.assemble("ldr pc, [r12,#0x%x]!".formatted(gotOffset)); + + Address funcEntry = space.getAddress(0x00400000); + AssemblyBuffer funcAsm = new AssemblyBuffer(asm, funcEntry); + + funcAsm.assemble("adds r0, #1", ctxThumb); + Address funcEnd = funcAsm.getNext(); + + byte[] pltBytes = pltAsm.getBytes(); + emu.getSharedState().setVar(pltEntry, pltBytes.length, false, pltBytes); + byte[] funcBytes = funcAsm.getBytes(); + emu.getSharedState().setVar(funcEntry, funcBytes.length, false, funcBytes); + // +1 for THUMB mode + byte[] gotBytes = arithmetic.fromConst(funcEntry.getOffset() + 1, 4); + emu.getSharedState().setVar(gotThumbFunc, gotBytes.length, false, gotBytes); + + PcodeThread thread = emu.newThread(); + thread.overrideCounter(pltEntry); + thread.overrideContextWithDefault(); + + try { + thread.run(); + } + catch (DecodePcodeExecutionException e) { + assertEquals(funcEnd, e.getProgramCounter()); + } + + Register r0 = emu.getLanguage().getRegister("r0"); + assertEquals(1, + arithmetic.toLong(thread.getState().getVar(r0, Reason.INSPECT), Purpose.INSPECT)); + } } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/JitCodeGeneratorTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/JitCodeGeneratorTest.java index 04f2d3543e..ad88075fbd 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/JitCodeGeneratorTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/JitCodeGeneratorTest.java @@ -2213,12 +2213,11 @@ public class JitCodeGeneratorTest extends AbstractJitTest { Translation tr = translateLang(ID_TOYBE64, 0x00400000, """ imm r0,#123 add r0,#7 - """, - Map.ofEntries( - Map.entry(0x00400002L, """ - r1 = sleigh_userop(r0, 4:8); - emu_exec_decoded(); - """))); + """, Map.ofEntries( + Map.entry(0x00400002L, """ + r1 = sleigh_userop(r0, 4:8); + emu_exec_decoded(); + """))); tr.runDecodeErr(0x00400004); assertEquals(123 + 7, tr.getLongRegVal("r0")); @@ -2230,12 +2229,11 @@ public class JitCodeGeneratorTest extends AbstractJitTest { Translation tr = translateLang(ID_TOYBE64, 0x00400000, """ imm r0,#123 add r0,#7 - """, - Map.ofEntries( - Map.entry(0x00400002L, """ - r1 = sleigh_userop(r0, 4:8); - emu_skip_decoded(); - """))); + """, Map.ofEntries( + Map.entry(0x00400002L, """ + r1 = sleigh_userop(r0, 4:8); + emu_skip_decoded(); + """))); tr.runDecodeErr(0x00400004); assertEquals(123, tr.getLongRegVal("r0")); @@ -2260,4 +2258,57 @@ public class JitCodeGeneratorTest extends AbstractJitTest { }).count(); assertEquals(1, countSCarrys); } + + @Test + public void testCtxHazardousFallthrough() throws Exception { + Translation tr = translateLang(ID_ARMv8LE, 0x00400000, """ + mov r0,#6 + mov r1,#7 + """, Map.ofEntries( + Map.entry(0x00400000L, """ + setISAMode(1:1); + emu_exec_decoded(); + """))); + + tr.runClean(); + assertEquals(6, tr.getLongRegVal("r0")); + // Should not execute second instruction, because of injected ctx change + assertEquals(0, tr.getLongRegVal("r1")); + } + + @Test + public void testCtxMaybeHazardousFallthrough() throws Exception { + /** + * For this test to produce the "MAYBE" case, the multiple paths have to be + * internal to an instruction (or inject). All that logic is only applied on an + * instruction-by-instruction basis. + */ + Translation tr = translateLang(ID_ARMv8LE, 0x00400000, """ + mov r0,#6 + mov r1,#7 + """, Map.ofEntries( + Map.entry(0x00400000L, """ + if (!ZR) goto ; + ISAModeSwitch = 1; + setISAMode(ISAModeSwitch); + + emu_exec_decoded(); + """))); + + tr.setLongRegVal("r1", 0); // Reset + tr.setLongRegVal("ZR", 0); + // Since ctx wasn't touched at runtime, we fall out of program + tr.runDecodeErr(0x00400008); + assertEquals(6, tr.getLongRegVal("r0")); + assertEquals(7, tr.getLongRegVal("r1")); + assertEquals(0, tr.getLongRegVal("ISAModeSwitch")); + + tr.setLongRegVal("r1", 0); // Reset + tr.setLongRegVal("ZR", 1); + // Hazard causes exit before 2nd instruction + tr.runClean(); + assertEquals(6, tr.getLongRegVal("r0")); + assertEquals(0, tr.getLongRegVal("r1")); + assertEquals(1, tr.getLongRegVal("ISAModeSwitch")); + } }