使用javassist编辑本机方法类?

时间:2012-09-30 18:37:49

标签: java javassist

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

该方法看起来像:

public static native void glEnable(int paramInt);

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

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

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

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

3 个答案:

答案 0 :(得分:3)

我知道它有点偏离主题(2年后),但如果有人感兴趣,我认为可以通过setNativeMethodPrefix() method和足够的字节码转换完成。

答案 1 :(得分: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生成的代理。现在,您的应用可以在生产模式(快速)或跟踪模式下运行,具体取决于某些配置设置。

如果你想放弃并继续前进,我完全明白了。)

答案 2 :(得分:0)

我知道该线程很旧,并且也接受了我的答案和提示,但是我想我可以用更多的细节和代码节省别人的时间。我希望前一段时间有人对我这样做=)

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

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

然后在变压器内部修改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这样的类的问题之一是,通常,除非在引导类加载器中进行某些操作,否则无法从修改后的方法调用代码。

有关jvm如何解析方法的详细信息,另请参见https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#setNativeMethodPrefix-java.lang.instrument.ClassFileTransformer-java.lang.String-