0x00 什么是 ASM
ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or to dynamically generate classes, directly in binary form.
ASM offers similar functionality as other Java bytecode frameworks, but is focused on performance. Because it was designed and implemented to be as small and as fast as possible.
0x10 為什么要操縱分析字節碼
- 程序分析,發現 bug,檢測無用代碼
- JaCoCo(Java Code Coverage Library 用于檢查單元測試覆蓋率)
- 產生代碼
- openJDK lambda、Groovy 編譯器、Kotlin 編譯器
- 優化、混淆代碼,注入調試及監控代碼等
- Aspectj
Proguard
可以利用 ASM 實現自己的代碼混淆工具
0x20 ASM 編程框架簡介
Core API
代理模式、訪問者模式
-
ClassReader
- 解析字節碼文件,調用 ClassVisitor 的特定的方法訪問 Class 的字段、方法以及字節碼
-
ClassVisitor
- 用于訪問 Java 類文件
- 必須以下面的順序調用該類的方法
{@code visit} [ {@code visitSource} ] [ {@code visitModule} ][ {@code visitNestHost} ][ {@code visitOuterClass} ] ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* ( {@code visitNestMember} | {@code visitInnerClass} | {@code visitField} | {@code visitMethod} )* {@code visitEnd}.
- 如果需要增加或者刪除類的字段/方法,可以自定義該類實現
-
FieldVisitor
- 用于訪問 Java 類的字段
- 必須以下面的順序調用該類的方法
( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code visitEnd}.
- 修改類字段的內容,通過自定義 ClassVisitor 已經可以實現,自定義該類的場景不多
-
MethodVisitor
- 用于訪問 Java 類的方法
- 必須以下面的順序調用該類的方法
( {@code visitParameter} )* [ {@code visitAnnotationDefault} ] ( {@code visitAnnotation} | {@code visitAnnotableParameterCount} | {@code visitParameterAnnotation} {@code visitTypeAnnotation} | {@code visitAttribute} )* [ {@code visitCode} ( {@code visitFrame} | {@code visit<i>X</i>Insn} | {@code visitLabel} | {@code visitInsnAnnotation} | {@code visitTryCatchBlock} | {@code visitTryCatchAnnotation} | {@code visitLocalVariable} | {@code visitLocalVariableAnnotation} | {@code visitLineNumber} )* {@code visitMaxs} ] {@code visitEnd}.
- 如果需要修改類方法的實現,需要自定義該類
-
ClassWriter
- 存儲類文件字節碼信息,用于生成類文件的 ClassVisitor
- ClassWriter 內部有個 FieldWriter 鏈表用于保存類文件的所有 Field 信息
- ClassWriter 內部有個 MethodWriter 鏈表用于保存類文件的所有 Method 信息
- 與一個或多個 ClassReader 及 ClassVisitor 一起使用修改類文件
- 存儲類文件字節碼信息,用于生成類文件的 ClassVisitor
Core API 實現原理
下面通過一個簡單的字節碼文件拷貝的例子來分析 ASM 的原理
ASM 實現字節碼拷貝關鍵代碼
......
// inputClass 為輸入字節碼文件
InputStream inputStream = new FileInputStream(inputClass);
final ClassReader classReader = new ClassReader(inputStream);
final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
classReader.accept(classWriter, ClassReader.EXPAND_FRAMES);
final byte[] newClassFile = classWriter.toByteArray();
......
WorkFlow
- ASM Core API 主要用到了兩種關鍵的設計模式,代理模式及訪問者模式
- ClassReader 負責讀取要修改的字節碼文件,同時根據字節碼文件的格式規定了訪問方式(也就是 ClassVisitor 以及 FieldVisitor、MethodVisitor 的 visitXxx 方法的調用順序)
- ClassWriter 繼承自 ClassVisitor,如果 ClassWriter 直接作為訪問者訪問 ClassReader,也就是上面的例子。字節碼最終不會修改只是保存到 ClassWriter 中
- 如果我們需要增加、刪除 Filed 或者 Method,我們只需要自定義 ClassVisitor 代理 ClassWriter,用自定義的 ClassVisitor 訪問 ClassReader 即可,這樣在 ClassReader 使用我們自定義的 ClassVisitor 的 visitXxx 遍歷字節碼的 Field、Method 時,我們可以重寫 visitXxx 方法來實現相應的功能
- 如果需要修改 Method 的內部實現,我們不僅需要自定義 ClassVisitor 還需要自定義 MethodVisitor 用于代理 ClassWriter 內部 MethodWriter 訪問 Method 的實現,并在訪問過程中做出修改
- ClassWriter 的 toByteArray 方法實際只是 dump 出內部兩個鏈表中保存的字節碼信息
Tree API
- 不介紹
0x30 使用 ASM 進行 AOP 編程 (Android studio 插件開發)
類中添加 Field
- 例子
public class ASMAddField {
public static final String LOG_TAG = "ASM.ASMAddField";
// 使用 ASM 增加 addField 字段
// public Object addField;
private ASMAddField() {
}
}
- 關鍵實現代碼
......
private class AddFieldAdapter extends ClassVisitor {
public AddFieldAdapter(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
// 檢查字段是否已經存在
if (name.equals(fieldName)) {
isFieldPresent = true;
}
return super.visitField(access, name, desc, signature, value);
}
@Override
public void visitEnd() {
if (!isFieldPresent) {
// 添加字段
FieldVisitor fieldVisitor =
cv.visitField(fieldACC, fieldName, fieldDesc, null, null);
if (fieldVisitor != null) {
fieldVisitor.visitEnd();
}
}
super.visitEnd();
}
}
......
類中刪除 Field
- 例子
public class ASMDeleteField {
public static final String LOG_TAG = "ASM.ASMDeleteField";
// 使用 ASM 刪除該字段
public Object deleteField = new Object();
private ASMDeleteField() {
}
}
- 關鍵實現代碼
private class DeleteFieldAdapter extends ClassVisitor {
public DeleteFieldAdapter(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
if (name.equals(fieldName)) {
return null;
}
return super.visitField(access, name, desc, signature, value);
}
}
類中增加 Method
- 例子
public class ASMAddMethod {
public static final String LOG_TAG = "ASM.ASMAddMethod";
private ASMAddMethod() {
}
// 使用 ASM 增加下面方法
// public static void addMethod() {
// Log.i(LOG_TAG, "this is add method");
// }
}
- 關鍵實現代碼
private class AddMethodAdapter extends ClassVisitor {
public AddMethodAdapter(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals(methodName) && desc.equals(methodDesc)) {
isMethodPresent = true;
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public void visitEnd() {
if (!isMethodPresent) {
// 增加方法及方法的內部實現,可以借助 ASM Bytecode Outline 2017 插件事先獲得方法的內部實現
MethodVisitor methodVisitor = cv.visitMethod(methodACC,
methodName, methodDesc, null, null);
if (methodVisitor != null) {
methodVisitor.visitCode();
methodVisitor.visitLdcInsn("ASM.ASMAddMethod");
methodVisitor.visitLdcInsn("this is add method");
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"android/util/Log",
"i",
"(Ljava/lang/String;Ljava/lang/String;)I",
false);
methodVisitor.visitInsn(POP);
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitMaxs(2, 0);
methodVisitor.visitEnd();
}
}
super.visitEnd();
}
}
類中刪除 Method
- 例子
public class ASMDeleteMethod {
public static final String LOG_TAG = "ASM.ASMDeleteMethod";
private ASMDeleteMethod() {
}
// 使用 ASM 刪除該方法
public static void deleteMethod() {
Log.i(LOG_TAG, "this is delete method");
}
}
- 關鍵實現代碼
private class DeleteMethodAdapter extends ClassVisitor {
public DeleteMethodAdapter(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (access == methodACC
&& name.equals(methodName)
&& desc.equals(methodDesc)) {
return null;
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
修改類中的 Method 實現
- 例子
public class ASMModifyMethod {
public static final String LOG_TAG = "ASM.ASMModifyMethod";
private ASMModifyMethod() {
}
// 使用 ASM 增加方法耗時打印
public static void modifyMethod() {
// long startTime = System.currentTimeMillis();
Log.i(LOG_TAG, "this is modify method");
// long endTime = System.currentTimeMillis();
// Log.i(LOG_TAG, "method consume time: " + (endTime - startTime) + "ms");
}
}
- 關鍵實現代碼
private class ModifyMethodAdapter extends ClassVisitor {
public ModifyMethodAdapter(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (mv != null
&& access == methodACC
&& name.equals(methodName)
&& desc.equals(methodDesc)) {
mv = new MethodVisitorImpl(ASM5, methodACC, methodDesc, mv);
}
return mv;
}
}
private class MethodVisitorImpl extends LocalVariablesSorter {
private int start;
private int end;
protected MethodVisitorImpl(int api, int access, String desc, MethodVisitor mv) {
super(api, access, desc, mv);
}
@Override
public void visitCode() {
super.visitCode();
mv.visitMethodInsn(INVOKESTATIC,
"java/lang/System",
"currentTimeMillis",
"()J",
false);
start = newLocal(Type.LONG_TYPE);
mv.visitVarInsn(LSTORE, start);
}
@Override
public void visitInsn(int opcode) {
if (opcode == RETURN || opcode == ATHROW) {
mv.visitMethodInsn(INVOKESTATIC,
"java/lang/System",
"currentTimeMillis",
"()J",
false);
end = newLocal(Type.LONG_TYPE);
mv.visitVarInsn(LSTORE, end);
mv.visitLdcInsn("ASM.ASMModifyMethod");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL,
"java/lang/StringBuilder",
"<init>",
"()V",
false);
mv.visitLdcInsn("method consume time: ");
mv.visitMethodInsn(INVOKEVIRTUAL,
"java/lang/StringBuilder",
"append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;",
false);
mv.visitVarInsn(LLOAD, end);
mv.visitVarInsn(LLOAD, start);
mv.visitInsn(LSUB);
mv.visitMethodInsn(INVOKEVIRTUAL,
"java/lang/StringBuilder",
"append",
"(J)Ljava/lang/StringBuilder;",
false);
mv.visitLdcInsn("ms");
mv.visitMethodInsn(INVOKEVIRTUAL,
"java/lang/StringBuilder",
"append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;",
false);
mv.visitMethodInsn(INVOKEVIRTUAL,
"java/lang/StringBuilder",
"toString",
"()Ljava/lang/String;",
false);
mv.visitMethodInsn(INVOKESTATIC,
"android/util/Log",
"i",
"(Ljava/lang/String;Ljava/lang/String;)I",
false);
mv.visitInsn(POP);
}
super.visitInsn(opcode);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack + 4, maxLocals + 4);
}
}
文章中的樣例 Demo 完成實現已放在 Github 中
Android Studio 字節碼插件:ASM Bytecode Outline 2017
0x40 ASM 在 Android 中的應用
- R 內聯
- D8 desugar
- Instant Run