instrumentation代码示例

非Attach方式

src\main\resources\META-INF\MANIFEST.MF

1
2
3
4
5
Premain-Class: org.example.AgentMain
Agent-Class: org.example.AgentMain
Can-Redefine-Classes: true
Can-Retransform-Classes: true

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
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.1</version>
</dependency>

<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>8.0</version>
</dependency>


<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifestEntries>
<Agent-Class>org.example.AgentMain</Agent-Class>
<Premain-Class>org.example.AgentMain</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>

MyTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.example;

public class MyTest {
public static void main(String[] args) {
new MyTest().foo();
}

public void foo() {
bar1();
bar2();
}

public void bar1() {

}

public void bar2() {

}
}

AgentMain

1
2
3
4
5
6
7
8
9
10
11
package org.example;

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

public class AgentMain {
public static void premain(String agentArgs, Instrumentation inst) throws
ClassNotFoundException, UnmodifiableClassException {
inst.addTransformer(new MyClassFileTransformer(), true);
}
}

MyMethodVisitor

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
package org.example;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.AdviceAdapter;

/**
* 用来处理访问一个方法触发的事件
* 在代码开始和结束前插入代码
*/
public class MyMethodVisitor extends AdviceAdapter {
protected MyMethodVisitor(MethodVisitor methodVisitor, int access, String name, String descriptor) {
super(ASM7, methodVisitor, access, name, descriptor);
}

@Override
protected void onMethodEnter() {
// <<<enter xxx
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("<<<enter " + this.getName());
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V", false);
super.onMethodEnter();
}

@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode);
// >>>exit xxx
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(">>>exit " + this.getName());
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V", false);
}
}

MyClassVisitor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.ASM7;

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

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exception) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exception);
if (name.equals("<init>")) return mv;
return new MyMethodVisitor(mv, access, name, descriptor);
}
}

MyClassFileTransformer

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
package org.example;

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

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

public class MyClassFileTransformer implements ClassFileTransformer {

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

启动

1
java -javaagent:'D:\Projects\2022SEU\JVMBookTest\target\JVMBookTest-1.0-SNAPSHOT.jar' org.example.MyTest

Attach方式(成功复现!)

  • 《深入理解JVM字节码》7.3 Page180-183
  • 参考

测试代码中的main方法,每3秒输出foo的返回值100。后面会Attach上MyTestMain,修改foo的字节码,让foo返回50

主要类

  • MyTestMain
  • AgentMain
  • MyAttachMain

环境配置

  • 添加依赖

attach的使用涉及com.sun.tools.attach,因此需要在POM中引入本地tools包,否则会找不到tools

tools包在JAVA_HOME,比如在我的设备中,其绝对位置为D:/Program Files/java/jdk1.8.0_261/lib/tools.jar

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<?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>my-javaagent</artifactId>
<version>1.0-SNAPSHOT</version>

<!--项目名-->
<name>my-javaagent</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>

<!--针对ASM的依赖-->
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.1</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>8.0</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<!--com.sun.tools的本地包-->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8.0</version>
<scope>system</scope>
<systemPath>D:/Program Files/java/jdk1.8.0_261/lib/tools.jar</systemPath>
</dependency>
</dependencies>

<build>
<!--生成jar包的名称-->
<finalName>my-attach-agent-project</finalName>

<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>

<!--配置打包插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifestEntries>
<!--根据自己的代理类名进行配置-->
<Agent-Class>org.example.AgentMain</Agent-Class>
<!--<Premain-Class>org.example.AgentMain</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.4</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.objectweb.asm</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
  • D:\Projects\2022SEU\my-javaagent\src\main\resources\META-INF\MANIFEST.MFMF文件配置,注意最后一行有空行
1
2
3
4
5
6
Manifest-Version: 1.0
Can-Redefine-Classes: true
Agent-Class: org.example.AgentMain
Can-Retransform-Classes: true
Permissions: all-permissions

AgentMain

代理类,包含了MethodVisitor、MyClassVisitor、MyClassFileTransformer以及agentmain(这里是动态配置,所以不用写入Premain类)

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package org.example;

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;

public class AgentMain {


public static class MyMethodVisitor extends AdviceAdapter {

protected MyMethodVisitor( MethodVisitor methodVisitor,int access, String name, String desc) {
super(ASM7,methodVisitor,access, name, desc);
}

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

public static class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(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);
System.out.println(name);
if ("foo".equalsIgnoreCase(name)) {
return new MyMethodVisitor(mv,access,name,descriptor);
}

return mv;
}
}

public static class MyClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("className:"+className);
// if (!"com.lgydojava.jvmdemo.MyTestMain".equalsIgnoreCase(className)) {
// return classfileBuffer;
// }

ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new MyClassVisitor(cw);
cr.accept(cv,ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
return cw.toByteArray();
}
}

public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
System.out.println("agentmain called");
inst.addTransformer(new MyClassFileTransformer(),true);
Class classes[] = inst.getAllLoadedClasses();

for (int i = 0; i < classes.length; i++) {
Class aClass = classes[i];
String name = aClass.getName();
System.out.println(name);
if (name.equals("org.example.MyTestMain")) {
System.out.println("Reloading: " + name);
inst.retransformClasses(aClass);
break;
}
}
}
}

MyTestMain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;

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);
}
}

private static int foo() {
//将原来返回100的,改为返回50
return 100;
}
}

MyAttachMain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.example;

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

import java.io.IOException;

public class MyAttachMain {

public static void main(String[] args) throws IOException, AttachNotSupportedException {
VirtualMachine vm = VirtualMachine.attach(String.valueOf(8792));
try {
vm.loadAgent("D:\\Projects\\2022SEU\\my-javaagent\\target\\my-attach-agent-project.jar");
} catch (Exception e) {
e.printStackTrace();
}finally {
vm.detach();
}
}
}

启动方法

  • 运行MyTestMain
  • 在命令行中输入
1
jps

显示MyTestMain的PID

  • 在MyAttachMain中填入PID
  • 在命令行输入以下命令,生成jar包
1
mvn clean package
  • 在MyAttachMain中loadAgent填入生成jar包的绝对位置
  • 运行MyAttachMain(不需要-javaagent,直接run即可)
  • 效果如图,从100变成50

通过Attach方式复现函数调用链Lab

配置同上(Attach方式)

AgentMain

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package org.example;

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;

public class AgentMain {
public static class MyMethodVisitor extends AdviceAdapter {

protected MyMethodVisitor( MethodVisitor methodVisitor,int access, String name, String desc) {
super(ASM7,methodVisitor,access, name, desc);
}

@Override
protected void onMethodEnter() {
// <<<enter xxx
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("<<<enter " + this.getName());
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V", false);
// mv.visitIntInsn(BIPUSH,50);
// mv.visitInsn(IRETURN);
super.onMethodEnter();
}

@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode);
// >>>exit xxx
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(">>>exit " + this.getName());
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V", false);
}
}

public static class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor classVisitor) {
super(ASM7, classVisitor);
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exception) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exception);
if ("foo".equals(name)) return new MyMethodVisitor(mv, access, name, descriptor);
return mv;
}
}

public static class MyClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
// 这个if不注释掉会导致进入去MethodEnter
// if (!"org.example.MyTestMain".equals(className)) return classfileBuffer;
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new MyClassVisitor(cw);
cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
return cw.toByteArray();
}
}

public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
System.out.println("agentmain called");
inst.addTransformer(new MyClassFileTransformer(),true);
Class classes[] = inst.getAllLoadedClasses();

for (int i = 0; i < classes.length; i++) {
Class aClass = classes[i];
String name = aClass.getName();
System.out.println(name);
if (name.equals("org.example.MyTestMain")) {
System.out.println("Reloading: " + name);
inst.retransformClasses(aClass);
break;
}
}
}
}

MyTestMain

这里在foo中加入了递增数字a和延迟时间,以观测变化

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
package org.example;

import java.util.concurrent.TimeUnit;

public class MyTestMain {
private static int a = 1;

public static void main(String[] args) throws InterruptedException {
while (true)
new MyTestMain().foo();
}

public void foo() throws InterruptedException {
System.out.println(a++);
TimeUnit.SECONDS.sleep(3);
bar1();
bar2();
}

public void bar1() {

}

public void bar2() {

}
}

MyAttachMain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.example;

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

import java.io.IOException;

public class MyAttachMain {

public static void main(String[] args) throws IOException, AttachNotSupportedException {
VirtualMachine vm = VirtualMachine.attach(String.valueOf(14500));
try {
vm.loadAgent("D:\\Projects\\2022SEU\\my-javaagent\\target\\my-attach-agent-project.jar");
} catch (Exception e) {
e.printStackTrace();
}finally {
vm.detach();
}
}
}

启动

  • 启动MyTestMain,效果如下

  • jps看以下PID并填入MyAttachMain并保存
  • mvn clean package打包
  • 运行MyAttachMain
  • 观察MyTestMain输出结果,如下

【注意】

  • 上述只显示foo是因为在MyClassVisitor中,限定了foo的调教,只要无条件输出new MyMethodVisitor(mv, access, name, descriptor),即可对所有函数调用进行打印
1
2
3
4
5
6
7
8
9
10
11
12
13
public static class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor classVisitor) {
super(ASM7, classVisitor);
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exception) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exception);
// if ("foo".equals(name)) return new MyMethodVisitor(mv, access, name, descriptor);
// return mv;
return new MyMethodVisitor(mv, access, name, descriptor);
}
}
  • 结果如下



----------- 本文结束 -----------




0%