Instrument 学习

  1. 1. Instrument 学习
    1. 1.1. Instrument
    2. 1.2. 使用方式

Instrument 学习

主要是解决jvm运行时类的重载。

Instrument

JDK从1.5版本引入了java.lang.instrument包,开发者可以更方便的实现字节码增强。

java.lang.instrument包的结构如下:

1
2
3
4
5
6
7
java.lang.instrument
- ClassDefinition
- ClassFileTransformer
- IllegalClassFormatException
- Instrumentation
- UnmodifiableClassException
- UnmodifiableModuleException

核心功能由接口java.lang.instrument.Instrumentation提供

使用方式

instrumention有两种使用方式

第一种

通过jvm的启动参数-javaagent来启动,例如

java -javaagent:myagent.jar Mymain

需要MANIFEST.MF配合找到Agent入口类,这样JVM在加载时会先执行AgentMain类的premain方法,再执行JAVA本身的main方法。

这种方法仅限于main方法之前执行,局限较大,重点学一下第二种

第二种:

jdk6之后引入了动态Attach Agent方案。instrument支持了在运行时对类定义的修改。要使用instrument的类修改功能,我们需要实现它提供的ClassFileTransformer接口,定义一个类文件转换器。接口中的transform()方法会在类文件被加载时调用,而在transform方法里,我们可以利用上文中的ASM或Javassist对传入的字节码进行改写或替换,生成新的字节码数组后返回。

测试:

先写一个MyTestMain类,这是我们要去修改的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.concurrent.TimeUnit;

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

public static int foo(){
return 100; //目标是修改之后return 50;
}
}

目标是注入之后使foo return 50

首先创建maven项目agent-demo

先按照asm的方式创建MyClassVisitor和MyMethodVisitor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;

public class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if ("foo".equals(name)) {
System.out.println("----准备修改foo方法----");
return new MyMethodVisitor(api,mv,access,name,descriptor);
}
return mv;
}
}

MyMethodVisitor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.commons.AdviceAdapter;

public class MyMethodVisitor extends AdviceAdapter {


protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
super(api, methodVisitor, access, name, descriptor);
}

@Override
protected void onMethodEnter() {
mv.visitIntInsn(BIPUSH, 50);
mv.visitInsn(IRETURN);
}
}

创建MyClassFileTransformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.security.ProtectionDomain;

public class MyClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
if (!"MyTestMain".equals(className)) return classfileBuffer;
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new MyClassVisitor(262144,cw);
cr.accept(cv,ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
return cw.toByteArray();
}
}

AgentMain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class AgentMain {
public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
System.out.println("---agent called---");
inst.addTransformer(new MyClassFileTransformer(),true);//添加类文件转换器,第二个参数必须设置为true,表示可以重新转换类文件
Class[] classes = inst.getAllLoadedClasses();
for (int i = 0; i < classes.length; i++) {
if ("MyTestMain".equals(classes[i].getName())) {
System.out.println("----start----");
inst.retransformClasses(classes[i]);
System.out.println("----end----");
break;
}
}
}
}

pom里还得设置一下

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
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<compilerArguments>
<verbose />
<!-- 将jdk的依赖jar打入项目中,这样项目中使用的jdk的依赖就尅正常使用 -->
<bootclasspath>/Library/java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home/jre/lib/rt.jar:/Library/java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home/jre/lib/jce.jar:/Library/java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home/jre/lib/jsse.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<!-- 打jar的文件清单,对应META-INF/MANIFEST.MF文件 -->
<manifestEntries>
<!-- 主程序启动类 -->
<Agent-Class>
AgentMain
</Agent-Class>
<!-- 允许重新定义类 -->
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<!-- 允许转换并重新加载类 -->
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>

</plugins>
</build>

把agent-demo打包为jar,记下路径

之后创建attach-demo,创建MyAttachMain

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
import com.sun.tools.attach.VirtualMachine;

import java.io.IOException;

public class MyAttachMain {
public static void main(String[] args) {
VirtualMachine vm = null;
try {
vm = VirtualMachine.attach("13566");
vm.loadAgent("/Users/fmyyy/tools/agent-demo/target/agent-demo-1.0-SNAPSHOT.jar");//java agent jar包路径
} catch (Exception e) {
e.printStackTrace();
} finally {
if (vm != null) {
try {
vm.detach();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}


运行MyTestMain,用jps记下pid,替换MyAttachMain里的id即可,运行MyAttachMain,回去查看MyTestMain就会发现已经修改成功:

1647219171394.png