mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GP-6158: Allows static methods to be marked @PcodeOp in UseropLibrary
This commit is contained in:
@@ -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<JitCallOtherOpIf> {
|
||||
};
|
||||
}
|
||||
|
||||
static <E> List<E> tail(List<E> list) {
|
||||
return list.subList(1, list.size());
|
||||
}
|
||||
|
||||
record PlacedParam<N>(Emitter<N> em, List<JitVal> args, List<Parameter> params) {}
|
||||
|
||||
/**
|
||||
* Emit code to implement the Direct strategy (see the class documentation)
|
||||
*
|
||||
@@ -244,7 +249,33 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
|
||||
};
|
||||
}
|
||||
|
||||
<N extends Next> ObjInv<?, LIB, N, ?> doInv(Emitter<N> em, List<JitVal> args,
|
||||
<N extends Next> PlacedParam<? extends Ent<N, ?>> placeNextParam(Emitter<N> em,
|
||||
List<JitVal> args, List<Parameter> 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));
|
||||
}
|
||||
|
||||
<N extends Next> Inv<?, N, Bot> doInvVirtual(Emitter<N> em, List<JitVal> args,
|
||||
List<Parameter> 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;
|
||||
}
|
||||
|
||||
<N extends Next> ObjInv<?, LIB, N, ?> doInvVirtualRec(Emitter<N> em, List<JitVal> args,
|
||||
List<Parameter> params) {
|
||||
if (params.isEmpty()) {
|
||||
/**
|
||||
@@ -254,20 +285,28 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
|
||||
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<? extends Ent<N, ?>> next = placeNextParam(em, args, params);
|
||||
ObjInv<?, LIB, ? extends Ent<N, ?>, ?> inv =
|
||||
doInvVirtualRec(next.em, next.args, next.params);
|
||||
return Inv.takeQArg(inv);
|
||||
}
|
||||
|
||||
<N extends Next> Inv<?, N, ?> doInvStaticRec(Emitter<N> em, List<JitVal> args,
|
||||
List<Parameter> params) {
|
||||
if (params.isEmpty()) {
|
||||
return Op.invokestatic(em, libType, method.getName(), MthDesc.reflect(method),
|
||||
false);
|
||||
}
|
||||
PlacedParam<? extends Ent<N, ?>> next = placeNextParam(em, args, params);
|
||||
Inv<?, ? extends Ent<N, ?>, ?> inv =
|
||||
doInvStaticRec(next.em, next.args, next.params);
|
||||
return Inv.takeQArg(inv);
|
||||
}
|
||||
|
||||
<N extends Next> Inv<?, N, ?> doInvStatic(Emitter<N> em, List<JitVal> args,
|
||||
List<Parameter> params) {
|
||||
return doInvStaticRec(em, args, params);
|
||||
}
|
||||
};
|
||||
|
||||
var tryCatchBlock = Misc.tryCatch(em, Lbl.create(),
|
||||
@@ -275,16 +314,9 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
|
||||
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<JitCallOtherOpIf> {
|
||||
// TODO: Can annotation specify signedness of return value?
|
||||
var write = new Object() {
|
||||
public <T extends BPrim<?>, JT extends SimpleJitType<T, JT>> Emitter<Bot> doWrite(
|
||||
Inv<?, Bot, Bot> inv, Class<?> returnType) {
|
||||
Inv<?, Bot, ?> inv, Class<?> returnType) {
|
||||
JT type = SimpleJitType.forJavaType(returnType);
|
||||
return inv
|
||||
.step(Inv::retQ, type.bType())
|
||||
|
||||
@@ -825,6 +825,25 @@ public interface Methods {
|
||||
return (Inv) inv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop an argument and a parameter without checking
|
||||
* <p>
|
||||
* 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 <MR> the return type
|
||||
* @param <SN1> the new remaining stack contents
|
||||
* @param <SN0> 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 <MR extends BType, SN1 extends Next, SN0 extends Ent<SN1, ?>> Inv<MR, SN1, ?>
|
||||
takeQArg(Inv<MR, SN0, ?> 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 <SN extends Next> Emitter<SN> retQVoid(Inv<?, SN, Bot> inv) {
|
||||
public static <SN extends Next> Emitter<SN> retQVoid(Inv<?, SN, ?> inv) {
|
||||
return inv.em;
|
||||
}
|
||||
|
||||
@@ -1007,7 +1026,7 @@ public interface Methods {
|
||||
* value
|
||||
*/
|
||||
public static <RT extends BNonVoid, SN extends Next> Emitter<Ent<SN, RT>>
|
||||
retQ(Inv<?, SN, Bot> inv, RT returnType) {
|
||||
retQ(Inv<?, SN, ?> inv, RT returnType) {
|
||||
return (Emitter) inv.em;
|
||||
}
|
||||
|
||||
|
||||
@@ -263,7 +263,13 @@ public abstract class AnnotatedPcodeUseropLibrary<T> 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(
|
||||
|
||||
@@ -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<byte[]> 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() {
|
||||
|
||||
@@ -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<byte[]> executor,
|
||||
@OpLibrary PcodeUseropLibrary<byte[]> library,
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user