mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GP-4643: Add a JIT-accelerated p-code emulator (API/scripting only)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user