前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从精准化测试看ASM在Android中的强势插入-字节码

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

作者头像
用户1907613
发布2021-07-19 10:01:12
6850
发布2021-07-19 10:01:12
举报
文章被收录于专栏:Android群英传

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

Class的文件格式

Class文件作为Java虚拟机所执行的直接文件,内部结构设计有着固定的协议,每一个Class文件只对应一个类或接口的定义信息。

每个Class文件都以8位为单位的字节流组成,下面是一个Class文件中所包括的内容,在Class文件中,各项内容按照严格顺序连续存放,Java虚拟机只要按照协议顺序来读取即可。

代码语言:javascript
复制
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代码还是有很大区别的。

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

字节码的执行过程

字节码在Java虚拟机中是以堆栈的方式进行运算的,类似CPU中的寄存器,在Java虚拟机中,它使用堆栈来完成运算,例如实现「a+b」的加法操作,在Java虚拟机中,首先会将「a」push到堆栈中,然后再将「b」push到堆栈中,最后执行「ADD」指令,取出用于计算的两个变量,完成计算后,将返回值「a+b」push到堆栈中,完成指令。

类型描述符

我们在Java代码中的类型,在字节码中,有相应的表示协议。

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;
  • 数组类型的描述符是一个方括号后面跟有该数组元素类型的描述符,多维数组则使用多个方括号

借助上面的协议分析,想要看到字节码中参数的类型,就比较简单了。

方法描述符

方法描述符(方法签名)是一个类型描述符列表,它用一个字符串描述一个方法的参数类型和返回类型。

方法描述符以左括号开头,然后是每个形参的类型描述符,然后是是右括号,接下来是返回类型的类型描述符,例如,该方法返回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

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

代码语言:javascript
复制
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(9, label0);
methodVisitor.visitLocalVariable("this", "Lcom/yw/asmtest/MainActivity;", null, label0, label4, 0);

这些都是调试代码和写入变量表的方法,我们不必关心。

剩下的代码,就是我们可以在ASM中所需要的代码。

向大家推荐下我的网站 https://xuyisheng.top/ 点击原文一键直达

专注 Android-Kotlin-Flutter 欢迎大家访问

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 群英传 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Class的文件格式
  • 字节码的执行过程
    • 类型描述符
      • 方法描述符
      • 字节码示例
      • ASM Code
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档