IAST前置知识,使用ASM进行字节码增强
ClassFile结构
ASM对Class文件的操作都是基于ClassFile的文件结构,了解ClassFile文件结构有助于更好的理解ASM。
u1、u2分别代表几个字节
1 | ClassFile { |
描述符
对ASM中常用的字段描述符的举例:
boolean flag
:Z
byte byteValue
:B
int intValue
:I
float floatValue
:F
double doubleValue
:D
String strValue
:Ljava/lang/String;
Object objValue
:Ljava/lang/Object;
byte[] bytes
:[B
String[] array
:[Ljava/lang/String;
Object[][] twoDimArray
:[[Ljava/lang/Object;
对方法描述符的举例:
int add(int a, int b)
:(II)I
void test(int a, int b)
:(II)V
boolean compare(Object obj)
:(Ljava/lang/Object;)Z
void main(String[] args)
:([Ljava/lang/String;)V
ASM作用
.java
文件经过javac编译为.class
,ASM可直接对.class
文件进行操作- ASM用来操作字节码文件,不止java语言,只要符合java字节码规范即可
运行方式
- 基于事件-核心API?
- 基于对象-树API?
体系结构
- org.objectweb.asm:包中定义类基于核心
API
的相关操作类; - org.objectweb.asm.commons:包中提供了实用的类或方法的
Adapter
方法转换器; - org.objectweb.asm.signature:包中重定义了泛型相关的操作类;
- org.objectweb.asm.tree:包中定义了基于树
API
的类,以及一些用于事件和树API
转换的工具类; - org.objectweb.asm.tree.analysis:包中提供了常见的类分析框架和分析器类;
- org.objectweb.asm.util:包中提供了基于核心
API
的常见工具类。
ASM生成类
ClassVisitor类
一个抽象类,常见实现类->ClassWrite、ClassNode
访问者模式
有调用顺序
[]
: 表示最多调用一次,可以不调用,但最多调用一次。()
和|
: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。*
: 表示方法可以调用0次或多次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15visit
[visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
(
visitAnnotation |
visitTypeAnnotation |
visitAttribute
)*
(
visitNestMember |
visitInnerClass |
visitRecordComponent |
visitField |
visitMethod
)*
visitEndvisit方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14public void visit(
final int version, // jdk版本
final int access, // 访问标识
final String name, // 类名
final String signature, // 当前类的泛型信息
final String superName, // 当前类的父类信息
final String[] interfaces) { // 实现接口
if (api < Opcodes.ASM8 && (access & Opcodes.ACC_RECORD) != 0) {
throw new UnsupportedOperationException("Records requires ASM8");
}
if (cv != null) {
cv.visit(version, access, name, signature, superName, interfaces);
}
}visitField方法
1
2
3
4
5
6
7
8
9
10
11public FieldVisitor visitField(
final int access, // 访问表示
final String name, // 字段名
final String descriptor, // 字段描述符
final String signature, // 泛型类型
final Object value) { // 字段值
if (cv != null) {
return cv.visitField(access, name, descriptor, signature, value);
}
return null;
}visitMethod方法
1
2
3
4
5
6
7
8
9
10
11public MethodVisitor visitMethod(
final int access, // 访问标识
final String name, // 方法名
final String descriptor, // 方法描述符
final String signature, // 泛型类型
final String[] exceptions) { // 异常
if (cv != null) {
return cv.visitMethod(access, name, descriptor, signature, exceptions);
}
return null;
}
FieldVisitor类
一个抽象类
在
FieldVisitor
类内定义的多个visitXxx()
方法,也需要遵循一定的调用顺序,如下所示:[]
: 表示最多调用一次,可以不调用,但最多调用一次。()
和|
: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。*
: 表示方法可以调用0次或多次。
1
2
3
4
5
6(
visitAnnotation |
visitTypeAnnotation |
visitAttribute
)*
visitEnd
MethodVisitor类
一个抽象方法
遵循一定的调用顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19(visitParameter)*
[visitAnnotationDefault]
(visitAnnotation | visitAnnotableParameterCount | visitParameterAnnotation | visitTypeAnnotation | visitAttribute)*
[
visitCode
(
visitFrame |
visitXxxInsn |
visitLabel |
visitInsnAnnotation |
visitTryCatchBlock |
visitTryCatchAnnotation |
visitLocalVariable |
visitLocalVariableAnnotation |
visitLineNumber
)*
visitMaxs
]
visitEndvisitXxx()
方法分成三组:- 第一组,在
visitCode()
方法之前的方法。这一组的方法,主要负责parameter、annotation和attributes等内容,这些内容并不是方法当中“必不可少”的一部分;暂时不去考虑这些内容,可以忽略这一组方法。 - 第二组,在
visitCode()
方法和visitMaxs()
方法之间的方法。这一组的方法,主要负责当前方法的“方法体”内的opcode内容。其中,visitCode()
方法,标志着方法体的开始,而visitMaxs()
方法,标志着方法体的结束。 - 第三组,是
visitEnd()
方法。这个visitEnd()
方法,是最后一个进行调用的方法。
- 第一组,在
ClassWriter类
ClassVistor
的实现类,在创建ClassWriter
对象的时候,要指定一个flags
参数,它可以选择的值有三个:
- 第一个,可以选取的值是
0
。ASM不会自动计算max stacks和max locals,也不会自动计算stack map frames。 - 第二个,可以选取的值是
ClassWriter.COMPUTE_MAXS
。ASM会自动计算max stacks和max locals,但不会自动计算stack map frames。 - 第三个,可以选取的值是
ClassWriter.COMPUTE_FRAMES
(推荐使用)。ASM会自动计算max stacks和max locals,也会自动计算stack map frames。
FieldWriter类
FieldVisitor
的具体实现类,FieldWriter
类并不带有public
修饰,因此它的有效访问范围只局限于它所处的package当中,不能像其它的public
类一样被外部所使用。
MethodWriter类
MethodWriter
类的父类是MethodVisitor
类。在ClassWriter
类里,visitMethod()
方法的实现就是通过MethodWriter
类来实现的。
MethodWriter
类并不带有public
修饰,因此它的有效访问范围只局限于它所处的package当中,不能像其它的public
类一样被外部所使用。
生成一个类
生成一个HelloWorld接口
1 | package run; |
生成一个HelloWorld类
1 | package run; |
ASM读取类
ClassReader类
用于读取
.class
文件,区别于ClassWriter
一个公共类,不继承
ClassVisitor
。在
ClassReader
类当中定义了5个构造方法。但是,从本质上来说,这5个构造方法本质上是同一个构造方法的不同表现形式。其中,最常用的构造方法有两个- 第一个是
ClassReader cr = new ClassReader("sample.HelloWorld");
- 第二个是
ClassReader cr = new ClassReader(bytes);
- 第一个是
accept方法,这个方法接收一个
ClassVisitor
类型的参数,将ClassReader
和ClassVisitor
进行连接。1
2
3public void accept(final ClassVisitor classVisitor, final int parsingOptions) {
accept(classVisitor, new Attribute[0], parsingOptions);
}parsingOptions
参数可以选取的值有以下5个:0
:会生成所有的ASM代码,包括调试信息、frame信息和代码信息。ClassReader.SKIP_CODE
:会忽略代码信息,例如,会忽略对于MethodVisitor.visitXxxInsn()
方法的调用。ClassReader.SKIP_DEBUG
:会忽略调试信息,例如,会忽略对于MethodVisitor.visitParameter()
、MethodVisitor.visitLineNumber()
和MethodVisitor.visitLocalVariable()
等方法的调用。ClassReader.SKIP_FRAMES
:会忽略frame信息,例如,会忽略对于MethodVisitor.visitFrame()
方法的调用。ClassReader.EXPAND_FRAMES
:会对frame信息进行扩展,例如,会对MethodVisitor.visitFrame()
方法的参数有影响。
读取一个类
编译HelloWorld类
编译
HelloWorld
类为Class文件,然后通过ClassReader
读取Class文件1
2
3
4
5
6package sample;
import java.io.Serializable;
public class HelloWorld extends Exception implements Serializable, Cloneable {
}
读取HelloWorld.class
使用
ClassReader
读取Class文件信息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
46package run;
import org.objectweb.asm.ClassReader;
import java.io.*;
import java.util.Arrays;
public class HelloWorldRun {
public static void main(String[] args) throws Exception {
// 获取Class绝对路径
String relativePath = "sample/HelloWorld.class";
String absolutePath = HelloWorldRun.class.getResource("/").getPath() + relativePath;
// 读取字节
File file = new File(absolutePath);
InputStream inputStream = new FileInputStream(file);
inputStream = new BufferedInputStream(inputStream);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
copy(inputStream, byteArrayOutputStream);
byte[] bytes = byteArrayOutputStream.toByteArray();
//(1)构建ClassReader
ClassReader cr = new ClassReader(bytes);
// (2) 调用getXxx()方法
int access = cr.getAccess();
System.out.println("access: " + access);
String className = cr.getClassName();
System.out.println("className: " + className);
String superName = cr.getSuperName();
System.out.println("superName: " + superName);
String[] interfaces = cr.getInterfaces();
System.out.println("interfaces: " + Arrays.toString(interfaces));
}
public static void copy(InputStream source, OutputStream target) throws IOException {
byte[] buf = new byte[8192];
int length;
while ((length = source.read(buf)) > 0) {
target.write(buf, 0, length);
}
}
}输出结果
1
2
3
4access: 33
className: sample/HelloWorld
superName: java/lang/Exception
interfaces: [java/io/Serializable, java/lang/Cloneable]
转换一个类
ClassReader
类,是ASM提供的一个类,可以直接拿来使用。ClassWriter
类,是ASM提供的一个类,可以直接拿来使用。ClassVisitor
类,是ASM提供的一个抽象类,因此需要写代码提供一个ClassVisitor
的子类,在这个子类当中可以实现对.class
文件进行各种处理操作。
为HelloWorld类添加接口
编写HelloWorld.java类如下:
1
2
3
4
5package sample;
public class HelloWorld {
}初始的HelloWorld.class文件:
1
2
3
4
5
6
7
8
9
10
11//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package sample;
public class HelloWorld {
public HelloWorld() {
}
}通过ASM的
ClassReader
、ClassWriter
、ClassVisitor
类对HelloWorld类进行转换,为其添加一个实现接口编写HelloWorldCloneVisitor类继承ClassVisitor类来实现具体功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package run;
import org.objectweb.asm.ClassVisitor;
public class HelloWorldCloneVisitor extends ClassVisitor {
public HelloWorldCloneVisitor(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
}
// 重写visit方法
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, new String[]{"java/lang/Cloneable"});
}
}编写HelloWorldTransform转换已有的class文件:
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
55package run;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import java.io.*;
public class HelloWorldTransform {
public static void main(String[] args) throws IOException {
// 获取Class绝对路径
String relativePath = "sample/HelloWorld.class";
String absolutePath = HelloWorldRun.class.getResource("/").getPath() + relativePath;
// 读取字节
File file = new File(absolutePath);
InputStream inputStream = new FileInputStream(file);
inputStream = new BufferedInputStream(inputStream);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
copy(inputStream, byteArrayOutputStream);
byte[] bytes1 = byteArrayOutputStream.toByteArray();
//(1)构建ClassReader
ClassReader cr = new ClassReader(bytes1);
//(2)构建ClassWriter
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//(3)串连ClassVisitor
int api = Opcodes.ASM9;
ClassVisitor cv = new HelloWorldCloneVisitor(api, cw);
//(4)结合ClassReader和ClassVisitor
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cv, parsingOptions);
//(5)生成byte[]
byte[] bytes2 = cw.toByteArray();
// (6) 重新写入class文件
OutputStream outputStream = new FileOutputStream(absolutePath);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
bufferedOutputStream.write(bytes2);
bufferedOutputStream.flush();
}
public static void copy(InputStream source, OutputStream target) throws IOException {
byte[] buf = new byte[8192];
int length;
while ((length = source.read(buf)) > 0) {
target.write(buf, 0, length);
}
}
}转换后的HelloWorld.class文件:
1
2
3
4
5
6
7
8
9
10
11//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package sample;
public class HelloWorld implements Cloneable {
public HelloWorld() {
}
}
为HelloWorld类添加字段
编写HelloWorld.java类如下:
1
2
3
4
5package sample;
public class HelloWorld {
}初始的HelloWorld.class文件:
1
2
3
4
5
6
7
8
9
10
11//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package sample;
public class HelloWorld {
public HelloWorld() {
}
}编写HelloWorldAddFieldVisitor类继承ClassVisitor类来实现具体功能:
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
40package run;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
public class HelloWorldAddFieldVisitor extends ClassVisitor {
private final int fieldAccess;
private final String fieldName;
private final String fieldDesc;
private boolean isFieldPresent;
public HelloWorldAddFieldVisitor(int api, ClassVisitor classVisitor, int fieldAccess, String fieldName, String fieldDesc) {
super(api, classVisitor);
// 字段访问标识
this.fieldAccess = fieldAccess;
// 字段名称
this.fieldName = fieldName;
// 字段描述符
this.fieldDesc = fieldDesc;
}
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
if (name.equals(fieldName)) {
isFieldPresent = true;
}
return super.visitField(access, name, descriptor, signature, value);
}
public void visitEnd() {
if (!isFieldPresent) {
FieldVisitor fv = super.visitField(fieldAccess, fieldName, fieldDesc, null, null);
if (fv != null) {
fv.visitEnd();
}
}
super.visitEnd();
}
}编写HelloWorldTransform转换已有的class文件:
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
55package run;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import java.io.*;
public class HelloWorldTransform {
public static void main(String[] args) throws IOException {
// 获取Class绝对路径
String relativePath = "sample/HelloWorld.class";
String absolutePath = HelloWorldRun.class.getResource("/").getPath() + relativePath;
// 读取字节
File file = new File(absolutePath);
InputStream inputStream = new FileInputStream(file);
inputStream = new BufferedInputStream(inputStream);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
copy(inputStream, byteArrayOutputStream);
byte[] bytes1 = byteArrayOutputStream.toByteArray();
//(1)构建ClassReader
ClassReader cr = new ClassReader(bytes1);
//(2)构建ClassWriter
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//(3)串连ClassVisitor
int api = Opcodes.ASM9;
ClassVisitor cv = new HelloWorldAddFieldVisitor(api, cw, Opcodes.ACC_PUBLIC, "objValue", "Ljava/lang/Object;");
//(4)结合ClassReader和ClassVisitor
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cv, parsingOptions);
//(5)生成byte[]
byte[] bytes2 = cw.toByteArray();
// (6) 重新写入class文件
OutputStream outputStream = new FileOutputStream(absolutePath);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
bufferedOutputStream.write(bytes2);
bufferedOutputStream.flush();
}
public static void copy(InputStream source, OutputStream target) throws IOException {
byte[] buf = new byte[8192];
int length;
while ((length = source.read(buf)) > 0) {
target.write(buf, 0, length);
}
}
}转换后的HelloWorld.class文件:
1
2
3
4
5
6
7
8
9
10
11
12
13//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package sample;
public class HelloWorld {
public Object objValue;
public HelloWorld() {
}
}