-
Notifications
You must be signed in to change notification settings - Fork 8
Java Agent
Java从1.5提供了java.lang.instrument包,用于给JVM植入java编程语言编写的Instrumentation,这种通过java.lang.instrument实现工具被称为java agent。java agent可以在JVM加载class时候修改目标字节码,或者修改已经加载的class的信息(已经加载的class不会再次初始化,可以调整方法体,但是不能添加/删除/重命名字段或方法,不能修改方法的签名或者更改继承)。
目前java agent支持两种模式加载,启动时加载,通过在JVM启动时配置-javaagent:jarpath[=options],还有一种通过Attach API动态加载java agent。
目前基于java agent实现应用的工具有很多,例如SkyWalking(APM),Arthas(诊断),ChaosBlade(混沌注入),JaCoCo(代码覆盖率)等等。
JVM命令行启动配置模式:
首先命令行需要增加-javaagent:jarpath[=options]参数,jarpath为java agent Jar包的路径,agent Jar中的manifest文件必须包含Premain-Class属性,属性值为agent class,该类必须包含premain方法,premain方法可以有两种形式:
public static void premain(String agentArgs, Instrumentation inst); (两个方法同时出现时,该方法优先级较高)
public static void premain(String agentArgs);java agent由system class loader负责加载(ClassLoader.getSystemClassLoader),所以在premain阶段,很多时候是看不到当前目标程序引用的资源的。通过agentArgs可以获取到Agent配置的options,具体参数的解析需要由Agent自身解析。
JVM启动后Attach模式:
在目标JVM启动后attach上去,agent Jar中的manifest文件必须包含Agent-Class属性,属性值为agent class,该类必须包含agentmain方法,agentmain方法可以有两种形式:
public static void agentmain(String agentArgs, Instrumentation inst);(两个方法同时出现时,该方法优先级较高)
public static void agentmain(String agentArgs);attach模式java agent也是由system class loader负责加载(ClassLoader.getSystemClassLoader)。
###二、Java Agent示例 目标代码:
package io.manbang.asm.demo;
/**
* @author weilong.hu
* @since 2021/11/23 14:17
*/
public class CargoTest {
public static void main(String[] args){
publishCargo();
}
/**
* 发货
*/
public static void publishCargo() {
System.out.println("publish cargo success...");
}
}java agent代码
package io.manbang.asm.demo;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
/**
* @author weilong.hu
* @since 2021/09/24 10:14
*/
public class Premain {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
if ("io/manbang/asm/demo/CargoTest".equals(className)) {
final ClassReader classReader = new ClassReader(classfileBuffer);
final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
//处理
final ClassVisitor classVisitor = new MyClassVisitor(classWriter);
classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);
final byte[] data = classWriter.toByteArray();
return data;
}
return null;
}
});
}
public static class MyClassVisitor extends ClassVisitor implements Opcodes {
public MyClassVisitor(ClassVisitor cv) {
super(ASM5, cv);
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
cv.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
exceptions);
//Base类中有两个方法:无参构造以及process方法,这里不增强构造方法
if (name.equals("publishCargo") && mv != null) {
mv = new MyMethodVisitor(mv);
}
return mv;
}
class MyMethodVisitor extends MethodVisitor implements Opcodes {
public MyMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
@Override
public void visitCode() {
super.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("start.......");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)
|| opcode == Opcodes.ATHROW) {
//方法在返回之前,打印"end"
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("end.......");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
mv.visitInsn(opcode);
}
}
}
}