Merge remote-tracking branch 'origin/GP-5301_Dan_testEmuThumbPlt'

This commit is contained in:
Ryan Kurtz
2025-04-04 12:51:15 -04:00
46 changed files with 1702 additions and 367 deletions
@@ -181,6 +181,11 @@ public interface EmuSyscallLibrary<T> extends PcodeUseropLibrary<T> {
return true;
}
@Override
public boolean modifiesContext() {
return false;
}
@Override
public boolean canInlinePcode() {
return false;
@@ -171,9 +171,10 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@Override
public void executeSleigh(String source) {
PcodeUseropLibrary<T> 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<T> implements PcodeThread<T> {
protected final PcodeArithmetic<T> arithmetic;
protected final ThreadPcodeExecutorState<T> state;
protected final InstructionDecoder decoder;
protected final PcodeUseropLibrary<T> library;
// Delay, and compute lazily
private PcodeUseropLibrary<T> library;
protected final PcodeThreadExecutor<T> executor;
protected final Register pc;
@@ -255,7 +258,6 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
PcodeExecutorState<T> 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<T> implements PcodeThread<T> {
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<T> implements PcodeThread<T> {
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<T> implements PcodeThread<T> {
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<T> implements PcodeThread<T> {
beginInstructionOrInject();
}
else if (!frame.isFinished()) {
executor.step(frame, library);
executor.step(frame, getUseropLibrary());
}
else {
advanceAfterFinished();
@@ -462,7 +468,7 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@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<T> implements PcodeThread<T> {
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<T> implements PcodeThread<T> {
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<T> implements PcodeThread<T> {
@Override
public void finishInstruction() {
assertMidInstruction();
executor.finish(frame, library);
executor.finish(frame, getUseropLibrary());
advanceAfterFinished();
}
@@ -669,6 +675,9 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@Override
public PcodeUseropLibrary<T> getUseropLibrary() {
if (library == null) {
library = createUseropLibrary();
}
return library;
}
@@ -697,7 +706,7 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@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);
}
@@ -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<T> extends DefaultPcodeThread<T> {
}
}
/**
* For incorporating the state modifier's userop behaviors
*/
protected class ModifierUseropLibrary implements PcodeUseropLibrary<T> {
/**
* A wrapper around {@link OpBehaviorOther}
*/
protected class ModifierUseropDefinition implements PcodeUseropDefinition<T> {
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<T> executor, PcodeUseropLibrary<T> library,
Varnode outVar, List<Varnode> 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<String, PcodeUseropDefinition<T>> userops;
private Map<String, PcodeUseropDefinition<T>> computeUserops() {
if (modifier == null) {
return Map.of();
}
Map<String, PcodeUseropDefinition<T>> userops = new HashMap<>();
for (Entry<Integer, OpBehaviorOther> entry : modifier.getPcodeOpMap().entrySet()) {
String name = language.getUserDefinedOpName(entry.getKey());
userops.put(name, new ModifierUseropDefinition(name, entry.getValue()));
}
return userops;
}
@Override
public Map<String, PcodeUseropDefinition<T>> 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<T> extends DefaultPcodeThread<T> {
}
}
@Override
protected PcodeUseropLibrary<T> createUseropLibrary() {
return super.createUseropLibrary().compose(new ModifierUseropLibrary());
}
@Override
public void overrideCounter(Address counter) {
super.overrideCounter(counter);
@@ -171,12 +267,4 @@ public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
frame.getBranched(), getCounter());
}
}
@Override
protected boolean onMissingUseropDef(PcodeOp op, String opName) {
if (modifier != null) {
return modifier.executeCallOther(op);
}
return super.onMissingUseropDef(op, opName);
}
}
@@ -180,6 +180,7 @@ public class JitCompiler {
* In production, this should be empty.
*/
public static final EnumSet<Diag> ENABLE_DIAGNOSTICS = EnumSet.noneOf(Diag.class);
/**
* Exclude a given address offset from ASM's {@link ClassWriter#COMPUTE_MAXS} and
* {@link ClassWriter#COMPUTE_FRAMES}.
@@ -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
*
* <p>
* 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
*
* <p>
* 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 <em>within
* an instruction step</em>.
*
* <p>
* 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.
*
* <p>
* This is true if there exists <em>any</em> 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.
*
* <p>
* 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
*
* <p>
* 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<Instruction> instructions;
private final AddrCtx entry;
private final PcodeUseropLibrary<Object> decodeLibrary;
private final Map<PcodeOp, Branch> branches;
private final Map<PcodeOp, PBranch> branches;
private final Map<PcodeOp, AddrCtx> 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<PcodeOp> code,
PcodeUseropLibrary<Object> decodeLibrary, List<Instruction> instructions,
Map<PcodeOp, Branch> branches, Map<PcodeOp, AddrCtx> entries) {
Map<PcodeOp, PBranch> branches, Map<PcodeOp, AddrCtx> 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<PcodeOp, Branch> getBranches() {
public Map<PcodeOp, PBranch> getBranches() {
return branches;
}
@@ -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);
}
}
}
@@ -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 {
* <p>
* 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.
*
* <p>
* 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<FixedLocal> 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<JitVal, VarHandler> handlers = new HashMap<>();
private final Map<Varnode, VarHandler> handlersPerVarnode = new HashMap<>();
private final NavigableMap<Address, JvmLocal> 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));
@@ -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<PcodeOp, Branch> 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<PcodeOp, JitBlock> 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();
}
@@ -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<JitVal> {
private final JitDataFlowModel dfm;
private final Map<PcodeOp, Branch> branches;
private final Map<PcodeOp, PBranch> branches;
/**
* Construct an executor from the given context
@@ -85,7 +85,7 @@ class JitDataFlowExecutor extends PcodeExecutor<JitVal> {
* @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<JitVal> {
* @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<JitVal> {
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));
}
@@ -226,6 +226,11 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary<JitVal> {
return decOp.hasSideEffects();
}
@Override
public boolean modifiesContext() {
return decOp.modifiesContext();
}
@Override
public boolean canInlinePcode() {
return decOp.canInlinePcode();
@@ -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}
*
@@ -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<Object>
private final Map<Address, RegisterValue> futCtx = new HashMap<>();
final List<PcodeOp> opsForThisStep = new ArrayList<>();
private final List<Branch> branchesForThisStep = new ArrayList<>();
private final List<SBranch> branchesForThisStep = new ArrayList<>();
private final Map<PcodeOp, DecodedPcodeOp> rewrites = new HashMap<>();
@@ -311,7 +312,7 @@ class DecoderExecutor extends PcodeExecutor<Object>
*/
@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<Object>
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<Object>
*/
@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<Object>
* 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<Object>
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<PcodeOp, JitBlock> blocks = splitter.splitBlocks();
JitBlock entry = blocks.firstEntry().getValue();
JitBlock exit = blocks.lastEntry().getValue();
Set<JitBlock> reachable = new HashSet<>();
collectReachable(reachable, entry);
Map<JitBlock, Reachability> 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<Object>
}
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<Object> 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)}
*
* <p>
* 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<JitBlock> into, JitBlock cur) {
if (!into.add(cur)) {
private void collectReachable(Map<JitBlock, Reachability> 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);
}
}
@@ -42,9 +42,10 @@ class DecoderForOnePassage {
private final int maxInstrs;
private final int maxStrides;
final Map<PcodeOp, IntBranch> internalBranches = new HashMap<>();
final SequencedMap<PcodeOp, ExtBranch> externalBranches = new LinkedHashMap<>();
final Map<PcodeOp, Branch> otherBranches = new HashMap<>();
final Map<PcodeOp, RIntBranch> internalBranches = new HashMap<>();
// Sequenced, because this is also the seed queue
final SequencedMap<PcodeOp, RExtBranch> externalBranches = new LinkedHashMap<>();
final Map<PcodeOp, PBranch> otherBranches = new HashMap<>();
final Map<AddrCtx, PcodeOp> firstOps = new HashMap<>();
final List<DecodedStride> 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<PcodeOp, ExtBranch> nextEnt = externalBranches.pollFirstEntry();
Entry<PcodeOp, RExtBranch> 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<PcodeOp> code = strides.stream().flatMap(b -> b.ops().stream()).toList();
List<Instruction> instructions =
strides.stream().flatMap(b -> b.instructions().stream()).toList();
Map<PcodeOp, Branch> branches = otherBranches;
Map<PcodeOp, PBranch> 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);
@@ -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;
}
}
}
/**
@@ -118,6 +118,11 @@ public class DecoderUseropLibrary extends AnnotatedPcodeUseropLibrary<Object> {
return rtOp.hasSideEffects();
}
@Override
public boolean modifiesContext() {
return rtOp.modifiesContext();
}
@Override
public boolean canInlinePcode() {
return rtOp.canInlinePcode();
@@ -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);
@@ -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]
@@ -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);
@@ -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]
@@ -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);
@@ -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, "<init>",
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 {
*
* <p>
* 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);
}
/**
@@ -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<JitBranchIndOp> {
/** 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<RIndBranch, JitBranchIndOp> {
/** 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);
}
}
@@ -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<JitBranchOp> {
GEN;
/**
* Emit code that exits via a direct branch
*
* <p>
* 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.
*
* <p>
* 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 <TB> the type of branch
* @param <TO> the type of op
*/
static abstract class BranchGen<TB extends RBranch, TO extends JitOp> {
/**
* 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.
*
* <p>
* This means <em>no</em> 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.
*
* <p>
* This means a context-modifying userop has <em>certainly</em> 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<RIntBranch, JitOp> {
/** 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<RExtBranch, JitOp> {
/** 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");
}
}
@@ -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<JitCBranchOp> {
/** 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.
* <p>
* 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");
}
}
@@ -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<JitCallOtherOpIf> {
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<JitCallOtherOpIf> {
* @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<JitCallOtherOpIf> {
@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);
}
@@ -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<JitUnimplementedOp> {
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",
@@ -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.
*
* <p>
* 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.
*
* <p>
* 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);
}
/**
@@ -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<JitFailVal> {
/** 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();
}
}
@@ -118,6 +118,7 @@ public interface ValGen<V extends JitVal> {
static <V extends JitVal> ValGen<V> lookup(V v) {
return (ValGen<V>) switch (v) {
case JitConstVal c -> ConstValGen.GEN;
case JitFailVal m -> FailValGen.GEN;
case JitVar vv -> VarGen.lookup(vv);
default -> throw new AssertionError();
};
@@ -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() {
@@ -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() {
@@ -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
@@ -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<ValUse> uses() {
return List.of();
}
@Override
public void addUse(JitOp op, int position) {
}
@Override
public void removeUse(JitOp op, int position) {
}
}
@@ -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;
/**
* <code>EmulateInstructionStateModifier</code> 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
* <code>EmulateInstructionStateModifier</code> 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<Integer, OpBehaviorOther> getPcodeOpMap() {
if (pcodeOpMap == null) {
return Map.of();
}
return Collections.unmodifiableMap(pcodeOpMap);
}
}
@@ -214,6 +214,7 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
private final AnnotatedPcodeUseropLibrary<T> 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<T> 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<T> implements PcodeUseropLibra
return hasSideEffects;
}
@Override
public boolean modifiesContext() {
return modifiesContext;
}
@Override
public boolean canInlinePcode() {
return canInline;
@@ -662,6 +669,17 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
*/
boolean hasSideEffects() default true;
/**
* Set to true to indicate the userop can modify the decode context.
*
* <p>
* 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.
*
@@ -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);
}
}
@@ -187,6 +187,19 @@ public interface PcodeUseropLibrary<T> {
*/
boolean hasSideEffects();
/**
* Indicates that this userop may modify the decode context.
*
* <p>
* 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.
@@ -203,6 +203,19 @@ public class SleighPcodeUseropDefinition<T> implements PcodeUseropDefinition<T>
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;
@@ -136,6 +136,15 @@
</unaffected>
</prototype>
<callotherfixup targetop="setISAMode">
<pcode incidentalcopy="true">
<!-- NOP -->
<body><![CDATA[
r0 = r0;
]]></body>
</pcode>
</callotherfixup>
<callfixup name="switch8_r3">
<target name="switch8_r3"/>
<target name="__ARM_common_switch8"/>
@@ -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) {
@@ -80,6 +80,15 @@
</prototype>
</default_proto>
<callotherfixup targetop="setISAMode">
<pcode incidentalcopy="true">
<!-- NOP -->
<body><![CDATA[
r0 = r0;
]]></body>
</pcode>
</callotherfixup>
<callfixup name="switch8_r3">
<target name="switch8_r3"/>
<pcode>
@@ -128,4 +128,12 @@
</prototype>
</default_proto>
<callotherfixup targetop="setISAMode">
<pcode incidentalcopy="true">
<!-- NOP -->
<body><![CDATA[
r0 = r0;
]]></body>
</pcode>
</callotherfixup>
</compiler_spec>
@@ -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
@@ -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
*
* <p>
* Absolute<br/>
* ClearExclusiveLocal<br/>
* DataMemoryBarrier<br/>
* DataSynchronizationBarrier<br/>
* ExclusiveAccess<br/>
* HintDebug<br/>
* HintPreloadData<br/>
* HintPreloadDataForWrite<br/>
* HintPreloadInstruction<br/>
* HintYield <br/>
* IndexCheck<br/>
* InstructionSynchronizationBarrier<br/>
* ReverseBitOrder<br/>
* SendEvent<br/>
* SignedDoesSaturate<br/>
* SignedSaturate<br/>
* UnsignedDoesSaturate<br/>
* UnsignedSaturate<br/>
* WaitForEvent<br/>
* WaitForInterrupt<br/>
* coprocessor_function<br/>
* coprocessor_function2<br/>
* coprocessor_load<br/>
* coprocessor_load2<br/>
* coprocessor_loadlong<br/>
* coprocessor_loadlong2<br/>
* coprocessor_movefrom<br/>
* coprocessor_movefrom2<br/>
* coprocessor_moveto<br/>
* coprocessor_moveto2<br/>
* coprocessor_store<br/>
* coprocessor_store2<br/>
* coprocessor_storelong<br/>
* coprocessor_storelong2<br/>
* disableDataAbortInterrupts<br/>
* disableFIQinterrupts<br/>
* disableIRQinterrupts<br/>
* enableDataAbortInterrupts<br/>
* enableFIQinterrupts<br/>
* enableIRQinterrupts<br/>
* hasExclusiveAccess<br/>
* isCurrentModePrivileged<br/>
* isFIQinterruptsEnabled<br/>
* isIRQinterruptsEnabled<br/>
* isThreadMode<br/>
* jazelle_branch<br/>
* setAbortMode<br/>
* setFIQMode<br/>
* setIRQMode<br/>
* setSupervisorMode<br/>
* setSystemMode<br/>
* setThreadModePrivileged<br/>
* setUndefinedMode<br/>
* setUserMode<br/>
* software_breakpoint<br/>
* software_interrupt<br/>
*/
}
/**
* 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
}
}
}
@@ -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<byte[]> 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<byte[]> 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<byte[]> 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));
}
}
@@ -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
* <em>internal</em> 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 <skip>;
ISAModeSwitch = 1;
setISAMode(ISAModeSwitch);
<skip>
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"));
}
}