From e8ff64a59e857b57ce752ca129c1ddd947112d99 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:25:25 +0000 Subject: [PATCH] GP-6158: Allows static methods to be marked @PcodeOp in UseropLibrary --- .../pcode/emu/jit/gen/op/CallOtherOpGen.java | 84 +++++++++++++------ .../pcode/emu/jit/gen/util/Methods.java | 23 ++++- .../exec/AnnotatedPcodeUseropLibrary.java | 8 +- .../exec/AnnotatedPcodeUseropLibraryTest.java | 17 ++++ .../jit/gen/AbstractJitCodeGeneratorTest.java | 19 +++++ .../gen/AbstractToyJitCodeGeneratorTest.java | 25 ++++++ .../ExerciseFiles/Debugger/termmines.c | 6 +- 7 files changed, 150 insertions(+), 32 deletions(-) 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 594e7086e0..fc629cf6fb 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 @@ -17,8 +17,7 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; +import java.lang.reflect.*; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -163,6 +162,12 @@ public enum CallOtherOpGen implements OpGen { }; } + static List tail(List list) { + return list.subList(1, list.size()); + } + + record PlacedParam(Emitter em, List args, List params) {} + /** * Emit code to implement the Direct strategy (see the class documentation) * @@ -244,7 +249,33 @@ public enum CallOtherOpGen implements OpGen { }; } - ObjInv doInv(Emitter em, List args, + PlacedParam> placeNextParam(Emitter em, + List args, List params) { + Parameter param = params.getFirst(); + if (param == outputParameter) { + var emNext = em.emit(Op::aload, localOut); + return new PlacedParam<>(emNext, args, tail(params)); + } + JitVal arg = args.getFirst(); + var emNext = doReadArg(em, arg, param); + return new PlacedParam<>(emNext, tail(args), tail(params)); + } + + Inv doInvVirtual(Emitter em, List args, + List params) { + var emLib = em + .emit(useropField::genLoad, localThis, gen) + .emit(Op::invokeinterface, T_PCODE_USEROP_DEFINITION, "getDefiningLibrary", + MDESC_PCODE_USEROP_DEFINITION__GET_DEFINING_LIBRARY) + .step(Inv::takeObjRef) + .step(Inv::ret) + .emit(Op::checkcast, libType); + var inv = doInvVirtualRec(emLib, args, params) + .step(Inv::takeQObjRef); + return inv; + } + + ObjInv doInvVirtualRec(Emitter em, List args, List params) { if (params.isEmpty()) { /** @@ -254,20 +285,28 @@ public enum CallOtherOpGen implements OpGen { return Op.invokevirtual(em, libType, method.getName(), MthDesc.reflect(method), false); } - Parameter param = params.getFirst(); - if (param == outputParameter) { - var emOut = em.emit(Op::aload, localOut); - var inv = doInv(emOut, args, params.subList(1, params.size())); - return Inv.takeQArg(inv); - } - JitVal arg = args.getFirst(); - // TODO: Annotation/attribute to describe signedness? - // TODO: Or a way to receive the byte size - var emRead = doReadArg(em, arg, param); - var inv = - doInv(emRead, args.subList(1, args.size()), params.subList(1, params.size())); + PlacedParam> next = placeNextParam(em, args, params); + ObjInv, ?> inv = + doInvVirtualRec(next.em, next.args, next.params); return Inv.takeQArg(inv); } + + Inv doInvStaticRec(Emitter em, List args, + List params) { + if (params.isEmpty()) { + return Op.invokestatic(em, libType, method.getName(), MthDesc.reflect(method), + false); + } + PlacedParam> next = placeNextParam(em, args, params); + Inv, ?> inv = + doInvStaticRec(next.em, next.args, next.params); + return Inv.takeQArg(inv); + } + + Inv doInvStatic(Emitter em, List args, + List params) { + return doInvStaticRec(em, args, params); + } }; var tryCatchBlock = Misc.tryCatch(em, Lbl.create(), @@ -275,16 +314,9 @@ public enum CallOtherOpGen implements OpGen { GenConsts.T_THROWABLE); em = tryCatchBlock.em(); - var emLib = em - .emit(useropField::genLoad, localThis, gen) - .emit(Op::invokeinterface, T_PCODE_USEROP_DEFINITION, "getDefiningLibrary", - MDESC_PCODE_USEROP_DEFINITION__GET_DEFINING_LIBRARY) - .step(Inv::takeObjRef) - .step(Inv::ret) - .emit(Op::checkcast, libType); - var inv = rec - .doInv(emLib, op.args(), Arrays.asList(parameters)) - .step(Inv::takeQObjRef); + var inv = Modifier.isStatic(method.getModifiers()) + ? rec.doInvStatic(em, op.args(), Arrays.asList(parameters)) + : rec.doInvVirtual(em, op.args(), Arrays.asList(parameters)); if (outputParameter != null) { if (outMpType != null && op instanceof JitCallOtherDefOp defOp) { @@ -300,7 +332,7 @@ public enum CallOtherOpGen implements OpGen { // TODO: Can annotation specify signedness of return value? var write = new Object() { public , JT extends SimpleJitType> Emitter doWrite( - Inv inv, Class returnType) { + Inv inv, Class returnType) { JT type = SimpleJitType.forJavaType(returnType); return inv .step(Inv::retQ, type.bType()) 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 e6e3c61c7e..cf074e22cb 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 @@ -825,6 +825,25 @@ public interface Methods { return (Inv) inv; } + /** + * Pop an argument and a parameter without checking + *

+ * NOTE: This should only be used with {@link MthDesc#reflect(Method)}. When dealing with a + * parameter list whose length is only known at runtime, recursion should be favored, so + * that each argument pushed by the emitter is provably paired with a parameter denoted by + * calling this method. + * + * @param the return type + * @param the new remaining stack contents + * @param the current stack contents having the popped argument on top + * @param inv the invocation object + * @return the invocation object with remaining parameters and stack contents + */ + public static > Inv + takeQArg(Inv inv) { + return (Inv) inv; + } + /** * Pop a polymorphic reference argument and match/check it against the next (right-most * unmatched parameter) @@ -976,7 +995,7 @@ public interface Methods { * @param inv the invocation object * @return the emitter typed with the resulting stack */ - public static Emitter retQVoid(Inv inv) { + public static Emitter retQVoid(Inv inv) { return inv.em; } @@ -1007,7 +1026,7 @@ public interface Methods { * value */ public static Emitter> - retQ(Inv inv, RT returnType) { + retQ(Inv inv, RT returnType) { return (Emitter) inv.em; } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java index 747a934edf..c8d9100716 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java @@ -263,7 +263,13 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra this.method = method; this.library = library; try { - this.handle = lookup.unreflect(method).bindTo(library); + MethodHandle unbound = lookup.unreflect(method); + if (Modifier.isStatic(method.getModifiers())) { + this.handle = unbound; + } + else { + this.handle = lookup.unreflect(method).bindTo(library); + } } catch (IllegalAccessException e) { throw new IllegalArgumentException( diff --git a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java index f9d6f4a1d3..b4f4979f19 100644 --- a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java +++ b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java @@ -31,6 +31,7 @@ import ghidra.GhidraTestApplicationLayout; import ghidra.app.plugin.processors.sleigh.*; import ghidra.framework.Application; import ghidra.framework.ApplicationConfiguration; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.pcode.utils.Utils; import ghidra.program.model.lang.Register; @@ -276,6 +277,22 @@ public class AnnotatedPcodeUseropLibraryTest extends AbstractGTest { assertRegVarnode(r0, library.outVar); } + @Test + public void testStaticMethod() throws Exception { + var library = new TestUseropLibrary() { + @PcodeUserop + private static int __testop(int a, int b) { + return (int) Math.pow(a, b); + } + }; + + PcodeExecutor executor = createBytesExecutor(); + Register r0 = executor.getLanguage().getRegister("r0"); + executeSleigh(executor, library, "r0 = __testop(4:4, 5:4);"); + assertEquals(1024, executor.getArithmetic() + .toLong(executor.getState().getVar(r0, Reason.INSPECT), Purpose.INSPECT)); + } + @Test public void testKitchenSink() throws Exception { var library = new TestUseropLibrary() { 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 b69643606e..02b6ccc1ec 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 @@ -238,6 +238,11 @@ public abstract class AbstractJitCodeGeneratorTest extends AbstractJitTest { return 2 * a + b; } + @PcodeUserop(functional = true) + public static long func_st_userop(long a, long b) { + return 3 * a + b; + } + @PcodeUserop(functional = true) public void func_mpUserop(@OpOutput int[] out, int[] a, int[] b) { gotFuncUseropCall = true; @@ -254,6 +259,20 @@ public abstract class AbstractJitCodeGeneratorTest extends AbstractJitTest { } } + @PcodeUserop(functional = true) + public static void func_st_mpUserop(@OpOutput int[] out, int[] a, int[] b) { + if (out == null) { + return; + } + + out[0] = b[0]; + out[1] = a[0]; + for (int i = 0; i < 8; i++) { + out[0] |= out[0] << 8; + out[1] |= out[1] << 8; + } + } + @PcodeUserop(canInline = true) public void sleigh_userop(@OpExecutor PcodeExecutor executor, @OpLibrary PcodeUseropLibrary library, 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 25feb88a3a..e73f95767a 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 @@ -497,6 +497,17 @@ public abstract class AbstractToyJitCodeGeneratorTest extends AbstractJitCodeGen assertEquals(0, tr.getLongRegVal("r0")); } + @Test + public void testCallOtherFuncJavaDefStatic() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + r0 = func_st_userop(6:8, 2:8); + """); + assertFalse(tr.library().gotFuncUseropCall); + tr.runFallthrough(); + assertFalse(tr.library().gotFuncUseropCall); + assertEquals(20, tr.getLongRegVal("r0")); + } + @Test public void testCallOtherFuncJavaDefMpInt() throws Exception { Translation tr = translateSleigh(getLanguageID(), """ @@ -524,6 +535,20 @@ public abstract class AbstractToyJitCodeGeneratorTest extends AbstractJitCodeGen assertEquals(0, tr.getLongRegVal("r0")); } + @Test + public void testCallOtherFuncJavaDefStaticMpInt() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + temp1:9 = zext(6:8); + temp2:9 = zext(2:8); + temp0:9 = func_st_mpUserop(temp1, temp2); + r0 = temp0(0); + """); + assertFalse(tr.library().gotFuncUseropCall); + tr.runFallthrough(); + assertFalse(tr.library().gotFuncUseropCall); + assertEquals(0x0606060602020202L, tr.getLongRegVal("r0")); + } + /** * Test that the emulator doesn't throw until the userop is actually encountered at run time. * diff --git a/GhidraDocs/GhidraClass/ExerciseFiles/Debugger/termmines.c b/GhidraDocs/GhidraClass/ExerciseFiles/Debugger/termmines.c index 7874b07c2e..f6fb8025c4 100644 --- a/GhidraDocs/GhidraClass/ExerciseFiles/Debugger/termmines.c +++ b/GhidraDocs/GhidraClass/ExerciseFiles/Debugger/termmines.c @@ -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. @@ -185,7 +185,7 @@ unsigned long parse_int(char *a, char *name, char *e; unsigned long val = strtoul(a, &e, 10); if (*e != 0 || val < min || max < val) { - fprintf(stderr, "Invalid %s: %s. Must be an integer between %d and %d.\n", + fprintf(stderr, "Invalid %s: %s. Must be an integer between %ld and %ld.\n", name, a, min, max); exit(-1); }