Java Agent + javassist 调试demo

java agent是基于java instrument实现,instrument的底层实现依赖于JVMTI,也就是JVM Tool Interface。

代码准备

本次实验在工程中新增了3个module,4个类

Module Class Describe
learn-main Handle 代理实验类
learn-main HandleMain 实验应用main class
learn-module AgentLauncher agent Premain class
learn-agent-main AgentmainAttachMain attach VirtualMachine main class

java代码如下

Handle:

public class Handle {
    
    

    public void invoke() {
    
    
        System.out.println("Handle.invoke....");
    }
    
    public void call() {
    
    
        System.out.println("Handle.call....");
    }

    public String hello(){
    
    
        return "hello ";
    }
}

HandleMain:

import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.Callable;

public class HandleMain {
    
    

    public static void main(String[] args) throws Exception {
    
    
        Handle handle = new Handle();
        Scanner scanner = new Scanner(System.in);
        while (true) {
    
    
            String txt = scanner.next();
            if (Objects.equals("exit", txt)) {
    
    
                break;
            }
            handle.invoke();
            handle.call();
            System.out.println("Handle.hello():" + handle.hello());
        }
    }
import javassist.*;
import javassist.bytecode.CodeAttribute;

import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.Exception;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Objects;
import java.util.concurrent.Callable;

public class AgentLauncher {
    
    

	// 启动时接入
    public static void premain(String agentArgs, Instrumentation inst) {
    
    
        System.out.println("premain.agentArgs: " + agentArgs);
        handle(agentArgs, inst);
    }

	// 运行时接入(VirtualMachine attach)
    public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
    
    
        System.out.println("agentmain.agentArgs: " + agentArgs);
        handle(agentArgs, inst);

		System.out.println("isRetransformClassesSupported:" + inst.isRetransformClassesSupported());
        System.out.println("isRedefineClassesSupported:" + inst.isRedefineClassesSupported());
        
		// 重新transform已经加载的类
        inst.retransformClasses(
                Class.forName("com.saleson.learn.java.agent.Handle")
        );
            }

    private static void handle(String agentArgs, Instrumentation inst) {
    
    
        System.out.println("agentArgs: " + agentArgs);
        if (StringUtils.isBlank(agentArgs)) {
    
    
            throw new NullPointerException("agentArgs is null.");
        }
        String[] args = agentArgs.split(",");
        if (args.length > 3) {
    
    
            throw new IllegalArgumentException("agentArgs is illegal.");
        }
        String mode, className = null, methodName = null;

        if (args.length == 1) {
    
    
            mode = args[0];
        } else if (args.length == 2) {
    
    
            mode = args[0];
            className = args[1];
        } else {
    
    
            mode = args[0];
            className = args[1];
            methodName = args[2];
        }
		javassistTransform(inst, className, methodName);
    }
    
    private static void javassistTransform(Instrumentation instrumentation, String className, String methodName) {
    
    
        instrumentation.addTransformer(new ClassFileTransformer() {
    
    
            @Override
            public byte[] transform(ClassLoader loader,
                                    String cn, Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain,
                                    byte[] classfileBuffer) throws IllegalClassFormatException {
    
    
                if (Objects.equals(className.replaceAll("\\.", "/"), cn)) {
    
    
                    try {
    
    
                        return javassistTransform(loader, classfileBuffer, methodName);
                    } catch (Throwable t) {
    
    
                        throw new IllegalClassFormatException(t.getClass().getName() + " -> " + t.getMessage());
                    }
                }
                return classfileBuffer;
            }
        }, true);
    }

    private static byte[] javassistTransform(ClassLoader loader, byte[] classfileBuffer, String methodName) throws Exception {
    
    
        ClassPool pool = ClassPool.getDefault();
        pool.appendClassPath(new LoaderClassPath(loader));
        CtClass ctClass = pool.makeClass(new ByteArrayInputStream(classfileBuffer));
        if ("*".equals(methodName) || StringUtils.isBlank(methodName)) {
    
    
            CtMethod[] ctmethods = ctClass.getMethods();
            for (CtMethod ctMethod : ctmethods) {
    
    
                CodeAttribute ca = ctMethod.getMethodInfo2().getCodeAttribute();
                if (ca == null) {
    
    
                    continue;
                }
                if (!ctMethod.isEmpty()) {
    
    
                    ctMethod.insertBefore("System.out.println(\"hello Im agent : " + ctMethod.getName() + "\");");
                }
            }
            return ctClass.toBytecode();
        }

        CtMethod ctMethod = ctClass.getDeclaredMethod(methodName);
        ctMethod.insertBefore("System.out.println(\"hello Im agent : " + ctMethod.getName() + "\");");
        return ctClass.toBytecode();
    }
}

AgentmainAttachMain:

import com.saleson.learn.util.Utils;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import java.util.List;

public class AgentmainAttachMain {
    
    

    public static void main(String[] args) {
    
    
        // check args
        if (args.length == 2 && !Utils.isBlank(args[0]) && !Utils.isBlank(args[1])) {
    
    
            unsafeExec(() -> attachAgent(args[0], args[1]));
            return;
        } else if (args.length != 3 || Utils.isBlank(args[0]) || Utils.isBlank(args[1]) || Utils.isBlank(args[2])) {
    
    
            throw new IllegalArgumentException("illegal args");
        }
        unsafeExec(() -> attachAgent(args[0], args[1], args[2]));
    }

    // 从表列表查找运行HandleMain的jvm pid
    private static void attachAgent(String agentJarPath, String cfg) throws Exception {
    
    
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor descriptor : list) {
    
    
            if (descriptor.displayName().endsWith("HandleMain")) {
    
    
                VirtualMachine virtualMachine = VirtualMachine.attach(descriptor.id());
                virtualMachine.loadAgent(agentJarPath, cfg);
                virtualMachine.detach();
            }
        }
    }

    // 指定jvm pid 加载Agent
    private static void attachAgent(String targetJvmPid, String agentJarPath, String cfg) throws Exception {
    
    
        VirtualMachine vmObj = VirtualMachine.attach(targetJvmPid);
        if (vmObj != null) {
    
    
            vmObj.loadAgent(agentJarPath, cfg);
            vmObj.detach();
        }
    }

    /**
     * 获取异常的原因描述
     *
     * @param t 异常
     * @return 异常原因
     */
    public static String getCauseMessage(Throwable t) {
    
    
        if (null != t.getCause()) {
    
    
            return getCauseMessage(t.getCause());
        }
        return t.getMessage();
    }

    interface Exec {
    
    
        void exec() throws Throwable;
    }

    private static void unsafeExec(Exec exec) {
    
    
        try {
    
    
            exec.exec();
        } catch (Throwable t) {
    
    
            t.printStackTrace();
            System.err.println("load jvm failed : " + getCauseMessage(t));
            System.exit(-1);
        }
    }
}

各module的 pom.xml

learn-module/pom.xml

    <dependencies>
		<dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <!-- maven 打包插件 打原始jar包 第三方依赖打入jar包中-->
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestEntries>
<!--                            <Class-Path>.</Class-Path>-->
                            <Premain-Class>com.saleson.learn.agent.instrument.AgentLauncher</Premain-Class>
                            <Agent-Class>com.saleson.learn.agent.instrument.AgentLauncher</Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
                        <phase>package</phase> <!-- 指定在打包节点执行jar包合并操作 -->
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

learn-agent-main/pom.xml

    <properties>
        <tools-jar>${java.home}/../lib/tools.jar</tools-jar>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8</version>
            <scope>system</scope>
            <systemPath>${tools-jar}</systemPath>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.saleson.learn.agent.AgentmainAttachMain</mainClass>
                        </manifest>
                        <manifestEntries>
                            <Class-Path>. ${java.home}/../lib/tools.jar</Class-Path>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

在项目下执行mvn clean install 命令打包编译。

agent 接入的两种方式

1、启动时接入

启动脚本的格式

java -javaagent:{
    
    agent.jar 路径}[={
    
    agent 参数}] {
    
    mainClass}

启动脚本:

$ java -javaagent:../../learn-module/target/learn-module-1.0-SNAPSHOT-jar-with-dependencies.jar=com.saleson.learn.java.agent.Handle,*  -jar learn-main-1.0-SNAPSHOT.jar

2、运行时接入

先启动运行HandleMain类

$ java -jar learn-main-1.0-SNAPSHOT.jar

然后通过ps命令查看jvm 的pid

$ ps -aux | grep learn-main
saleson          57375   0.0  0.0  4268776    828 s004  S+    6:41下午   0:00.00 grep learn-main
saleson          57340   0.0  0.2 10035276  30568 s003  S+    6:41下午   0:00.17 java -jar learn-main-1.0-SNAPSHOT.jar

运行AgentmainAttachMain类,完成agent运行时的接入
运行脚本格式:

java -jar {
    
    attachMain.jar} {
    
    pid} {
    
    agent.jar绝对路径} {
    
    agent参数}

运行脚本:

$ java -jar learn-agent-main-1.0-SNAPSHOT.jar 57340 /Users/saleson/IdeaProjects/learn/learn-module/target/learn-module-1.0-SNAPSHOT-jar-with-dependencies.jar com.saleson.learn.java.agent.Handle,*

运行后:
在这里插入图片描述

在这里插入图片描述

Idea调试

本地module调试

添加Debug Configuration
在这里插入图片描述

Field Content
Main class com.saleson.learn.java.agent.HandleMain
VM options -javaagent:learn-module/target/learn-module-1.0-SNAPSHOT.jar=com.saleson.learn.java.agent.Handle,*

在AgentLauncher.premain()方法中设置断点,运行Debug
在这里插入图片描述

本地lib调试

在另一个项目中新增Main class和实验所用的类:

EHandle.java
EHandleMain.java

将learn-module-1.0-SNAPSHOT.jar以lib的形式添加到Idea工程中,操作路径:
File > Project Structure > Project Settings > Libraries
在这里插入图片描述

添加Debug Configuration
在这里插入图片描述

Field Content
Main class com.saleson.java.agent.EHandleMain
VM options -javaagent:/Users/saleson/IdeaProjects/learn/learn-module/target/learn-module-1.0-SNAPSHOT.jar=com.saleson.learn.java.agent.Handle,*

在AgentLauncher.premain()方法中设置断点,运行Debug
在这里插入图片描述

采用jdwp进行调试

接下来使用运行时接入的方式来实验使用jdwp进行远程调试

添加jdwp参数运行learn-main-1.0-SNAPSHOT.jar

$ java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5050 -jar learn-main-1.0-SNAPSHOT.jar

在AgentLauncher.agentmain()方法中设置断点,再运行AgentmainAttachMain类(不要忘记pid等参数)
在这里插入图片描述

在learn工程(agent源码)中添加Remote Debug Configuration
在这里插入图片描述

运行learn-main-1.0-SNAPSHOT.jar的命令窗口将日志打印出来
在这里插入图片描述

参考

Java程序员必知:深入理解Instrument
本地调试和远程调试javaagent的premain方法
Java Agent(二)Attach机制及运行时加载agent
【精通 JVM 原理】浅析 JavaAgent & Instrumentation 机制
Java Agent基本简介和使用
javaagent使用指
Javaagent使用指南

猜你喜欢

转载自blog.csdn.net/Mr_rain/article/details/124148243