ASM字节码基础
Posted 乌龟先生n
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ASM字节码基础相关的知识,希望对你有一定的参考价值。
ASM字节码基础
一、几个重要概念
ASM官网
1.1 内部名
已编译的类中,类或接口类型使用内部名表示。
一个类的内部名就是这个类的全限定名,并将其中的点号换成/表示。
例如:String的内部名是java/lang/String
1.2 类型描述符
除了类或接口类型之外的其他类型,在已编译类中都是用类型描述符表示的。
Java类型 | 类型描述符 |
---|---|
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; |
规律如下:
- 几个特殊的基本数据类型,boolean类型是Z,long类型是J,其他都是首字母大小
- 基础数据类型是单个字符,末尾无分号
- 类类型的描述符=L + 内部名 + ;
- 数组类型的描述符是左方括号后面跟该数组元素类型的描述符:
Object[] --> [Ljava/lang/Object;
Object[][] --> [[Ljava/lang/Object;
切记: 类描述符末尾的分号不能丢!!
1.3 方法描述符
(类型0的描述符类型1的描述符…)返回类型描述符
举例:
源文件中的方法声明 | 方法描述符 |
---|---|
void m(int i, float f) | (IF)V |
int m(Object) | (Ljava/lang/Object;)I |
int[] m(int i, String s) | (ILjava/lang/String;)[I |
Object m(int[] i) | ([I)Ljava/lang/Object; |
存疑:有范型类型怎么表示?
这个问题,其实很好确定,使用javap -verbose 查看一下带有范型类型的源码对应的字节码即可。
例如:
- 源码:
import java.util.ArrayList;
import java.util.List;
public class Main
public static void main(String[] args) throws Exception
List<String> list = new ArrayList<>();
System.out.println(list);
- javap -verbose查看字节码:
public com.yyg.asmdemo.Main();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/yyg/asmdemo/Main;
public static void main(java.lang.String[]) throws java.lang.Exception;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_1
12: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
15: return
LineNumberTable:
line 8: 0
line 9: 8
line 10: 15
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 args [Ljava/lang/String;
8 8 1 list Ljava/util/List;
LocalVariableTypeTable:
Start Length Slot Name Signature
8 8 1 list Ljava/util/List<Ljava/lang/String;>;
// Ljava/util/List<Ljava/lang/String;>; 就是List<String>的类型描述符
Exceptions:
throws java.lang.Exception
范型类型会在运行时被擦除,但是字节码插桩还是针对的字节码,属于编译时,而且ASM能作用的时间节点只能是编译时,不包括运行时。
1.4 回头看,总结内部名、类型描述符、方法描述符三个概念的关系
- 有了内部名的概念,就能表示类或接口类型的类型描述符
- 有了类型描述符,就能够表示方法描述符
因为描述一个方法跟具体的方法名无关,区分不同方法的标识是参数列表+返回值类型,所以方法描述符又需要类型描述符的概念铺垫。这三个概念是层层递进的关系的。
二、ASM的核心api
2.1 ClassReader: 读取class二进制字节码文件到内存中
2.2 ClassVisitor:定义的方法对应类的各个结构部分
2.3 ClassWriter:将ClassReader读入到内存的字节码重写回文件中
一般实现修改字节码的模板代码:
// (1) 第一步:获取clazz字节码文件的路径,也就是编译好的.class文件
Class clazz = XXX.class;
String classFilePath = Utils.getClassFilePath(clazz)
//(2)第二步:创建ClassReader对象,把class文件的二进制流作为参数传入ClassReader对象
ClassReader classReader = new ClassReader(new FileInputStream(classFilePath));
//(3)第三步:创建需要解析、修改的ClasVisitor对象
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
//(4)第四步:创建自定义的ClassVisitor(ClassWriter),并将classReader对象作为参数传入
MyClassVisitor myClassVisitor = new MyClassVisitor(Opcodes.AMS5, classWriter);
//(5)第五步:调用classReader.accept(classVisitor, 0)
classReader.accept(myClassVisitor, 0);
//(6)在MyClassVisitor中实现字节码的修改逻辑,FieldVisitor、MethodVisitor等内部方法的逻辑编写。
三、常见字节码修改逻辑的模版代码
操作逻辑 | 实现方案 | 模板代码 | 注意事项 |
---|---|---|---|
给类新增字段 | 在ClassVisitor#visitEnd()中编写逻辑 | fv.visitEnd()和cv.visitEnd()不能漏写 | |
给类方法的开头新增代码 | 在自定义的MethodVisitor#visitCode中编写代码 | … | |
给类方法的末尾新增代码 | 在自定义的MethodVisitor#visitInsn中编写代码,并且前置条件(opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) or opcode == Opcodes.ATHROW为true | … |
字节码字节码操作 ASM 组成部分
1. ASM的两个组成部分
从组成结构.上来说, ASM分成两部分,- -部分为Core API,另一部分为Tree API。
●其中,Core API包括asm.jar、asm-util,jar和asm-commons.jar ;
●其中,Tree API包括asm-tree.jar和asm- analysis.jar。
从两者的关系来说, Core API是基础,而Tree API
以上是关于ASM字节码基础的主要内容,如果未能解决你的问题,请参考以下文章
字节码Java Instrumentation 简介 以及 ASM 组合案例