前言
最近因爲工作需要,需要使用代碼注入的功能,這裏簡單介紹下代碼注入的流程和心得。
- 這篇文章主要是包含了我收集的一些有用的東西,沒有其他的一些具體分享。
前置知識
- Android的代碼注入需要對Class的一些指令比較熟悉,纔可以完成基本的操作。
Class文件的結構
Class文件的結構大概如下所示,其中*
表示0個或者多個。
- 摘錄自:ASM4-Guide
- Core API / Classes / Structure / Overview
類字段 | 對應屬性字段 |
---|---|
Modifiers, name, super class, interfaces | |
Constant pool: numeric, string and type constants | |
Source file name(optional) | |
Enclosing class reference | |
Annotation* | |
Attribute* | |
Inner class* | Name |
Field* | Modifiers, name, type |
Annotation* | |
Attribute* | |
Method* | Modifiers, name, return and parameter typers |
Annotation* | |
Attribute* | |
Compiled code |
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,動態方法調用點 |
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自動埋點方案實踐