From 7d2a11d8038248d7e67cbad8dbccd520181eb445 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Mon, 22 Dec 2025 15:57:37 +0000 Subject: [PATCH] GP-6159: Require op argument in PcodeUseropDefinition.execute(). --- .../sys/AnnotatedEmuSyscallUseropLibrary.java | 17 +-- .../emu/sys/UseropEmuSyscallDefinition.java | 22 +++- .../pcode/emu/jit/gen/FieldForPcodeOp.java | 106 ++++++++++++++++++ .../pcode/emu/jit/gen/FieldForUserop.java | 8 +- .../ghidra/pcode/emu/jit/gen/GenConsts.java | 38 +++++-- .../pcode/emu/jit/gen/JitCodeGenerator.java | 37 +++++- .../pcode/emu/jit/gen/StaticFieldReq.java | 1 + .../pcode/emu/jit/gen/op/CallOtherOpGen.java | 55 +++------ .../emu/jit/gen/tgt/JitCompiledPassage.java | 30 +++-- .../pcode/emu/jit/gen/util/Methods.java | 88 +++++++++++++++ .../java/ghidra/pcode/exec/PcodeProgram.java | 23 ++++ .../ghidra/pcode/exec/PcodeUseropLibrary.java | 18 +-- .../jit/gen/AbstractJitCodeGeneratorTest.java | 5 +- .../gen/AbstractToyJitCodeGeneratorTest.java | 28 ++++- 14 files changed, 379 insertions(+), 97 deletions(-) create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForPcodeOp.java diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/AnnotatedEmuSyscallUseropLibrary.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/AnnotatedEmuSyscallUseropLibrary.java index bcb1e5267a..6a9aa07e73 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/AnnotatedEmuSyscallUseropLibrary.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/AnnotatedEmuSyscallUseropLibrary.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -59,7 +59,8 @@ public abstract class AnnotatedEmuSyscallUseropLibrary extends AnnotatedPcode * An annotation to export a method as a system call in the library. * *

- * The method must also be exported in the userop library, likely via {@link PcodeUserop}. + * The method must also be exported in the userop library, likely via + * {@link ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.PcodeUserop @PcodeUserop}. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @@ -120,12 +121,14 @@ public abstract class AnnotatedEmuSyscallUseropLibrary extends AnnotatedPcode /** * Export a userop as a system call * + * @param number the opIndex assigned to the userop * @param opdef the userop + * @param convention the syscall calling convention for the emulated platform * @return the syscall definition */ - public UseropEmuSyscallDefinition newBoundSyscall(PcodeUseropDefinition opdef, - PrototypeModel convention) { - return new UseropEmuSyscallDefinition<>(opdef, program, convention, dtMachineWord); + public UseropEmuSyscallDefinition newBoundSyscall(long number, + PcodeUseropDefinition opdef, PrototypeModel convention) { + return new UseropEmuSyscallDefinition<>(number, opdef, program, convention, dtMachineWord); } protected void mapAndBindSyscalls(Class cls) { @@ -149,7 +152,7 @@ public abstract class AnnotatedEmuSyscallUseropLibrary extends AnnotatedPcode } PrototypeModel convention = mapConventions.get(number); EmuSyscallDefinition existed = - syscallMap.put(number, newBoundSyscall(opdef, convention)); + syscallMap.put(number, newBoundSyscall(number, opdef, convention)); if (existed != null) { throw new IllegalArgumentException("Duplicate @" + EmuSyscall.class.getSimpleName() + " annotated methods with name " + name); diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/UseropEmuSyscallDefinition.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/UseropEmuSyscallDefinition.java index 2445942ccd..f73db9f268 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/UseropEmuSyscallDefinition.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/UseropEmuSyscallDefinition.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,11 +22,12 @@ import ghidra.lifecycle.Unfinished; import ghidra.pcode.emu.sys.EmuSyscallLibrary.EmuSyscallDefinition; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; +import ghidra.program.model.address.Address; import ghidra.program.model.data.DataType; import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.listing.Program; import ghidra.program.model.listing.VariableStorage; -import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.pcode.*; /** * A system call that is defined by delegating to a p-code userop @@ -56,6 +57,7 @@ public class UseropEmuSyscallDefinition implements EmuSyscallDefinition { return dtPointer; } + protected final PcodeOp op; // fabricated for analyses that provide originating op info protected final PcodeUseropDefinition opdef; protected final List inVars; protected final Varnode outVar; @@ -64,12 +66,13 @@ public class UseropEmuSyscallDefinition implements EmuSyscallDefinition { * Construct a syscall definition * * @see AnnotatedEmuSyscallUseropLibrary + * @param number the opIndex assigned to this userop * @param opdef the wrapped userop definition * @param program the program, used for storage computation * @param convention the "syscall" calling convention * @param dtMachineWord the "pointer" data type */ - public UseropEmuSyscallDefinition(PcodeUseropDefinition opdef, Program program, + public UseropEmuSyscallDefinition(long number, PcodeUseropDefinition opdef, Program program, PrototypeModel convention, DataType dtMachineWord) { this.opdef = opdef; @@ -87,9 +90,16 @@ public class UseropEmuSyscallDefinition implements EmuSyscallDefinition { outVar = getSingleVnStorage(vss[0]); inVars = Arrays.asList(new Varnode[inputCount]); + Varnode[] opIns = new Varnode[inputCount + 1]; + opIns[0] = new Varnode(program.getAddressFactory().getConstantAddress(number), 4); for (int i = 0; i < inputCount; i++) { - inVars.set(i, getSingleVnStorage(vss[i + 1])); + Varnode vnIn = getSingleVnStorage(vss[i + 1]); + inVars.set(i, vnIn); + opIns[i + 1] = vnIn; } + + op = new PcodeOp(new SequenceNumber(Address.NO_ADDRESS, 0), PcodeOp.CALLOTHER, opIns, + outVar); } /** @@ -109,7 +119,7 @@ public class UseropEmuSyscallDefinition implements EmuSyscallDefinition { @Override public void invoke(PcodeExecutor executor, PcodeUseropLibrary library) { try { - opdef.execute(executor, library, outVar, inVars); + opdef.execute(executor, library, op, outVar, inVars); } catch (PcodeExecutionException e) { throw e; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForPcodeOp.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForPcodeOp.java new file mode 100644 index 0000000000..ee354abba9 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForPcodeOp.java @@ -0,0 +1,106 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.jit.gen; + +import static ghidra.pcode.emu.jit.gen.GenConsts.*; +import static org.objectweb.asm.Opcodes.*; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.objectweb.asm.ClassVisitor; + +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; + +public class FieldForPcodeOp implements StaticFieldReq> { + + static String nameVn(Varnode vn) { + if (vn == null) { + return "null"; + } + return "%s_%x_%d".formatted(vn.getAddress().getAddressSpace().getName(), vn.getOffset(), + vn.getSize()); + } + + static String nameInputs(Varnode[] inputs) { + return Stream.of(inputs).map(FieldForPcodeOp::nameVn).collect(Collectors.joining("__")); + } + + private final PcodeOp op; + private final FieldForVarnode outVnReq; + private final List inVnReqs; + + public FieldForPcodeOp(JitCodeGenerator gen, PcodeOp op) { + this.op = op; + + this.outVnReq = + op.getOutput() == null ? null : gen.requestStaticFieldForVarnode(op.getOutput()); + this.inVnReqs = Stream.of(op.getInputs()).map(gen::requestStaticFieldForVarnode).toList(); + } + + @Override + public String name() { + return "%s__%s__%s".formatted(nameVn(op.getOutput()), op.getMnemonic(), + nameInputs(op.getInputs())); + } + + @Override + public Emitter genClInitCode(Emitter em, JitCodeGenerator gen, + ClassVisitor cv) { + Fld.decl(cv, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, GenConsts.T_PCODE_OP, name()); + + var emIns = em + .emit(gen::genAddress, op.getSeqnum().getTarget()) + .emit(Op::ldc__i, op.getSeqnum().getTime()) + .emit(Op::ldc__i, op.getOpcode()) + .emit(Op::ldc__i, op.getNumInputs()) + .emit(Op::anewarray, T_VARNODE); + for (int i = 0; i < op.getNumInputs(); i++) { + emIns = emIns + .emit(Op::dup) + .emit(Op::ldc__i, i) + .emit(inVnReqs.get(i)::genLoad, gen) + .emit(Op::aastore); + } + var emOut = outVnReq == null + ? emIns.emit(Op::aconst_null, T_VARNODE) + : emIns.emit(outVnReq::genLoad, gen); + return emOut + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, "createOp", + MDESC_JIT_COMPILED_PASSAGE__CREATE_OP, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::putstatic, gen.typeThis, name(), T_PCODE_OP); + } + + @Override + public Emitter>> genLoad(Emitter em, + JitCodeGenerator gen) { + return em + .emit(Op::getstatic, gen.typeThis, name(), T_PCODE_OP); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForUserop.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForUserop.java index 03e994e11d..fa80c0fdfb 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForUserop.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForUserop.java @@ -39,8 +39,8 @@ import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; * @param userop the definition to pre-fetch * @see JitDataFlowUseropLibrary */ -public record FieldForUserop(PcodeUseropDefinition userop) - implements InstanceFieldReq>> { +public record FieldForUserop(PcodeUseropDefinition userop) + implements InstanceFieldReq>> { @Override public String name() { return "userop_" + userop.getName(); @@ -81,10 +81,10 @@ public record FieldForUserop(PcodeUseropDefinition userop) @Override public - Emitter>>> + Emitter>>> genLoad(Emitter em, Local> localThis, JitCodeGenerator gen) { return em .emit(Op::aload, localThis) - .emit(Op::getfield, gen.typeThis, name(), T_PCODE_USEROP_DEFINITION); + .emit(Op::getfield, gen.typeThis, name(), T_PCODE_USEROP_DEFINITION__BYTEARR); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java index 1a8201ec48..d707210ee9 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java @@ -39,6 +39,7 @@ import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; import ghidra.program.model.lang.RegisterValue; +import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; /** @@ -86,10 +87,14 @@ public interface GenConsts { public static final TRef T_LOWLEVEL_ERROR = Types.refOf(LowlevelError.class); public static final TRef T_MATH = Types.refOf(Math.class); public static final TRef T_OBJECT = Types.refOf(Object.class); + public static final TRef T_PCODE_OP = Types.refOf(PcodeOp.class); @SuppressWarnings({ "unchecked", "rawtypes" }) public static final TRef> T_PCODE_USEROP_DEFINITION = (TRef) Types.refOf(PcodeUseropDefinition.class); @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final TRef> T_PCODE_USEROP_DEFINITION__BYTEARR = + (TRef) T_PCODE_USEROP_DEFINITION; + @SuppressWarnings({ "unchecked", "rawtypes" }) public static final TRef> T_PCODE_USEROP_LIBRARY = (TRef) Types.refOf(PcodeUseropLibrary.class); public static final TRef T_PRINT_STREAM = Types.refOf(PrintStream.class); @@ -189,6 +194,17 @@ public interface GenConsts { public static final MthDesc, Ent, TRef>> MDESC_JIT_COMPILED_PASSAGE__CREATE_EXIT_SLOT = MthDesc.returns(T_EXIT_SLOT).param(Types.T_LONG).param(T_REGISTER_VALUE).build(); + public static final MthDesc, + Ent>, TInt>, TInt>, TRef>, + TRef>> MDESC_JIT_COMPILED_PASSAGE__CREATE_OP = + MthDesc.derive(JitCompiledPassage::createOp) + .check(MthDesc::returns, T_PCODE_OP) + .check(MthDesc::param, T_ADDRESS) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::param, TARR_VARNODE) + .check(MthDesc::param, T_VARNODE) + .check(MthDesc::build); public static final MthDesc, Ent>, TRef>, TLong>, TInt>> MDESC_JIT_COMPILED_PASSAGE__CREATE_VARNODE = @@ -204,17 +220,20 @@ public interface GenConsts { public static final MthDesc, Ent>> MDESC_JIT_COMPILED_PASSAGE__GET_LANGUAGE = MthDesc.returns(T_LANGUAGE).param(T_STRING).build(); - public static final MthDesc>, + public static final MthDesc>, Ent>> MDESC_JIT_COMPILED_PASSAGE__GET_USEROP_DEFINITION = - MthDesc.returns(T_PCODE_USEROP_DEFINITION).param(T_STRING).build(); + MthDesc.deriveInst(JitCompiledPassage::getUseropDefinition) + .check(MthDesc::returns, T_PCODE_USEROP_DEFINITION__BYTEARR) + .check(MthDesc::param, T_STRING) + .check(MthDesc::build); public static final MthDesc>>, TRef>, - TRef>> MDESC_JIT_COMPILED_PASSAGE__INVOKE_USEROP = - MthDesc.returns(Types.T_VOID) - .param(T_PCODE_USEROP_DEFINITION) - .param(T_VARNODE) - .param(TARR_VARNODE) - .build(); + Ent>>, + TRef>> MDESC_JIT_COMPILED_PASSAGE__INVOKE_USEROP = + MthDesc.deriveInst(JitCompiledPassage::invokeUserop) + .check(MthDesc::returns, Types.T_VOID) + .check(MthDesc::param, T_PCODE_USEROP_DEFINITION__BYTEARR) + .check(MthDesc::param, T_PCODE_OP) + .check(MthDesc::build); public static final MthDesc>, TRef>, TRef>> MDESC_JIT_COMPILED_PASSAGE__MP_INT_BINOP = @@ -346,7 +365,6 @@ public interface GenConsts { MthDesc.returns(Types.T_VOID).param(T_STRING).build(); public static final MthDesc, Ent>> MDESC_STRING__FORMATTED = MthDesc.returns(T_STRING).param(TARR_OBJECT).build(); - public static final MthDesc> MDESC_$DOUBLE_UNOP = MthDesc.returns(Types.T_DOUBLE).param(Types.T_DOUBLE).build(); public static final MthDesc> MDESC_$FLOAT_UNOP = diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java index ded00e1043..cbc9f550c8 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java @@ -21,6 +21,7 @@ import static org.objectweb.asm.Opcodes.*; import java.io.*; import java.lang.invoke.MethodHandles.Lookup; import java.util.*; +import java.util.stream.Stream; import org.apache.commons.lang3.reflect.TypeLiteral; import org.objectweb.asm.*; @@ -198,7 +199,23 @@ public class JitCodeGenerator { * @param vn the varnode */ public VarnodeKey(Varnode vn) { - this(vn.getSpace(), vn.getOffset(), vn.getSize()); + this(vn == null ? 0 : vn.getSpace(), vn == null ? 0 : vn.getOffset(), + vn == null ? 0 : vn.getSize()); + } + } + + /** + * The key for a p-code op, to ensure we control "equality" + */ + record PcodeOpKey(VarnodeKey out, int opcode, List ins) { + /** + * Extract/construct thhe key for a given op + * + * @param op the p-code op + */ + public PcodeOpKey(PcodeOp op) { + this(new VarnodeKey(op.getOutput()), op.getOpcode(), + Stream.of(op.getInputs()).map(VarnodeKey::new).toList()); } } @@ -219,6 +236,7 @@ public class JitCodeGenerator { private final Map fieldsForArrDirect = new HashMap<>(); private final Map fieldsForContext = new HashMap<>(); private final Map fieldsForVarnode = new HashMap<>(); + private final Map fieldsForOp = new HashMap<>(); private final Map fieldsForUserop = new HashMap<>(); private final Map fieldsForExitSlot = new HashMap<>(); @@ -507,13 +525,25 @@ public class JitCodeGenerator { }); } + /** + * Request a field for the given p-code op + *

+ * This will request fields for each varnode for the op's operands + * + * @param op the p-code op + * @return the field request + */ + public FieldForPcodeOp requestStaticFieldForOp(PcodeOp op) { + return fieldsForOp.computeIfAbsent(new PcodeOpKey(op), ok -> new FieldForPcodeOp(this, op)); + } + /** * Request a field for the given userop * * @param userop the userop * @return the field request */ - public FieldForUserop requestFieldForUserop(PcodeUseropDefinition userop) { + public FieldForUserop requestFieldForUserop(PcodeUseropDefinition userop) { return fieldsForUserop.computeIfAbsent(userop.getName(), n -> { FieldForUserop f = new FieldForUserop(userop); return f; @@ -990,6 +1020,9 @@ public class JitCodeGenerator { for (FieldForVarnode fVn : fieldsForVarnode.values()) { em = fVn.genClInitCode(em, this, cv); } + for (FieldForPcodeOp fOp : fieldsForOp.values()) { + em = fOp.genClInitCode(em, this, cv); + } return em; } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/StaticFieldReq.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/StaticFieldReq.java index 8448ce5f11..b12b1ea4af 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/StaticFieldReq.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/StaticFieldReq.java @@ -28,6 +28,7 @@ import ghidra.pcode.emu.jit.gen.util.Types.BNonVoid; * @param the JVM type of the field */ public interface StaticFieldReq extends FieldReq { + /** * Emit the field declaration and its initialization bytecode * diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java index 8cf638b3d6..594e7086e0 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java @@ -48,7 +48,6 @@ import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.OpOutput; import ghidra.pcode.exec.PcodeUseropLibrary; import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; import ghidra.program.model.pcode.PcodeOp; -import ghidra.program.model.pcode.Varnode; /** * The generator for a {@link JitCallOtherOpIf callother}. @@ -66,12 +65,10 @@ import ghidra.program.model.pcode.Varnode; * *

* For the Standard strategy, we emit code to retire the program counter, decode context, and all - * live variables. We then request a field to hold the userop and emit code to load it. We then emit - * code to prepare its arguments and place them on the stack, namely the output varnode and an array - * for the input varnodes. We request a field for each varnode and emit code to load them as needed. - * For the array, we emit code to construct and fill it. We then emit code to invoke - * {@link JitCompiledPassage#invokeUserop(PcodeUseropDefinition, Varnode, Varnode[])}. The userop - * definition handles retrieving all of its inputs and writing the output, directly to the + * live variables. We then request a field to hold the {@link PcodeOp#CALLOTHER} p-code op and the + * userop, and emit code to load them. We then emit code to invoke + * {@link JitCompiledPassage#invokeUserop(PcodeUseropDefinition, PcodeOp)}. The userop definition + * handles retrieving all of its inputs and writing the output, directly to the * {@link JitBytesPcodeExecutorState state}. Thus, we now need only to emit code to re-birth all the * live variables. If any errors occur, execution is interrupted as usual, and our state is * consistent. @@ -92,16 +89,6 @@ public enum CallOtherOpGen implements OpGen { /** The generator singleton */ GEN; - private static Emitter>> - genLoadVarnodeOrNull(Emitter em, Local> localThis, - JitCodeGenerator gen, Varnode vn) { - if (vn == null) { - return em.emit(Op::aconst_null, T_VARNODE); - } - FieldForVarnode field = gen.requestStaticFieldForVarnode(vn); - return em.emit(field::genLoad, gen); - } - /** * Emit code to implement the Standard strategy (see the class documentation) * @@ -127,35 +114,25 @@ public enum CallOtherOpGen implements OpGen { * NOTE: The output variable should be "alive", so we need not store it into a local. It'll * be made alive in the return block transition. */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + FieldForUserop useropField = gen.requestFieldForUserop((PcodeUseropDefinition) userop); + FieldForPcodeOp opField = gen.requestStaticFieldForOp(op); + BlockTransition transition = VarGen.computeBlockTransition(localThis, gen, block, null); PcGen pcGen = PcGen.loadOffset(gen.getAddressForOp(op)); - var emArr = em + return new LiveOpResult(em .emit(transition::genFwd) - .emit(gen::genRetirePcCtx, localThis, pcGen, gen.getExitContext(op), - RetireMode.SET) + .emit(gen::genRetirePcCtx, localThis, pcGen, gen.getExitContext(op), RetireMode.SET) .emit(Op::aload, localThis) - .emit(gen.requestFieldForUserop(userop)::genLoad, localThis, gen) - .emit(CallOtherOpGen::genLoadVarnodeOrNull, localThis, gen, op.getOutput()) - .emit(Op::ldc__i, op.getNumInputs() - 1) - .emit(Op::anewarray, T_VARNODE); - - for (int i = 1; i < op.getNumInputs(); i++) { - emArr = emArr - .emit(Op::dup) - .emit(Op::ldc__i, i - 1) - .emit(CallOtherOpGen::genLoadVarnodeOrNull, localThis, gen, op.getInput(i)) - .emit(Op::aastore); - } - - return new LiveOpResult(emArr + .emit(useropField::genLoad, localThis, gen) + .emit(opField::genLoad, gen) .emit(Op::invokeinterface, T_JIT_COMPILED_PASSAGE, "invokeUserop", MDESC_JIT_COMPILED_PASSAGE__INVOKE_USEROP) .step(Inv::takeArg) .step(Inv::takeArg) - .step(Inv::takeArg) .step(Inv::takeObjRef) .step(Inv::retVoid) .emit(transition::genInv)); @@ -199,10 +176,10 @@ public enum CallOtherOpGen implements OpGen { * @return the result of emitting the userop's bytecode */ public static > OpResult - genRunDirectStrategy(Emitter em, - Local> localThis, JitCodeGenerator gen, JitCallOtherOpIf op, - JitBlock block, Scope scope) { - FieldForUserop useropField = gen.requestFieldForUserop(op.userop()); + genRunDirectStrategy(Emitter em, Local> localThis, + JitCodeGenerator gen, JitCallOtherOpIf op, JitBlock block, Scope scope) { + @SuppressWarnings({ "rawtypes", "unchecked" }) + FieldForUserop useropField = gen.requestFieldForUserop((PcodeUseropDefinition) op.userop()); // Set live = gen.vsm.getLiveVars(block); /** diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java index 9a7dd8a04f..102c191985 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java @@ -34,8 +34,7 @@ import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressFactory; import ghidra.program.model.lang.*; -import ghidra.program.model.pcode.PcodeOp; -import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.pcode.*; import ghidra.program.util.DefaultLanguageService; /** @@ -2077,6 +2076,22 @@ public interface JitCompiledPassage { return new Varnode(factory.getAddressSpace(space).getAddress(offset), size); } + /** + * Construct a p-code op + * + * @param factory the language's address factory + * @param space the name of the space of the op's sequence number + * @param offset the address offset of the op's sequence number + * @param index the index of the op's sequence number + * @param op the opcode + * @param inputs the inputs + * @param output the output + * @return the op + */ + static PcodeOp createOp(Address target, int sq, int opcode, Varnode[] inputs, Varnode output) { + return new PcodeOp(new SequenceNumber(target, sq), opcode, inputs, output); + } + /** * Get this instance's bound thread. * @@ -2148,15 +2163,12 @@ public interface JitCompiledPassage { * via the Standard strategy. * * @param userop the userop definition - * @param output an optional output operand - * @param inputs the input operands + * @param op the {@link PcodeOp#CALLOTHER} op * @see JitDataFlowUseropLibrary - * @see PcodeUseropDefinition#execute(PcodeExecutor, PcodeUseropLibrary, Varnode, List) + * @see PcodeUseropDefinition#execute(PcodeExecutor, PcodeUseropLibrary, PcodeOp) */ - default void invokeUserop(PcodeUseropDefinition userop, Varnode output, - Varnode[] inputs) { - userop.execute(thread().getExecutor(), thread().getUseropLibrary(), output, - Arrays.asList(inputs)); + default void invokeUserop(PcodeUseropDefinition userop, PcodeOp op) { + userop.execute(thread().getExecutor(), thread().getUseropLibrary(), op); } /** diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Methods.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Methods.java index d5304c416c..e6e3c61c7e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Methods.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Methods.java @@ -166,6 +166,24 @@ public interface Methods { return new MthDescCheckedBuilderR<>(); } + /** + * Begin building an instance method descriptor derived from the given method reference + *

+ * This implicitly drops the object reference that is normally included in static references + * to instance methods. + * + * @see #derive(Function) + * @param the return type, boxed + * @param the object reference type, dropped + * @param another argument type, boxed + * @param func the method reference + * @return the checked builder + */ + public static MthDescCheckedBuilderR> + deriveInst(BiFunction func) { + return new MthDescCheckedBuilderR<>(); + } + /** * Begin building a method descriptor derived from the given method reference * @@ -199,6 +217,21 @@ public interface Methods { return new MthDescCheckedBuilderR<>(); } + /** + * Begin building a method descriptor derived from the given method reference + * + * @see #derive(Function) + * @param the object type, dropped + * @param another argument type, boxed + * @param another argument type, boxed + * @param func the method reference + * @return the checked builder + */ + public static MthDescCheckedBuilderR, A1>> + deriveInst(A3Consumer func) { + return new MthDescCheckedBuilderR<>(); + } + /** * Begin building a method descriptor derived from the given method reference * @@ -234,6 +267,61 @@ public interface Methods { return new MthDescCheckedBuilderR<>(); } + /** + * Begin building an instance method descriptor derived from the given method reference + * + * @see #derive(Function) + * @param the object reference + * @param another argument type, boxed + * @param another argument type, boxed + * @param another argument type, boxed + * @param func the method reference + * @return the checked builder + */ + public static + MthDescCheckedBuilderR, A2>, A1>> + deriveInst(A4Consumer func) { + return new MthDescCheckedBuilderR<>(); + } + + /** + * Begin building a method descriptor derived from the given method reference + * + * @see #derive(Function) + * @param the return type, boxed + * @param the first argument type, boxed + * @param another argument type, boxed + * @param another argument type, boxed + * @param another argument type, boxed + * @param func the method reference + * @return the checked builder + */ + public static + MthDescCheckedBuilderR, A3>, A2>, A1>, A0>> + derive(A5Function func) { + return new MthDescCheckedBuilderR<>(); + } + + /** + * Begin building a method descriptor derived from the given method reference + * + * @see #derive(Function) + * @param the return type, boxed + * @param the first argument type, boxed + * @param another argument type, boxed + * @param another argument type, boxed + * @param another argument type, boxed + * @param func the method reference + * @return the checked builder + */ + public static MthDescCheckedBuilderR, A5>, A4>, A3>, A2>, A1>, A0>> + derive(A7Function func) { + return new MthDescCheckedBuilderR<>(); + } + /** * Specify the return type of a checked builder *

diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeProgram.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeProgram.java index e5089e4162..a444b34cf7 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeProgram.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeProgram.java @@ -17,6 +17,7 @@ package ghidra.pcode.exec; import java.io.IOException; import java.util.*; +import java.util.Map.Entry; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.template.OpTpl; @@ -254,4 +255,26 @@ public class PcodeProgram { } return useropNames.get(opNo); } + + /** + * For testing/debug only: Get the userop number for a given name + * + * @implNote There is no index by name, so this exhaustively searches the language- and + * library-defined userops. + * @param name the name + * @return the number, or -1 if not found + */ + public int getUseropNumber(String name) { + for (int i = 0; i < language.getNumberOfUserDefinedOpNames(); i++) { + if (name.equals(language.getUserDefinedOpName(i))) { + return i; + } + } + for (Entry ent : useropNames.entrySet()) { + if (name.equals(ent.getValue())) { + return ent.getKey(); + } + } + return -1; + } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java index fec9200b81..98f3621df8 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java @@ -126,23 +126,7 @@ public interface PcodeUseropLibrary { * @param executor the executor invoking this userop. * @param library the complete library for this execution. Note the library may have been * composed from more than the one defining this userop. - * @param outVar if invoked as an rval, the destination varnode for the userop's output. - * Otherwise, {@code null}. - * @param inVars the input varnodes as ordered in the source. - * @see AnnotatedPcodeUseropLibrary.AnnotatedPcodeUseropDefinition - */ - default void execute(PcodeExecutor executor, PcodeUseropLibrary library, - Varnode outVar, List inVars) { - execute(executor, library, null, outVar, inVars); - } - - /** - * Invoke/execute the userop. - * - * @param executor the executor invoking this userop. - * @param library the complete library for this execution. Note the library may have been - * composed from more than the one defining this userop. - * @param op the CALLOTHER p-code op + * @param op the {@link PcodeOp#CALLOTHER} op * @param outVar if invoked as an rval, the destination varnode for the userop's output. * Otherwise, {@code null}. * @param inVars the input varnodes as ordered in the source. diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractJitCodeGeneratorTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractJitCodeGeneratorTest.java index 80cf726aea..b69643606e 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractJitCodeGeneratorTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractJitCodeGeneratorTest.java @@ -49,6 +49,7 @@ import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.*; +import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; import ghidra.program.util.DefaultLanguageService; import ghidra.util.NumericUtilities; @@ -220,12 +221,14 @@ public abstract class AbstractJitCodeGeneratorTest extends AbstractJitTest { public static class TestUseropLibrary extends AnnotatedPcodeUseropLibrary { boolean gotJavaUseropCall = false; + PcodeOp recordedOp = null; boolean gotFuncUseropCall = false; boolean gotSleighUseropCall = false; @PcodeUserop - public long java_userop(long a, long b) { + public long java_userop(long a, long b, @OpOp PcodeOp op) { gotJavaUseropCall = true; + recordedOp = op; return 2 * a + b; } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractToyJitCodeGeneratorTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractToyJitCodeGeneratorTest.java index bddc24526a..25feb88a3a 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractToyJitCodeGeneratorTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractToyJitCodeGeneratorTest.java @@ -26,11 +26,14 @@ import org.junit.Ignore; import org.junit.Test; import org.objectweb.asm.tree.MethodInsnNode; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator.PcodeOpKey; import ghidra.pcode.exec.InterruptPcodeExecutionException; import ghidra.pcode.exec.SleighLinkException; import ghidra.pcode.floatformat.FloatFormat; -import ghidra.program.model.lang.Endian; -import ghidra.program.model.lang.LanguageID; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressFactory; +import ghidra.program.model.lang.*; +import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; public abstract class AbstractToyJitCodeGeneratorTest extends AbstractJitCodeGeneratorTest { @@ -434,10 +437,21 @@ public abstract class AbstractToyJitCodeGeneratorTest extends AbstractJitCodeGen Translation tr = translateSleigh(getLanguageID(), """ r0 = java_userop(6:8, 2:8); """); + AddressFactory factory = tr.program().getLanguage().getAddressFactory(); + Register regR0 = tr.program().getLanguage().getRegister("r0"); + assertFalse(tr.library().gotJavaUseropCall); tr.runFallthrough(); assertTrue(tr.library().gotJavaUseropCall); assertEquals(14, tr.getLongRegVal("r0")); + + int opNo = tr.program().getUseropNumber("java_userop"); + PcodeOp exp = new PcodeOp(Address.NO_ADDRESS, 0, PcodeOp.CALLOTHER, new Varnode[] { + new Varnode(factory.getConstantAddress(opNo), 4), + new Varnode(factory.getConstantAddress(6), 8), + new Varnode(factory.getConstantAddress(2), 8) + }, new Varnode(regR0.getAddress(), regR0.getNumBytes())); + assertEquals(new PcodeOpKey(exp), new PcodeOpKey(tr.library().recordedOp)); } @Test @@ -445,10 +459,20 @@ public abstract class AbstractToyJitCodeGeneratorTest extends AbstractJitCodeGen Translation tr = translateSleigh(getLanguageID(), """ java_userop(6:8, 2:8); """); + AddressFactory factory = tr.program().getLanguage().getAddressFactory(); + assertFalse(tr.library().gotJavaUseropCall); tr.runFallthrough(); assertTrue(tr.library().gotJavaUseropCall); assertEquals(0, tr.getLongRegVal("r0")); + + int opNo = tr.program().getUseropNumber("java_userop"); + PcodeOp exp = new PcodeOp(Address.NO_ADDRESS, 0, PcodeOp.CALLOTHER, new Varnode[] { + new Varnode(factory.getConstantAddress(opNo), 4), + new Varnode(factory.getConstantAddress(6), 8), + new Varnode(factory.getConstantAddress(2), 8) + }, null); + assertEquals(new PcodeOpKey(exp), new PcodeOpKey(tr.library().recordedOp)); } @Test