从精准化测试看ASM在Android中的强势插入-字节码

字节码是ASM的基础,要想熟练的使用ASM,那么了解字节码就是必备基础。java

Class的文件格式

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文件中能够则包含多个类。当一个Java文件是描述一个包含内部类的类,那么该Java文件则会被编译为两个类文件,文件名上经过「$」来区分,主类文件中包含对其内部类的引用,定义了内部方法的内部类会包含外部引用
  • 字节码文件中不包含注释,只有有效的可执行代码,例如类、字段、方法和属性
  • 字节码文件中不包含package和import部分, 全部类型名字都必须是彻底限定的
  • 字节码文件还包含常量池(constant pool),这些内容是编译时生成的,常量池本质上就是一个数组存储了类中出现的全部数值、字符串和类型常量,这些常量仅须要在这个常量池部分中定义一次,就能够利用其索引,在类文件中的全部其余各部分进行引用

字节码的执行过程

字节码在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
  • Java基本类型的描述符是单个字符,例如Z表示boolean、C表示char
  • 类的类型的描述符是这个类的全限定名,前面加上字符L , 后面跟上一个「;」,例如String的类型描述符为Ljava/lang/String;
  • 数组类型的描述符是一个方括号后面跟有该数组元素类型的描述符,多维数组则使用多个方括号

借助上面的协议分析,想要看到字节码中参数的类型,就比较简单了。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

字节码示例

咱们来看下这段简单的代码,在字节码下是怎样的。

image-20210623103259980

经过ASMPlugin,咱们看下生成的字节码,以下所示。

image-20210623103419893

能够发现,这里主要分红了两个部分——init和onCreate。

Java中的每个方法在执行的时候,Java虚拟机都会为其分配一个「栈帧」,栈帧是用来存储方法中计算所须要的全部数据的。

其中第0个元素就是「this」,若是方法有参数传入会排在它的后面。

字节码中有不少指令,下面对一些比较经常使用的指令进行下讲解。

  • ALOAD 0:这个指令是LOAD系列指令中的一个,它的意思表示push当前第0个元素到堆栈中。代码上至关于使用「this」,A表示这个数据元素的类型是一个引用类型。相似的指令还有:ALOAD,ILOAD,LLOAD,FLOAD,DLOAD,它们的做用就是针对不用数据类型而准备的LOAD指令
  • INVOKESPECIAL:这个指令是调用系列指令中的一个。其目的是调用对象类的方法。后面须要给上父类的方法完整签
  • INVOKEVIRTUAL:这个指令区别于INVOKESPECIAL的是,它是根据引用调用对象类的方法
  • INVOKESTATIC:调用类的静态方法

你们不用彻底掌握这些指令,结合代码来看的话,仍是能看懂的,咱们须要的是修改字节码,而不是从0开始。

对于Java源文件:若是只有一个方法,编译生成时,也会有两个方法,其中一个是默认构造函数 对于Kotlin源文件:若是只有一个方法,编译生成时,会产生四个方法,一个是默认构造函数,还有两个是kotlin合成的方法,以及退出时清除内存的默认函数

ASM Code

再结合ASM Code来看,仍是上面的例子。

默认的构造函数。

image-20210623105109646

onCreate:

image-20210623105143214

这里面有些生成的代码,例如:

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 欢迎你们访问

相关文章
相关标签/搜索