使用Javassist,有没有办法将代码注入本机方法?在这种情况下,我试图让我的游戏中的OpenGL调用在调用时打印出他们的名字和值,但是当我假设添加了openGL dll代码时,我的所有尝试都会遇到错误。
该方法看起来像:
public static native void glEnable(int paramInt);
由于这些方法最初没有正文,我发现实际添加代码的唯一方法是:
CtBehavior method = cl.getDeclaredBehaviors()[0];
method.setBody("System.out.println(\"Called.\");");
注入本身可以工作,但是一旦加载了库,系统就会失败,说该方法已经有了代码。
我宁愿不使用任何预制工具进行通话跟踪,因为我需要格式化并打印出用户列表。有办法处理这个吗? 如果没有,是否有某种方法可以在另一个类中找到对OpenGL方法的所有调用,并追加对跟踪器类的附加调用?
答案 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调用之外什么都不做的简单实例,或者它可以返回记录每个方法的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-。