ASM是很是強大的JAVA字節碼生成和修改工具,具備性能優異、文檔齊全、比較易用等優勢。官方網站:http://asm.ow2.org/html
要想熟練的使用ASM,須要對java字節碼有必定的瞭解,本文重點對java函數的字節碼進行介紹。本文部份內容參考官方文檔:http://download.forge.objectweb.org/asm/asm4-guide.pdfjava
1.JAVA虛擬機執行模型android
在JVM執行模型裏,每一個方法都是在線程中執行,而每一個線程對應本身的棧,每一個棧由幀組成。每一個幀對應一個方法調用,每次調用一個方法,web
會將新幀壓入當前線程的執行棧,當方法返回時(異常退出也是返回),再將這個幀從執行棧彈出。數組
每一個幀主要包括兩部分,一個局部變量表和一個操做數棧,關係以下圖所示:數據結構
這裏注意,局部變量表是根據索引訪問的列表,相似數組;而操做數棧則是「後入先出」的棧,這裏很是重要,由於java函數的字節碼指令基本上都是對這兩個數據結構進行操做。jvm
局部變量表和操做數棧的大小取決於方法代碼,在編譯時計算,並隨字節碼指令一塊兒寫入class文件中,ide
public int gogo() { Log.i("zkw", "hello"); return 888; }
這是一個java方法,編譯成class以後內容以下:函數
// access flags 0x1 public gogo()I LDC "zkw" LDC "hello" INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I POP SIPUSH 888 IRETURN MAXSTACK = 2 MAXLOCALS = 1
最下面兩行的MAXSTACK和MAXLOCALS的值就是操做數棧和局部變量表的大小。工具
局部變量表和操做數棧中的每一個槽(slot)能夠保存除long和double以外的任意java值,而long和double須要兩個槽,好比向局部變量表儲存一個int和一個long,則表中第一個位置是int值,第二和第三個位置存的是long值。
還有一點須要注意,若是是非靜態方法,局部變量表的第0個位置爲"this"。
2.字節代碼指令
Java類型被編譯成class後,都是用類型描述符表示的,以下圖:
方法也一樣會被編譯成方法描述符,以下:
字節碼指令是由操做碼和參數組成:
字節碼指令分爲兩種:
仍是用上面的代碼舉例子,咱們直接看字節碼:
// access flags 0x1 public gogo()I LDC "zkw" LDC "hello" INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I POP SIPUSH 888 IRETURN MAXSTACK = 2 MAXLOCALS = 1
LDC是將參數中的值壓入操做數棧,因此前兩行執行完,操做數棧應該長這樣[...,"zkw","hello"],前面...是以前壓入的值,
而後INVOKESTATIC指令彈出以前壓入的參數,而後調用Log.i靜態方法,最後將int結果壓入棧,此時操做數棧應該長這樣[...,int結果]
因爲沒有使用Log.i的返回值,因此直接將返回值從操做數棧POP出去,
接下來SIPUSH將888壓入操做數棧,此時棧長這樣[...,888]
而後IRETURN從操做數棧彈出int值並返回,方法調用結束。
這裏咱們沒有看到對局部變量表的操做,下面稍微修改下gogo方法:
public int gogo() { int a = Log.i("zkw", "hello"); return a; }
爲了看到如何操做局部變量表,咱們獲取Log.i返回的int值,並將其return,編譯以後以下:
// access flags 0x1 public gogo()I LDC "zkw" LDC "hello" INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I ISTORE 1 ILOAD 1 IRETURN MAXSTACK = 2 MAXLOCALS = 2
當INVOKESTATIC指令執行以後,操做數棧爲[...,int值],局部變量表爲[this]
看到INVOKESTATIC以後,多了個ISTORE指令,ISTORE 1指令是彈出操做數棧棧頂的值(也就是log.i的返回值),將其存入局部變量表索引爲1的位置(思考一下爲何不是0),當ISTORE執行完,操做數棧爲[...],局部變量表爲[this,int值]。
而後執行ILOAD 1,該指令取出局部變量表1位置的值,並壓入操做數棧,此時操做數棧爲[...int值],局部變量表爲[this]。
而後IRETURN從操做數棧彈出int值,並將其return,執行結束。
3.棧映射幀
java1.6以後還引入了棧映射幀,用於加快虛擬機中類驗證過程的速度。這個映射幀主要記錄每一個指令執行前的局部變量表和操做數棧中包含的類型狀態。這個幀和所謂的棧幀沒有關係,這個映射幀僅僅標示當前局部變量表和操做數棧的狀態。
當jvm進入一個方法時,根據方法描述符就能夠肯定初始幀的狀態,例如方法com.demo.Foo.gogo(int a)的局部變量表的初始狀態爲[com.demo.Foo, I],而操做數棧初始狀態確定是空的。因此這個方法的初始幀爲[com.demo.Foo, I],[]
爲了節省空間,編譯方法時並不會爲每條指令生成一個映射幀,事實上,它僅爲跳轉指令(包括if else,try cache等)生成映射幀。
爲了節省更多空間,對每一個須要生成映射幀的地方作壓縮,僅僅儲存與前一幀的差異,好比與前一幀的狀態同樣時,使用F_SAME助記符,當比前一幀增長了3個之內的局部變量時,使用F_APPEND [],當增長了3個以上的局部變量時,使用F_FULL []。說了這麼多可能有點暈了,看例子吧。
咱們修改上面的例子,增長一些局部變量和條件判斷:
public int gogo(int c) { int a = Log.i("zkw", "hello"); float f = 0.4f; if (a > 0) { Log.i("zkw", ">>0"); } else { Log.i("zkw", "<<0"); } return a; }
代碼中增長了兩個局部變量a和f,看看編譯後的字節碼:
// access flags 0x1 public gogo(I)I LDC "zkw" LDC "hello" INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I ISTORE 2 LDC 0.4 FSTORE 3 ILOAD 2 IFLE L0 LDC "zkw" LDC ">>0" INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I POP GOTO L1 L0 FRAME APPEND [I F] LDC "zkw" LDC "<<0" INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I POP L1 FRAME SAME ILOAD 2 IRETURN MAXSTACK = 2 MAXLOCALS = 4
咱們假定這個方法是com.demo.Foo類的,那麼這個方法的初始幀狀態應該是[com.demo.Foo, I],[],字節碼中不會標示初始幀狀態。
而後代碼繼續往下走,咱們增長了兩個局部變量int a和float f,因此幀狀態出現變化,這個變化會在第一個跳轉目標裏展現出來,請看L0下面的FRAME APPEND [I F],意思是相比於以前的幀狀態增長了兩個局部變量,類型是int和float,此時幀狀態更新成[com.demo.Foo, I, I, F],[]。
以後碰見了下一個跳轉目標L1,這時候的局部變量沒有變化,因此使用FRAME SAME標示。
這些FRAME指令僅僅是標示幀狀態的變化,沒有對局部變量表和操做數棧作任何操做,目的是加快java虛擬機中類驗證過程的速度。
以前說F_APPEND是標示增長3個以內的幀變化,那3個以外呢,咱們繼續修改gogo方法,增長兩個局部變量:
public int gogo(int c) { int a = Log.i("zkw", "hello"); float f = 0.4f; short s = 12; long l = 10003983839L; if (a > 0) { Log.i("zkw", ">>0"); } else { Log.i("zkw", "<<0"); } return a; }
看到咱們增長了short s和long l,看看編譯後啥樣:
// access flags 0x1 public gogo(I)I LDC "zkw" LDC "hello" INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I ISTORE 2 LDC 0.4 FSTORE 3 BIPUSH 12 ISTORE 4 LDC 10003983839 LSTORE 5 ILOAD 2 IFLE L0 LDC "zkw" LDC ">>0" INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I POP GOTO L1 L0 FRAME FULL [com/demo/Foo I I F I J] [] LDC "zkw" LDC "<<0" INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I POP L1 FRAME SAME ILOAD 2 IRETURN MAXSTACK = 2 MAXLOCALS = 7
看到標紅的那行,使用了FRAME FULL的指令,後面參數就是徹底的局部變量表狀態。
本文爲原創,轉載請註明出處:http://www.cnblogs.com/coding-way/p/6600647.html