Android 进阶——代码插桩必知必会之初识ASM7字节码操作库完全攻略(一)

文章大纲

  • 引言
  • 一、ASM库概述
  • 二、ASM库的架构模型概述
    • 1、核心API概述
    • 2、树 API概述
  • 三、ASM库核心组件和接口类
    • 1、ClassVisitor
    • 2、ClassWriter
      • 2.1、ClassWriter 核心方法
      • 2.2、AnnotationWriter、FieldWriter、MethodWriter、SignatureWriter
    • 3、FieldVisitor 、MethodVisitor 、AnnotationVisitor 等
    • 4、ClassReader和SignatureReader
  • 四、查看ASM 需要传入的字节码指令

引言

对于我们Java 程序员来说,或许对于Java源文件,再熟悉不过了,毕竟整天都是与之打交道,但是是否是真的已经很熟悉了,镇的了解了吗?我想大家都知道Java 源文件经过编译之后转为.class字节码文件,但是或许很多人不知道其实.class字节码文件也是可以被编辑的,学习字节码操作相关的,需要具备一些JVM 设计规范和字节码文件的相关知识,接下来就进入正文开始ASM 字节码操作库的小结,代码操作库有很多种,这里总结下最常用的ASM,代码插桩需要掌握一系列的知识:

  • Java ——代码插桩必知必会之Java字节指令码和Java文件概述
  • Android 进阶——代码插桩必知必会之初识ASM7字节码操作库完全攻略(一)
  • Android 进阶——代码插桩必知必会之ASM7字节码操作库实战完全攻略(二)

一、ASM库概述

ASM 是一个Java 字节码(.class)操控框架,它可以用来动态生成类的字节码或者改变现有类的字节码。借由ASM可以直接创建或修改字节码文件,也就能在类被加载到JVM执行之前动态改变原有的类行为。其目的是生成、转换和分析字节数组来表示的已编译 Java 类。因为无论是在磁盘的存储形式还是JVM的加载皆采用这种字节数组形式,Java 字节码文件按照Java 虚拟机规范中的格式进行组织并存储。为此,ASM把Java class 抽象为一棵树,使用 “Visitor” 模式遍历整个二进制结构,基于事件驱动的处理方式使得用户只需要关注于对其编程有意义的部分,而不必深入Java 类文件格式的所有细节。ASM 从字节码文件中读取所有相关信息(包括类名称、方法、属性、数值常数、 字符串、 Java 标识符、 Java类型、 Java 类结构元素以及 Java 字节码指令等)并提供了字节码级别的接口来读写和转换这些字节数组且在访问到对应的信息时提供对应的回调方法。通俗来说就是 ASM提供了一系列的API,能够让我们通过Java 字节码指令去处理Java字节码 (因为字节码文件就是主要就是由16进制形式的字节码指令组成的),欲了解更多请自己参阅官网

ASM 的名字没有任何含义,就是任性引用了C库中的一些可以操作汇编语言函数名称中的asm,而且ASM 的使用范围仅限于
对类(.class)文件的读、写、转换和分析(即加载前的过程进行干预),至于类的加载过程就超出了其能力范围。

Android 进阶——代码插桩必知必会之初识ASM7字节码操作库完全攻略(一)_第1张图片

二、ASM库的架构模型概述

ASM 库提供了两个用于生成和转换已编译类的 API:核心 API(,以基于事件的形式来表示类,)和树 API,以基于对象的形式来表示类

1、核心API概述

核心 API基于事件的形式来表示类,把类抽象为一系列事件,每个事件表示类的一个元素(比如它的一个标头、一个字段、一个方法声明、一条指令等等)。基于事件的 API 定义了一组可能事件,以及这些事件必须遵循的发生顺序,还提供了一个类分析器,为每个被分析元素生成一个事件,还提供一个类写入器,由这些事件的序列生成经过编译的类。其组织结构是围绕事件生成器(类分析器)、事件使用器(类写入器)和各种预定义的事件筛选器进行的,在这一结构中可以添加用户定义的生成器、使用器和筛选器,将事件生成器(负责执行生成或转换过程)、筛选器和使用器组件组装为可能很复杂的体系结构,
‰ 然后启动事件生成器,以执行生成或转换过程。

2、树 API概述

树 API基于对象模型来表示类, 把类抽象为对象树, 每个对象表示类的一部分( 比如类本身、一个字段、一个方法、一条指令等等,每个对象都有一些引用,指向表示其组成部分的对象)。基于对象的 API 提供了一种方法,可以将表示一个类的事件序列转换为表示同一个类的对象树,也可以反过来,将对象树表示为等价的事件序列。换言之,基于对象的 API 构建在基于事件的API 之上,用于操作类树的类生成器或转换器组件是可以组成链的,它们之间的链接代表着转换的顺序,使用 “Visitor” 模式遍历整个二进制结构,基于事件驱动的处理方式使得用户只需要关注于对其编程有意义的部分,而不必深入Java 类文件格式的所有细节。如下所示的复杂体系结构:
Android 进阶——代码插桩必知必会之初识ASM7字节码操作库完全攻略(一)_第2张图片
其中的箭头表示在类分析器、写入器或转换器之间进行的基于事件或基于对象的通信, 在整个链中的任何位置, 都可能会在基于事件与基于对象的表示之间进行转换。

三、ASM库核心组件和接口类

ASM通过树这种数据结构来抽象复杂的字节码结构并利用 Push 模型来对树进行遍历,在遍历过程中对字节码进行修改。所谓的 Push 模型类似于简单的访问者(Visitor) 设计模式,因为字节码结构是固定的,所以不需要专门抽象出一种 Vistable 接口,而只需要提供 Visitor 接口来遍历一些复杂的数据结构,其中Visitor 相当于用户派出的代表,深入到算法内部,由算法安排访问行程,而Visitor 代表可以更换,但对算法流程无法干涉,因此是被动的。

1、ClassVisitor

ASM用于生成和变转字节码文件的API是基于 ClassVisitor 抽象类的,该类中的每个方法都对应于同名的类文件结构部分。对于字节码中简单的部分只需调用一个方法就能完成对应部分的字节码构建并返回 void;有些复杂部分的内容(例如visitAnnotation、 visitField 和 visitMethod 方法,它们分别返AnnotationVisitor、 FieldVisitor 和 MethodVisitor)则用一个初始方法调用来访问并返回一个辅助的访问者类。ClassVisitor作为Java类的访问者角色,封装了在读取Class字节码时会触发的一系列事件,如类头解析完成、注解解析、字段解析、方法解析等并按照以下顺序调用: visit() >visitSource() >visitModule() >visitNestHost() >visitOuterClass() > visitAnnotation() > visitTypeAnnotation() > visitAttribute()> visitNestMember() > visitInnerClass() > visitField()> visitMethod()> visitEnd(),当分析到方法时就会回调visitMethod方法,进入到方法体内部时也会回调对应的方法。简而言之,ClassVisitor (包含其子类)在ASM 访问到对应类的元素时回自动回调对应的方法

类/接口 说明
AnnotationVisitor 定义在解析注解时会触发的一系列的事件,解析到一个基本值类型的注解、enum值类型的注解、Array值类型的注解、注解值类型的注解时,会调用对应的方法,下同。
FieldVisitor 定义在解析字段时触发的事件,如解析到字段上的注解、解析到字段相关的属性等。
MethodVisitor 定义在解析方法时触发的事件,如方法上的注解、属性、代码等。
SignatureVisitor 定义在解析Signature时会触发的事件,如正常的Type参数、类或接口的边界等。

各个 ClassVisitor通过责任链 (Chain-of-responsibility) 模式,可以非常简单的封装对字节码的各种修改,而无须关注字节码的内部字节偏移,通过这些对应的Visitor可以访问字节码文件中对应的组成部分,而且ClassVisitor会自己去控制这些过程,用户要做的只是覆写相应的 visit 方法,比如 visitMethod会返回一个实现 MethordVisitor接口的实例,visitField会返回一个实现 FieldVisitor接口的实例,完成子过程后控制返回到父过程,继续访问下一节点。

部分方法 说明
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) 访问class的头部信息时,version为class版本(编译版本),access为访问修饰符,name为类名称,signature为class的签名,可能是null,superName为超类名称,interfaces为接口的名称
AnnotationVisitor visitAnnotation(String descriptor, boolean visible) 访问class的注解信息时,descriptor为签名描述信息,visible为是否运行时可见
void visitAttribute(Attribute attribute) 访问该类的非标准属性。
void visitInnerClass(String name, String outerName, String innerName, int access) 访问class中内部类的信息,而且这个内部类不一定是被访问类的成员(有可能是一段方法中的匿名内部类或者声明在一个方法中的类等等)。name为内部类的名称,outerName为内部类所在类的名称,innerName为内部类的名称
void visitOuterClass(String owner, String name, String descriptor) 访问该类的外部类,仅当类具有封闭类时,才必须调用此方法。owner为拥有该类的class名称,name为包含该类的方法的名称,如果该类未包含在其封闭类的方法中,则返回null,descriptor为签名描述信息
void visitEnd() 结束访问class时
FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) 访问class中字段的信息,返回一个FieldVisitor用于操作字段相关的信息,access为访问修饰符,name为类名称,signature为class的签名,可能是null,descriptor为描述信息
MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) 访问class中方法的信息,返回一个MethodVisitor用于操作字段相关的信息,access为访问修饰符,name为方法名称,signature为方法的签名,可能是null,descriptor为描述信息,exceptions为异常
ModuleVisitor visitModule(String name, int access, String version) 访问对应的模块。
AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) 访问类签名中类型的注释。

2、ClassWriter

ClassWriter类是ASM中主要用于生成一个类的字节码文件的类继承了ClassVisitor抽象类,通过toByteArray()方法返回生成的字节码的字节流,将字节流写回文件即可生产调整后的 class 文件(所有 visit 事件的时间上先后调用,最终转换成字节码的空间位置前后的调整)。可以通过ClassWriter中的visitXxxx方法返回对应各部分的访问者去创建字节码的文件中的各个部分,传入对应的字节码指令也是通过visitXxxxxx方法,如果要创建则传入对应的创建类型的字节码指令

2.1、ClassWriter 核心方法

部分方法 说明
public ClassWriter(final int flags) 构造ClassWriter对象,flag取值为0、1、2(0时表示需要手动计算最大操作数栈、局部变量表、桢变化;ClassWriter.COMPUTE_MAXS表示自动计算局部变量表和操作数栈,但是必须要调用visitMaxs,方法参数会被忽略。桢变化需要手动计算ClassWriter.COMPUTE_FRAMES表示全自动计算,但是必须要调用visitMaxs,方法参数会被忽略。但ClassWriter.COMPUTE_MAXS比0慢10%,比COMPUTE_FRAMES慢一倍。)
public final void visit(final int version, final int access,final String name,final String signature,final String superName,final String[] interfaces) 构造class文件的头部信息,version为指定的JDK版本(取值为Opcodes定义的常量),access为类的修饰符(同version),name为类的名称,signature与泛型相关的,若传入null则表示该字段不是泛型的签名,superName为要继承父类的全限定名,Interfaces为要实现的接口全限定名
public final FieldVisitor visitField(final int access,final String name,final String descriptor,final String signature,final Object value) 构造class文件的成员属性,name为成员属性名,descriptor为属性的类型签名,value为属性的值,只适用于静态字段,若当前要生成的字段不是静态的则传入null
public final MethodVisitor visitMethod(final int access,final String name,final String descriptor,final String signature,final String[] exceptions) **构造class文件的方法“签名”**并返回都构造方法体的对象(即方法的修饰符、方法名、返回值及全限定的参数)
public final AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) 构造class 的注解对象,descriptor为注解的描述名,visible为运行时是否可见
public byte[] toByteArray() 返回生成的字节码的字节流,将字节流写回文件即可生产调整后的 class 文件

2.2、AnnotationWriter、FieldWriter、MethodWriter、SignatureWriter

类/接口 说明
AnnotationWriter 实现了AnnotationVisitor,用于创建注解相关字节码指令来创建注解部分字节码,下类似。
FieldWriter 实现了FieldVisitor,用于创建字段相关字节码。
MethodWriter 实现了MethodVisitor,用于创建方法相关字节码。
SignatureWriter 实现了SignatureVisitor,用于创建泛型相关字节码。
AnnotationWriter 实现了AnnotationVisitor,用于创建注解相关字节码。

3、FieldVisitor 、MethodVisitor 、AnnotationVisitor 等

FieldVisitor 、MethodVisitor 、AnnotationVisitor 虽然名称形式有点类似,但是三者在继承关系除了都是继承自Object的抽象类之外并无其他类之间的关系,正如名称所示,它们都是ASM 提供出来供我们传入对应字节码指令来生成对应部分的方法,比如说MethodVisitor 用于创建方法体,不同类型的字节码指令对应不同的方法。

4、ClassReader和SignatureReader

ClassReader类可以从字节数组直接或 class 文件间接的获得字节码数据(因为它是按照Java虚拟机规范中定义的方式来解析class文件中的内容),调用accept方法时开始分析字节码并构建出字节码文件在内存中的抽象的结构树,是字节码的读取与分析引擎,它采用类似SAX的事件读取机制,每当有事件发生时,调用注册的ClassVisitor、AnnotationVisitor、FieldVisitor、MethodVisitor做相应的处理(当然也可以不通过 ClassReader类,自行手工控制这个流程,只要确保各个 visit 事件被先后正确的调用,最后就能生成可以被正确加载的字节码),调用accep方法之后解析字节码中常量池之后的所有元素并构造成Attribute链,如果attribute名称符合在accept中attribute数组中指定的attribute名,则替换传入的attribute数组对应的项,其中 accept(final ClassVisitor classVisitor, final int parsingOptions)方法中的parsingOptions参数代表用于解析class的选项,有以下取值:

  • ClassReader.SKIP_CODE——过代码属性的标志

  • ClassReader.SKIP_FRAMES——跳过StackMap和StackMapTable属性的标志,跳过MethodVisitor.visitFrame方法,对于我们开发者来说最好选这个。

  • ClassReader.SKIP_DEBUG——跳过SourceFile,SourceDebugExtension,LocalVariableTable,LocalVariableTypeTable和LineNumberTable属性的标志,跳过ClassVisitor.visitSource, MethodVisitor.visitLocalVariable, MethodVisitor.visitLineNumber方法。

  • ClassReader.EXPAND_FRAMES——用于展开堆栈映射帧的标志,这会大大降低性能,不过建议使用这个标志。

简而言之,ClassReader字节码分析器就是读取并解析class文件中的内容,在遇到合适的字段时调用ClassVisitor中相对应的方法。而SignatureReader类负责对类定义、字段定义、方法定义、本地变量定义的签名的解析,当范型被引入时,用于存储范型定义时的元数据(因为元数据在运行时会被擦除)。剩下的还有对字节码中属性的类进行抽象的Attribute;用于存储字节码二进制存储的容器ByteVector字节码指令的一些常量定义的Opcodes接口以及类型相关的常量定义以及一些基于其上的操作的Type类。

四、查看ASM 需要传入的字节码指令

ASM 需要传入的字节码指令有两种方法:

  • 可以借助ASM Bytecode Outline插件通过.java文件转为使用ASM 时所用到的字节码指令,其中第一栏Bytecode直接调入ASM提供的工具类传入的原始的字节码,第二栏ASMified为使用ASM 生成class的代码,第三栏Groovified为使用Groovy 语言生成class的代码。
    Android 进阶——代码插桩必知必会之初识ASM7字节码操作库完全攻略(一)_第3张图片
  • 使用javap反编译.class文件查看

javap -c是用于把.class文件反编译为字节码,可以不用带上后缀,javap -verbose 可输出更多完整的信息。

Android 进阶——代码插桩必知必会之初识ASM7字节码操作库完全攻略(一)_第4张图片
PS:ASM的基本操作见下篇。

你可能感兴趣的:(Android,进阶)