https://blog.csdn.net/fyyyr/article/details/102816064
ASM基础
ASM是一个Java字节码操作框架,可用于class文件的修改。
其原理是将class文件载入,然后构建成一棵树。然后根据用户自定义的修改类对该树进行加工,加工完成后即可得到修改后的class文件。
故而ASM中使用了visitor模式:class文件的结构是固定的,根据其构造出的树作为被访问者,则其节点也是固定的。只需要对每个节点定义一个访问者即可进行指定的修改。
由于修改class主要涉及字段和方法,故最常用的visitor是FieldVisitor和MethodVisitor。
以FieldVisitor为例,当ASM使用FieldVisitor来处理一个class的树时,则该class的每个方法都会被传入定义的FieldVisitor。于是只需要在FieldVisitor对特定的方法进行过滤处理即可。
ASM的官方文档地址为:
https://asm.ow2.io/javadoc/overview-summary.html
打开可以看到其类库的内容:
其第一个包org.objectweb.asm为核心core包,包含了主要的功能接口和对象。
环境搭建
ASM使用的是com.sun.xml.internal.ws.org.objectweb.asm,因此不需要额外引入库文件。
创建一个JBoss工程,载入class并修改,然后将修改后的class文件进行保存:
import com.sun.xml.internal.ws.org.objectweb.asm.ClassAdapter;
import com.sun.xml.internal.ws.org.objectweb.asm.ClassReader;
import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
// 载入class文件
FileInputStream fis = new FileInputStream("D:\\Test\\Hello.class");
// 修改
ClassReader cr = new ClassReader(fis);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdapter classAdapter = new ASMTest(cw);
cr.accept(classAdapter, ClassReader.SKIP_DEBUG);
// 保存class文件
FileOutputStream fos = new FileOutputStream("D:\\Test\\Change\\Hello.class");
fos.write(cw.toByteArray());
fos.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这就是总体的框架。至于修改,由ASMTest类负责。
新建一个Java类ASMTest:
import com.sun.xml.internal.ws.org.objectweb.asm.*;
public class ASMTest extends ClassAdapter {
public ASMTest(ClassVisitor cv) {
super(cv);
}
// 字段处理
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
return this.cv.visitField(access, name, desc, signature, value);
}
// 方法处理
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return this.cv.visitMethod(access, name, desc, signature, exceptions);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
上面定义了负责进行修改的类ASMTest。直接运行main(),会在目标路径下生成一个class。由于ASMTest没有进行任何修改,故而生成的class与原始class内容相同。
解析
ClassReader
ClassReader能够处理class文件的字节码数据,构建出一棵该类的抽象树。然后执行传入的参数对象所包含的操作,从而对抽象树进行加工。
ClassAdapter
ClassAdapter继承自ClassVisitor,负责对class树进行修改,开发者需要对其继承并重载对应的修改方法。
也就是说,所有的visitor都集成在了ClassAdapter的方法中,只需要顺序调用ClassAdapter的所有方法,即可实现所有visitor顺序访问class树。
ClassAdapter的定义为:
public class ClassAdapter implements ClassVisitor {
protected ClassVisitor cv;
public ClassAdapter(ClassVisitor cv) {
this.cv = cv;
}
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.cv.visit(version, access, name, signature, superName, interfaces);
}
public void visitSource(String source, String debug) {
this.cv.visitSource(source, debug);
}
public void visitOuterClass(String owner, String name, String desc) {
this.cv.visitOuterClass(owner, name, desc);
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return this.cv.visitAnnotation(desc, visible);
}
public void visitAttribute(Attribute attr) {
this.cv.visitAttribute(attr);
}
public void visitInnerClass(String name, String outerName, String innerName, int access) {
this.cv.visitInnerClass(name, outerName, innerName, access);
}
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
return this.cv.visitField(access, name, desc, signature, value);
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return this.cv.visitMethod(access, name, desc, signature, exceptions);
}
public void visitEnd() {
this.cv.visitEnd();
}
}
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
其所有方法都可被重载。前面的例子中,ASMTest重载了visitField()和visitMethod(),从而对字段和方法进行修改。
ClassAdapter的所有方法是按已安排好的顺序来调用的,也就是ClassAdapter所有方法的定义顺序。这一点通常不需要开发者关心。
关于每个方法的具体说明,可参考其父类ClassVisitor的文档:
https://asm.ow2.io/javadoc/org/objectweb/asm/ClassVisitor.html
修改
以前面ASMTest为例。
设Hello.class的实现为:
public class Hello {
String words = "Hello world!";
int value = 1;
public void say() {
System.out.println(words);
}
public void think() {
words = "new thought";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
变量
变量有个descriptor属性,即描述符,是个字符串,每种类型都对应一个字符串:
java类型descriptor
boolean
Z
char
C
byte
B
short
S
int
I
float
F
long
J
double
D
Object
Ljava/lang/Object;
int[]
[I
Object[][]
[[Ljava/lang/Object;
删除成员变量
删除Hello.class中的变量value。
public class ASMTest extends ClassAdapter {
public ASMTest(ClassVisitor cv) {
super(cv);
}
public FieldVisitor visitField(final int access, final String name,final String desc, final String signature, final Object value) {
if (name.equals("value")) {
return null;
}
return cv.visitField(access, name, desc, signature, value);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Hello.class的所有成员变量会依次传入visitField()。当传入的变量名为value时,直接return null;,会将该变量从class树中删除。
修改成员变量权限
修改Hello.class中的变量value权限为public。
public class ASMTest extends ClassAdapter {
public ASMTest(ClassVisitor cv) {
super(cv);
}
public FieldVisitor visitField(final int access, final String name,final String desc, final String signature, final Object value) {
if (name.equals("value")) {
return cv.visitField(Opcodes.ACC_PUBLIC, name, desc, signature, value);
}
return cv.visitField(access, name, desc, signature, value);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
添加成员变量
为Hello.class添加int变量tInt,其初始值为3。
public class ASMTest extends ClassAdapter {
public ASMTest(ClassVisitor cv) {
super(cv);
}
public void visitEnd() {
FieldVisitor fv = this.cv.visitField(Opcodes.ACC_PRIVATE, "temp", "I", null, 3);
if (fv!=null){
fv.visitEnd();
}
super.visitEnd();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
要点:
在所有字段都访问完成后,会调用ClassVisitor.visitEnd()作为结束,此时即可进行成员变量的添加。
调用visitField()来访问成员变量。若要访问的成员变量不存在,则创建。
visitField()的定义为:
public FieldVisitor visitField(int access,
java.lang.String name,
java.lang.String descriptor,
java.lang.String signature,
java.lang.Object value)
1
2
3
4
5
其中:
descriptor: 类型描述符,即该成员变量的类型。
signature: 签名。默认null即可。
value: 初始值。
方法
方法的描述符descriptor是个字符串,包含:
java类型descriptor
void m(int i, float)
(IF)V
int m(Object o)
(Ljava/lang/Object;)I
int[] m(int i, String s)
(ILjava/lang/String;)[I
Object m(int[] i)
([I)Ljava/lang/Object;
包含两部分:
()内的是参数类型,多个参数直接拼接即可。例如(IF),指有2个参数,第一个是int,第二个是float。
()后的是返回值类型。
删除方法
删除Hello.class中的方法think。
public class ASMTest extends ClassAdapter {
public ASMTest(ClassVisitor cv) {
super(cv);
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals("think")) {
return null;
}
return this.cv.visitMethod(access, name, desc, signature, exceptions);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Hello.class的所有方法会依次传入visitMethod()。当传入的方法名为think时,直接return null;,会将该方法从class树中删除。
上面这种写法是使用方法名来进行判断。然而,有些类有多个同名方法,使用上面的写法会将所有同名方法都删除。若要只删除某个方法,则需要同时对descriptor进行判断:
if (name.equals("think") && desc.equals("I")) {
return null;
}
1
2
3
修改/添加
方法的修改/添加较为复杂。对于修改,常规做法是拦截到目标方法后,返回一个新的MethodVisitor:
public class ASMTest extends ClassAdapter {
public ASMTest(ClassVisitor cv) {
super(cv);
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals("think")) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);
return new NewMethodAdapter(mv);
}
return this.cv.visitMethod(access, name, desc, signature, exceptions);
}
}
class NewMethodAdapter extends MethodAdapter {
public NewMethodAdapter(MethodVisitor mv) {
super(mv);
}
public void visitCode() {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
MethodAdapter继承自MethodVisitor。通过重载MethodAdapter的各个方法来实现对类方法的修改。可参考MethodVisitor的官方文档。