BruceFan's Blog

Stay hungry, stay foolish

0%

JAVA ASM字节码框架

ASM是一款基于JAVA字节码层面的代码分析和修改工具;无需提供源代码即可对应用增添所需的Debug代码。用于应用API性能分析,代码优化和代码混淆等工作。ASM的目标是生成,转换和分析已编译的JAVA class文件,可使用ASM工具读、写、转换JVM指令集。
ASM工具提供两种方式来产生和转换已编译的class文件:

  • 基于事件的表示模型使用一个有序的事件序列表示一个class文件,class文件中的每一个元素使用一个事件来表示,比如class的头部,变量,方法声明,JVM指令都有相对应的事件表示,ASM使用自带的事件解析器能将每一个class文件解析成一个事件序列。
  • 基于对象的表示模型则使用对象树结构来解析每一个class文件。

基于事件模型的ASM工具使用生产者-消费者模型转换/产生一个class文件。其转换过程中涉及到自定义的事件生产者自定义的事件过滤器自定义的事件消费者这三种组件。其中使用ClassReader来解析每一个class文件中的事件元素;使用自定义的各种基于方法/变量/声明/类注释的元素适配器来过滤和修改class事件元序列中的相应事件对象;最后使用ClassWriter来重新将更新后的class事件序列转换成class字节码供JVM加载执行。整个生产/转换class文件的过程如下图所示,起点和终点分别是ClassReader(class文件解析器)和ClassWriter(class事件序列转换到class字节码),中间的过程由若干个自定义的事件过滤器组成。

class文件的结构

class文件保持固定的结构信息,而且保留了几乎所有的源代码文件中的符号。一个class文件整体结构由几个区域组成,一个区域用来描述类的modifier,name,父类,接口和注释。一个区域用来描述类中变量的modfier,名字,类型和注释。一个区域用来描述类中方法和构造函数的modifier,名字参数类型,返回类型,注释等信息,当然也包含已编译成java字节码指令序列的方法具体内容。还有一个作为class文件的静态池区域,用来保存所有的数字,字符串,类型的常量,这些常量只被定义过一次且被其他class中区域所引用。class文件与源代码文件的关系:一个java文件最后会被编译成N(1 <= N)个class文件。
下表展示了一个class文件的总体概貌:

Modifiers, name, super class, interfaces
Constant pool: numeric, string and type constants
Source file name(optional)
Enclosing class reference
Annotation*
Attribute*
Inner class* Name
Field* Modifiers, name, type
Annotation*
Attribute*
Method* Modifiers, name, return and parameter types
Annotation*
Attribute*
Compiled code

class文件的内部命名

原Java类型与class文件内部类型对应关系:

Java type Type 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;

原Java方法声明与class文件内部方法声明的对应关系:

Method declaration in source file Method descriptor
void m(int i, float f) (IF)V
int m(Object o) (Ljava/langObject;)I
int[] m(int i, String s) (ILjava/lang/String;)[I
Object m(int [] i) ([I)Ljava/lang/Object;

ASM工具的接口和组件

ASM工具生产和转换class文件内容的所有工作都是基于ClassVisitor这个抽象类进行的。ClassVisitor抽象类中的每一个方法会对应到class文件的相应区域,每个方法负责处理class文件相应区域的字节码内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class ClassVisitor {
public ClassVisitor(int api);
public ClassVisitor(int api, ClassVisitor cv);
public void visit(int version, int access, String name, String signture, String superName, String[] interfaces);
public void visitSource(String source, String debug);
public void visitOuterClass(String owner, String name, String desc);
AnnotationVisitor visitAnnotation(String desc, boolean visible);
public void visitAttribute(Attribute attr);
public void visitInnerClass(String name, String outerName, String innerName, int access);
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value);
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions);
void visitEnd();
}

某些区域方法直接完成该区域字节码内容的处理并返回空,某些复杂class区域的方法需返回更加细节的XXXVisitor对象,此XXXVisitor对象将负责处理此class区域的更加细节的字节码内容,开发者可以编写继承自XXXVisitor抽象类的自定义类,在成员函数中实现对细节字节码操作的逻辑代码。比如,visitField方法用来负责class文件中变量区域的字节码内容修改,该区域又可细分出多种属性数据对象(注释,参数值),这里需要编写继承自FieldVisitor抽象类的自定义类完成这些细分数据对象的字节码内容操作。

1
2
3
4
5
6
7
public abstract class FieldVisitor {
public FieldVisitor(int api);
public FieldVisitor(int api, FieldVisitor fv);
public AnnotationVisitor visitAnnotation(String desc, boolean visible);
public void visitAttribute(Attribute attr);
public void visitEnd();
}

基于ClassVistor API的访问方式,ASM工具提供了三种核心组件用来实现class的产生和转换工作。ClassReader负责解析class文件字节码数组,然后将相应区域的内容对象传递给ClassVisitor实例中相应的visitXXX方法,ClassReader可以看作是一个事件生产者。ClassWriter继承自ClassVistor抽象类,负责将对象化的class文件内容重构成一个二进制格式的class字节码文件,ClassWriter可以看作是一个事件消费者。继承自ClassVistor抽象类的自定义类负责class文件各个区域内容的修改和生成,它可以看作是一个事件过滤器,一次生产消费过程中这样的事件过滤器可以有N个(0<=N)。

遍历class字节码类信息

下面的例子用来打印class字节码内容,这里以java.lang.runnable为例。
ClassPrinter.java

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
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import java.io.IOException;
import javax.print.attribute.Attribute;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;

public class ClassPrinter extends ClassVisitor {
public ClassPrinter() {
super(Opcodes.ASM5);
}
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
System.out.println(name + " extends " + superName + " {");
}
public void visitSource(String source, String debug) {}
public void visitOuterClass(String owner, String name, String desc) {}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return null;
}
public void visitAttribute(Attribute attr) {}
public void visitInnerClass(String name, String outerName, String innerName, int access) {}
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
System.out.println(" " + desc + " " + name);
return null;
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
System.out.println(" " + name + desc);
return null;
}
public void visitEnd() {
System.out.println("}");
}
public static void main(String args[]) throws IOException {
// ClassReader作为字节码生产者,ClassPrinter作为字节码消费者
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0);
}
}

输出:

1
2
3
java/lang/Runnable extends java/lang/Object {
run()V
}

产生自定义类对应的class字节码

需要产生如下接口的字节码:

1
2
3
4
5
6
7
package pkg;
public interface Comparable extends Mesurable {
int LESS = -1;
int EQUAL = 0;
int GREATER = 1;
int compareTo(Object o);
}

所需代码如下:
ClassGenerator.java

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
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

public class ClassGenerator {
public static void main(String[] args) throws IOException {
// 生成一个类只需要ClassWriter组件即可
ClassWriter cw = new ClassWriter(0);
// 通过visit方法确定类的头部信息
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC+Opcodes.ACC_ABSTRACT+Opcodes.ACC_INTERFACE,
"com/asm5/Comparable", null, "java/lang/Object", new String[] {"com/asm5/Mesurable"});
// 定义类的属性
cw.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC, "LESS", "I",
null, new Integer(-1)).visitEnd();
cw.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC, "EQUAL", "I",
null, new Integer(0)).visitEnd();
cw.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC, "GREATER", "I",
null, new Integer(1)).visitEnd();
// 定义类的方法
cw.visitMethod(Opcodes.ACC_PUBLIC+Opcodes.ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I",
null, null).visitEnd();
cw.visitEnd(); // 使cw类已经完成
// 将cw转换成字节数组写到文件里面去
byte[] data = cw.toByteArray();
File file = new File("/Users/fan/Comparable.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();
// 动态加载生成的class字节码
MyClassLoader myClassLoader = new MyClassLoader();
Class c = myClassLoader.defineClass("com.asm5.Comparable", data);
System.out.println(c.getName());
Field f[] = c.getFields();
for (int i = 0; i < f.length; i++)
System.out.println(f[i].toString());
}
}

class MyClassLoader extends ClassLoader {
public Class defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}

增加class中的方法或者变量

在visitEnd中增加想要增加的方法或者变量,首先,实现如下类:

1
2
3
4
5
6
7
package com.asm5;

public class C {
public void m() throws InterruptedException {
Thread.sleep(100);
}
}

目标就是为这个类的class文件添加一个计时器,修改成如下效果:

1
2
3
4
5
6
7
8
9
10
package com.asm5;

public class C {
public static long timer;
public void m() throws InterruptedException{
timer -= System.currentTimeMillis();
Thread.sleep(100);
timer += System.currentTimeMillis();
}
}

我们需要在m()方法的最前面增加指令,在RETURN指令前增加指令,同时指令必须位于xRETURN和ATHROW之前,因为这些指令都会结束方法的执行。
实现元素适配器:
AddTimeClassAdapter.java

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
package com.asm5;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class AddTimeClassAdapter extends ClassVisitor {
private String owner;
private boolean isInterface;
public AddTimeClassAdapter(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
// visit the head of the class
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
cv.visit(version, access, name, signature, superName, interfaces);
owner = name; // the internal name of the class
isInterface = (access & Opcodes.ACC_INTERFACE) != 0; // 判断类是否是接口
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (!name.equals("<init>") && ! isInterface && mv != null) {
// 为方法添加计时功能
mv = new AddTimeMethodAdapter(mv);
}
return mv;
}
@Override
public void visitEnd() {
// 添加字段
if (!isInterface) {
FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_STATIC,
"timer", "J", null, null);
if (fv != null) {
fv.visitEnd();
}
}
cv.visitEnd();
}

class AddTimeMethodAdapter extends MethodVisitor {

public AddTimeMethodAdapter(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
// Starts the visit of the method's code, if any(i.e. non abstract method)
@Override
public void visitCode() {
mv.visitCode();
// Visits a field instruction
mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");
// Visits a method instruction
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
"currentTimeMillis", "()J", isInterface);
// Visits a zero operand instruction
mv.visitInsn(Opcodes.LSUB);
mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "timer", "J");
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)
|| opcode == Opcodes.ATHROW) {
mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
"currentTimeMillis", "()J", isInterface);
mv.visitInsn(Opcodes.LADD);
mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "timer", "J");
}
mv.visitInsn(opcode);
}
@Override
public void visitMaxs(int maxStack, int maxLocal) {
mv.visitMaxs(maxStack+4, maxLocal);
}
}
}

修改class字节码文件:
Generator.java

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
package com.asm5;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

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

public class Generator {
public static void main(String[] args) {
try {
ClassReader cr = new ClassReader("com/asm5/C");
// ClassWriter extends ClassVisitor
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor classAdapter = new AddTimeClassAdapter(cw);
// 使给定的访问者访问Java类的ClassReader
cr.accept(classAdapter, ClassReader.SKIP_DEBUG);
byte[] data = cw.toByteArray();
File file = new File("/Users/fan/Computer/Java/ASMTest/bin/com/asm5/C.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();
System.out.println("success!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

下面是一个测试类:

1
2
3
4
5
6
7
8
9
10
11
package com.asm5;

public class Test {
public static void main(String[] args) throws InterruptedException, NoSuchFieldException,
SecurityException, IllegalArgumentException, IllegalAccessException {
C c = new C();
c.m();
Class cc = c.getClass();
System.out.println(cc.getField("timer").get(c));
}
}

输出结果为:100
ASM工具的作用还远不止如此,这里只是为了学习写的一些简单的用法,后面会写具体的应用,用到其他功能时再补充。还有:学习一个新的技术或工具时,有官方文档一定要多看官方文档!
reference
http://www.cnblogs.com/liuling/archive/2013/05/25/asm.html
http://blog.csdn.net/xysmiracle/article/details/38293795
http://asm.ow2.org/asm50/javadoc/user/index.html