文章目录
1 简介
官网:https://asm.ow2.io/
javadoc :https://asm.ow2.io/javadoc/org/objectweb/asm/package-summary.html
(the ASM name does not mean anything: it is just a reference to the
__asm__
keyword in C, which allows some functions to be implemented in assembly language.)
ASM是一个多功能的java 字节码分析操控框架,可用于分析现有的类、或是动态的生成二进制的类。ASM提供了一些通用的字节码转化器和分析算法,复杂的自定义的转化器和分析工具可在此基础上进行二次开发。
另外,Java也有一些其他的字节码框架,但是ASM的主要特点是高性能,因为ASM一开始的设计目标就是代码短小、且尽可能的运行快。
asm作为一个java字节码分析操作框架,要了解asm,首先了解一下java字节码结构。
2. java字节码结构
相对于其他的语言的可执行文件,java字节码结构比较简单。一个字节码包含如下内容:
- 类的修饰符(public,private)、类名、父类、接口和类的注解;
- field 节,每个field都有一个field 节,描述该成员的修饰符、名字、类型、和该成员上的标注;
- 方法节,每个方法(包括构造方法)都有一个method节,描述该方法的修饰符、名字、返回类型、参数列表、方法代码、和该成员上的标注;
字节码总体结构如下图所示:
java源码与字节码的区别:
- 一个字节码只和一个class对应。但是一个java 类源文件中可以包含多个类(但是只有一个public的类);
- 字节码中没有代码注释;
- 字节码中没有
package
,import
语句了。所有的类型都是全限定类名。 - 另外,字节码中存在常量池节(
constant pool section
) 。该节是一个数组,包含该字节码中出现的所有的数自、字符串、类型常量。这些常量只定义一次,就是在常量池中,其他节中通过数组下标(index)进行引用。
另外,类的内部名字(internal name)使用全限定名描述,但是和源码中全限定名描述不同的是内部名字用斜杠代替点号,例如对于String,其内部名字是:java/lang/String
。
字节码中的类型描述符:
字节码中的方法描述符:
3.asm 对字节码的描述、建模与处理
ASM 提供了两种API来完成java字节码的处理工作
-
基于事件(event)的核心API。这种方式是将一个类建模表示为事件序列。每个事件(event)表示类的一个成员,例如类的头部、成员、方法、或是方法中的一条指令,都是一个事件。基于事件的API定义了可能的事件、及事件的顺序约束,另外,还提供了类
parser
来解析字节码生成事件序列、类wirter
来根据一系列的事件生成字节码。这种方式和XML解析中的SAX模型类似。 -
基于对象(object)的tree API。这种方式是将一个类建模表示象树;每个对象表示一个类的一部分,例如成员、方法、指令等,都是对象树上的一个对象。基于对象的tree API是基于事件的API构建的。这种方式和XML解析中的DOM模型类似。
4.asm 的基于事件的核心API
基于事件的核心API主要以classVistor
,ClassReader
,ClassWriter
来处理字节码。他们的作用如下:
-
ClassReader: 解析字节妈,然后调用相应的
ClassVisitor
对象的visitXxx m
方法(在调用ClassReader
的accept
方法时,ClassReader
会绑定一个ClassVistor
对象) -
ClassWriter :
ClassWriter
是ClassVisitor
抽象类的子类,用于直接构建二进制形式的编译后的类; -
classVistor :用于处理事件,Vistor之间形成一个责任链。一个处理完后会将控制传递到下一个vistor;
5. ASM 中的 ClassVisitor
抽象类
public abstract class ClassVisitor {
public ClassVisitor(int api);
public ClassVisitor(int api, ClassVisitor cv);
public void visit (int version, int access, String name,
String signature, 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**();
}
ClassVisitor
中的每个方法和字节码的结构对应。如果字节码中的section内容简单,则对应的visist方法会返回void。如果字节码中节的内容比较复杂、且内容长度可变,那么方法返回一个辅助visitor 类(例如,AnnotationVisitor,
FieldVisitor 或是 MethodVisitor),来继续访问。对于AnnotationVisitor
,
FieldVisitor
, MethodVisitor
的处理原则同ClassVisitor
,对于子的复杂的节处理,又会返回一个辅助Vistor。例如,对于FieldVistor,其方法如下:
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();
}
在解析一个类时,调用的visitXX方法的顺序原则如下:
visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd
6.使用asm解析已有的类的示例
这是一个简单示例,对于已有的类,打印出来。类似与javap 工具。
6.1 继承ClassVistor类,打印出所有访问(visit)到的数据
public class ClassPrinter extends ClassVisitor {
public ClassPrinter() {
super(ASM4);
}
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
// 输出访问到的类的header
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("}");
}
}
6.2 将ClassPrinter(ClassVistor) 与ClassReader绑定,使得ClassReader读取到的事件能被ClassPrinter消费
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0); // 在这行ClassReader会去解析 Runable类,然后根据解析到内容,生成相关的事件,并调用ClassPrinter去消费处理
程序输出如下:
java/lang/Runnable extends java/lang/Object {
run()V
}
7.使用asm生成类
以如下接口为例进行说明:
package pkg;
public interface Comparable extends Mesurable {
int LESS = -1;
int EQUAL = 0;
int GREATER = 1;
int compareTo(Object o);
}
上述接口可通过调用ClasVistor
6次生成,调用形式如下:
ClassWriter cw = new ClassWriter(0);
//生成接口的header,包括名字,父接口,描述符
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"pkg/Comparable", null, "java/lang/Object",
new String[] { "pkg/Mesurable" });
//生成一个属性成员
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I",
null, new Integer(-1)).visitEnd();
//生成一个属性成员
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I",
null, new Integer(0)).visitEnd();
//生成一个属性成员
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I",
null, new Integer(1)).visitEnd();
//生成一个方法
cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo",
"(Ljava/lang/Object;)I", null, null).visitEnd();
cw.visitEnd();
//结果保存在ClasWriter中,获取流式的结果
byte[] b = cw.toByteArray();
上述生成的类数据可以保存到文件中,也可以通过classloader
进行加载到JVM中使用,例如:
class MyClassLoader extends ClassLoader {
public Class defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
然后使用自定义的类加载器进行加载:
Class c = myClassLoader.defineClass("pkg.Comparable", b);
8.使用ASM对类进行变形
对类进行变形的基本流程:使用ClassReader进行读取,然后调用ClassVistor处理,最后通过ClassWriter输出。
示例代码如下:
//类字节码以字节数组的方式读取,也可以以其他方式进行
byte[] b1 = ...;
ClassWriter cw = new ClassWriter(0);
// cv 会将所有的事件传递到cw上(classVistor之间形成链)
ClassVisitor cv = new ClassVisitor(ASM4, cw) { };
//指定cr要处理的字节码
ClassReader cr = new ClassReader(b1);
//cr开始解析,调用cv处理事件,cv会将事件传递到cw上
cr.accept(cv, 0);
//获取转化后的结果
byte[] b2 = cw.toByteArray(); // b2 represents the same class as b1
上述代码的处理过程如下:
8.1 事件过滤
如果某个Vistor只重写了父类ClassVistor
的某些方法,则该vistor不会响应其他方法。例如,ChangerVersionAdapter
继承了ClassVistor
,只重写了visit
方法,则它的上游(如ClassReader)在解析到类header section时,调用它的visit方法,在解析到其他事件时,就不会调用ChangerVersionAdapter
了。
示例:
public class ChangeVersionAdapter extends ClassVisitor {
public ChangeVersionAdapter(ClassVisitor cv) {
super(ASM4, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
cv.visit(V1_5, access, name, signature, superName, interfaces);
}
}