mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-21 07:21:35 +08:00
Merge remote-tracking branch 'origin/GP-5301_Dan_testEmuThumbPlt'
This commit is contained in:
+5
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+98
-10
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+228
-12
@@ -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));
|
||||
|
||||
+12
-3
@@ -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();
|
||||
}
|
||||
|
||||
+16
-8
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -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();
|
||||
|
||||
+10
-1
@@ -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}
|
||||
*
|
||||
|
||||
+91
-23
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-15
@@ -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);
|
||||
|
||||
+32
-7
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
@@ -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();
|
||||
|
||||
+4
-2
@@ -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);
|
||||
|
||||
+4
-2
@@ -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]
|
||||
|
||||
+4
-2
@@ -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);
|
||||
|
||||
+4
-2
@@ -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);
|
||||
|
||||
+51
-24
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+48
-4
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+169
-33
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+98
-24
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+9
-3
@@ -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);
|
||||
}
|
||||
|
||||
+2
-1
@@ -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",
|
||||
|
||||
+22
-10
@@ -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();
|
||||
};
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
}
|
||||
}
|
||||
+32
-17
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+19
-1
@@ -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.
|
||||
|
||||
+13
@@ -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
|
||||
|
||||
|
||||
+86
-100
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+59
-4
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+63
-12
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user