字節碼操縱框架ASM

引言

在我們實際的開發過程中,很多場景需要AOP的編程思想,在開發者無感知地侵入式的插如自己的業務邏輯,比如我最近做的一個埋點統計的一些場景,在開發者無感知情況下,將生命週期上報執行邏輯代碼植入到我們現有的APP的某些頁面的Class裏面,將用戶事件的邏輯代碼植入到對應的事件響應方法裏面。這裏我們就引出了字節碼操作框架ASM,通過ASM修改編譯過程生成的java字節碼,植入埋點上報的業務邏輯方法,從而不需要開發者在每個頁面生命週期或者基類生命週期,事件響應方法裏面做大量的埋點代碼重複工作。

介紹

ASM 是一個 Java 字節碼操控框架。它能被用來動態生成類或者增強既有類的功能。ASM 可以直接產生二進制 class 文件,也可以在類被加載入 Java 虛擬機之前動態改變類行爲。Java class 被存儲在嚴格格式定義的 .class 文件裏,這些類文件擁有足夠的元數據來解析類中的所有元素:類名稱、方法、屬性以及 Java 字節碼(指令)。ASM 從類文件中讀入信息後,能夠改變類行爲,分析類信息,甚至能夠根據用戶要求生成新類。

ASM效果展示

這是一個簡單的埋點工程,在工程裏面的頁面MainActivity裏面添加事件
在這裏插入圖片描述
通過ASM插件的加入,最終編譯出來的Class字節碼文件
在這裏插入圖片描述
但是在編譯出來的class字節碼文件裏面多出了一行代碼,這就是植入的一行埋點統計方法

  ...
  this.findViewById(2131427418).setOnClickListener(new OnClickListener() {
              public void onClick(View var1) {
                  //多出來的代碼,即工具植入的埋點方法代碼 
                  Monitor.INSTANCE.onViewClick(var1);
                  Log.d(MainActivity.TAG, "onclick btn_test1 ");
              }
          });
  ...

看到這,想必大家就見識到ASM黑科技的威力了,通過ASM可以對java字節碼進行各種修改,達到我們所需要的目的。(ps:很多流氓sdk的廣告植入大部分都是採用類似的方案)

ASM初步使用
  • 1.工程引入
dependencies {
    ...
    compile 'org.ow2.asm:asm:5.2'
    compile 'org.ow2.asm:asm-commons:5.2'
}

  • 2.調用ASM的類讀寫器
    ClassReader classReader = new ClassReader(file.bytes)
    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
    ClassVisitor cv = new ClickEventVisitor(classWriter)
    classReader.accept(cv, ClassReader.EXPAND_FRAMES)
    byte[] code = classWriter.toByteArray()
    FileOutputStream fos = new FileOutputStream(file.absolutePath)
    fos.write(code)
    fos.close()
  • 3.實現ClassVisitor植入埋點代碼
class ClickEventVisitor extends ClassVisitor {

    ClickEventVisitor(ClassVisitor cv) {
        super(Opcodes.ASM5, cv)
    }

    @Override
    MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        def methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions)
        methodVisitor = new AdviceAdapter(Opcodes.ASM5, methodVisitor, access, name, desc) {
            boolean isInject() {
                return name == "onClick"
            }
            @Override
            protected void onMethodEnter() {
                super.onMethodEnter()
                //判斷是否需要植入代碼
                if (!isInject()) {
                    return
                }
                //這裏就是我們植入的一段埋點代碼 即調用Monitor.INSTANCE.onViewClick方法
                mv.visitFieldInsn(GETSTATIC, "com/zgkxzx/malfurion/Monitor", "INSTANCE", "Lcom/zgkxzx/malfurion/Monitor;")
                mv.visitVarInsn(ALOAD, 1)
                mv.visitMethodInsn(INVOKEVIRTUAL, "com/zgkxzx/malfurion/Monitor", "onViewClick",
                        "(Landroid/view/View;)V", false)

            }
        }
        return methodVisitor
    }
}

在 ASM 中,提供了一個 ClassReader類,這個類可以直接由字節數組或由 class 文件間接的獲得字節碼數據,它能正確的分析字節碼,構建出抽象的樹在內存中表示字節碼。它會調用 accept方法,這個方法接受一個實現了 ClassVisitor接口的對象實例作爲參數,然後依次調用 ClassVisitor接口的各個方法。字節碼空間上的偏移被轉換成 visit 事件時間上調用的先後,所謂 visit 事件是指對各種不同 visit 函數的調用,ClassReader知道如何調用各種 visit 函數。在這個過程中用戶無法對操作進行干涉,所以遍歷的算法是確定的,用戶可以做的是提供不同的 Visitor 來對字節碼樹進行不同的修改。ClassVisitor會產生一些子過程,比如 visitMethod會返回一個實現 MethordVisitor接口的實例,visitField會返回一個實現 FieldVisitor接口的實例,完成子過程後控制返回到父過程,繼續訪問下一節點。因此對於 ClassReader來說,其內部順序訪問是有一定要求的。實際上用戶還可以不通過 ClassReader類,自行手工控制這個流程,只要按照一定的順序,各個 visit 事件被先後正確的調用,最後就能生成可以被正確加載的字節碼。當然獲得更大靈活性的同時也加大了調整字節碼的複雜度。

初步使用小節只是ASM的初步引入基本使用,後續章節會深入字節碼,對ASM的幾個重要的接口
1.ClassVisitor 類訪問器
2.FieldVisitor fang 變量訪問器
3.MethodVisitor 方法訪問器
4.AnnomatioinVisitor 註解訪問器
進行深入講解和使用

ASM植入過程分析

正常的java IDE在對java文件編譯後運行的流程(ps:這裏的圖用的經典的HelloWorld做編譯分析,更容易理解)
在這裏插入圖片描述

在使用我們自己的插件後,整個IDE工程的編譯流程
在這裏插入圖片描述
可以看出,在javac將程序員編寫的java文件編譯成class字節碼文件後,myPlugin插件工具對生成的class文件進行植入式修改變成新的class文件,最後通過java指令執行class運行起來(Android通過ART虛擬機執行Dex文件,dex也是有這些class打包生成的),從而達到植入的目的。這裏我們對比下剛纔工程的植入的代碼的字節碼文件二進制對比
在這裏插入圖片描述可以看出,文件的二進制基本上一樣,就是對文件下面的二進制進行了植入。我們在對比下字節碼文件
在這裏插入圖片描述
對比可以很容易看出,注入的字節碼文件對常量池進行添加,對方法的code屬性進行了添加調用埋點統計的方法,從而無感知的實現了埋點的功能。

總結

ASM字節碼操縱框架的出現爲我們開發人員編寫各種框架各種SDK工具實現更多的可能,本章只是簡單的介紹和引入(內容比較多一章寫不完)。後面會分小章進行ASM重要類方法的精講。謝謝-

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章