5

使用 Javassist,有没有办法将代码注入本机方法?在这种情况下,我试图让我的游戏中的 OpenGL 调用在调用时打印出它们的名称和值,但是当我假设添加了 openGL dll 代码时,我所有的尝试都遇到了错误。

该方法看起来像:

public static native void glEnable(int paramInt);

由于这些方法最初没有主体,因此我发现实际添加代码的唯一方法是:

CtBehavior method = cl.getDeclaredBehaviors()[0];
method.setBody("System.out.println(\"Called.\");");

注入本身可以工作,但是一旦加载库,系统就会失败,说明该方法已经有代码。

我宁愿不使用任何预制工具进行呼叫跟踪,因为我需要为用户格式化和打印列表。有没有办法处理这个?如果没有,是否有某种方法可以在另一个类中找到对 OpenGL 方法的所有调用并将附加调用附加到跟踪器类?

4

3 回答 3

4

我知道这有点离题(2 年后),但如果有人感兴趣,我认为可以使用setNativeMethodPrefix() 方法和适当的字节码转换来完成。

于 2014-09-24T08:47:39.367 回答
1
With Javassist, is there any way to inject code into a native method?

从未尝试过,但我并不惊讶它不起作用。本机代码是 - 本机。它是一堆与 Java 字节码无关的平台特定位。Javassist 完全是关于 Java 字节码的。

您是否考虑过使用基于代理的 AOP?查看 http://static.springsource.org/spring/docs/current/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies

我不建议您在程序中实际使用 Spring,但它可能会给您一些关于如何解决问题的想法。我认为基于代理的 AOP 可能对您有用的原因是您不理会基于 OpenGL 的类,它只使用普通的本机方法。您生成一个纯 Java 的代理类,但具有与原始类相同的方法。您调用包含所需调用跟踪代码的代理类上的方法,以及使用其本机方法调用“普通对象”上的相应方法。

Spring 中的文档说他们在幕后使用 JDK 动态代理或 CGLIB。所以......我认为您可以直接使用其中一种技术来替代您的 javassist 解决方案。

希望这可以帮助。

[更新]

在上面的文字中,我以为您在谈论由您编写的主要具有实例方法的类。如果你说的是包装整个 OpenGL API,主要是静态方法,那么 AOP 代理方法就没有那么吸引人了。你想这样做有多糟糕?你可以:

  • 创建一个自定义类 - 一个带有工厂方法的单例类。您的单例类包装了整个 OpenGL API。没有记录/跟踪代码。只是对 API 的裸调用。
  • 修改整个应用程序中的每个调用以使用您的包装器,而不是直接调用 OpenGL

此时,您的应用程序与您现在所拥有的完全一样。

现在增强你的单例类的工厂方法以返回除了 OpenGL 调用之外什么都不做的准系统实例,或者它可以返回一个 CGLIB 生成的代理来记录每个方法。现在,您的应用可以在生产模式(快速)或跟踪模式下运行,具体取决于某些配置设置。

如果您想放弃并继续前进,我完全明白:)

于 2012-09-30T21:15:36.150 回答
1

我知道这个线程很旧,并且已经接受了答案并暗示了我的答案,但我想我可以用更多的细节和代码来节省别人的时间。我希望有人前段时间对我做过=)

我试图包装 java.lang.Thread#sleep 函数。我找到了 bytebuddy 的示例,但有方法替换,没有 javassist 的示例。最后我做到了。第一步是注册变压器和设置前缀:

instrumentation.addTransformer(transformer, true);
instrumentation.setNativeMethodPrefix(transformer, "MYPREFIX_");

然后在transformer里面修改Thread类:

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
    if (!"java/lang/Thread".equals(className)) {
        return null;
    }
    try {
        // prepare class pool with classfileBuffer as main source
        ClassPool cp = new ClassPool(true);
        cp.childFirstLookup = true;
        cp.insertClassPath(new ByteArrayClassPath(Thread.class.getName(), classfileBuffer));
        CtClass cc = cp.get(Thread.class.getName());

        // add native method with prefix
        CtMethod nativeSleep = CtMethod.make("void MYPREFIX_sleep(long millis) throws InterruptedException;", cc);
        nativeSleep.setModifiers(Modifier.PRIVATE + Modifier.STATIC + Modifier.NATIVE);
        cc.addMethod(nativeSleep);

        // replace old native method
        CtMethod m = cc.getDeclaredMethod("sleep", new CtClass[]{CtClass.longType});
        m.setModifiers(Modifier.PUBLIC + Modifier.STATIC); // remove native flag
        m.setBody("{" +
                "System.out.println(\"Start sleep\");" +
                "MYPREFIX_sleep($1);" +
                "}");
        byte[] byteCode = cc.toBytecode();
        cc.detach();
        return byteCode;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

然后开始改造:

instrumentation.retransformClasses(Thread.class);

像 java.lang.Thread 这样的类的问题之一是,通常你不能从修改的方法调用你的代码,除非你在引导类加载器中推送一些东西。

另请参阅https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#setNativeMethodPrefix-java.lang.instrument.ClassFileTransformer-java.lang.String-了解 jvm 的详细信息解决方法。

于 2020-11-23T18:06:24.700 回答