使用attach方式实现类的动态修改和字节码增强

本文详细介绍了如何使用Java Attach Agent技术,通过ClassFileTransformer动态地修改GdMyTestMain类的字节码,实现在两个JVM之间的通信,重点展示了agentmain方法的使用和字节码操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

结论:attach有使两个jvm相互通信的能力,使用socket进行通信。–查了源码。

一:创建工程 attachdemo

File–>New–>Project…–>左边选择"Maven"–>工程名为:attachdemo 填写各项数据后–> Finish

二:实际代码

2.1.增强类GdAgentMain.java

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.AdviceAdapter;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import static org.objectweb.asm.Opcodes.ASM7;
/**
 * 功能:对 GdMyTestMain.class进行字节码增加,即修改返回值60。
 *
 * 动态 Attach 的 agent 会执行 agentmain 方法,而不是 premain 方法。
 */
public class GdAgentMain {
    public static class MyGdMethodVisitor extends AdviceAdapter {
        protected MyGdMethodVisitor(MethodVisitor mv, int access, String name, String desc) {
            super(ASM7, mv, access, name, desc);
        }

        @Override
        protected void onMethodEnter() {
            // 在方法开始插入 return 60;
            mv.visitIntInsn(BIPUSH, 60);//BIPUSH:当int取值-128~127时,JVM采用bipush指令将常量压入栈中
            mv.visitInsn(IRETURN); //visitInsn、visitVarInsn、visitMethodInsn等以Insn结尾的方法可以添加方法实现的字节码
        }
    }

    public static class MyGdClassVisitor extends ClassVisitor {

        public MyGdClassVisitor(ClassVisitor classVisitor) {
            super(ASM7, classVisitor);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
            // 只转换 GetAge 方法
            if ("GetAge".equals(name)) {
                return new MyGdMethodVisitor(mv, access, name, descriptor);
            }
            return mv;
        }
    }

    public static class MyGdClassFileTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
            if (!"GdMyTestMain".equals(className)) return bytes;
            ClassReader cr = new ClassReader(bytes);
            ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
            ClassVisitor cv = new MyGdClassVisitor(cw); //使用自己的访问器
            cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
            return cw.toByteArray();
        }
    }

    public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
        System.out.println("agentmain called");
        inst.addTransformer(new MyGdClassFileTransformer(), true); //1.把自己的实现类变形器加载进来。
        Class classes[] = inst.getAllLoadedClasses();
        for (int i = 0; i < classes.length; i++) {
            if (classes[i].getName().equals("GdMyTestMain")) { //2.过滤GdMyTestMain的类。
                System.out.println("Reloading: " + classes[i].getName());
                inst.retransformClasses(classes[i]); // 3.重新加载GdMyTestMain类。
                break;
            }
        }
    }
}

2.2 pom.xml 的内容

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>JavaAttachAgent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>7.1</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-commons</artifactId>
            <version>7.1</version>
        </dependency>
        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8.0</version>
            <scope>system</scope>
            <systemPath>/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/lib/tools.jar</systemPath>
        </dependency>
    </dependencies>

    <build>
        <finalName>gd-my-attach-agent</finalName>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Agent-Class>GdAgentMain</Agent-Class>
                            <Premain-Class>GdAgentMain</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>

                        <goals>
                            <goal>shade</goal>
                        </goals>

                        <configuration>
                            <relocations>
                                <relocation>
                                    <pattern>org.ow2.asm</pattern>
                                    <shadedPattern>me.ya.agent.hidden.org.ow2.asm</shadedPattern>
                                </relocation>
                                <relocation>
                                    <pattern>org.objectweb.asm</pattern>
                                    <shadedPattern>me.ya.agent.hidden.org.objectweb.asm</shadedPattern>
                                </relocation>
                            </relocations>

                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
            <!-- 打jar包时,排除下面两个java文件打包进去 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.0.2</version>
                <configuration>
                    <excludes>
                        <exclude>GdMyTestMain.java</exclude>
                        <exclude>GdMyAttachMain.java</exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2.3 GdMyAttachMain.java 的代码

import com.sun.tools.attach.VirtualMachine;

public class GdMyAttachMain {
    /**
     * PID 通过 jps查看对应的进程ID-->GdMyTestMain
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
       String PID="8127";
//        String PID=args[0];
        System.out.println(PID);
        VirtualMachine vm = VirtualMachine.attach(PID);
        try {
            vm.loadAgent("/Users/lichunmei/Documents/workspace/study/attachdemo/target/gd-my-attach-agent.jar");
        } finally {
            vm.detach();
        }
    }
}

2.4 GdMyTestMain.java的代码

import java.util.concurrent.TimeUnit;

public class GdMyTestMain {
    public static void main(String[] args) throws InterruptedException {
        while (true) {
            System.out.println(GetAge());
            TimeUnit.SECONDS.sleep(4);
        }
    }

    public static int GetAge() {
        return 100; // 修改后 return 60;
    }
}

三:运行测试步骤

1. 只把GdAgentMain.java文件打包到 gd-my-attach-agent.jar 里。在pom.xml添加了排除另两个类。
> mvn clean package

2. 直接在idea 中运行 GdMyTestMain.java的 main.
jps 查到其进程号,假设为 76173. 修改 GdMyAttachMain.java
--里修改加载的路径为gd-my-attach-agent.jar的全路径。
--PID="8127"
-在idea中运行 GdMyAttachMain.java 的main,执行完就结束。

4.看到  GdMyTestMain 的输出从100 到 60 .
结果如下:
100
100
100
100
objc[8127]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/bin/java (0x10fad44c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x1197964e0). One of the two will be used. Which one is undefined.
agentmain called
Reloading: GdMyTestMain
60
60
60
60
60
60

备注:上面抛了objc[7816]:.........的问题,据说"这是Mac版IDEA自身的BUG,你可以忽略它!!"

总结:字节码增加是针对目标jvm增加。最终依然只有目标一个jvm。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值