美文网首页
java instrumentation attach模式快速入

java instrumentation attach模式快速入

作者: 青_雉 | 来源:发表于2020-12-16 22:24 被阅读0次

    本文旨在简单粗暴体验instrumentation attach模式的玩法,给读者一个直观的体验,概念方面不多介绍

    场景

    有一个spring的http接口定义如下,每次调用返回一个随机uuid,此处的RandomUtil采用的hutool的工具类。

    @GetMapping("/play")
    @ResponseBody
    public String health() {
      return RandomUtil.simpleUUID();
    }
    

    期望通过编写一个agent,attach到当前进程实现串改程序逻辑,每次调用都返回hello

    开发一个agent jar

    入口函数

    先要写一个agentmain函数,类似我们写helloword的main函数

    public static void agentmain(String agentArgs, Instrumentation inst)  throws ClassNotFoundException, UnmodifiableClassException, InterruptedException {
      //注册字节码转换逻辑
      inst.addTransformer(new PlayClassFileTransformer(), true);
      //使之生效
      inst.retransformClasses(RandomUtil.class);
      System.out.println("Agent Main Done");
    }
    

    字节码转换逻辑

    函数内部注册了PlayClassFileTransformer, 内部实现逻辑:

    • 如果不是RandomUtil,则返回null,表示不作替换
    • 否则替换新的字节码内容,字节码内容来自本地提前准备好的一个class文件
    public class PlayClassFileTransformer implements ClassFileTransformer {
    
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            if (!"cn/hutool/core/util/RandomUtil".equals(className)){
                return null;
            }
    
            return getBytesFromFile("/Users/***/code/***/instrument-play/docs/RandomUtil.class");
        }
    
        public static byte[] getBytesFromFile(String fileName) {
            try {
                // precondition
                File file = new File(fileName);
                InputStream is = new FileInputStream(file);
                long length = file.length();
                byte[] bytes = new byte[(int) length];
    
                // Read in the bytes
                int offset = 0;
                int numRead = 0;
                while (offset <bytes.length
                    && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
                    offset += numRead;
                }
    
                if (offset < bytes.length) {
                    throw new IOException("Could not completely read file "
                        + file.getName());
                }
                is.close();
                return bytes;
            } catch (Exception e) {
                System.out.println("error occurs in _ClassTransformer!"
                    + e.getClass().getName());
                return null;
            }
        }
    }
    

    注意: 此处字节码是我提前准备好的,基于hutool的源码随便改了一笔,把simpleUUID函数的返回值改为了hello。

    retransformClasses

    注册完转换器后,替换逻辑执行的时机需要依赖于此,所以需要手动执行这个函数,否则替换逻辑是不生效的。以下引用一小段ClassFileTransformer的注释:

    the transformer will be called for every new class definition and every class redefinition.

    另外,此处因为需要指定需要retransform的类型,所以agent的工程里也引入了对hutool的依赖:

    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>4.1.3</version>
      <scope>provided</scope>
    </dependency>
    

    manifest文件

    jar包生成后需要manifest文件,所以添加如下maven配置:

    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-assembly-plugin</artifactId>
      <configuration>
        <descriptorRefs>
          <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive>
          <manifestEntries>
            <Agent-Class>org.example.instrument.AgentMain</Agent-Class>
            <Can-Redefine-Classes>true</Can-Redefine-Classes>
            <Can-Retransform-Classes>true</Can-Retransform-Classes>
          </manifestEntries>
        </archive>
      </configuration>
    
      <executions>
        <execution>
          <goals>
            <goal>attached</goal>
          </goals>
          <phase>package</phase>
        </execution>
      </executions>
    </plugin>
    

    打包

    mvn clean package
    

    ATTACH

    接下来需要把agent jar attach到目标进程上去。

    此处我们假设目标进程,已启动,进程号为8888,则attach的代码如下:

    public static void main(String[] args) throws InterruptedException, IOException, AgentLoadException, AgentInitializationException, AttachNotSupportedException {
        VirtualMachine vmObj = null;
        try {
            vmObj = VirtualMachine.attach("8888");
            if (vmObj != null) {
                vmObj.loadAgent("<jar path>/instrument-play-1.0-SNAPSHOT-jar-with-dependencies.jar", null);
            }
        } finally {
            if (null != vmObj) {
                vmObj.detach();
            }
        }
    }
    

    效果体验

    完整源码

    查看

    相关文章

      网友评论

          本文标题:java instrumentation attach模式快速入

          本文链接:https://www.haomeiwen.com/subject/jtoogktx.html