插樁的三種方法:AspectJ、ASM、ReDex

插樁,顧名思義,就是在代碼編譯期間修改已有的代碼獲證生成新代碼。


基礎(chǔ)知識(shí)

1、使用插樁的場(chǎng)景

  • 代碼生成
  • 代碼監(jiān)控
  • 代碼修改
  • 代碼分析
    從技術(shù)實(shí)現(xiàn)上看,編譯插樁是從代碼編譯的流程介入可以分為兩類:
  • Java文件,類似APT、AndroidAnnotation這些代碼生成的場(chǎng)景,他們生成的都是Java文件,是在編譯的最開(kāi)始接入。


  • 字節(jié)碼(Bytecode),字節(jié)碼的方式可以操作“.class”的Java字節(jié)碼,也可以操作“.dex”的Dalvik字節(jié)碼,這取決于我們使用的插樁方法。


2、字節(jié)碼

對(duì)于Java平臺(tái),Java虛擬機(jī)運(yùn)行的是class文件,內(nèi)部對(duì)應(yīng)的是Java字節(jié)碼。而針對(duì)Android的字節(jié)碼是Google專門(mén)為其設(shè)計(jì)的一種Dalvik字節(jié)碼,雖然增加了指令長(zhǎng)度但卻縮減了指令數(shù)量,執(zhí)行更會(huì)快速。
它們的主要區(qū)別有:

  • 體系架構(gòu),Java虛擬機(jī)是基于棧實(shí)現(xiàn),而Android虛擬機(jī)是基于寄存器實(shí)現(xiàn),在ARM平臺(tái),寄存器實(shí)現(xiàn)性能會(huì)高于棧實(shí)現(xiàn)。


  • 格式結(jié)構(gòu),對(duì)于Class文件,每個(gè)文件都會(huì)有自己的單獨(dú)的常量池以及其他一些公共字段。對(duì)于Dex文件,整個(gè)Dex中所有Class共用一個(gè)常量池和公共字段,所以整體結(jié)構(gòu)更加緊湊,因而大大減少了體積。
  • 指令優(yōu)化,Dalvik字節(jié)碼對(duì)大量的指令專門(mén)做了精簡(jiǎn)和優(yōu)化。

編譯插樁的三種方法

AspectJ和ASM框架的輸入和輸出都是Class文件,他們是我們最常使用的java字節(jié)碼處理框架。


1、AspectJ

AspectJ是Java中流行的AOP編程擴(kuò)展框架,從底層實(shí)現(xiàn)上來(lái)看,AspectJ內(nèi)部使用的是BCEL框架來(lái)完成,在使用上來(lái)看,AspectJ框架有自己的一定優(yōu)勢(shì):

  • 成熟穩(wěn)定
  • 使用簡(jiǎn)單
    我們完全不用關(guān)心底層Java字節(jié)碼的處理流程,就可以輕松實(shí)現(xiàn)編譯插樁功能。但是它的功能無(wú)法滿足某些場(chǎng)景的需求,如果想針對(duì)所有函數(shù)都做插樁,AspectJ會(huì)帶來(lái)不少的性能影響。

2、ASM

ASM的功能非常強(qiáng)大,它可以滿足100%的場(chǎng)景,其主要特點(diǎn)有:

  • 操作靈活,可以根據(jù)需求自定義修改、插入、刪除。
  • 上手難, 需要對(duì)Java字節(jié)碼有比較深入的了解。
    ASM更加高效直接,因而有時(shí)需要掌握一些必不可少的Java字節(jié)碼知識(shí)換人Java虛擬機(jī)運(yùn)行機(jī)制。Java虛擬機(jī)是基于棧實(shí)現(xiàn)的,Java虛擬機(jī)的描述:

每一條 Java 虛擬機(jī)線程都有自己私有的 Java 虛擬機(jī)棧,這個(gè)棧與線程同時(shí)創(chuàng)建,用于存儲(chǔ)棧幀(Stack Frame)。
所以在多線程應(yīng)用中多個(gè)線程就會(huì)有多個(gè)棧,每個(gè)棧都有自己的棧幀。



如下圖所示,我們可以簡(jiǎn)單的認(rèn)為棧幀包含3個(gè)重要的內(nèi)容:本地變量表(Local Variable Array)、操作數(shù)棧(Operand Stack)和常量池引用(Constant Pool Reference)。


  • 本地變量表,可以認(rèn)為本地變量表是存放臨時(shí)數(shù)據(jù)的,并且本地變量表有個(gè)很重要的功能就是用來(lái)傳遞方法調(diào)用時(shí)的參數(shù),當(dāng)調(diào)用方法的時(shí)候,參數(shù)會(huì)依次傳遞到本地變量表中從0開(kāi)始的位置上,并且如果調(diào)用的方法是實(shí)例方法,那么我們可以通過(guò)第0個(gè)本地變量中獲取當(dāng)前實(shí)例的引用,也就是this所指向的對(duì)象。
  • 操作數(shù)棧,可以認(rèn)為操作數(shù)棧是一個(gè)用于存放指令執(zhí)行所需要的數(shù)據(jù)位置,指令從操作數(shù)棧中取走數(shù)據(jù)并將操作結(jié)構(gòu)重入棧。
    由于本地變量表和操作數(shù)棧的最大深度是在編譯時(shí)就確定的,所以在使用ASM進(jìn)行字節(jié)碼操作后需要調(diào)用ASM提供的visitMaxs方法來(lái)設(shè)置maxLocal和maxStack數(shù),不過(guò),ASM為了方便用戶使用,已經(jīng)提供了自動(dòng)計(jì)算的方法,在實(shí)例化ClassWriter操作類的時(shí)候傳入COMPUTE_MAXS,ASM就會(huì)自動(dòng)計(jì)算本地變量表和操作數(shù)棧。
    在具體的字節(jié)碼處理過(guò)程中,特別需要注意的是本地變量表和操作數(shù)棧的數(shù)據(jù)交換和try catch block的處理。
  • 數(shù)據(jù)交換,如下圖所示,在經(jīng)過(guò)IADD指令操作后,又通過(guò)ISTORE 0 指令將棧頂int值存入第1個(gè)本地變量中個(gè),用于臨時(shí)保存,在最后的加法過(guò)程中,將0和1位置的本地變量取出壓入操作數(shù)棧中功IADD指令使用。


  • 遺產(chǎn)處理,在字節(jié)碼操作過(guò)程中需要特別注意異常處理對(duì)操作數(shù)棧的影響,如果在try和catch之間拋出了一個(gè)可捕獲的異常,那么當(dāng)前的操作數(shù)棧會(huì)被清空,并將異常對(duì)象壓入這個(gè)空棧中,執(zhí)行過(guò)程在catch處繼續(xù)。幸運(yùn)的是,如果生產(chǎn)了錯(cuò)誤的字節(jié)碼,編譯器可以辨別出該情況并導(dǎo)致編譯異常,ASM中也提供了字節(jié)碼Verify的接口,可以在修改完成后驗(yàn)證一下字節(jié)碼是否正常。
    如果想在一個(gè)方法執(zhí)行完成后增加代碼,ASM相對(duì)也要簡(jiǎn)單很多,可以在字節(jié)碼中出現(xiàn)的每一條RETURN系或者ATHROW的指令前,增加處理的邏輯即可。

ReDex

ReDex不僅只是作為一款Dex優(yōu)化工具,它也提供了很多的小工具和功能,比如在ReDex里提供了一個(gè)簡(jiǎn)單的Method Tracing和Block Tracing工具,這個(gè)工具可以在所有方法或者指定方法前插入一跟蹤代碼。
ReDex的這個(gè)功能并不是完整的AOP工具,但它提供了一系列指令生成API和Opcode插入API,我們可以參照這個(gè)功能實(shí)現(xiàn)自己的字節(jié)碼注入工具,這個(gè)功能的代碼在Instrument.cpp中。
由于Dalvik字節(jié)碼發(fā)展時(shí)間尚端,而且因?yàn)镈ex格式更加緊湊,修改起來(lái)往往牽一發(fā)而動(dòng)全身,并且Dalvik字節(jié)碼的處理相比Java字節(jié)碼會(huì)更加復(fù)雜一些,所以直接操作Dalivk字節(jié)碼的工具并不是很多。
市面上大部分需要直接修改Dex的情況是逆向的,很多同學(xué)都采用手動(dòng)書(shū)寫(xiě)Smail代碼然后編譯回去,總結(jié)一下Dex字節(jié)碼庫(kù):
-ASMDEX,開(kāi)發(fā)者是ASM庫(kù)的作者,但很久未更新了

  • Dexter, Google官方開(kāi)發(fā)的Dex操作庫(kù),更新很頻繁,但是使用起來(lái)很復(fù)雜
  • Dexmaker,用來(lái)生成Dalvik字節(jié)碼的代碼
  • Soot,修改Dex的方法很另類,是將Dalvik字節(jié)碼轉(zhuǎn)換成一種Jimple three-address code,然后插入Jimple Opcode后再轉(zhuǎn)回Dalvik字節(jié)碼。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容