• 『Java』初试JavaAgent实现修改字节码和插桩


    Java Agent机制

    Java Agent提供两个类方法来修改JVM字节码,前者在加载class前修改、后者支持对已加载class的字节码修改并重加载

    public static void premain(String agentArgs, Instrumentation inst)
    public static void agentmain(String agentArgs, Instrumentation inst)
    
    • 1
    • 2
    1. premain: 属于Agent模式,启动Java之前添加-javaagent:xxx.jar参数,启动后首先会运行jar包的premain方法
    2. agentmain: 属于attach模式,通过运行过程中的进程id动态修改已加载的字节码

    Java Agent程序

    MANIFEST.MF

    无论是哪种模式,需要将Agent程序打包成jar才能加载,并且拥有/resources/META-INF/MANIFEST.MF,包含以下内容:
    (最后一行必须为空)

    Manifest-Version: 1.0
    Premain-Class: Xxx
    Agent-Class: Xxx
    Can-Redefine-Classes: true
    Can-Retransform-Classes: true
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    指定Premain-ClassAgent-Class用于确定Agent程序入口,就会调用该类的preman或agentmain方法;
    指定Can-Redefine-ClassesCan-Retransform-Classes参数开启修改字节码功能

    premain

    实现premain方法,并且添加一个自定义transformer,把操作放在自定义的transformer内,该transformer要实现ClassFileTransformer.transform()

    主程序运行时:加载每个类前都会进入transform(),可以获取到它的加载器、类名、字节码buffer等

    import java.lang.instrument.ClassFileTransformer;
    import java.lang.instrument.IllegalClassFormatException;
    import java.lang.instrument.Instrumentation;
    import java.security.ProtectionDomain;
    
    public class PremainTest {
        public static void premain(String agentArgs, Instrumentation inst) {
            inst.addTransformer(new DefineTransformer(), true);
        }
    
        static class DefineTransformer implements ClassFileTransformer {
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            	// 输出jvm加载的类的名称
                System.out.println("premain load Class:" + className);
                return classfileBuffer;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    agentmain

    对于agentmain也是一样的流程,但多一步inst.retransformClasses()的操作让JVM重新加载修改过的类的字节码,否则修改不会生效

    在transform()内部修改字节码这里使用Javassist作为演示

    https://ho1aas.blog.csdn.net/article/details/123205525

    ......
        public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
            inst.addTransformer(new DefineTransformer(), true);
            Class classes[] = inst.getAllLoadedClasses();
            for (int i = 0; i < classes.length; i++) {
                if (classes[i].getName().equals("TargetClass")) {
                    inst.retransformClasses(classes[i]);
                    break;
                }
            }
        }
        static class DefineTransformer implements ClassFileTransformer{
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
                if("TargetClass".equals(className)){
                    try {
                        ClassPool classPool = ClassPool.getDefault();
                        CtClass clazz = classPool.get(className);
                        CtMethod method = clazz.getDeclaredMethod("testMethod");
                        method.setBody("{return true;}");
    
                        byte[] bytes = clazz.toBytecode();
                        clazz.detach();
                        return bytes;
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                }
                return classfileBuffer;
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    使用agentmain的Agent jar包写好后,再写一个触发的程序:在jvm搜索待修改类的进程,然后attach进行Agent修改即可

    import com.sun.tools.attach.*;
    import java.util.List;
    
    public class AgentMainTest {
        public static void main(String[] args) throws Exception{
            List<VirtualMachineDescriptor> list = VirtualMachine.list();
            for (VirtualMachineDescriptor desc : list) {
                if(desc.displayName().equals("TargetClass")){
                    String pid = desc.id();
                    String agentPath = "Agentmain.jar";
                    VirtualMachine vm = VirtualMachine.attach(pid);
                    vm.loadAgent(agentPath);
                    vm.detach();
                    break;
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Maven生成Agent jar包

    Agent机制只认jar包,还要涉及到MANIFEST编写和打包javassist等依赖,因此使用maven打包就比较方便

    使用maven的插件maven-assembly-plugin可以做到自定义jar包名称、自动生成MANIFEST、自动打包依赖

        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.pluginsgroupId>
                    <artifactId>maven-assembly-pluginartifactId>
                    <version>2.4version>
                    <configuration>
                        <appendAssemblyId>falseappendAssemblyId>
                        
                        <finalName>${project.artifactId}-${project.version}finalName>
                        <descriptorRefs>
                            <descriptorRef>jar-with-dependenciesdescriptorRef>
                        descriptorRefs>
                        <archive>
                            <manifest>
                                <addClasspath>trueaddClasspath>
                            manifest>
                            
                            <manifestEntries>
                                <Premain-Class>PremainTestPremain-Class>
                                <Agent-Class>PremainTestAgent-Class>
                                <Can-Redefine-Classes>trueCan-Redefine-Classes>
                                <Can-Retransform-Classes>trueCan-Retransform-Classes>
                            manifestEntries>
                        archive>
                    configuration>
                    <executions>
                        <execution>
                            <id>make-assemblyid>
                            <phase>packagephase>
                            <goals>
                                <goal>assemblygoal>
                            goals>
                        execution>
                    executions>
                plugin>
            plugins>
        build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    mvn clean然后mvn install即可

    Agent demo示例

    代码

    目标类

    public class Main {
        final static String flag = "0";
        public static void main(String[] args) throws Exception{
            while (true) {
                System.out.println(test(flag));
                Thread.sleep(3000);
            }
        }
    
        static boolean test(String flag){
            return !flag.equals("0");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    循环输出false

    在这里插入图片描述

    Agent

    import javassist.*;
    
    import java.lang.instrument.ClassFileTransformer;
    import java.lang.instrument.Instrumentation;
    import java.lang.instrument.UnmodifiableClassException;
    import java.security.ProtectionDomain;
    
    public class PremainTest {
        public static void premain(String agentArgs, Instrumentation inst) {
            inst.addTransformer(new JavassistTransformer(), true);
        }
    
        public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
            inst.addTransformer(new JavassistTransformer(), true);
            Class classes[] = inst.getAllLoadedClasses();
            for (int i = 0; i < classes.length; i++) {
                if (classes[i].getName().equals("Main")) {
                    inst.retransformClasses(classes[i]);
                    break;
                }
            }
        }
    
        static class JavassistTransformer implements ClassFileTransformer{
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
                if("Main".equals(className)){
                    try {
                        ClassPool classPool = ClassPool.getDefault();
                        CtClass clazz = classPool.get(className);
    
                        CtMethod method = clazz.getDeclaredMethod("test");
    					
    					// 完全修改test类方法体或者修改返回值
                        
                        // method.insertAfter("return true;");
                        method.setBody("{return flag.equals(\"0\");}");
    
                        byte[] bytes = clazz.toBytecode();
                        clazz.detach();
    
                        return bytes;
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                }
                return classfileBuffer;
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    pom.xml下载javassist

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
    
        <groupId>org.examplegroupId>
        <artifactId>JavassistAgentTestartifactId>
        <version>1.0-SNAPSHOTversion>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
            <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        properties>
    
        <packaging>jarpackaging>
    
        <dependencies>
            
            <dependency>
                <groupId>org.javassistgroupId>
                <artifactId>javassistartifactId>
                <version>3.22.0-GAversion>
            dependency>
    
        dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.pluginsgroupId>
                    <artifactId>maven-assembly-pluginartifactId>
                    <version>2.4version>
                    <configuration>
                        <appendAssemblyId>falseappendAssemblyId>
                        <finalName>${project.artifactId}-${project.version}finalName>
                        <descriptorRefs>
                            <descriptorRef>jar-with-dependenciesdescriptorRef>
                        descriptorRefs>
                        <archive>
                            <manifest>
                                <addClasspath>trueaddClasspath>
                            manifest>
                            <manifestEntries>
                                <Premain-Class>PremainTestPremain-Class>
                                <Agent-Class>PremainTestAgent-Class>
                                <Can-Redefine-Classes>trueCan-Redefine-Classes>
                                <Can-Retransform-Classes>trueCan-Retransform-Classes>
                            manifestEntries>
                        archive>
                    configuration>
                    <executions>
                        <execution>
                            <id>make-assemblyid>
                            <phase>packagephase>
                            <goals>
                                <goal>assemblygoal>
                            goals>
                        execution>
                    executions>
                plugin>
            plugins>
        build>
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    Agent模式运行

    IDEA添加vm option -javaagent运行目标类即可

    在这里插入图片描述
    修改成功

    在这里插入图片描述

    Attach模式运行

    先运行目标类再打开attach模式

    在这里插入图片描述
    动态修改已加载的字节码成功

    在这里插入图片描述

    参考

    https://ho1aas.blog.csdn.net/article/details/123205525
    https://www.cnblogs.com/rickiyang/p/11368932.html
    https://javasec.org/javase/JavaAgent/

    欢迎关注我的CSDN博客 :@Ho1aAs
    版权属于:Ho1aAs
    本文链接:https://ho1aas.blog.csdn.net/article/details/126158738
    版权声明:本文为原创,转载时须注明出处及本声明

  • 相关阅读:
    UI组件使用技巧
    vue.js毕业设计,基于vue.js前后端分离外卖点餐系统(H5移动项目) 开题报告
    Docker:Harbor
    gin路由相关方法
    Monkey测试
    Springboot项目连接Redis(jedis)
    VUE2.x
    区块链的两个核心概念之一签名, 另一个是共识.
    【单调队列】 单调队列的“扫描线”理解
    软考通过率低吗?怎么备考?
  • 原文地址:https://blog.csdn.net/Xxy605/article/details/126158738