1 概念
1.1是什么
简单来说, ASM是一个操作Java字节码的类库。
1.2 如何处理
拆分-修改-合并”。
ASM处理字节码(ByteCode)数据的思路是这样的:
- 第一步,将.class文件拆分成多个部分;
- 第二步,对某一个部分的信息进行修改;
- 第三步,将多个部分重新组织成一个新的.class文件。
1.3 能做什么
- 父类:修改成一个新的父类
- 接口:添加一个新的接口、删除已有的接口
- 字段:添加一个新的字段、删除已有的字段
- 方法:添加一个新的方法、删除已有的方法、修改已有的方法
- ……(省略)
2 结构
2.1 两个模型
- 基于
事件的形式 来表示类 (内存低,速度快,重点讲解) - 另一个是树 API,以
基于对象的形式来表示类。 (内存占用大,类转换方便)
API结构
2.2 事件模型
类是用一系列事件来表示的,每个事件表示类的一个元素
流程处理图如下:
这三个类的作用,可以简单理解成这样:
- ClaassReader类,负责读取.class文件里的内容,然后拆分成各个不同的部分。
ClassVisitor类,负责对.class文件中某一部分里的信息进行修改。- FieldVisitor类,负责对字段的信息进行修改。
- MethodVisitor类,负责对方法的信息进行修改。
- ClassWriter类,负责将各个不同的部分重新组合成一个完整的.class文件。
3. ClassVisitor
3.1 概述
用于生成和变转已编译类的ASM API是基于ClassVisitor抽象类的。这个类中的每个方法都对应于同名的类文件结构部分
三个基于 ClassVisitor API 的核心组件,用于生成和变化类
ClassReader类分析以字节数组形式给出的已编译类,并针对在其accept方法参数 中传送的ClassVisitor实例,调用相应的visitXxx方法。这个类可以看作一个事件产生器。ClassWriter类是ClassVisitor抽象类的一个子类,它直接以二进制形式生成编 译后的类。它会生成一个字节数组形式的输出,其中包含了已编译类,可以用toByteArray方法来提取。这个类可以看作一个事件使用器。ClassVisitor类将它收到的所有方法调用都委托给另一个ClassVisitor类。这个 类可以看作一个事件筛选器。 (可以从网络中间人攻击理解)
例子
object ByteCodeModify {
@JvmStatic
fun main(args: Array<String>) {
val readClassFilePath = "buildSrc/build/classes/java/main/ByteCodeTest.class"
val writeClassFilePath = "buildSrc/src/main/java/ByteCodeTestModify.class"
// 1 定义ClassReader,并且读取字节码
val classReader = ClassReader(File(readClassFilePath).inputStream())
//2 定义中间人ClassWriter,用于获取字节码
val classWriter = ClassWriter(ClassWriter.COMPUTE_MAXS)
//3 定义中间人ClassVisitor,这里用系统TraceClassVisitor,并委托给classWriter
val classVisitor = TraceClassVisitor(classWriter, Textifier(),PrintWriter(System.*out*, true))
//4 开始事件调用
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES or ClassReader.SKIP_FRAMES)
File(writeClassFilePath).*outputStream*().write(classWriter.toByteArray())
}
}
3.2 事件调用顺序
visit visitSource? visitOuterClass? ( visitAnnotation |
visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd
重点关注
public abstract class ClassVisitor {
public void visit( // 只调用一次
final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces);
public FieldVisitor visitField( // 访问字段 , 调用一次或多次
final int access,
final String name,
final String descriptor, //描述符
final String signature,
final Object value);
public MethodVisitor visitMethod( // 访问方法,调用一次或多次
final int access, //访问标识
final String name, //名称
final String descriptor, //描述符
final String signature, //属性表的一部分信息
final String[] exceptions); //属性表的一部分信息
public void visitEnd(); //结束,调用一次
// ......
}
这里以visitMethod时间分析,对应着class文件的方法属性
method_info {
u2 access_flags; //对于visitMethod access
u2 name_index; //对于visitMethod name
u2 descriptor_index; //对于visitMethod descriptor
u2 attributes_count;
attribute_info attributes[attributes_count]; 、//对于visitMethod exceptions
}
4 MethodVisitor
4.1 概述
用于生成和转换已编译方法的 ASM API 是基于 MethodVisitor ,它 由 ClassVisitor的 visitMethod 方法返回。
abstract class MethodVisitor { // public accessors ommited MethodVisitor(int api);
MethodVisitor(int api, MethodVisitor mv);
AnnotationVisitor visitAnnotationDefault();
AnnotationVisitor visitAnnotation(String desc, boolean visible); AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible);
void visitAttribute(Attribute attr);
void visitCode();
void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack);
void visitInsn(int opcode);
void visitIntInsn(int opcode, int operand);
void visitVarInsn(int opcode, int var);
void visitTypeInsn(int opcode, String desc);
void visitFieldInsn(int opc, String owner, String name, String desc);
void visitMethodInsn(int opc, String owner, String name, String desc); void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs);
void visitJumpInsn(int opcode, Label label);
void visitLabel(Label label);
void visitLdcInsn(Object cst);
void visitIincInsn(int var, int increment);
void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels); void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels); void visitMultiANewArrayInsn(String desc, int dims);
void visitTryCatchBlock(Label start, Label end, Label handler,
String type);
void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index);
void visitLineNumber(int line, Label start);
void visitMaxs(int maxStack, int maxLocals);
void visitEnd();
}
从ClassFile的角度来讲,这些visitXxxInsn()方法的本质就是组装instruction的内容。我们可以参考 Java Virtual Machine Specification的 Chapter 6. The Java Virtual Machine Instruction Set部分。
4.2 生成方法体
以一个类的默认构造函数为例
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitCode(); //方法体的开始
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(1, 1); //方法体的结束
methodVisitor.visitEnd(); //方法的结束
visitCode和 visitMaxs 方法可用于检测该方法的字节代码在一个事件序列中的 开始与结束。和类的情况一样,visitEnd 方法也必须在最后调用,用于检测一个方法在一个事件序列中的结束
必须调用的3个指令
- videocode
- .......
- videoMaxs
- visitEnd
4.3 转化方法
方法转化时机
其实就是在
ClassVisitor的visitMethod定义新的MethodVisitor对象
MethodVisitor子类
MethodVisitor //抽象类
LocalVariablesSorter //解决局部变量的下标问题
GeneratorAdapter //封装指令
AdviceAdapter //提供两个方法,方法体的开始和结束
AdciceAdapter,提供两个方法方便插入指令
val newMethodVisitor =
object : AdviceAdapter(Opcodes.ASM9, methodVisitor, access, name, descriptor) {
@Override
override fun onMethodEnter() {
// 方法开始
super.onMethodEnter()
println("aAdviceAdapter onMethodEnter name:${name} ")
}
@Override
override fun onMethodExit(opcode: Int) {
// 方法结束
super.onMethodExit(opcode)
println("AdviceAdapter onMethodExit name:${name} opcode:${opcode}")
}
}
GeneratorAdapter
LocalVariablesSorter
特点是:可以引入新的局部变量,并且能够对局部变量进行排序
newLocal 方法,它会根据指定的类型创建一个新的本地变量,并直接分配一个本地变量的引用 index,其优势在于可以尽量复用以前的局部变量,而不需要我们考虑本地变量的分配和覆盖问题**