介紹ASM
ASM是一款基于java字節(jié)碼層面的代碼分析和修改工具。無(wú)需提供源代碼即可對(duì)應(yīng)用嵌入所需debug代碼拥诡,用于應(yīng)用API性能分析触趴。ASM可以直接產(chǎn)生二進(jìn)制class文件,也可以在類被加入JVM之前動(dòng)態(tài)修改類行為渴肉。
ASM庫(kù)結(jié)構(gòu)
- Core 為其他包提供基礎(chǔ)的讀冗懦、寫(xiě)、轉(zhuǎn)化Java字節(jié)碼和定義的API宾娜,并且可以生成Java字節(jié)碼和實(shí)現(xiàn)大部分字節(jié)碼的轉(zhuǎn)換
- Tree提供了Java字節(jié)碼在內(nèi)存中的表現(xiàn)
- Analysis為存儲(chǔ)在tree包結(jié)構(gòu)中的java方法字節(jié)碼提供基本的數(shù)據(jù)流統(tǒng)計(jì)和類型檢查算法
- Commons提供一些常用的簡(jiǎn)化字節(jié)碼生成轉(zhuǎn)化和適配器
- Util包含一些幫助類和簡(jiǎn)單的字節(jié)碼修改批狐,有利于在開(kāi)發(fā)或者測(cè)試中使用
- XML提供一個(gè)適配器將XML和SAX-comliant轉(zhuǎn)化成字節(jié)碼結(jié)構(gòu)扇售,可以允許使用XSLT去定義字節(jié)碼轉(zhuǎn)化前塔。
class文件結(jié)構(gòu)
ASM 是基于java字節(jié)碼層面的代碼分析和修改工具。所以學(xué)習(xí)ASM之前承冰,還得不下class文件結(jié)構(gòu)华弓,java類型,java方法等知識(shí)
Class文件結(jié)構(gòu)如下:
| Header|
| --------- ------ |
| Modifiers, name, super class, interfaces |
| Constant pool: numeric, string and type constants |
| Source file name (optional) |
| Enclosing class reference |
| Annotation* |
| Attribute* |
member | attribute |
---|---|
Inner class* | Name |
Field* | Modifiers, name, type |
Annotation* | |
Attribute* | |
Method* | Modifiers, name, return and parameter types |
Annotation* | |
Attribute* | |
Compiled code |
翻譯成中文:
| Header|
| --------- ------ |
| Modifiers, name, super class, interfaces 修飾(public/private等)困乒,名稱寂屏,父類,實(shí)現(xiàn)的接口 |
| Constant pool: numeric, string and type constants 常量池,數(shù)字迁霎,字符串吱抚,類型常量(枚舉類型) |
| Source file name (optional) 原文件名稱,(可選) |
| Enclosing class reference 外部類的引用 |
| Annotation* Class的注解 |
| Attribute* Class屬性 |
member | attribute |
---|---|
Inner class* 內(nèi)部類 | Name 名稱 |
Field* 成員變量 | Modifiers, name, type 修飾符考廉,名稱秘豹,類型 |
Annotation* 注解 | |
Attribute* 屬性 | |
Method* 方法 | Modifiers, name, return and parameter types Modifiers, name, return and parameter types 修飾符,名稱昌粤,返回類型既绕,參數(shù)類型 |
Annotation* 注解 | |
Attribute* 類型 | |
Compiled code 編譯的代碼 |
- 每個(gè)類、字段涮坐、方法和方法代碼的屬性有屬于自己的名稱記錄在類文件格式的JVM規(guī)范的部分凄贩,這些屬性展示了字節(jié)碼多方面的信息,例如源文件名袱讹、內(nèi)部類疲扎、簽名、代碼行數(shù)廓译、本地變量表和注釋评肆。JVM規(guī)范允許定義自定義屬性,這些屬性會(huì)被標(biāo)準(zhǔn)的VM(虛擬機(jī))忽略非区,但是可以包含附件信息瓜挽。
- 方法代碼表包含一系列對(duì)java虛擬機(jī)的指令。有些指令在代碼中使用偏移量征绸,當(dāng)指令從方法代碼被插入或者移除時(shí)久橙,全部偏移量的值可能需要調(diào)整。
原java類型與class文件內(nèi)部類型對(duì)應(yīng)關(guān)系
Java type | Type descriptor |
---|---|
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; |
原java方法聲明與class文件內(nèi)部聲明的對(duì)應(yīng)關(guān)系
Method declaration in source file | Method descriptor |
---|---|
void m(int i, float f) | (IF)V |
int m(Object o) | (Ljava/lang/Object;)I |
int[] m(int i, String s) | (ILjava/lang/String;)[I |
Object m(int[] i) | ([I]Ljava/lang/Object; |
參數(shù)描述在前面管怠,返回值描述在后面
ASM的處理流程淆衷,生產(chǎn)者消費(fèi)者模式
在Core包中邏輯上分為2部分:
- 字節(jié)碼生產(chǎn)者,例如ClassReader
- 字節(jié)碼消費(fèi)者渤弛,例如writers(ClassWriter, FieldWriter, MethodWriter和AnnotationWriter)祝拯,adapters(ClassAdapter和MethodAdapter)
下圖是生產(chǎn)者和消費(fèi)者交互的時(shí)序圖:
官網(wǎng)提供的時(shí)序圖:
網(wǎng)友畫(huà)的時(shí)序圖:
通過(guò)時(shí)序圖可以看出ASM在處理class文件的整個(gè)過(guò)程。ASM通過(guò)樹(shù)這種數(shù)據(jù)結(jié)構(gòu)來(lái)表示復(fù)雜的字節(jié)碼結(jié)構(gòu)她肯,并利用Push模型來(lái)對(duì)樹(shù)進(jìn)行遍歷佳头。
- ASM中提供一個(gè)ClassReader類,調(diào)用accept方法晴氨,接受一個(gè)實(shí)現(xiàn)了抽象類ClassVisitor的對(duì)象實(shí)例作為參數(shù)康嘉,然后依次調(diào)用ClassVisitor的各個(gè)方法。字節(jié)碼空間上的偏移被轉(zhuǎn)成各種visitXXX方法籽前。使用者只需要在對(duì)應(yīng)的的方法上進(jìn)行需求操作即可亭珍,無(wú)需考慮字節(jié)偏移敷钾。
- 這個(gè)過(guò)程中ClassReader可以看作是一個(gè)事件生產(chǎn)者,ClassWriter繼承自ClassVisitor抽象類肄梨,負(fù)責(zé)將對(duì)象化的class文件內(nèi)容重構(gòu)成一個(gè)二進(jìn)制格式的class字節(jié)碼文件阻荒,ClassWriter可以看作是一個(gè)事件的消費(fèi)者。
示例:攔截Android中 Activity生命周期方法众羡,執(zhí)行的時(shí)長(zhǎng)财松。
首先定義一個(gè)ActivityTimeManager記錄方法的使用時(shí)長(zhǎng),以onCreate方法為例纱控。
public class ActivityTimeManger {
public static HashMap<String, Long> startTimeMap = new HashMap<>();
public static void onCreateStart(Activity activity) {
startTimeMap.put(activity.toString(), System.currentTimeMillis());
}
public static void onCreateEnd(Activity activity) {
Long startTime = startTimeMap.get(activity.toString());
if (startTime == null) {
return;
}
long coastTime = System.currentTimeMillis() - startTime;
System.out.println(activity.toString() + " onCreate coast Time" + coastTime);
startTimeMap.remove(activity.toString());
… …
}
在Activity編譯的時(shí)候辆毡,在onCreate方法中,前后各插入ActivityTimeManger. onCreateStart() 和
ActivityTimeManger. onCreateEnd() 方法
原始的Activity甜害,onCreate方法:
public class TestActivity extends Activity{
public void onCreate() {
System.out.println("onCreate");
}
}
使用javap –c 命令 查看class文件的字節(jié)碼舶掖,如下:
public com.test.aop.main.TestActivity();
Code:
0: aload_0
1: invokespecial #8 // Method android/app/Activity."<init>":()V
4: return
public void onCreate();
Code:
0: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #21 // String onCreate
5: invokevirtual #22 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
加上ActivityTimeManger. onCreateStart(),ActivityTimeManger. onCreateEnd()之后的源碼如下:
public class TestActivity extends Activity{
public void onCreate() {
ActivityTimeManger.onCreateStart(this);
System.out.println("onCreate");
ActivityTimeManger.onCreateEnd(this);
}
使用javap –c 命令 查看class文件的字節(jié)碼尔店,如下:
public com.test.aop.main.TestActivity();
Code:
0: aload_0
1: invokespecial #8 // Method android/app/Activity."<init>":()V
4: return
public void onCreate();
Code:
0: aload_0
1: invokestatic #15 // Method com/test/aop/tools/ActivityTimeManger.onCreateStart:(Landroid/app/Activity;)V
4: getstatic #21 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #27 // String onCreate
9: invokevirtual #28 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_0
13: invokestatic #34 // Method com/test/aop/tools/ActivityTimeManger.onCreateEnd:(Landroid/app/Activity;)V
16: return
紅色部分是增加ActivityTimeManger. onCreateStart()眨攘,ActivityTimeManger. onCreateEnd()2個(gè)方法后,增加的字節(jié)碼嚣州。
所以我們?cè)趺词褂肁SM鲫售,對(duì)class文件進(jìn)行修改。把紅色部分的字節(jié)碼插入到class文件中呢该肴?
先輸入文件情竹。把class文件重命名為.opt文件,修改完后匀哄,再重命名回去蚕苇。
public static void processClass(File file) {
System.out.println("start process class " + file.getPath());
File optClass = new File(file.getParent(), file.getName() + ".opt");
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
inputStream = new FileInputStream(file);
outputStream = new FileOutputStream(optClass);
byte[] bytes = referHack(inputStream);
outputStream.write(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (file.exists()) {
file.delete();
}
optClass.renameTo(file);
}
referHack 方法
private static byte[] referHack(InputStream inputStream) {
try {
ClassReader classReader = new ClassReader(inputStream);
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
ClassVisitor changeVisitor = new ChangeVisitor(classWriter);
classReader.accept(changeVisitor, ClassReader.EXPAND_FRAMES);
return classWriter.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
return null;
}
創(chuàng)建ClassReader咆霜,生產(chǎn)者靖苇,讀出class字節(jié)碼鼓寺,輸出給ClassWriter消費(fèi)。
自定義ChangeVisitor 來(lái)處理class字節(jié)碼法梯。
public static class ChangeVisitor extends ClassVisitor {
// 記錄文件名
private String owner;
private ActivityAnnotationVisitor fileAnnotationVisitor = null;
public ChangeVisitor(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this.owner = name;
}
@Override
// 處理class文件的注解
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
System.out.println("visitAnnotation: desc=" + desc + " visible=" + visible);
AnnotationVisitor annotationVisitor = super.visitAnnotation(desc, visible);
if (desc != null) {
// 如果注解不是空苔货,傳遞給ActivityAnnotationVisitor處理。
fileAnnotationVisitor = new ActivityAnnotationVisitor(Opcodes.ASM5, annotationVisitor, desc);
return FileAnnotationVisitor;
}
return annotationVisitor;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
// 獲取到原始的MethodVisitor
MethodVisitor mv = this.cv.visitMethod(access, name, desc, signature, exceptions);
// 如果文件的注解不為空立哑,說(shuō)明文件要進(jìn)行修改夜惭。則創(chuàng)建RedefineAdvice,修改方法
if (fileAnnotationVisitor!= null) {
return new RedefineAdvice(mv, access, owner, name, desc);
}
return mv;
}
}
ChangeVisitor刁憋,繼承ClassVisitor滥嘴,class文件的訪問(wèn)木蹬,可以重寫(xiě)
visitAnnotation(), 獲取或者修改注解
visitMethod()至耻,獲取或者修改方法
visitField()若皱,獲取或者修改成員變量
這段代碼的邏輯是:先判斷這個(gè)class文件是否有注解。如果有注解尘颓,則先解析注解走触。如果注解不為空,則說(shuō)明有方法需要修改則創(chuàng)建RedefineAdvice疤苹,訪問(wèn)和修改方法互广。
看下ActivityAnnotationVisitor,對(duì)注解的訪問(wèn)和解析卧土。
public static class ActivityAnnotationVisitor extends AnnotationVisitor {
public String desc;
public String name;
public String value;
public ActivityAnnotationVisitor(int api, AnnotationVisitor av, String paramDesc) {
super(api, av);
this.desc = paramDesc;
}
public void visit(String paramName, Object paramValue) {
this.name = paramName;
this.value = paramValue.toString();
System.out.println("visitAnnotation: name=" + name + " value=" + value);
}
}
記錄注解的名稱和值惫皱,描述。
RedefineAdvice尤莺,對(duì)方法的修改
public static class RedefineAdvice extends AdviceAdapter {
String owner = "";
ActivityAnnotationVisitor activityAnnotationVisitor = null;
protected RedefineAdvice(MethodVisitor mv, int access, String className, String name, String desc) {
super(Opcodes.ASM5, mv, access, name, desc);
owner = className;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
System.out.println("visitAnnotation: desc=" + desc + " visible=" + visible);
AnnotationVisitor annotationVisitor = super.visitAnnotation(desc, visible);
// 先判斷方法上是否有注解旅敷,如果有注解,則使用ActivityAnnotationVisitor解析注解
if (desc != null) {
activityAnnotationVisitor = new ActivityAnnotationVisitor(Opcodes.ASM5, annotationVisitor, desc);
return activityAnnotationVisitor;
}
return annotationVisitor;
}
@Override
// 修改方法入口颤霎,在方法執(zhí)行前媳谁,插入字節(jié)碼
protected void onMethodEnter() {
if (activityAnnotationVisitor == null) {
return;
}
super.onMethodEnter();
//插入字節(jié)碼,ALOAD
mv.visitVarInsn(ALOAD, 0);
//插入字節(jié)碼INVOKESTATIC友酱,調(diào)用ActivityTimeManger.onCreateStart().
// onCreate使用注解寫(xiě)入
mv.visitMethodInsn(INVOKESTATIC, "com/test/aop/tools/ActivityTimeManger",
activityAnnotationVisitor.value+"Start",
"(Landroid/app/Activity;)V");
}
//在方法執(zhí)行結(jié)束前晴音,插入字節(jié)碼
@Override
protected void onMethodExit(int opcode) {
if (activityAnnotationVisitor == null) {
return;
}
super.onMethodExit(opcode);
//插入字節(jié)碼,ALOAD
mv.visitVarInsn(ALOAD, 0);
//插入字節(jié)碼INVOKESTATIC缔杉,調(diào)用ActivityTimeManger.onCreateEnd().
// onCreate使用注解寫(xiě)入
mv.visitMethodInsn(INVOKESTATIC, "com/test/aop/tools/ActivityTimeManger",
activityAnnotationVisitor.value+"End",
"(Landroid/app/Activity;)V");
}
}
整段代碼邏輯是:
先查找方法上的注解锤躁,如果方法上有注解,則獲取注解的value或详。在方法執(zhí)行前后进苍,插入字節(jié)碼。通過(guò)重寫(xiě)onMethodEnter和onMethodExit方法鸭叙。
所以在原來(lái)的TestActivity上觉啊,增加注解class注解和方法注解,然后通過(guò)processClass()處理沈贝,就能在記錄Activity方法執(zhí)行的時(shí)間杠人。
@FileAnnotation("TestActivity")
public class TestActivity extends Activity{
@ActivityAnnotation("onCreate")
public void onCreate() {
System.out.println("onCreate");
}
編譯后的class文件,反編譯后宋下,結(jié)果如下:
@FileAnnotation
public class TestActivity extends Activity {
@ActivityAnnotation
public void onCreate() {
ActivityTimeManger.onCreateStart(this);
System.out.println("onCreate");
ActivityTimeManger.onCreateEnd(this);
}
}
備注:Android App目前大部分都是通過(guò)gradle編譯嗡善。所以以上字節(jié)碼處理代碼,都需要寫(xiě)在自定義的gradle插件中学歧,自定義一個(gè)Transform處理罩引。
關(guān)于怎么自定義gradle插件和Transform ,可以百度枝笨,或者google袁铐。這里就不在寫(xiě)了揭蜒。
以上所有代碼鏈接