javaagent技術&Attach技術


  之前見過好多種-javaagent 參數,比如我們IDEA啟動一個類的時候就會有好多的javaagent。 好像又叫探針技術,簡單研究下其過程。

  Java 5 中提供的 Instrument 包啟動時往 Java 虛擬機中掛上一個用戶定義的 hook 程序,可以在裝入特定類的時候改變特定類的字節碼,從而改變該類的行為。Instrument 包是在整個虛擬機上掛了一個鈎子程序,每次裝入一個新類的時候,都必須執行一遍這段程序,即使這個類不需要改變。一個核心類是sun.instrument.InstrumentationImpl, 這個類可以動態的增加轉換器或者獲取當前JVM加載的所有的類信息。

  參考: https://www.jacoco.org/jacoco/trunk/doc/agent.html

第一種: 使用 premain 可以在類第一次加載之前修改類信息,加載之后修改需要重新創建類加載器, premain是Java SE5開始就提供的代理方式。而且使用時必須在命令行指定代理jar,並且代理類必須在main方法前啟動。

第二種: Java SE6開始,提供了在應用程序的VM啟動后在動態添加代理的方式,即agentmain方式。

1. premain 使用

1. 簡單使用

1. agent.MyAgent

package agent;

import java.lang.instrument.Instrumentation;

public class MyAgent {


    /**
     * JVM 在類加載前會調用到此函數
     *
     * @param agentOps
     * @param inst
     */
    public static void premain(String agentOps, Instrumentation inst) {
        System.out.println("agent.MyAgent.premain start ");
        System.out.println(agentOps);
        System.out.println(inst);
        System.out.println("agent.MyAgent.premain end ");
    }

}

2. 編寫META-INF/MANIFEST.MF

Manifest-Version: 1.0
Can-Retransform-Classes: true
Premain-Class: agent.MyAgent

這個配置文件需要注意格式,如果之前打過jar 包應該會注意。 最后有個空行, 每個key后面的冒號 和 value 之間有個空格

3. 最后的目錄結構如下:

$ ls -R
.:
agent/  META-INF/

./agent:
MyAgent.class

./META-INF:
MANIFEST.MF

4. 生成jar 包

D:\agentjar>jar cvfm agent.jar ./META-INF/MANIFEST.MF ./
已添加清單
正在添加: agent/(輸入 = 0) (輸出 = 0)(存儲了 0%)
正在添加: agent/MyAgent.class(輸入 = 754) (輸出 = 415)(壓縮了 44%)
正在忽略條目META-INF/
正在忽略條目META-INF/MANIFEST.MF

5. 新建測試類:

public class PlainTest {

    public static void main(String[] args) throws InterruptedException {
        new PlainTest().test();
    }

    public void test() throws InterruptedException {
        Thread.sleep(5 * 1000);
        System.out.println("cn.qz.PlainTest.test\t" + 111222);
    }
}

6. 編譯運行測試:

$ java -javaagent:D:/agentjar/agent.jar PlainTest
agent.MyAgent.premain start
null
sun.instrument.InstrumentationImpl@5fe5c6f
agent.MyAgent.premain end
cn.qz.PlainTest.test    111222

測試傳遞參數: (可以傳遞參數給指定的方法, 方法內部也可以根據參數進行一些特殊的處理)

$ java -javaagent:D:/agentjar/agent.jar=key1=value1,key2=value2 PlainTest
agent.MyAgent.premain start
key1=value1,key2=value2
sun.instrument.InstrumentationImpl@5fe5c6f
agent.MyAgent.premain end
cn.qz.PlainTest.test    111222

7. 使用IDEA 的方式進行調試

  同樣的jar 包指定使用之前打的jar,和探針對應的類可以在DIEA 中使用java 文件進行調試。

(1) 目錄結構

 (2) agent.MyAgent

package agent;

import java.lang.instrument.Instrumentation;

public class MyAgent {

    /**
     * JVM 在類加載前會調用到此函數
     *
     * @param agentOps
     * @param inst
     */
    public static void premain(String agentOps, Instrumentation inst) {
        System.out.println("agent.MyAgent.premain XXX start ");
        System.out.println(agentOps);
        System.out.println(inst);
        System.out.println("agent.MyAgent.premain XXX end ");
    }

}

(3) 增加測試類

package cn.qz;

public class PlainTest {

    public static void main(String[] args) throws InterruptedException {
        new PlainTest().test();
    }

    public void test() throws InterruptedException {
        Thread.sleep(5 * 1000);
        System.out.println("cn.qz.PlainTest.test\t" + 111222);
    }
}

(4) 編輯增加代理 idea 中 Add VM Operations 增加參數:  -javaagent:D:\agentjar\agent.jar

(5) 測試查看運行結果:

agent.MyAgent.premain XXX start 
null
sun.instrument.InstrumentationImpl@1eb44e46
agent.MyAgent.premain XXX end 
cn.qz.PlainTest.test    111222

(6) debug 到agent.MyAgent#premain 方法內部, 查看調用鏈:

 2. premain 實現監測方法執行時間的操作

  基於javassit 對字節碼進行增強。

1. pom 增加

        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.28.0-GA</version>
        </dependency>

2. agent.MyAgent 源碼

package agent;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class MyAgent {

    /**
     * 有該方法會優先執行該方法
     *
     * @param agentOps
     * @param inst
     */
    public static void premain(String agentOps, Instrumentation inst) {
        System.out.println("====premain 方法執行");
        System.out.println(agentOps);
        System.out.println(inst);
        System.out.println("====premain2 方法執行");

        /**
         * 添加一個轉換器, 字節碼加載到虛擬機前會調用此類的transform 方法
         */
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                if (!className.startsWith("cn/qz")) {
                    return null;
                }

                System.out.println("ClassLoader : " + loader);
                System.out.println("className : " + className);
                //創建類,這是一個單例對象
                ClassPool cp = ClassPool.getDefault();
                //我們需要構建的類
                try {
                    CtClass ctClass = cp.get(className.replace("/", "."));
                    CtMethod[] declaredMethods = ctClass.getDeclaredMethods();
                    for (CtMethod method : declaredMethods) {
                        // 修改方法體來實現, 增加兩個局部變量用於記錄執行時間
                        method.addLocalVariable("startTimeAgent", CtClass.longType);
                        method.insertBefore("startTimeAgent = System.currentTimeMillis();");
                        method.addLocalVariable("methodNameAgent", cp.get(String.class.getName()));
                        method.insertBefore("methodNameAgent = \"" + method.getLongName() + "\";");
                        method.insertAfter("System.out.println(methodNameAgent + \" exec time is :\" + (System.currentTimeMillis() - startTimeAgent) + \"ms\");");
                    }
                    return ctClass.toBytecode();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        });
    }

}

3. 使用上面測試類進行測試查看日志

====premain 方法執行
null
sun.instrument.InstrumentationImpl@1eb44e46
====premain2 方法執行
ClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
className : cn/qz/PlainTest
cn.qz.PlainTest.test    111222
cn.qz.PlainTest.test() exec time is :5001ms
cn.qz.PlainTest.main(java.lang.String[]) exec time is :5002ms

4. 打包:

(1) 下載pom 依賴的jar 包, 進入項目目錄(有pom.xml的目錄),cmd執行如下命令

mvn dependency:copy-dependencies -DoutputDirectory=dependency_lib

(2) 構造目錄結構:

$ ls -R
.:
agent/  dependency_lib/  META-INF/

./agent:
'MyAgent$1.class'   MyAgent.class

./dependency_lib:
javassist-3.28.0-GA.jar

./META-INF:
MANIFEST.MF

(3) 修改MANIFEST.MF

Manifest-Version: 1.0
Can-Retransform-Classes: true
Class-Path: dependency_lib/javassist-3.28.0-GA.jar  
Premain-Class: agent.MyAgent

(4) 替換編譯后的class 文件

(5) 打包

D:\agentjar>jar cvfm agent.jar ./META-INF/MANIFEST.MF ./
已添加清單
正在添加: agent/(輸入 = 0) (輸出 = 0)(存儲了 0%)
正在添加: agent/MyAgent$1.class(輸入 = 3142) (輸出 = 1533)(壓縮了 51%)
正在添加: agent/MyAgent.class(輸入 = 941) (輸出 = 524)(壓縮了 44%)
正在添加: dependency_lib/(輸入 = 0) (輸出 = 0)(存儲了 0%)
正在添加: dependency_lib/javassist-3.28.0-GA.jar(輸入 = 851531) (輸出 = 794719)(壓縮了 6%)
正在忽略條目META-INF/
正在忽略條目META-INF/MANIFEST.MF

(6) 編寫測試類編譯后測試:

package cn.qz;

import java.util.concurrent.CountDownLatch;

public class PlainTest {

    public static void main(String[] args) throws InterruptedException {
        new PlainTest().test();
        CountDownLatch countDownLatch = new CountDownLatch(1);
        countDownLatch.await();
    }

    public void test() throws InterruptedException {
        Thread.sleep(5 * 1000);
        System.out.println("cn.qz.PlainTest.test\t" + 111222);
    }
}

 1》測試:

D:\agentjar>java -javaagent:D:\agentjar\agent.jar cn.qz.PlainTest
====premain 方法執行
null
sun.instrument.InstrumentationImpl@6979e8cb
====premain2 方法執行
ClassLoader : jdk.internal.loader.ClassLoaders$AppClassLoader@383534aa
className : cn/qz/PlainTest
cn.qz.PlainTest.test    111222
cn.qz.PlainTest.test() exec time is :5001ms

 2》 使用arthas 實時反編譯查看生成的類信息

       /*
        * Decompiled with CFR.
        */
       package cn.qz;

       import java.util.concurrent.CountDownLatch;

       public class PlainTest {
           /*
            * WARNING - void declaration
            */
           public static void main(String[] stringArray) throws InterruptedException {
               void var2_1;
               void var4_2;
               long startTimeAgent = System.currentTimeMillis();
               String methodNameAgent = "cn.qz.PlainTest.main(java.lang.String[])";
               new PlainTest().test();
               CountDownLatch countDownLatch = new CountDownLatch(1);
/*10*/         countDownLatch.await();
               Object var6_4 = null;
               System.out.println(new StringBuffer().append((String)var4_2).append(" exec time is :").append(System.currentTimeMillis() - var2_1).append("ms").toString());
           }

           /*
            * WARNING - void declaration
            */
           public void test() throws InterruptedException {
               void var1_1;
               void var3_2;
               long startTimeAgent = System.currentTimeMillis();
               String methodNameAgent = "cn.qz.PlainTest.test()";
/*14*/         Thread.sleep(5000L);
/*15*/         System.out.println("cn.qz.PlainTest.test\t111222");
               Object var5_3 = null;
               System.out.println(new StringBuffer().append((String)var3_2).append(" exec time is :").append(System.currentTimeMillis() - var1_1).append("ms").toString());
           }
       }

3. 使用asm實現方法查看執行時長 

  jdk 自身的包也封裝了ASM相關。 下面的簡單測試是基於jdk 自帶的asm, 不需要引入其他的包。asm 是基於字節碼的, 所以如果用asm 需要對字節碼簡單的了解。

1. 原生類查看字節碼指令

(1) 類

package cn.qz;

public class Client {

    public static void main(String[] args) {
        System.out.println(new Client().test1("3", "2"));
    }

    public String test1(String test1, String param2) {
        long l = System.currentTimeMillis();
        // 代碼邏輯
        long l2 = System.currentTimeMillis() - l;
        System.out.println("The cost time of test1() is " + l2 + " ms");
        return "123";
    }

}

(2) javap 查看相關字節碼指令

D:\study\agentmvn\target\classes>javap -c cn.qz.Client
Compiled from "Client.java"
public class cn.qz.Client {
  public cn.qz.Client();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #3                  // class cn/qz/Client
       6: dup
       7: invokespecial #4                  // Method "<init>":()V
      10: ldc           #5                  // String 3
      12: ldc           #6                  // String 2
      14: invokevirtual #7                  // Method test1:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
      17: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      20: return

  public java.lang.String test1(java.lang.String, java.lang.String);
    Code:
       0: invokestatic  #9                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_3
       4: invokestatic  #9                  // Method java/lang/System.currentTimeMillis:()J
       7: lload_3
       8: lsub
       9: lstore        5
      11: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      14: new           #10                 // class java/lang/StringBuilder
      17: dup
      18: invokespecial #11                 // Method java/lang/StringBuilder."<init>":()V
      21: ldc           #12                 // String The cost time of test1() is
      23: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      26: lload         5
      28: invokevirtual #14                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      31: ldc           #15                 // String  ms
      33: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      36: invokevirtual #16                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      39: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      42: ldc           #17                 // String 123
      44: areturn
}

2. agent.MyAgent 代理類

package agent;

import java.lang.instrument.Instrumentation;

public class MyAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new ProfilingTransformer());
    }

}

3. agent.ProfilingTransformer

package agent;

import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class ProfilingTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        try {
            // 排除一些不需要處理的
            if (!className.startsWith("cn/qz")) {
                return classfileBuffer;
            }

            return getBytes(loader, className, classfileBuffer);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return classfileBuffer;
    }

    private byte[] getBytes(ClassLoader loader, String className, byte[] classfileBuffer) {
        ClassReader cr = new ClassReader(classfileBuffer);
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new ChangeVisitor(cw);
        cr.accept(cv, ClassReader.EXPAND_FRAMES);
        return cw.toByteArray();
    }

}

4. agent.ChangeVisitor

package agent;

import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.AdviceAdapter;

public class ChangeVisitor extends ClassVisitor {

    ChangeVisitor(ClassVisitor classVisitor) {
        super(Opcodes.ASM4, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions);
        if (name.equals("<init>")) {
            return methodVisitor;
        }

        return new ChangeAdapter(Opcodes.ASM4, methodVisitor, access, name, desc);
    }

    static class ChangeAdapter extends AdviceAdapter {
        
        private int startTimeId = -1;

        private String methodName = null;

        ChangeAdapter(int api, MethodVisitor mv, int access, String name, String desc) {
            super(api, mv, access, name, desc);
            methodName = name;
        }

        @Override
        protected void onMethodEnter() {
            super.onMethodEnter();
            startTimeId = newLocal(Type.LONG_TYPE);
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitIntInsn(LSTORE, startTimeId);
        }

        @Override
        protected void onMethodExit(int opcode) {
            super.onMethodExit(opcode);

            int durationId = newLocal(Type.LONG_TYPE);
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitVarInsn(LLOAD, startTimeId);
            mv.visitInsn(LSUB);
            mv.visitVarInsn(LSTORE, durationId);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
            mv.visitInsn(DUP);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
            mv.visitLdcInsn("The cost time of " + methodName + "() is ");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitVarInsn(LLOAD, durationId);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
            mv.visitLdcInsn(" ms");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }
    }
}

5. 測試類cn.qz.PlainTest

package cn.qz;


public class PlainTest {

    public static void main(String[] args) throws InterruptedException {
        PlainTest apiTest = new PlainTest();
        String res01 = apiTest.test1(111, 17);
        System.out.println(res01);
        Thread.sleep(500* 1000);
    }

    public String test1(int uId, int age) throws InterruptedException {
        Thread.sleep(5 * 1000);
        return "hello world!";
    }

}

6. 用-javaagent:D:/agentjar/agent.jar 代理后查看測試結果:

The cost time of test1() is 5000 ms
hello world!

7. arthas 反編譯查看asm 字節碼處理過的類:

       /*
        * Decompiled with CFR.
        */
       package cn.qz;

       public class PlainTest {
           public static void main(String[] stringArray) throws InterruptedException {
               long l = System.currentTimeMillis();
               PlainTest apiTest = new PlainTest();
/* 8*/         String res01 = apiTest.test1(111, 17);
/* 9*/         System.out.println(res01);
/*10*/         Thread.sleep(500000L);
/*11*/         long l2 = System.currentTimeMillis() - l;
               System.out.println("The cost time of main() is " + l2 + " ms");
           }

           public String test1(int n, int n2) throws InterruptedException {
               long l = System.currentTimeMillis();
/*14*/         Thread.sleep(5000L);
               long l2 = System.currentTimeMillis() - l;
               System.out.println("The cost time of test1() is " + l2 + " ms");
               return "hello world錛?";
           }
       }

 2. agentmain 的使用

  Java SE6開始,提供了在應用程序的VM啟動后在動態添加代理的方式,即agentmain方式。大體可以理解為JVM 啟動了一個AttachListener, 可以接收一些參數然后做對應的處理。也被稱為Attach 技術。

1. 簡單使用

1. 編寫agent.MyAgent

package agent;

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class MyAgent {

    public static void agentmain(String agentOps, Instrumentation inst) throws UnmodifiableClassException {
        System.out.println("====agentmain 方法執行");
        System.out.println(agentOps);
        System.out.println(inst);
        System.out.println("====agentmain 方法執行2");

        // 通過Instrumentation 獲取到加載的所有類的信息。 實際類型是: sun.instrument.InstrumentationImpl
        Class[] classes = inst.getAllLoadedClasses();
        for (Class cls : classes) {
            if (cls.getName().startsWith("cn.qz")) {
                System.out.println("agent.MyAgent.agentmain\t" + cls.getName());
            }
        }
    }
}

 2. 類似於上面premain 編寫 META-INF/MANIFEST.MF

Manifest-Version: 1.0
Can-Retransform-Classes: true
Agent-Class: agent.MyAgent

  注意最后的空行

3. 生成jar 包

D:\agentjar>jar cvfm agent.jar ./META-INF/MANIFEST.MF ./
已添加清單
正在添加: agent/(輸入 = 0) (輸出 = 0)(存儲了 0%)
正在添加: agent/MyAgent.class(輸入 = 1065) (輸出 = 598)(壓縮了 43%)
正在忽略條目META-INF/
正在忽略條目META-INF/MANIFEST.MF

 4. 編寫一個正常運行的類

package cn.qz;

public class PlainTest {

    public static void main(String[] args) throws InterruptedException {
        while (true) {
            Thread.sleep(5 * 1000);
            new PlainTest().test();
        }
    }

    public void test() throws InterruptedException {
//        System.out.println("cn.qz.PlainTest.test\t" + 111222);
    }
}

5. 用agentmain 獲取上面JVM中所有的類

  由於agent main方式無法向premain方式那樣在命令行指定代理jar,因此需要借助Attach Tools API。  需要將 jdk/lib/tools.jar 引入到classpath 中, 然后獲取到所有的JVM, 然后指定的JVM 執行指定的代理。

  VirtualMachine.list() 可以達到類似於jps 的效果, 可以獲取當前的所有的JVM 以及相關啟動類和pid 信息。 

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

import java.util.List;

public class AttachTest {

    public static void main(String[] args) throws Exception {
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor virtualMachineDescriptor : list) {
            // 程序以cn.qz 開頭
            boolean b = virtualMachineDescriptor.displayName().startsWith("cn.qz");
            if (b) {
                System.out.println(virtualMachineDescriptor.displayName() + "\t" + virtualMachineDescriptor.id());
                VirtualMachine vm = VirtualMachine.attach(virtualMachineDescriptor.id());
                vm.loadAgent("D:\\agentjar\\agent.jar");
                break;
            }
        }
    }

}

結果:

(1) AttachTest 相當於一個獨立的進程,控制台如下:

cn.qz.PlainTest    11104
======開始agentmain 方法====

(2) PlainTest 進程如下:

====agentmain 方法執行
null
sun.instrument.InstrumentationImpl@629aa6f8
====agentmain 方法執行2
agent.MyAgent.agentmain    cn.qz.PlainTest

也可以類似於premain 在IDEA調試。我們通過vm.loadAgent 調的方法實際是在另一個指定的VM內部調用的, 查看調用鏈如下: (可以看到是在另一個監聽線程里面為入口開始調用的)

2. agentmain 實現監測方法執行時間

1. 修改agent 類 (用javassit 對指定類增強)

package agent;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class MyAgent {

    public static void agentmain(String agentOps, Instrumentation inst) throws UnmodifiableClassException {
        System.out.println("====agentmain 方法執行");
        System.out.println(agentOps);
        System.out.println(inst);
        System.out.println("====agentmain 方法執行2");

        // 通過Instrumentation 獲取到加載的所有類的信息。 實際類型是: sun.instrument.InstrumentationImpl
        Class[] classes = inst.getAllLoadedClasses();
        for (Class cls : classes) {
            if (cls.getName().startsWith("cn.qz")) {
                System.out.println("agent.MyAgent.agentmain\t" + cls.getName());

                try {
                    ClassPool cp = ClassPool.getDefault();
                    CtClass ctClass = cp.get(cls.getName());
                    CtMethod[] declaredMethods = ctClass.getDeclaredMethods();
                    for (CtMethod method : declaredMethods) {
                        // 修改方法體來實現, 增加兩個局部變量用於記錄執行時間
                        method.addLocalVariable("startTimeAgent", CtClass.longType);
                        method.insertBefore("startTimeAgent = System.currentTimeMillis();");
                        method.addLocalVariable("methodNameAgent", cp.get(String.class.getName()));
                        method.insertBefore("methodNameAgent = \"" + method.getLongName() + "\";");
                        method.insertAfter("System.out.println(methodNameAgent + \" exec time is :\" + (System.currentTimeMillis() - startTimeAgent) + \"ms\");");
                    }
                    ClassDefinition classDefinition = new ClassDefinition(cls, ctClass.toBytecode());
                    inst.redefineClasses(classDefinition);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2. 修改MANIFEST.MF

Manifest-Version: 1.0
Can-Retransform-Classes: true
Can-Redefine-Classes: true
Agent-Class: agent.MyAgent

  注意最后有空格,Can-Redefine-Classes 是允許重新定義class。

3. 重新測試

(1) 查看控制台:

cn.qz.PlainTest.test() exec time is :0ms
cn.qz.PlainTest.test() exec time is :0ms
...

(2) 用arthas 反編譯查看類

       /*
        * Decompiled with CFR.
        */
       package cn.qz;

       public class PlainTest {
           public static void main(String[] args) throws InterruptedException {
               String methodNameAgent = "cn.qz.PlainTest.main(java.lang.String[])";
               long startTimeAgent = System.currentTimeMillis();
               while (true) {
/* 7*/             Thread.sleep(5000L);
                   new PlainTest().test();
               }
           }

           /*
            * WARNING - void declaration
            */
           public void test() throws InterruptedException {
               void var1_2;
               void var3_1;
               String methodNameAgent = "cn.qz.PlainTest.test()";
               long startTimeAgent = System.currentTimeMillis();
               Object var5_3 = null;
               System.out.println(new StringBuffer().append((String)var3_1).append(" exec time is :").append(System.currentTimeMillis() - var1_2).append("ms").toString());
           }
       }

 

 補充: sun.instrument.InstrumentationImpl 核心類源碼如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package sun.instrument;

import java.lang.instrument.ClassDefinition;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.jar.JarFile;

public class InstrumentationImpl implements Instrumentation {
    private final TransformerManager mTransformerManager = new TransformerManager(false);
    private TransformerManager mRetransfomableTransformerManager = null;
    private final long mNativeAgent;
    private final boolean mEnvironmentSupportsRedefineClasses;
    private volatile boolean mEnvironmentSupportsRetransformClassesKnown;
    private volatile boolean mEnvironmentSupportsRetransformClasses;
    private final boolean mEnvironmentSupportsNativeMethodPrefix;

    private InstrumentationImpl(long var1, boolean var3, boolean var4) {
        this.mNativeAgent = var1;
        this.mEnvironmentSupportsRedefineClasses = var3;
        this.mEnvironmentSupportsRetransformClassesKnown = false;
        this.mEnvironmentSupportsRetransformClasses = false;
        this.mEnvironmentSupportsNativeMethodPrefix = var4;
    }

    public void addTransformer(ClassFileTransformer var1) {
        this.addTransformer(var1, false);
    }

    public synchronized void addTransformer(ClassFileTransformer var1, boolean var2) {
        if (var1 == null) {
            throw new NullPointerException("null passed as 'transformer' in addTransformer");
        } else {
            if (var2) {
                if (!this.isRetransformClassesSupported()) {
                    throw new UnsupportedOperationException("adding retransformable transformers is not supported in this environment");
                }

                if (this.mRetransfomableTransformerManager == null) {
                    this.mRetransfomableTransformerManager = new TransformerManager(true);
                }

                this.mRetransfomableTransformerManager.addTransformer(var1);
                if (this.mRetransfomableTransformerManager.getTransformerCount() == 1) {
                    this.setHasRetransformableTransformers(this.mNativeAgent, true);
                }
            } else {
                this.mTransformerManager.addTransformer(var1);
            }

        }
    }

    public synchronized boolean removeTransformer(ClassFileTransformer var1) {
        if (var1 == null) {
            throw new NullPointerException("null passed as 'transformer' in removeTransformer");
        } else {
            TransformerManager var2 = this.findTransformerManager(var1);
            if (var2 != null) {
                var2.removeTransformer(var1);
                if (var2.isRetransformable() && var2.getTransformerCount() == 0) {
                    this.setHasRetransformableTransformers(this.mNativeAgent, false);
                }

                return true;
            } else {
                return false;
            }
        }
    }

    public boolean isModifiableClass(Class<?> var1) {
        if (var1 == null) {
            throw new NullPointerException("null passed as 'theClass' in isModifiableClass");
        } else {
            return this.isModifiableClass0(this.mNativeAgent, var1);
        }
    }

    public boolean isRetransformClassesSupported() {
        if (!this.mEnvironmentSupportsRetransformClassesKnown) {
            this.mEnvironmentSupportsRetransformClasses = this.isRetransformClassesSupported0(this.mNativeAgent);
            this.mEnvironmentSupportsRetransformClassesKnown = true;
        }

        return this.mEnvironmentSupportsRetransformClasses;
    }

    public void retransformClasses(Class<?>... var1) {
        if (!this.isRetransformClassesSupported()) {
            throw new UnsupportedOperationException("retransformClasses is not supported in this environment");
        } else {
            this.retransformClasses0(this.mNativeAgent, var1);
        }
    }

    public boolean isRedefineClassesSupported() {
        return this.mEnvironmentSupportsRedefineClasses;
    }

    public void redefineClasses(ClassDefinition... var1) throws ClassNotFoundException {
        if (!this.isRedefineClassesSupported()) {
            throw new UnsupportedOperationException("redefineClasses is not supported in this environment");
        } else if (var1 == null) {
            throw new NullPointerException("null passed as 'definitions' in redefineClasses");
        } else {
            for(int var2 = 0; var2 < var1.length; ++var2) {
                if (var1[var2] == null) {
                    throw new NullPointerException("element of 'definitions' is null in redefineClasses");
                }
            }

            if (var1.length != 0) {
                this.redefineClasses0(this.mNativeAgent, var1);
            }
        }
    }

    public Class[] getAllLoadedClasses() {
        return this.getAllLoadedClasses0(this.mNativeAgent);
    }

    public Class[] getInitiatedClasses(ClassLoader var1) {
        return this.getInitiatedClasses0(this.mNativeAgent, var1);
    }

    public long getObjectSize(Object var1) {
        if (var1 == null) {
            throw new NullPointerException("null passed as 'objectToSize' in getObjectSize");
        } else {
            return this.getObjectSize0(this.mNativeAgent, var1);
        }
    }

    public void appendToBootstrapClassLoaderSearch(JarFile var1) {
        this.appendToClassLoaderSearch0(this.mNativeAgent, var1.getName(), true);
    }

    public void appendToSystemClassLoaderSearch(JarFile var1) {
        this.appendToClassLoaderSearch0(this.mNativeAgent, var1.getName(), false);
    }

    public boolean isNativeMethodPrefixSupported() {
        return this.mEnvironmentSupportsNativeMethodPrefix;
    }

    public synchronized void setNativeMethodPrefix(ClassFileTransformer var1, String var2) {
        if (!this.isNativeMethodPrefixSupported()) {
            throw new UnsupportedOperationException("setNativeMethodPrefix is not supported in this environment");
        } else if (var1 == null) {
            throw new NullPointerException("null passed as 'transformer' in setNativeMethodPrefix");
        } else {
            TransformerManager var3 = this.findTransformerManager(var1);
            if (var3 == null) {
                throw new IllegalArgumentException("transformer not registered in setNativeMethodPrefix");
            } else {
                var3.setNativeMethodPrefix(var1, var2);
                String[] var4 = var3.getNativeMethodPrefixes();
                this.setNativeMethodPrefixes(this.mNativeAgent, var4, var3.isRetransformable());
            }
        }
    }

    private TransformerManager findTransformerManager(ClassFileTransformer var1) {
        if (this.mTransformerManager.includesTransformer(var1)) {
            return this.mTransformerManager;
        } else {
            return this.mRetransfomableTransformerManager != null && this.mRetransfomableTransformerManager.includesTransformer(var1) ? this.mRetransfomableTransformerManager : null;
        }
    }

    private native boolean isModifiableClass0(long var1, Class<?> var3);

    private native boolean isRetransformClassesSupported0(long var1);

    private native void setHasRetransformableTransformers(long var1, boolean var3);

    private native void retransformClasses0(long var1, Class<?>[] var3);

    private native void redefineClasses0(long var1, ClassDefinition[] var3) throws ClassNotFoundException;

    private native Class[] getAllLoadedClasses0(long var1);

    private native Class[] getInitiatedClasses0(long var1, ClassLoader var3);

    private native long getObjectSize0(long var1, Object var3);

    private native void appendToClassLoaderSearch0(long var1, String var3, boolean var4);

    private native void setNativeMethodPrefixes(long var1, String[] var3, boolean var4);

    private static void setAccessible(final AccessibleObject var0, final boolean var1) {
        AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Object run() {
                var0.setAccessible(var1);
                return null;
            }
        });
    }

    private void loadClassAndStartAgent(String var1, String var2, String var3) throws Throwable {
        ClassLoader var4 = ClassLoader.getSystemClassLoader();
        Class var5 = var4.loadClass(var1);
        Method var6 = null;
        NoSuchMethodException var7 = null;
        boolean var8 = false;

        try {
            var6 = var5.getDeclaredMethod(var2, String.class, Instrumentation.class);
            var8 = true;
        } catch (NoSuchMethodException var13) {
            var7 = var13;
        }

        if (var6 == null) {
            try {
                var6 = var5.getDeclaredMethod(var2, String.class);
            } catch (NoSuchMethodException var12) {
            }
        }

        if (var6 == null) {
            try {
                var6 = var5.getMethod(var2, String.class, Instrumentation.class);
                var8 = true;
            } catch (NoSuchMethodException var11) {
            }
        }

        if (var6 == null) {
            try {
                var6 = var5.getMethod(var2, String.class);
            } catch (NoSuchMethodException var10) {
                throw var7;
            }
        }

        setAccessible(var6, true);
        if (var8) {
            var6.invoke((Object)null, var3, this);
        } else {
            var6.invoke((Object)null, var3);
        }

        setAccessible(var6, false);
    }

    private void loadClassAndCallPremain(String var1, String var2) throws Throwable {
        this.loadClassAndStartAgent(var1, "premain", var2);
    }

    private void loadClassAndCallAgentmain(String var1, String var2) throws Throwable {
        this.loadClassAndStartAgent(var1, "agentmain", var2);
    }

    private byte[] transform(ClassLoader var1, String var2, Class<?> var3, ProtectionDomain var4, byte[] var5, boolean var6) {
        TransformerManager var7 = var6 ? this.mRetransfomableTransformerManager : this.mTransformerManager;
        return var7 == null ? null : var7.transform(var1, var2, var3, var4, var5);
    }

    static {
        System.loadLibrary("instrument");
    }
}

  可以理解為其內部的方法都是JVM 發起調用的。

 

關於javassit和asm 使用參考: https://www.cnblogs.com/qlqwjy/p/15216085.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM