之前简单研究过ASM这个字节码修改框架,最近要用到,故简单复习下。顺便翻译下官方文档(翻译主要是给自己看的,因此比较随意,自己看一眼就明白的直接跳过了,大家权且当作是读书笔记吧)。
字节码修改的必要性
- 字节码的分析、翻译及自动生成很常见
- 直接作用于class文件的优势是,可以适用于闭源软件商业软件等拿不到源码的修改翻译,并且可以在运行时修改字节码,其操作对于源代码开发者是透明的。
ASM的优势
- 足够小足够快
- API足够好用
- 有Eclipse及Intellij插件
- 应用广泛
- 开源协议宽泛
概览
ASM设计用于字节码的改写、生成。其有两套API一套基于访问者模式、一套基于树的数据结构。基于访问者模式的api,类中的每个数据结构都是一个Event,类的生成同样基于这样的Event。基于树的数据结构的api是面向对象的一种设计,类被表示为一个对象。这两套api的区别类似xml解析中的SAX和DOM。两套api都是对同一个class操作的,如果用户需要修改相关联的类需要自己手动管理。
功能介绍
asm.jar 包含时间模式的API,适用于class文件的读写。
asm-util.jar 包含一些工具类,基于base api 用于协助开发调试
asm-commons.jar 提供了一些很有用的预定义基于事件的类转换器
asm-tree.jar 提供了基于对象的api并提供两种api的转化
asm-analysis.jar 提供了基于对象api的类的分析框架类
字节码文件格式概览
- 描述类访问控制权限,类名,父类,接口和注解
- 每个被声明的成员变量,同样包括访问控制权限、名称、类型、注解
- 方法及构造函数,包括访问控制权限,名称、名称、返回值类型、参数类型、注解等。同时还包含方法体(一系列jvm指令)
类源码和字节码区别:每个class文件只包含一个类,没有注释。没有package 和 import部分,所有的类型必须使用全名。另一个重要的区别在于,编译后的字节码有一个常量存放区。存放着类中出现的所有的数字、字符串、类型常量。这些常量只被定义一次,被类的其他部分通过索引引用。不过使用ASM的话常量定义区对我们而言是透明的。另一个区别在于源文件和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
bytecode中type表示如下:类似jni接口
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;
方法描述也类似jni
Method declaration in source file Method descriptor
void m(int i, float f) (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;
接口和组件
class的生成和转换时基于ClassVisitor抽象类的。该类的每个方法都对应class的一个结构。简单的结构对应的方法入参为结构的描述,方法返回void即可。复杂的使用一个init方法来访问,并且返回一个描述改结构的visitor类。例如visitAnnotation、visitField\visitMethod需要返回AnnotationVisitor、FieldVisitor、MethodVisitor。这样整个结构可以被递归的调用
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方法调用必须要按照以下顺序:visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )* ( visitInnerClass | visitField | visitMethod )* visitEnd
基于ClassVisitor ASM提供了 ClassReader来解析字节流格式的class. ClassWriter 用于生成二进制格式的class. ClassVisitor 可以看作是Event的过滤器