字节码是ASM的基础,要想熟练的使用ASM,那么了解字节码就是必备基础。java
Class文件做为Java虚拟机所执行的直接文件,内部结构设计有着固定的协议,每个Class文件只对应一个类或接口的定义信息。数组
每一个Class文件都以8位为单位的字节流组成,下面是一个Class文件中所包括的内容,在Class文件中,各项内容按照严格顺序连续存放,Java虚拟机只要按照协议顺序来读取便可。markdown
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
复制代码
在Class文件结构中,上面各项的含义以下。网络
Name | 含义 |
---|---|
magic | 做为一个魔数,肯定这个文件是不是一个能被虚拟机接受的class文件,值固定为0xCAFEBABE。 |
minor_version,major_version | 分别表示class文件的副,主版本号,不一样版本的虚拟机实现支持的Class文件版本号不一样。 |
constant_pool_count | 常量池计数器,constant_pool_count的值等于常量池表中的成员数加1。 |
constant_pool | 常量池,constant_pool是一种表结构,包含class文件结构及其子结构中引用的全部字符常量、类或接口名、字段名和其余常量。 |
access_flags | access_flags是一种访问标志,表示这个类或者接口的访问权限及属性,包括有ACC_PUBLIC,ACC_FINAL,ACC_SUPER等等。 |
this_class | 类索引,指向常量池表中项的一个索引。 |
super_class | 父类索引,这个值必须为0或者是对常量池中项的一个有效索引值,若是为0,表示这个class只能是Object类,只有它是惟一没有父类的类。 |
interfaces_count | 接口计算器,表示当前类或者接口的直接父接口数量。 |
interfaces[] | 接口表,里面的每一个成员的值必须是一个对常量池表中项的一个有效索引值。 |
fields_count | 字段计算器,表示当前class文件中fields表的成员个数,每一个成员都是一个field_info。 |
fields | 字段表,每一个成员都是一个完整的fields_info结构,表示当前类或接口中某个字段的完整描述,不包括父类或父接口的部分。 |
methods_count | 方法计数器,表示当前class文件methos表的成员个数。 |
methods | 方法表,每一个成员都是一个完整的method_info结构,能够表示类或接口中定义的全部方法,包括实例方法,类方法,以及类或接口初始化方法。 |
attributes_count | 属性表,其中是每个attribute_info,包含如下这些属性,InnerClasses,EnclosingMethod,Synthetic,Signature,Annonation等。 |
以上内容来自网络,我也不知道从哪copy来的。函数
字节码和Java代码仍是有很大区别的。oop
字节码在Java虚拟机中是以堆栈的方式进行运算的,相似CPU中的寄存器,在Java虚拟机中,它使用堆栈来完成运算,例如实现「a+b」的加法操做,在Java虚拟机中,首先会将「a」push到堆栈中,而后再将「b」push到堆栈中,最后执行「ADD」指令,取出用于计算的两个变量,完成计算后,将返回值「a+b」push到堆栈中,完成指令。网站
咱们在Java代码中的类型,在字节码中,有相应的表示协议。this
Java Type | Type description |
---|---|
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; |
void | V |
引用类型 | L |
借助上面的协议分析,想要看到字节码中参数的类型,就比较简单了。spa
方法描述符(方法签名)是一个类型描述符列表,它用一个字符串描述一个方法的参数类型和返回类型。设计
方法描述符以左括号开头,而后是每一个形参的类型描述符,而后是是右括号,接下来是返回类型的类型描述符,例如,该方法返回void,则是V,要注意的是,方法描述符中不包含方法的名字或参数名。
Java方法声明 | 方法描述符 | 说明 |
---|---|---|
void m(int i, float f) | (IF)V | 接收一个int和float型参数且无返回值 |
int m(Object o) | (Ljava/lang/Object;)I | 接收Object型参数返回int |
int[] m(int i, String s) | (ILjava/lang/String;)[I | 接受int和String返回一个int[] |
Object m(int[] i) | ([I)Ljava/lang/Object; | 接受一个int[]返回Object |
咱们来看下这段简单的代码,在字节码下是怎样的。
经过ASMPlugin,咱们看下生成的字节码,以下所示。
能够发现,这里主要分红了两个部分——init和onCreate。
Java中的每个方法在执行的时候,Java虚拟机都会为其分配一个「栈帧」,栈帧是用来存储方法中计算所须要的全部数据的。
其中第0个元素就是「this」,若是方法有参数传入会排在它的后面。
字节码中有不少指令,下面对一些比较经常使用的指令进行下讲解。
你们不用彻底掌握这些指令,结合代码来看的话,仍是能看懂的,咱们须要的是修改字节码,而不是从0开始。
对于Java源文件:若是只有一个方法,编译生成时,也会有两个方法,其中一个是默认构造函数 对于Kotlin源文件:若是只有一个方法,编译生成时,会产生四个方法,一个是默认构造函数,还有两个是kotlin合成的方法,以及退出时清除内存的默认函数
再结合ASM Code来看,仍是上面的例子。
默认的构造函数。
onCreate:
这里面有些生成的代码,例如:
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(9, label0);
methodVisitor.visitLocalVariable("this", "Lcom/yw/asmtest/MainActivity;", null, label0, label4, 0);
复制代码
这些都是调试代码和写入变量表的方法,咱们没必要关心。
剩下的代码,就是咱们能够在ASM中所须要的代码。
向你们推荐下个人网站 xuyisheng.top/ 专一 Android-Kotlin-Flutter 欢迎你们访问