Java 13+无需特定选项使用Javassist插桩Native方法的方案
Java 13+ 无需废弃选项的Native方法Javassist插桩方案
核心思路:类加载阶段插桩(推荐)
在类被JVM加载前通过Agent修改字节码,避开Java 13+对运行时重定义类添加方法的限制(无需启用废弃的-XX:+AllowRedefinitionToAddDeleteMethods选项)。具体逻辑是:
- 重命名原Native方法(添加前缀标记)
- 新增一个与原方法签名完全一致的非Native方法,在其中插入日志逻辑后调用重命名后的Native方法
代码示例
// 在Agent的premain/agentmain的transform方法中执行 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { // 仅处理目标类 if (!className.equals("com/example/NativeClass")) { return classfileBuffer; } try { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass(new ByteArrayInputStream(classfileBuffer)); // 定位目标Native方法 CtMethod nativeMethod = ctClass.getDeclaredMethod("doSomething"); // 重命名原Native方法,保留原实现 String originalMethodName = "original_" + nativeMethod.getName(); nativeMethod.setName(originalMethodName); // 创建与原方法同名的非Native方法 CtMethod wrapperMethod = CtNewMethod.copy(nativeMethod, nativeMethod.getName(), ctClass, null); // 移除native修饰符 wrapperMethod.setModifiers(wrapperMethod.getModifiers() & ~Modifier.NATIVE); // 插入日志逻辑,然后调用重命名后的Native方法 wrapperMethod.setBody("{" + " System.out.println(\"调用Native方法 " + nativeMethod.getName() + ",参数:\" + $1);" + " " + originalMethodName + "($1);" + "}"); ctClass.addMethod(wrapperMethod); return ctClass.toBytecode(); } catch (Exception e) { e.printStackTrace(); return classfileBuffer; } }
运行时重定义的替代方案(仅当必须在类加载后插桩时使用)
如果需要在类已加载后进行插桩,无法新增方法,只能修改原Native方法的实现:
- 通过
Unsafe获取原Native方法的入口地址 - 用Javassist移除原方法的
native修饰符,插入日志逻辑后,借助JNI/JNA调用原Native入口地址
关键代码片段
获取原Native方法的入口地址
// 反射获取Unsafe实例 Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); // 获取目标Native方法的入口地址 Method originalMethod = NativeClass.class.getDeclaredMethod("doSomething", String.class); long nativeAddr = unsafe.getNativeAddress(originalMethod);
修改原方法体并调用原Native实现
CtClass ctClass = ClassPool.getDefault().get("com.example.NativeClass"); CtMethod targetMethod = ctClass.getDeclaredMethod("doSomething"); // 移除native修饰符 targetMethod.setModifiers(targetMethod.getModifiers() & ~Modifier.NATIVE); // 插入日志+调用原Native逻辑(需借助JNI工具类) targetMethod.setBody("{" + " System.out.println(\"调用Native方法 doSomething,参数:\" + $1);" + " com.example.NativeInvoker.callNative(" + nativeAddr + ", this, $1);" + "}"); // 重定义类 instrumentation.redefineClasses(new ClassDefinition(NativeClass.class, ctClass.toBytecode()));
JNI工具类实现(NativeInvoker)
// C代码片段 #include <jni.h> #include <stdint.h> typedef void (*DoSomethingFunc)(JNIEnv*, jobject, jstring); JNIEXPORT void JNICALL Java_com_example_NativeInvoker_callNative(JNIEnv *env, jclass cls, jlong addr, jobject obj, jstring arg) { DoSomethingFunc func = (DoSomethingFunc)addr; func(env, obj, arg); }
内容的提问来源于stack exchange,提问作者ainlolcat
相关产品推荐
相关产品推荐

