运动健身、早睡早起、三餐规律、多读好书并保持输入输出,如果你真的想不明白自己要什么,做这些永远不会错。坚持一年,就算你还是没有目标,也能有一个好的身体、博学的脑袋,这些足以让你超越80%的同龄人。
前言
最近因为工作需要,需要使用代码注入的功能,这里简单介绍下代码注入的流程和心得。
- 这篇文章主要是包含了我收集的一些有用的东西,没有其他的一些具体分享。
文章目录
前置知识
- Android的代码注入需要对Class的一些指令比较熟悉,才可以完成基本的操作。
Java
Class文件的结构
Class文件的结构大概如下所示,其中*
表示0个或者多个。
- 摘录自:ASM4-Guide
- Core API / Classes / Structure / Overview
Java基本类型与Class类型
- 摘录自:ASM4-Guide
- Core API / Classes / Structure / Overview
Class字段 | 含义 |
---|---|
B | 基本类型byte |
C | 基本类型char |
D | 基本类型double |
F | 基本类型float |
I | 基本类型int |
J | 基本类型long |
S | 基本类型short |
Z | 基本类型boolean |
V | 特殊类型void |
L | 对象类型,以分号结尾,如Ljava/lang/Object; |
[Ljava/lang/String; | 数组类型,每一位使用一个前置的[ 字符来表示 |
Java方法声明与Class声明
-
摘录自:ASM4-Guide
- Core API / Classes / Structure / Overview
-
括号中的是参数声明,括号后紧跟着的是返回类型。
Class字段 | Java方法声明 |
---|---|
(IF)V | void m(int i, float f) |
(Ljava/lang/Object;)I | int m(Object o) |
(ILjava/lang/String;)[I | int[] m(int i, String s); |
([I)Ljava/lang/Object; | Object m(int[] i); |
Class修饰符
修饰符 | ID | 说明 |
---|---|---|
ACC_PUBLIC | 0X0001 | public类型 |
ACC_FINAL | 0X0010 | 声明为final,只有类可以设置 |
ACC_SUPER | 0X0020 | 使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK1.0.2发生过改变,为了区别这条指令使用哪种语意,JDK1.0.2之后编译出来的类都为真 |
ACC_INTERFACE | 0X0200 | 接口 |
ACC_ABSTRACT | 0X0400 | abstract类型,对于接口或者抽象类来说,此标志值为真,其他类为假 |
ACC_SYNTHETIC | 0X1000 | 这个类并非由用户代码产生 |
ACC_ANNOTATION | 0X2000 | 注解 |
ACC_ENUM | 0X4000 | 枚举 |
Class常量池类型
常量池的类型 | 说明 |
---|---|
CONSTANT_Utf8_info | tag标志位为1, UTF-8编码的字符串 |
CONSTANT_Integer_info | tag标志位为3, 整形字面量 |
CONSTANT_Float_info | tag标志位为4, 浮点型字面量 |
CONSTANT_Long_info | tag标志位为5, 长整形字面量 |
CONSTANT_Double_info | tag标志位为6, 双精度字面量 |
CONSTANT_Class_info | tag标志位为7, 类或接口的符号引用 |
CONSTANT_String_info | tag标志位为8,字符串类型的字面量 |
CONSTANT_Fieldref_info | tag标志位为9, 字段的符号引用 |
CONSTANT_Methodref_info | tag标志位为10,类中方法的符号引用 |
CONSTANT_InterfaceMethodref_info | tag标志位为11, 接口中方法的符号引用 |
CONSTANT_NameAndType_info | tag 标志位为12,字段和方法的名称以及类型的符号引用 |
CONSTANT_Method-Handle_info | tag标志位为15,方法句柄 |
CONSTANT_Method-Type_info | tag标志位为16,方法类型 |
CONSTANT_Invoke-Dynamic_info | tag标志位为18,动态方法调用点 |
方法调用指令
关于方法的调用,Java 共提供了 5 个指令,来调用不同类型的函数:
调用指令 | 说明 | 绑定类型 |
---|---|---|
invokestatic | 用来调用静态方法。 | 静态绑定 |
invokevirtual | 用于调用非私有实例方法,比如 public 和 protected,大多数方法调用属于这一种。 | 动态绑定 |
invokeinterface | 和上面这条指令类似,不过作用于接口类。 | 动态绑定 |
invokespecial | 用于调用私有实例方法、构造器及 super 关键字等。 | 静态绑定 |
invokedynamic | 用于调用动态方法。 | 动态绑定 |
PS:
- 静态绑定,指的是能够直接识别目标方法的情况。
- 动态绑定指的是需要在运行过程中根据调用者的类型来确定目标方法的情况。
相比于静态绑定的方法调用,动态绑定的调用会更加耗时一些。由于方法的调用非常的频繁,JVM 对动态调用的代码进行了比较多的优化,比如使用方法表来加快对具体方法的寻址,以及使用更快的缓冲区来直接寻址( 内联缓存)。
大多数普通方法调用,使用的是invokevirtual
指令,它其实和invokeinterface
是一类的,都属于虚方法调用。很多时候,JVM 需要根据调用者的动态类型,来确定调用的目标方法,这就是动态绑定的过程。
invokevirtual
指令有多态查找的机制,该指令运行时,解析过程如下:
- 找到操作数栈顶的第一个元素所指向的对象实际类型,记做 c;
- 如果在类型 c 中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验。如果通过则返回这个方法直接引用,查找过程结束,不通过则返回
java.lang.IllegalAccessError
。 - 否则,按照继承关系从下往上依次对 c 的各个父类进行第二步的搜索和验证过程;
- 如果始终没找到合适的方法,则抛出
java.lang.AbstractMethodError
异常,这就是 Java 语言中方法重写的本质。
ASM的文档
- ASM官网:https://asm.ow2.io/,源码:https://gitlab.ow2.org/asm/asm
- 官方文档:ASM 4.0 - A Java bytecode engineering library
- 官方文档:ASM 6 Developer Guide
- 官方FAQ:Frequently Asked Questions
如果啃不动英文的文档,可以看这个博主的译文
IBM Developer社区的文章也是非常的不错的,可以作为入门用。
ASM的整体架构
- 整体架构图:
- 原图源自:ASM 6 Developer Guide。
ASM Core API调用流程图
实践
直接使用Sample的例子对照着改就好了,O(∩_∩)O
实践 | 地址 |
---|---|
添加变量 | AddFieldAdapter.java |
添加方法 | AddMethodAdapter.java |
删除变量 | RemoveFieldAdapter.java |
删除方法 | RemoveMethodAdapter.java |
添加耗时统计 | AddTimerAdapter,还有这部分也是:Other AddTimerAdapter |
添加注解 | AddAnnotationAdapter.java |
删除注解 | RemoveAnnotationAdapter.java |
使用Transformer添加变量 | AddFieldTransformer.java |
使用Transformer添加方法 | AddMethodTransformer.java |
使用Transformer删除变量 | RemoveFieldTransformer.java |
使用Transformer删除方法 | RemoveMethodTransformer.java |
- 更多内容请见:lptr / asm - guide/examples/src。
文档
MethodVisitor
org.objectweb.asm.MethodVisitor
- ASM提供的接口和Java class的指令一致,可以对照着Class指令找对应的方法。
参数 | 说明 | 解释 |
---|---|---|
invokevirtual | Invoke instance method; dispatch based on class | 执行一般实例方法,创建完实例对象后,obj.method() 调用的 |
invokespecial | Invoke instance method; special handling for superclass, private, and instance initialization method invocations | 实例初始化方法(构造函数)、父类的方法(super.method() 方式调用)、私有方法 |
invokeinterface | Invoke interface method | 执行接口方法 |
invokestatic | Invoke a class (static) method | 执行静态方法 |
invokedynamic | Invoke dynamic method | jdk1.7新增,执行动态方法,不需要在编译时确定 |
Sample & QA
增加注解
注入静态变量
- 关于静态变量的注入只有基本类型可以注入。否则会抛出
Unexpected static-value type
的Exception
,比如Unexpected static-value type java.lang.Class
。 - 代码摘录自:JarClassFileReader.java
private DexValue getStaticValue(Object value, DexType type) {
if (value == null) {
return null;
}
DexItemFactory factory = parent.application.getFactory();
if (type == factory.booleanType) {
int i = (Integer) value;
assert 0 <= i && i <= 1;
return DexValueBoolean.create(i == 1);
}
if (type == factory.byteType) {
return DexValueByte.create(((Integer) value).byteValue());
}
if (type == factory.shortType) {
return DexValueShort.create(((Integer) value).shortValue());
}
if (type == factory.charType) {
return DexValueChar.create((char) ((Integer) value).intValue());
}
if (type == factory.intType) {
return DexValueInt.create((Integer) value);
}
if (type == factory.floatType) {
return DexValueFloat.create((Float) value);
}
if (type == factory.longType) {
return DexValueLong.create((Long) value);
}
if (type == factory.doubleType) {
return DexValueDouble.create((Double) value);
}
if (type == factory.stringType) {
return new DexValueString(factory.createString((String) value));
}
throw new Unreachable("Unexpected static-value type " + type);
}
- 如果是对象,需要在类的构造方法中进行初始化。
- 具体使用ASM在类构造方法中完成初始化,可见:How to add static final field with initializer using ASM?
附录
字节码工具
- Android Studio
- JBE - Java Bytecode Editor, is a bytecode editor suitable for viewing and modifying java class files.
- 可以方便的查看Java字节码,便于比对注入的字节码是否符合要求。
- 下载地址:Java Bytecode Editor 0.1.1
ASM
- ASM官网:https://asm.ow2.io/,源码:https://gitlab.ow2.org/asm/asm
- 官方文档:ASM 4.0 - A Java bytecode engineering library
- 官方文档:ASM 6 Developer Guide
- ASM 简介
- Using the ASM framework to implement common Java bytecode transformation patterns
- 有详细注释:Java:用ASM实现动态代理实体类
Gradle
- https://developer.android.com/studio/build/gradle-tips
- 在项目中使用Gradle和ASM做一些优化:Gradle插件、代码注入
- Android ASM自动埋点方案实践