GP-4643: Add a JIT-accelerated p-code emulator (API/scripting only)

This commit is contained in:
Dan
2025-01-03 10:27:38 -05:00
parent 20285e267d
commit a8fae1fe5b
320 changed files with 32638 additions and 630 deletions

View File

@@ -0,0 +1,136 @@
/* ###
* 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;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import org.junit.Before;
import generic.test.AbstractGTest;
import ghidra.GhidraTestApplicationLayout;
import ghidra.app.plugin.assembler.AssemblyBuffer;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
import ghidra.lifecycle.Unfinished;
import ghidra.pcode.emu.jit.JitPassage.AddrCtx;
import ghidra.pcode.emu.jit.JitPassage.ExitPcodeOp;
import ghidra.pcode.emu.jit.analysis.JitAnalysisContext;
import ghidra.pcode.emu.jit.decode.JitPassageDecoderTestAccess;
import ghidra.pcode.exec.PcodeProgram;
import ghidra.pcode.exec.PcodeUseropLibrary;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.pcode.PcodeOp;
public class AbstractJitTest extends AbstractGTest {
public static PcodeOp assertOp(int opcode, PcodeOp op) {
assertEquals(opcode, op.getOpcode());
return op;
}
@Before
public void setUp() throws IOException {
if (!Application.isInitialized()) {
Application.initializeApplication(
new GhidraTestApplicationLayout(new File(getTestDirectoryPath())),
new ApplicationConfiguration());
}
}
/**
* Generate a p-code program from the given instruction sequence
*
* <p>
* The instructions are considered in list order, regardless of their addresses. The caller must
* ensure the order is consistent. An empty instruction list is not allowed, and all
* instructions must be from the same Sleigh language.
*
* <p>
* If there are gaps with fall-through a special {@link ExitPcodeOp} is inserted that, if
* executed blindly, would result in an infinite loop. The intent here is to cue the executor to
* abandon this program and re-visit the decoder for further ops.
*
* <p>
* An entry address must be given, because the lowest address instruction is not necessarily the
* entry point, but must appear first in the list. If the given entry is not the lowest address,
* this will insert a {@link PcodeOp#BRANCH} op at the start to ensure control is immediately
* given to the specified entry.
*
* @param instructions the instructions
* @param entry the address of the first instruction to execute
* @return the p-code program
*/
public static JitPassage makePassageFromInstructions(Iterable<Instruction> instructions,
AddrCtx entry) {
return Unfinished.TODO("Don't use this");
}
public static JitPassage makePassageFromPcode(PcodeProgram program, JitPcodeThread thread) {
if (program instanceof JitPassage passage) {
return passage;
}
return JitPassageDecoderTestAccess.simulateFromPcode(program, thread);
}
public static JitAnalysisContext makeContext(SleighLanguage language, String sleigh,
PcodeUseropLibrary<?> library) {
@SuppressWarnings("unchecked")
final PcodeUseropLibrary<byte[]> myLib = (PcodeUseropLibrary<byte[]>) library;
JitPcodeEmulator emu = new JitPcodeEmulator(language, new JitConfiguration(),
MethodHandles.publicLookup()) {
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return super.createUseropLibrary().compose(myLib);
}
};
JitPcodeThread thread = emu.newThread();
PcodeProgram program = emu.compileSleigh("test", sleigh);
return makeContext(program, thread);
}
public static JitAnalysisContext makeContext(PcodeProgram program) {
JitPcodeEmulator emu = new JitPcodeEmulator(program.getLanguage(), new JitConfiguration(),
MethodHandles.publicLookup());
JitPcodeThread thread = emu.newThread();
return makeContext(program, thread);
}
public static JitAnalysisContext makeContext(PcodeProgram program, JitPcodeThread thread) {
return new JitAnalysisContext(thread.getMachine().getConfiguration(),
makePassageFromPcode(program, thread));
}
public JitPassage decodePassage(JitPcodeThread thread) {
int maxOps = thread.getMachine().getConfiguration().maxPassageOps();
return thread.passageDecoder.decodePassage(thread.getCounter(), thread.getContext(),
maxOps);
}
public JitPassage decodePassage(AssemblyBuffer asm) {
JitPcodeEmulator emu = new JitPcodeEmulator(asm.getAssembler().getLanguage(),
new JitConfiguration(), MethodHandles.lookup());
byte[] bytes = asm.getBytes();
emu.getSharedState().setVar(asm.getEntry(), bytes.length, false, bytes);
JitPcodeThread thread = emu.newThread();
thread.overrideCounter(asm.getEntry());
return decodePassage(thread);
}
}

View File

@@ -0,0 +1,111 @@
/* ###
* 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.analysis;
import static org.junit.Assert.assertEquals;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
import org.junit.Test;
import generic.Unique;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.processors.sleigh.SleighLanguageHelper;
import ghidra.pcode.emu.jit.AbstractJitTest;
import ghidra.pcode.emu.jit.analysis.JitAllocationModel.MultiLocalVarHandler;
import ghidra.pcode.emu.jit.analysis.JitType.DoubleJitType;
import ghidra.pcode.emu.jit.var.JitVar;
import ghidra.pcode.emu.jit.var.JitVarnodeVar;
import ghidra.pcode.exec.*;
import junit.framework.AssertionFailedError;
public class JitAllocationModelTest extends AbstractJitTest {
public static <T> Stream<T> filterByType(Stream<?> in, Class<T> cls) {
return in.<T> mapMulti((e, d) -> {
if (cls.isInstance(e)) {
d.accept(cls.cast(e));
}
});
}
public static Stream<JitVarnodeVar> varnodeVars(JitDataFlowModel dfm) {
return filterByType(dfm.allValues().stream(), JitVarnodeVar.class);
}
@Test
public void testMultiPrecisionInt() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
temp:32 = zext(0x1234:2);
goto 0x1234;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
JitVarScopeModel vsm = new JitVarScopeModel(cfm, dfm);
JitTypeModel tm = new JitTypeModel(dfm);
JitAllocationModel am = new JitAllocationModel(context, dfm, vsm, tm);
JitVarnodeVar tempVar = Unique.assertOne(varnodeVars(dfm)
.filter(v -> v.varnode().isUnique()));
if (!(am.getHandler(tempVar) instanceof MultiLocalVarHandler handler)) {
throw new AssertionFailedError();
}
/**
* TODO: Might like to assert more details, but this mp-int aspect of the JIT-based emulator
* is still a work in progress.
*/
assertEquals(8, handler.parts().size());
}
@Test
public void testVarnodeReuse() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
r0 = r1 + r2;
r0 = r0 f+ r2;
r0 = r0 f+ r2;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
JitVarScopeModel vsm = new JitVarScopeModel(cfm, dfm);
JitTypeModel tm = new JitTypeModel(dfm);
JitAllocationModel am = new JitAllocationModel(context, dfm, vsm, tm);
List<JitVarnodeVar> r0Vars = varnodeVars(dfm)
.filter(v -> v.varnode().toString(language).equals("r0"))
.sorted(Comparator.comparing(JitVar::id))
.toList();
/**
* NOTE: Variables are coalesced by varnode, so all of these will receive the same handler,
* and so will all have the same type. There being two float ops will cause the type and
* allocation models to choose F8 for that handler.
*/
assertEquals(List.of(DoubleJitType.F8, DoubleJitType.F8, DoubleJitType.F8),
r0Vars.stream().map(v -> am.getHandler(v).type()).toList());
}
}

View File

@@ -0,0 +1,482 @@
/* ###
* 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.analysis;
import static org.junit.Assert.*;
import java.util.*;
import java.util.stream.Collectors;
import org.junit.Test;
import ghidra.app.plugin.assembler.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.processors.sleigh.SleighLanguageHelper;
import ghidra.pcode.emu.jit.AbstractJitTest;
import ghidra.pcode.emu.jit.JitPassage;
import ghidra.pcode.emu.jit.JitPassage.*;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.*;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.LanguageID;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.util.DefaultLanguageService;
import junit.framework.AssertionFailedError;
public class JitControlFlowModelTest extends AbstractJitTest {
public static PcodeOp assertCopyConst(long imm, PcodeOp op) {
assertOp(PcodeOp.COPY, op);
Varnode input = op.getInput(0);
assertTrue(input.isConstant());
assertEquals(imm, input.getOffset());
return op;
}
record ExpectedBlock(List<PcodeOp> ops) {
public static ExpectedBlock sub(PcodeProgram program, PcodeOp start, PcodeOp endIncl) {
int startIdx = program.getCode().indexOf(start);
int endIdxIncl = program.getCode().indexOf(endIncl);
return new ExpectedBlock(
List.copyOf(program.getCode().subList(startIdx, endIdxIncl + 1)));
}
}
enum BrType {
INT, ERR, EXT, IND
}
enum BrFlow {
BR, FT
}
record ExpectedBranch(PcodeOp from, PcodeOp to, BrType type, BrFlow flow, long addr) {}
record From(PcodeOp op, BrFlow flow) {
From {
assertNotNull(op);
}
}
public static class ExpectationsAsserter {
private final List<ExpectedBlock> eBlocks;
private final Set<ExpectedBranch> eBranches;
private final JitControlFlowModel cfm;
private final Map<PcodeOp, PcodeOp> opMap = new HashMap<>(); // because of rewrites
private final Map<PcodeOp, JitBlock> aOpToBlock = new HashMap<>();
private final Map<JitBlock, Set<ExpectedBranch>> eBranchesFrom = new HashMap<>();
private final Map<JitBlock, Set<ExpectedBranch>> eBranchesTo = new HashMap<>();
private final Map<JitBlock, Set<ExpectedBranch>> eBranchesOut = new HashMap<>();
public ExpectationsAsserter(List<ExpectedBlock> eBlocks, Set<ExpectedBranch> eBranches,
JitControlFlowModel cfm) {
this.eBlocks = eBlocks;
this.eBranches = eBranches;
this.cfm = cfm;
}
public void assertOpEquivalence(PcodeOp eOp, PcodeOp aOp) {
assertEquals("expected: %s but was: %s".formatted(
PcodeOp.getMnemonic(eOp.getOpcode()),
PcodeOp.getMnemonic(aOp.getOpcode())),
eOp.getOpcode(), aOp.getOpcode());
assertEquals(eOp.getOutput(), aOp.getOutput());
assertEquals(eOp.getNumInputs(), aOp.getNumInputs());
for (int i = 0; i < eOp.getNumInputs(); i++) {
assertEquals(eOp.getInput(i), aOp.getInput(i));
}
opMap.put(eOp, aOp);
}
public void assertBlockEquivalence(ExpectedBlock eBlock, JitBlock aBlock) {
assertEquals(eBlock.ops.size(), aBlock.getCode().size());
for (int i = 0; i < eBlock.ops.size(); i++) {
PcodeOp aOp = aBlock.getCode().get(i);
assertOpEquivalence(eBlock.ops.get(i), aOp);
aOpToBlock.put(aOp, aBlock);
}
}
public void checkAndSortBranches() {
for (ExpectedBranch eBranch : eBranches) {
PcodeOp aFromOp = opMap.get(eBranch.from);
JitBlock aFromBlock = aOpToBlock.get(aFromOp);
assertEquals(aFromOp, aFromBlock.getCode().getLast());
if (eBranch.to != null) {
PcodeOp aToOp = opMap.get(eBranch.to);
JitBlock aToBlock = aOpToBlock.get(aToOp);
assertEquals(aToOp, aToBlock.getCode().getFirst());
eBranchesFrom.computeIfAbsent(aFromBlock, fb -> new HashSet<>()).add(eBranch);
eBranchesTo.computeIfAbsent(aToBlock, tb -> new HashSet<>()).add(eBranch);
}
else {
eBranchesOut.computeIfAbsent(aFromBlock, fb -> new HashSet<>()).add(eBranch);
}
}
}
public void assertBranchEquivalence(ExpectedBranch eBranch, Branch aBranch) {
assertEquals(opMap.get(eBranch.from), aBranch.from());
assertEquals("expected: %s but was %s".formatted(eBranch, aBranch),
eBranch.flow == BrFlow.FT, aBranch.isFall());
switch (eBranch.type) {
case INT -> {
if (!(aBranch instanceof IntBranch ib)) {
throw new AssertionFailedError();
}
assertEquals(opMap.get(eBranch.to), ib.to());
}
case ERR -> {
if (!(aBranch instanceof ErrBranch)) {
throw new AssertionFailedError();
}
}
case EXT -> {
if (!(aBranch instanceof ExtBranch ext)) {
throw new AssertionFailedError();
}
assertEquals(eBranch.addr, ext.to().address.getOffset());
}
case IND -> {
if (!(aBranch instanceof IndBranch)) {
throw new AssertionFailedError();
}
}
default -> throw new AssertionFailedError();
}
}
public void assertBranchesEquivalent(Set<ExpectedBranch> eBranches,
Set<? extends Branch> aBranches) {
assertEquals("expected:" + eBranches + " but was " + aBranches, eBranches.size(),
aBranches.size());
Map<From, ? extends Branch> aBranchMap = aBranches.stream()
.collect(Collectors.toMap(
b -> new From(b.from(), b.isFall() ? BrFlow.FT : BrFlow.BR), b -> b));
for (ExpectedBranch eBranch : eBranches) {
Branch aBranch = aBranchMap.get(new From(opMap.get(eBranch.from), eBranch.flow));
assertNotNull("Did not see expected branch " + eBranch + " in " + aBranches,
aBranch);
assertBranchEquivalence(eBranch, aBranch);
}
}
public void assertBlockBranchEquivalence(ExpectedBlock eBlock, JitBlock aBlock) {
assertBranchesEquivalent(eBranchesFrom.getOrDefault(aBlock, Set.of()),
Set.copyOf(aBlock.branchesFrom()));
assertBranchesEquivalent(eBranchesTo.getOrDefault(aBlock, Set.of()),
Set.copyOf(aBlock.branchesTo()));
assertBranchesEquivalent(eBranchesOut.getOrDefault(aBlock, Set.of()),
Set.copyOf(aBlock.branchesOut()));
}
public void assertFlowEquivalence(ExpectedBranch eBranch, BlockFlow aFlow) {
assertBranchEquivalence(eBranch, aFlow.branch());
assertEquals(aOpToBlock.get(aFlow.branch().to()), aFlow.to());
}
public void assertFlowsEquivalent(Set<ExpectedBranch> eBranches, Set<BlockFlow> aFlows) {
assertEquals(eBranches.size(), aFlows.size());
Map<From, BlockFlow> aFlowMap = aFlows.stream()
.collect(Collectors.toMap(b -> new From(b.branch().from(),
b.branch().isFall() ? BrFlow.FT : BrFlow.BR), b -> b));
for (ExpectedBranch eBranch : eBranches) {
BlockFlow aFlow = aFlowMap.get(new From(opMap.get(eBranch.from), eBranch.flow));
assertNotNull("Did not see expected flow " + eBranch + " in " + aFlows, aFlow);
assertFlowEquivalence(eBranch, aFlow);
}
}
public void assertBlockFlowEquivalence(ExpectedBlock eBlock, JitBlock aBlock) {
assertFlowsEquivalent(eBranchesFrom.getOrDefault(aBlock, Set.of()),
Set.copyOf(aBlock.flowsFrom().values()));
assertFlowsEquivalent(eBranchesTo.getOrDefault(aBlock, Set.of()),
Set.copyOf(aBlock.flowsTo().values()));
}
public void assertEquivalence() {
List<JitBlock> aBlocks = List.copyOf(cfm.getBlocks());
assertEquals(eBlocks.size(), aBlocks.size());
for (int i = 0; i < eBlocks.size(); i++) {
assertBlockEquivalence(eBlocks.get(i), aBlocks.get(i));
}
checkAndSortBranches();
for (int i = 0; i < eBlocks.size(); i++) {
assertBlockBranchEquivalence(eBlocks.get(i), aBlocks.get(i));
assertBlockFlowEquivalence(eBlocks.get(i), aBlocks.get(i));
}
}
}
public static void assertCfmExpectations(List<ExpectedBlock> eBlocks,
Set<ExpectedBranch> eBranches, JitControlFlowModel cfm) {
new ExpectationsAsserter(eBlocks, eBranches, cfm).assertEquivalence();
}
@Test(expected = UnterminatedFlowException.class)
public void testSingleBlockNoBranching() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
r0 = r1;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
new JitControlFlowModel(context);
}
@Test
public void testSingleBlockTerminatingBranch() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
goto 0x1234;
""", PcodeUseropLibrary.NIL);
PcodeOp opBranch = assertOp(PcodeOp.BRANCH, program.getCode().get(0));
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
assertCfmExpectations(
List.of(
new ExpectedBlock(List.of(opBranch))),
Set.of(
new ExpectedBranch(opBranch, null, BrType.EXT, BrFlow.BR, 0x1234)),
cfm);
}
@Test
public void testSingleBlockTerminatingConditionalBranch() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
if (r0) goto 0x5678;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
PcodeOp opCBranch = assertOp(PcodeOp.CBRANCH, program.getCode().get(0));
PcodeOp opBranch = assertOp(PcodeOp.BRANCH, program.getCode().get(1));
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
assertCfmExpectations(
List.of(
new ExpectedBlock(List.of(opCBranch)),
new ExpectedBlock(List.of(opBranch))),
Set.of(
new ExpectedBranch(opCBranch, null, BrType.EXT, BrFlow.BR, 0x5678),
new ExpectedBranch(opCBranch, opBranch, BrType.INT, BrFlow.FT, 0),
new ExpectedBranch(opBranch, null, BrType.EXT, BrFlow.BR, 0x1234)),
cfm);
}
@Test
public void testSingleBlockLoop() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
<L1>
goto <L1>;
""", PcodeUseropLibrary.NIL);
PcodeOp opBranch = assertOp(PcodeOp.BRANCH, program.getCode().get(0));
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
assertCfmExpectations(
List.of(
new ExpectedBlock(List.of(opBranch))),
Set.of(
new ExpectedBranch(opBranch, opBranch, BrType.INT, BrFlow.BR, 0)),
cfm);
}
@Test
public void testSingleBlockConditionalLoop() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
<L1>
if (r0) goto <L1>;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
PcodeOp opCBranch = assertOp(PcodeOp.CBRANCH, program.getCode().get(0));
PcodeOp opBranch = assertOp(PcodeOp.BRANCH, program.getCode().get(1));
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
assertCfmExpectations(
List.of(
new ExpectedBlock(List.of(opCBranch)),
new ExpectedBlock(List.of(opBranch))),
Set.of(
new ExpectedBranch(opCBranch, opCBranch, BrType.INT, BrFlow.BR, 0),
new ExpectedBranch(opCBranch, opBranch, BrType.INT, BrFlow.FT, 0),
new ExpectedBranch(opBranch, null, BrType.EXT, BrFlow.BR, 0x1234)),
cfm);
}
@Test
public void testDegenerateIf() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
if (r0) goto <L1>;
<L1>
goto 0x1234;
""", PcodeUseropLibrary.NIL);
PcodeOp opCBranch = assertOp(PcodeOp.CBRANCH, program.getCode().get(0));
PcodeOp opBranch = assertOp(PcodeOp.BRANCH, program.getCode().get(1));
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
assertCfmExpectations(
List.of(
new ExpectedBlock(List.of(opCBranch)),
new ExpectedBlock(List.of(opBranch))),
Set.of(
new ExpectedBranch(opCBranch, opBranch, BrType.INT, BrFlow.BR, 0),
new ExpectedBranch(opCBranch, opBranch, BrType.INT, BrFlow.FT, 0),
new ExpectedBranch(opBranch, null, BrType.EXT, BrFlow.BR, 0x1234)),
cfm);
}
@Test
public void testIfWithManyOps() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
if (r0 == r1 + 0x12) goto <L1>;
r1 = 1;
r2 = 2;
<L1>
r3 = 3;
r4 = 4;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
PcodeOp opFirst = program.getCode().get(0);
PcodeOp opCBranch = assertOp(PcodeOp.CBRANCH, program.getCode().get(2));
PcodeOp opAfterCBranch = assertCopyConst(1, program.getCode().get(3));
PcodeOp opBeforeL1 = assertCopyConst(2, program.getCode().get(4));
PcodeOp opAtL1 = assertCopyConst(3, program.getCode().get(5));
PcodeOp opBranch = assertOp(PcodeOp.BRANCH, program.getCode().get(7));
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
assertCfmExpectations(
List.of(
ExpectedBlock.sub(program, opFirst, opCBranch),
ExpectedBlock.sub(program, opAfterCBranch, opBeforeL1),
ExpectedBlock.sub(program, opAtL1, opBranch)),
Set.of(
new ExpectedBranch(opCBranch, opAfterCBranch, BrType.INT, BrFlow.FT, 0),
new ExpectedBranch(opCBranch, opAtL1, BrType.INT, BrFlow.BR, 0),
new ExpectedBranch(opBeforeL1, opAtL1, BrType.INT, BrFlow.FT, 0),
new ExpectedBranch(opBranch, null, BrType.EXT, BrFlow.BR, 0x1234)),
cfm);
}
@Test
public void testTwoInstructionsNoBranching() throws Exception {
SleighLanguage language = (SleighLanguage) DefaultLanguageService.getLanguageService()
.getLanguage(new LanguageID("Toy:BE:64:default"));
Address addr0 = language.getDefaultSpace().getAddress(0);
Assembler asm = Assemblers.getAssembler(language);
AssemblyBuffer buf = new AssemblyBuffer(asm, addr0);
buf.assemble("imm r0, #0x123");
buf.assemble("add r0, r0");
JitPassage passage = decodePassage(buf);
PcodeOp opLast = assertOp(PcodeOp.UNIMPLEMENTED, passage.getCode().getLast());
JitAnalysisContext context = makeContext(passage);
JitControlFlowModel cfm = new JitControlFlowModel(context);
assertCfmExpectations(
List.of(
new ExpectedBlock(passage.getCode())),
Set.of(
new ExpectedBranch(opLast, null, BrType.ERR, BrFlow.BR, 0x4)),
cfm);
}
@Test
public void testInstructionsConditionalBranch() throws Exception {
SleighLanguage language = (SleighLanguage) DefaultLanguageService.getLanguageService()
.getLanguage(new LanguageID("Toy:BE:64:default"));
Address addr0 = language.getDefaultSpace().getAddress(0);
Assembler asm = Assemblers.getAssembler(language);
AssemblyBuffer buf = new AssemblyBuffer(asm, addr0);
Address patchAt = buf.getNext();
buf.assemble("breq 0"); // Uses CBRANCH to skip BRANCH
buf.assemble("imm r0, #123");
Address breqTo = buf.getNext();
buf.assemble("add r0, r0");
buf.assemble(patchAt, "breq 0x%x".formatted(breqTo.getOffset()));
JitPassage passage = decodePassage(buf);
// For sanity, and so I can reason out the results
assertEquals("""
<JitPassage:
0,00000000.0: $U800:1 = BOOL_NEGATE Z
1,00000000.1: CBRANCH *[ram]0x2:8, $U800:1
2,00000000.2: BRANCH *[ram]0x4:8
3,00000002.0: C = COPY 0:1
4,00000002.1: V = COPY 0:1
5,00000002.2: r0 = COPY 0x7b:8
6,00000002.3: N = INT_SLESS r0, 0:8
7,00000002.4: Z = INT_EQUAL r0, 0:8
8,00000004.0: C = INT_CARRY r0, r0
9,00000004.1: V = INT_SCARRY r0, r0
10,00000004.2: r0 = INT_ADD r0, r0
11,00000004.3: N = INT_SLESS r0, 0:8
12,00000004.4: Z = INT_EQUAL r0, 0:8
13,00000006.0: UNIMPLEMENTED
>""", passage.format(true));
PcodeOp opFirst = passage.getCode().getFirst();
PcodeOp opCBranch = assertOp(PcodeOp.CBRANCH, passage.getCode().get(1));
PcodeOp opBranch = assertOp(PcodeOp.BRANCH, passage.getCode().get(2));
PcodeOp opStartImm = assertCopyConst(0, passage.getCode().get(3));
PcodeOp opEndImm = assertOp(PcodeOp.INT_EQUAL, passage.getCode().get(7));
PcodeOp opStartAdd = assertOp(PcodeOp.INT_CARRY, passage.getCode().get(8));
PcodeOp opLast = assertOp(PcodeOp.UNIMPLEMENTED, passage.getCode().getLast());
JitAnalysisContext context = makeContext(passage);
JitControlFlowModel cfm = new JitControlFlowModel(context);
assertCfmExpectations(
List.of(
ExpectedBlock.sub(passage, opFirst, opCBranch),
ExpectedBlock.sub(passage, opBranch, opBranch),
ExpectedBlock.sub(passage, opStartImm, opEndImm),
ExpectedBlock.sub(passage, opStartAdd, opLast)),
Set.of(
new ExpectedBranch(opCBranch, opStartImm, BrType.INT, BrFlow.BR, 0),
new ExpectedBranch(opCBranch, opBranch, BrType.INT, BrFlow.FT, 0),
new ExpectedBranch(opBranch, opStartAdd, BrType.INT, BrFlow.BR, 0),
new ExpectedBranch(opEndImm, opStartAdd, BrType.INT, BrFlow.FT, 0),
new ExpectedBranch(opLast, null, BrType.ERR, BrFlow.BR, 0)),
cfm);
}
}

View File

@@ -0,0 +1,660 @@
/* ###
* 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.analysis;
import static org.junit.Assert.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.junit.Test;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.processors.sleigh.SleighLanguageHelper;
import ghidra.pcode.emu.jit.AbstractJitTest;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.BlockFlow;
import ghidra.pcode.emu.jit.op.*;
import ghidra.pcode.emu.jit.var.*;
import ghidra.pcode.emu.jit.var.JitVal.ValUse;
import ghidra.pcode.exec.*;
import ghidra.program.model.pcode.PcodeOp;
import junit.framework.AssertionFailedError;
public class JitDataFlowModelTest extends AbstractJitTest {
private Predicate<JitOp> opType(Class<? extends JitOp> cls) {
return cls::isInstance;
}
private Function<JitCallOtherOpIf, JitVal> otherArg(int i) {
return op -> op.args().get(i);
}
record ExpectedOp<T extends JitOp>(int e, Class<T> cls) {
private static int nextE = 0;
public ExpectedOp(Class<T> cls) {
this(nextE++, cls);
}
}
record ExpectedVal<T extends JitVal>(int e, Class<T> cls, String str, int size) {
private static int nextE = 0;
public ExpectedVal(Class<T> cls, String str, int size) {
this(nextE++, cls, str, size);
}
}
enum Dir {
IN, OUT
}
record ExpectedEdge<OT extends JitOp, VT extends JitVal>(Dir dir, ExpectedOp<OT> op,
Function<? super OT, JitVal> valGetter, Function<VT, JitOp> opGetter,
ExpectedVal<VT> v) {
public static <OT extends JitOp, VT extends JitVal> ExpectedEdge<OT, VT> in(
ExpectedOp<OT> op, Function<? super OT, JitVal> valGetter,
Predicate<JitOp> opPredicate, ExpectedVal<VT> v) {
return new ExpectedEdge<>(Dir.IN, op, valGetter, val -> {
for (ValUse use : val.uses()) {
if (opPredicate.test(use.op())) {
return use.op();
}
}
throw new NoSuchElementException();
}, v);
}
public static <VT extends JitOutVar, OT extends JitDefOp> ExpectedEdge<OT, VT> out(
ExpectedVal<VT> v, ExpectedOp<OT> op) {
return new ExpectedEdge<>(Dir.OUT, op, JitDefOp::out, JitOutVar::definition, v);
}
public static <VT extends JitVal> ExpectedEdge<JitPhiOp, VT> phi(ExpectedOp<JitPhiOp> op,
Predicate<BlockFlow> flowPredicate, ExpectedVal<VT> v) {
return in(op, phi -> {
for (Entry<BlockFlow, JitVal> ent : phi.options().entrySet()) {
if (flowPredicate.test(ent.getKey())) {
return ent.getValue();
}
}
throw new NoSuchElementException();
}, jitOp -> jitOp instanceof JitPhiOp, v);
}
public VT getActualVal(JitOp jitOp) {
try {
return Objects.requireNonNull(v.cls.cast(valGetter.apply(op.cls.cast(jitOp))));
}
catch (Exception e) {
throw new AssertionError(
"Could not get actual value for " + this + " where op=" + jitOp, e);
}
}
public OT getActualOp(JitVal jitVal) {
try {
return Objects.requireNonNull(op.cls.cast(opGetter.apply(v.cls.cast(jitVal))));
}
catch (Exception e) {
throw new AssertionError(
"Could not get actual op for " + this + " where value=" + jitVal, e);
}
}
}
record OpMatch(ExpectedOp<?> eOp, JitOp aOp) {}
record ValMatch(ExpectedVal<?> eVal, JitVal aVal) {}
public static class ExpectationsAsserter {
private final List<ExpectedOp<?>> eOps;
private final Set<ExpectedVal<?>> eVals;
private final Set<ExpectedEdge<?, ?>> eEdges;
private final JitDataFlowModel dfm;
private final SleighLanguage language;
private final Deque<OpMatch> opQueue = new LinkedList<>();
private final Deque<ValMatch> valQueue = new LinkedList<>();
private final BidiMap<JitOp, ExpectedOp<?>> opsMap = new DualHashBidiMap<>();
private final BidiMap<JitVal, ExpectedVal<?>> valsMap = new DualHashBidiMap<>();
private final Set<ExpectedEdge<?, ?>> edgesVisitedO2V = new HashSet<>();
private final Set<ExpectedEdge<?, ?>> edgesVisitedV2O = new HashSet<>();
private final Map<ExpectedOp<?>, Set<ExpectedEdge<?, ?>>> edgesO2V = new HashMap<>();
private final Map<ExpectedVal<?>, Set<ExpectedEdge<?, ?>>> edgesV2O = new HashMap<>();
public ExpectationsAsserter(List<ExpectedOp<?>> eOps, Set<ExpectedVal<?>> eVals,
Set<ExpectedEdge<?, ?>> eEdges, JitAnalysisContext context, JitDataFlowModel dfm) {
this.eOps = eOps;
this.eEdges = eEdges;
this.eVals = eVals;
this.dfm = dfm;
this.language = context.getLanguage();
// Queue only those matching the listing
// synthetic ones should be at end of list, but must be included
List<PcodeOp> ops = context.getPassage().getCode();
for (int i = 0; i < ops.size(); i++) {
JitOp aOp = dfm.getJitOp(ops.get(i));
ExpectedOp<?> eOp = eOps.get(i);
opQueue.add(new OpMatch(eOp, aOp));
}
for (ExpectedEdge<?, ?> ee : eEdges) {
edgesO2V.computeIfAbsent(ee.op, e -> new HashSet<>()).add(ee);
edgesV2O.computeIfAbsent(ee.v, e -> new HashSet<>()).add(ee);
}
}
public void assertOpMatch(OpMatch match) {
ExpectedOp<?> ePrior = opsMap.get(match.aOp);
JitOp aPrior = opsMap.getKey(match.eOp);
if (ePrior != null || aPrior != null) {
assertSame(ePrior, match.eOp);
assertSame(aPrior, match.aOp);
return;
}
assertTrue("Expected op of type %s but got %s".formatted(match.eOp.cls, match.aOp),
match.eOp.cls.isInstance(match.aOp));
opsMap.put(match.aOp, match.eOp);
Set<JitVal> aAllIn = Set.copyOf(match.aOp.inputs());
Set<JitVal> aAllOut =
match.aOp instanceof JitDefOp defOp ? Set.of(defOp.out()) : Set.of();
Set<JitVal> eAllIn = new HashSet<>();
Set<JitVal> eAllOut = new HashSet<>();
for (ExpectedEdge<?, ?> ee : edgesO2V.getOrDefault(match.eOp, Set.of())) {
edgesVisitedO2V.add(ee);
JitVal aVal = ee.getActualVal(match.aOp);
valQueue.add(new ValMatch(ee.v, aVal));
(switch (ee.dir) {
case IN -> eAllIn;
case OUT -> eAllOut;
}).add(aVal);
}
assertEquals("Values input to %s do not match".formatted(match.aOp), eAllIn, aAllIn);
assertEquals("Value output from %s does not match".formatted(match.aOp),
eAllOut, aAllOut);
}
private String varnodeToString(JitVarnodeVar vv) {
if (vv.varnode().isUnique()) {
return "$U";
}
return vv.varnode().toString(language);
}
public void assertValMatch(ValMatch match) {
ExpectedVal<?> ePrior = valsMap.get(match.aVal);
JitVal aPrior = valsMap.getKey(match.eVal);
if (ePrior != null || aPrior != null) {
assertSame(ePrior, match.eVal);
assertSame(aPrior, match.aVal);
return;
}
assertTrue(match.eVal.cls.isInstance(match.aVal));
valsMap.put(match.aVal, match.eVal);
assertEquals(match.eVal.size, match.aVal.size());
switch (match.aVal) {
case JitConstVal cv -> assertEquals(match.eVal.str, cv.value().toString(16));
case JitVarnodeVar vv -> assertEquals(match.eVal.str, varnodeToString(vv));
default -> throw new AssertionFailedError("Unrecognized val type: " + match.aVal);
}
Set<JitOp> aAllUses =
match.aVal.uses().stream().map(ValUse::op).collect(Collectors.toSet());
Set<JitOp> aAllDefs =
match.aVal instanceof JitOutVar out ? Set.of(out.definition()) : Set.of();
Set<JitOp> eAllUses = new HashSet<>();
Set<JitOp> eAllDefs = new HashSet<>();
for (ExpectedEdge<?, ?> ee : edgesV2O.getOrDefault(match.eVal, Set.of())) {
edgesVisitedV2O.add(ee);
JitOp aOp = ee.getActualOp(match.aVal);
opQueue.add(new OpMatch(ee.op, aOp));
(switch (ee.dir) {
case IN -> eAllUses;
case OUT -> eAllDefs;
}).add(aOp);
}
assertEquals("Ops using %s do not match".formatted(match.aVal), eAllUses, aAllUses);
assertEquals("Op defining %s does not match".formatted(match.aVal), eAllDefs, aAllDefs);
}
public void processQueues() {
while (!opQueue.isEmpty() || !valQueue.isEmpty()) {
while (!opQueue.isEmpty()) {
assertOpMatch(opQueue.poll());
}
while (!valQueue.isEmpty()) {
assertValMatch(valQueue.poll());
}
}
}
public void assertEquivalence() {
processQueues();
assertEquals(opsMap.keySet(), dfm.allOps());
assertEquals(valsMap.keySet(), dfm.allValues());
assertEquals("Not all expected ops were found.", Set.copyOf(eOps),
Set.copyOf(opsMap.values()));
assertEquals("Not all expected values were found.", eVals,
Set.copyOf(valsMap.values()));
assertEquals(eEdges, edgesVisitedO2V);
assertEquals(eEdges, edgesVisitedV2O);
}
}
public static void assertDfmExpectations(List<ExpectedOp<?>> eOps, Set<ExpectedVal<?>> eVals,
Set<ExpectedEdge<?, ?>> eEdges, JitAnalysisContext context, JitDataFlowModel dfm) {
new ExpectationsAsserter(eOps, eVals, eEdges, context, dfm).assertEquivalence();
}
@Test
public void testOnlyConstant() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
r0 = 0x5678;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
ExpectedOp<JitCopyOp> eCopy = new ExpectedOp<>(JitCopyOp.class);
ExpectedOp<JitBranchOp> eBranch = new ExpectedOp<>(JitBranchOp.class);
ExpectedVal<JitConstVal> e5678 = new ExpectedVal<>(JitConstVal.class, "5678", 8);
ExpectedVal<JitOutVar> eR0 = new ExpectedVal<>(JitOutVar.class, "r0", 8);
assertDfmExpectations(
List.of(
eCopy,
eBranch),
Set.of(e5678, eR0),
Set.of(
ExpectedEdge.in(eCopy, JitCopyOp::u, op -> true, e5678),
ExpectedEdge.out(eR0, eCopy)),
context, dfm);
}
@Test
public void testInput() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
r0 = r1;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
ExpectedOp<JitCopyOp> eCopy = new ExpectedOp<>(JitCopyOp.class);
ExpectedOp<JitBranchOp> eBranch = new ExpectedOp<>(JitBranchOp.class);
ExpectedOp<JitPhiOp> ePhi = new ExpectedOp<>(JitPhiOp.class);
ExpectedVal<JitOutVar> eR0 = new ExpectedVal<>(JitOutVar.class, "r0", 8);
ExpectedVal<JitOutVar> eR1 = new ExpectedVal<>(JitOutVar.class, "r1", 8);
ExpectedVal<JitInputVar> eR1In = new ExpectedVal<>(JitInputVar.class, "r1", 8);
assertDfmExpectations(
List.of(
eCopy,
eBranch,
ePhi),
Set.of(eR1In, eR1, eR0),
Set.of(
ExpectedEdge.phi(ePhi, flow -> true, eR1In),
ExpectedEdge.out(eR1, ePhi),
ExpectedEdge.in(eCopy, JitCopyOp::u, op -> true, eR1),
ExpectedEdge.out(eR0, eCopy)),
context, dfm);
}
@Test
public void testThroughReg() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
r1 = 0x5678;
r0 = r1;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
// Just checking that the r1 used is the same as the r1 defined above
ExpectedOp<JitCopyOp> eCopy1 = new ExpectedOp<>(JitCopyOp.class);
ExpectedOp<JitCopyOp> eCopy2 = new ExpectedOp<>(JitCopyOp.class);
ExpectedOp<JitBranchOp> eBranch = new ExpectedOp<>(JitBranchOp.class);
ExpectedVal<JitConstVal> e5678 = new ExpectedVal<>(JitConstVal.class, "5678", 8);
ExpectedVal<JitOutVar> eR1 = new ExpectedVal<>(JitOutVar.class, "r1", 8);
ExpectedVal<JitOutVar> eR0 = new ExpectedVal<>(JitOutVar.class, "r0", 8);
assertDfmExpectations(
List.of(eCopy1,
eCopy2,
eBranch),
Set.of(e5678, eR1, eR0),
Set.of(
ExpectedEdge.in(eCopy1, JitCopyOp::u, op -> true, e5678),
ExpectedEdge.out(eR1, eCopy1),
ExpectedEdge.in(eCopy2, JitCopyOp::u, op -> true, eR1),
ExpectedEdge.out(eR0, eCopy2)),
context, dfm);
}
@Test
public void testThroughUnique() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
temp:8 = 0x5678;
r0 = temp;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
ExpectedOp<JitCopyOp> eCopy1 = new ExpectedOp<>(JitCopyOp.class);
ExpectedOp<JitCopyOp> eCopy2 = new ExpectedOp<>(JitCopyOp.class);
ExpectedOp<JitBranchOp> eBranch = new ExpectedOp<>(JitBranchOp.class);
ExpectedVal<JitConstVal> e5678 = new ExpectedVal<>(JitConstVal.class, "5678", 8);
ExpectedVal<JitOutVar> eTemp = new ExpectedVal<>(JitOutVar.class, "$U", 8);
ExpectedVal<JitOutVar> eR0 = new ExpectedVal<>(JitOutVar.class, "r0", 8);
assertDfmExpectations(
List.of(eCopy1,
eCopy2,
eBranch),
Set.of(e5678, eTemp, eR0),
Set.of(
ExpectedEdge.in(eCopy1, JitCopyOp::u, op -> true, e5678),
ExpectedEdge.out(eTemp, eCopy1),
ExpectedEdge.in(eCopy2, JitCopyOp::u, op -> true, eTemp),
ExpectedEdge.out(eR0, eCopy2)),
context, dfm);
}
@Test
public void testInputLoop() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
<loop>
r0 = r1;
goto <loop>;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
ExpectedOp<JitCopyOp> eCopy = new ExpectedOp<>(JitCopyOp.class);
ExpectedOp<JitBranchOp> eBranch = new ExpectedOp<>(JitBranchOp.class);
ExpectedOp<JitPhiOp> ePhi = new ExpectedOp<>(JitPhiOp.class);
ExpectedVal<JitOutVar> eR0 = new ExpectedVal<>(JitOutVar.class, "r0", 8);
ExpectedVal<JitOutVar> eR1 = new ExpectedVal<>(JitOutVar.class, "r1", 8);
ExpectedVal<JitInputVar> eR1In = new ExpectedVal<>(JitInputVar.class, "r1", 8);
assertDfmExpectations(
List.of(
eCopy,
eBranch,
ePhi),
Set.of(eR1In, eR1, eR0),
Set.of(
ExpectedEdge.phi(ePhi, flow -> flow.from() == null, eR1In),
ExpectedEdge.phi(ePhi, flow -> flow.from() != null, eR1),
ExpectedEdge.out(eR1, ePhi),
ExpectedEdge.in(eCopy, JitCopyOp::u, opType(JitCopyOp.class), eR1),
ExpectedEdge.out(eR0, eCopy)),
context, dfm);
}
@Test
public void testOnlyConstLoop() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
r1 = 0x5678;
<loop>
r0 = r1;
goto <loop>;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
ExpectedOp<JitCopyOp> eCopy1 = new ExpectedOp<>(JitCopyOp.class);
ExpectedOp<JitCopyOp> eCopy2 = new ExpectedOp<>(JitCopyOp.class);
ExpectedOp<JitBranchOp> eBranch = new ExpectedOp<>(JitBranchOp.class);
ExpectedOp<JitPhiOp> ePhi = new ExpectedOp<>(JitPhiOp.class);
ExpectedVal<JitConstVal> e5678 = new ExpectedVal<>(JitConstVal.class, "5678", 8);
ExpectedVal<JitOutVar> eR1_1 = new ExpectedVal<>(JitOutVar.class, "r1", 8);
ExpectedVal<JitOutVar> eR1_2 = new ExpectedVal<>(JitOutVar.class, "r1", 8);
ExpectedVal<JitOutVar> eR0 = new ExpectedVal<>(JitOutVar.class, "r0", 8);
assertDfmExpectations(
List.of(
eCopy1,
eCopy2,
eBranch,
ePhi),
Set.of(e5678, eR1_1, eR1_2, eR0),
Set.of(
ExpectedEdge.in(eCopy1, JitCopyOp::u, opType(JitCopyOp.class), e5678),
ExpectedEdge.out(eR1_1, eCopy1),
ExpectedEdge.phi(ePhi, flow -> flow.from().start().getTime() == 0, eR1_1),
ExpectedEdge.phi(ePhi, flow -> flow.from().start().getTime() == 1, eR1_2),
ExpectedEdge.out(eR1_2, ePhi),
ExpectedEdge.in(eCopy2, JitCopyOp::u, opType(JitCopyOp.class), eR1_2),
ExpectedEdge.out(eR0, eCopy2)),
context, dfm);
}
@Test
public void testForLoop() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
// Want to see r0 as input or as previous iteration's value
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
<loop>
r0 = r0 - 1;
if r0 > 0 goto <loop>;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
ExpectedOp<JitPhiOp> ePhi = new ExpectedOp<>(JitPhiOp.class);
ExpectedOp<JitIntSubOp> eIntSub = new ExpectedOp<>(JitIntSubOp.class);
ExpectedOp<JitIntLessOp> eIntLess = new ExpectedOp<>(JitIntLessOp.class);
ExpectedOp<JitCBranchOp> eCBranch = new ExpectedOp<>(JitCBranchOp.class);
ExpectedOp<JitBranchOp> eBranch = new ExpectedOp<>(JitBranchOp.class);
ExpectedVal<JitConstVal> e1 = new ExpectedVal<>(JitConstVal.class, "1", 8);
ExpectedVal<JitOutVar> eR0_1 = new ExpectedVal<>(JitOutVar.class, "r0", 8);
ExpectedVal<JitInputVar> eR0In = new ExpectedVal<>(JitInputVar.class, "r0", 8);
ExpectedVal<JitOutVar> eU = new ExpectedVal<>(JitOutVar.class, "$U", 1);
ExpectedVal<JitOutVar> eR0_2 = new ExpectedVal<>(JitOutVar.class, "r0", 8);
ExpectedVal<JitConstVal> e0 = new ExpectedVal<>(JitConstVal.class, "0", 8);
assertDfmExpectations(
List.of(
eIntSub,
eIntLess,
eCBranch,
eBranch,
ePhi),
Set.of(e1, eR0_1, eR0In, eU, eR0_2, e0),
Set.of(
ExpectedEdge.phi(ePhi, flow -> flow.from() == null, eR0In),
ExpectedEdge.phi(ePhi, flow -> flow.from() != null, eR0_2),
ExpectedEdge.out(eR0_1, ePhi),
ExpectedEdge.in(eIntSub, JitIntSubOp::l, opType(JitIntSubOp.class), eR0_1),
ExpectedEdge.in(eIntSub, JitIntSubOp::r, opType(JitIntSubOp.class), e1),
ExpectedEdge.out(eR0_2, eIntSub),
ExpectedEdge.in(eIntLess, JitIntLessOp::l, opType(JitIntLessOp.class), e0),
ExpectedEdge.in(eIntLess, JitIntLessOp::r, opType(JitIntLessOp.class), eR0_2),
ExpectedEdge.out(eU, eIntLess),
ExpectedEdge.in(eCBranch, JitCBranchOp::cond, opType(JitCBranchOp.class), eU)),
context, dfm);
}
public static class MyLibrary extends AnnotatedPcodeUseropLibrary<Object> {
@PcodeUserop(functional = true)
public void v_op0() {
}
@PcodeUserop(functional = true)
public void v_op1(long p1) {
}
@PcodeUserop(functional = true)
public long l_op0() {
return 0;
}
@PcodeUserop(functional = true)
public long l_op1(long p1) {
return p1;
}
}
public static final PcodeUseropLibrary<?> MY_LIB = new MyLibrary();
@Test
public void testCallOtherVoidOp0() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
JitAnalysisContext context = makeContext(language, """
v_op0();
goto 0x1234;
""", MY_LIB);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
ExpectedOp<JitCallOtherOp> eCallOther = new ExpectedOp<>(JitCallOtherOp.class);
ExpectedOp<JitBranchOp> eBranch = new ExpectedOp<>(JitBranchOp.class);
assertDfmExpectations(
List.of(
eCallOther,
eBranch),
Set.of(),
Set.of(),
context, dfm);
}
@Test
public void testCallOtherVoidOp1() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
JitAnalysisContext context = makeContext(language, """
v_op1(r0);
goto 0x1234;
""", MY_LIB);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
ExpectedOp<JitCallOtherOp> eCallOther = new ExpectedOp<>(JitCallOtherOp.class);
ExpectedOp<JitBranchOp> eBranch = new ExpectedOp<>(JitBranchOp.class);
ExpectedOp<JitPhiOp> ePhi = new ExpectedOp<>(JitPhiOp.class);
ExpectedVal<JitInputVar> eR0In = new ExpectedVal<>(JitInputVar.class, "r0", 8);
ExpectedVal<JitOutVar> eR0 = new ExpectedVal<>(JitOutVar.class, "r0", 8);
assertDfmExpectations(
List.of(
eCallOther,
eBranch,
ePhi),
Set.of(eR0In, eR0),
Set.of(
ExpectedEdge.phi(ePhi, flow -> true, eR0In),
ExpectedEdge.out(eR0, ePhi),
ExpectedEdge.in(eCallOther, otherArg(0), opType(JitCallOtherOp.class), eR0)),
context, dfm);
}
@Test
public void testCallOtherLongOp0() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
JitAnalysisContext context = makeContext(language, """
r1 = l_op0();
goto 0x1234;
""", MY_LIB);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
ExpectedOp<JitCallOtherDefOp> eCallOther = new ExpectedOp<>(JitCallOtherDefOp.class);
ExpectedOp<JitBranchOp> eBranch = new ExpectedOp<>(JitBranchOp.class);
ExpectedVal<JitOutVar> eR1 = new ExpectedVal<>(JitOutVar.class, "r1", 8);
assertDfmExpectations(
List.of(
eCallOther,
eBranch),
Set.of(eR1),
Set.of(
ExpectedEdge.out(eR1, eCallOther)),
context, dfm);
}
@Test
public void testCallOtherLongOp1() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
JitAnalysisContext context = makeContext(language, """
r1 = l_op1(r0);
goto 0x1234;
""", MY_LIB);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
ExpectedOp<JitCallOtherDefOp> eCallOther = new ExpectedOp<>(JitCallOtherDefOp.class);
ExpectedOp<JitBranchOp> eBranch = new ExpectedOp<>(JitBranchOp.class);
ExpectedOp<JitPhiOp> ePhi = new ExpectedOp<>(JitPhiOp.class);
ExpectedVal<JitInputVar> eR0In = new ExpectedVal<>(JitInputVar.class, "r0", 8);
ExpectedVal<JitOutVar> eR0 = new ExpectedVal<>(JitOutVar.class, "r0", 8);
ExpectedVal<JitOutVar> eR1 = new ExpectedVal<>(JitOutVar.class, "r1", 8);
assertDfmExpectations(
List.of(
eCallOther,
eBranch,
ePhi),
Set.of(eR0In, eR0, eR1),
Set.of(
ExpectedEdge.phi(ePhi, flow -> true, eR0In),
ExpectedEdge.out(eR0, ePhi),
ExpectedEdge.in(eCallOther, otherArg(0), opType(JitCallOtherDefOp.class), eR0),
ExpectedEdge.out(eR1, eCallOther)),
context, dfm);
}
/**
* NOTE: cat, subpiece, etc., do not currrently have much meaning except to indicate a
* dependence. At one point these "synthetic" ops were meant to be translated into JVM bytecode,
* but instead, variable accesses are coalesced during allocation/assignment, and then
* sub-accesses are encoded as such.
*/
}

View File

@@ -0,0 +1,83 @@
/* ###
* 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.analysis;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.processors.sleigh.SleighLanguageHelper;
import ghidra.pcode.emu.jit.AbstractJitTest;
import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
public class JitOpUseModelTest extends AbstractJitTest {
public static class MyLib extends AnnotatedPcodeUseropLibrary<byte[]> {
@PcodeUserop
public void pcodeop_one(@OpState PcodeExecutorState<byte[]> state, @OpOutput Varnode out,
Varnode in1) {
}
}
MyLib lib = new MyLib();
@Test
public void testImmediateOverwrite() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
JitAnalysisContext context = makeContext(language, """
r0 = r1;
r0 = r2;
goto 0x1234;
""", lib);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
JitVarScopeModel vsm = new JitVarScopeModel(cfm, dfm);
JitOpUseModel oum = new JitOpUseModel(context, cfm, dfm, vsm);
PcodeOp copyOp = assertOp(PcodeOp.COPY, context.getPassage().getCode().getFirst());
assertFalse(oum.isUsed(dfm.getJitOp(copyOp)));
}
/**
* Because the userop could technically access any varnode, then any live varnode at the time of
* the userop call must be considered used.
*
* @throws Exception because
*/
@Test
public void testInterveningCallOther() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
JitAnalysisContext context = makeContext(language, """
r0 = r1;
r0 = pcodeop_one(r1);
goto 0x1234;
""", lib);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
JitVarScopeModel vsm = new JitVarScopeModel(cfm, dfm);
JitOpUseModel oum = new JitOpUseModel(context, cfm, dfm, vsm);
PcodeOp copyOp = assertOp(PcodeOp.COPY, context.getPassage().getCode().getFirst());
assertTrue(oum.isUsed(dfm.getJitOp(copyOp)));
}
}

View File

@@ -0,0 +1,176 @@
/* ###
* 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.analysis;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import generic.Unique;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.processors.sleigh.SleighLanguageHelper;
import ghidra.pcode.emu.jit.AbstractJitTest;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock;
import ghidra.pcode.emu.jit.analysis.JitType.DoubleJitType;
import ghidra.pcode.emu.jit.analysis.JitType.LongJitType;
import ghidra.pcode.emu.jit.op.*;
import ghidra.pcode.emu.jit.var.JitConstVal;
import ghidra.pcode.emu.jit.var.JitVal;
import ghidra.pcode.exec.*;
import ghidra.program.model.pcode.PcodeOp;
public class JitTypeModelTest extends AbstractJitTest {
@Test
public void testDefault() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
r0 = r1;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
JitTypeModel tm = new JitTypeModel(dfm);
JitBlock block = Unique.assertOne(cfm.getBlocks());
JitVal r0 = Unique.assertOne(dfm.getOutput(block, language.getRegister("r0")));
assertEquals(LongJitType.I8, tm.typeOf(r0));
}
@Test
public void testFloatThroughTerminalCopy() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
r1 = r1 f+ r1;
r0 = r1;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
JitTypeModel tm = new JitTypeModel(dfm);
JitBlock block = Unique.assertOne(cfm.getBlocks());
JitVal r0 = Unique.assertOne(dfm.getOutput(block, language.getRegister("r0")));
assertEquals(DoubleJitType.F8, tm.typeOf(r0));
}
@Test
public void testFloatConstant() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
r0 = r1 f+ 0x5678;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
JitTypeModel tm = new JitTypeModel(dfm);
PcodeOp op = assertOp(PcodeOp.FLOAT_ADD, context.getPassage().getCode().getFirst());
JitFloatAddOp fAddOp = (JitFloatAddOp) dfm.getJitOp(op);
JitConstVal c1234 = (JitConstVal) fAddOp.r();
assertEquals(DoubleJitType.F8, tm.typeOf(c1234));
}
@Test
public void testCBranchInput() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
<loop>
if (r0) goto <loop>;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
JitTypeModel tm = new JitTypeModel(dfm);
PcodeOp op = assertOp(PcodeOp.CBRANCH, context.getPassage().getCode().getFirst());
JitCBranchOp cbranch = (JitCBranchOp) dfm.getJitOp(op);
JitVal r0 = cbranch.cond();
assertEquals(LongJitType.I8, tm.typeOf(r0));
}
@Test
public void testBranchIndInput() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
goto [r0];
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
JitTypeModel tm = new JitTypeModel(dfm);
PcodeOp op = Unique.assertOne(context.getPassage().getCode());
JitBranchIndOp branchind = (JitBranchIndOp) dfm.getJitOp(op);
JitVal r0 = branchind.target();
assertEquals(LongJitType.I8, tm.typeOf(r0));
}
@Test
public void testLoop() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
<loop>
r0 = r0 f+ r1;
goto <loop>;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
JitTypeModel tm = new JitTypeModel(dfm);
JitBlock block = Unique.assertOne(cfm.getBlocks());
JitVal r0 = Unique.assertOne(dfm.getOutput(block, language.getRegister("r0")));
assertEquals(DoubleJitType.F8, tm.typeOf(r0));
}
@Test
public void testViaSharedUse() throws Exception {
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", """
r0 = r0 f+ r1;
r2 = r1;
goto 0x1234;
""", PcodeUseropLibrary.NIL);
JitAnalysisContext context = makeContext(program);
JitControlFlowModel cfm = new JitControlFlowModel(context);
JitDataFlowModel dfm = new JitDataFlowModel(context, cfm);
JitTypeModel tm = new JitTypeModel(dfm);
JitBlock block = Unique.assertOne(cfm.getBlocks());
JitVal r0 = Unique.assertOne(dfm.getOutput(block, language.getRegister("r0")));
assertEquals(DoubleJitType.F8, tm.typeOf(r0));
JitVal r2 = Unique.assertOne(dfm.getOutput(block, language.getRegister("r2")));
assertEquals(DoubleJitType.F8, tm.typeOf(r2));
}
}

View File

@@ -0,0 +1,39 @@
/* ###
* 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.decode;
import ghidra.pcode.emu.jit.JitPassage;
import ghidra.pcode.emu.jit.JitPassage.AddrCtx;
import ghidra.pcode.emu.jit.JitPcodeThread;
import ghidra.pcode.exec.PcodeProgram;
public class JitPassageDecoderTestAccess {
public static JitPassage simulateFromPcode(PcodeProgram program, JitPcodeThread thread) {
JitPassageDecoder decoder = new JitPassageDecoder(thread);
DecoderForOnePassage d4passage = new DecoderForOnePassage(decoder, AddrCtx.NOWHERE, 0);
d4passage.externalBranches.clear();
DecoderForOneStride d4stride = new DecoderForOneStride(decoder, d4passage, AddrCtx.NOWHERE);
DecoderExecutor exec = new DecoderExecutor(d4stride, AddrCtx.NOWHERE);
d4passage.firstOps.put(AddrCtx.NOWHERE, exec.rewrite(program.getCode().getFirst()));
exec.execute(program);
exec.checkFallthroughAndAccumulate(program);
d4passage.strides.add(d4stride.toStride());
return d4passage.finish();
}
}