天天看點

Java位元組碼修改架構ASM

位元組碼相對java的意義類似彙編相對c的意義,底層了解的越多越深入,程式就越神奇,一切想法皆有可能實作。學習了下位元組碼架構asm,總結分享下:

api概述。

一、asm庫提供了兩類api接口模型來産生或者修改類位元組碼:

(1)核心api: 基于事件,每個事件代表類的一個元素,如頭事件、方法事件、字段事件等。特點是更快耗費更少的記憶體。

(2)樹型api: 基于對象樹狀結構,字段方法等都可以看做對象樹的一部分。使用相對簡單,但耗費記憶體。

二、api包結構大緻如下:

(1)事件、解析器、生産器類api在包路徑org.objectweb.asm及org.objectweb.asm.signature内。

(2)開發和調試中可以用到的一些核心api在包路徑org.objectweb.asm.util内。

(3)基于對象樹的api在org.objectweb.asm.tree内,同時包括了和事件類api互相轉換的工具。

(4)org.objectweb.asm.tree.analysis為對象樹類api提供了類分析架構。

核心類ap i- class

一、類結構

(1)虛拟機内部定義的類結構圖:

Java位元組碼修改架構ASM

(2)類名稱在虛拟機内部标示有所不同,如java.lang.string内部标示為java/lang/string

(3)編譯後的class的字段類型如下:

Java位元組碼修改架構ASM

(4)方法描述内部辨別如下:

Java位元組碼修改架構ASM

二、類相關接口api:

(1)classreader可以看作事件的産生器,能夠讀取解析類的二進制位元組數組,邊解析邊把類的字段或者方法等資訊以事件傳遞給classvisitor的相關visitxxx方法。常見代碼:

// classprinter是classvisitor的執行個體,在内部的visitxxx方法内部定義自己的業務邏輯,如列印輸出

classprinter cp = new classprinter();

classreader cr = new classreader("java.lang.runnable");

cr.accept(cp, 0);

(2)classwriter可以看做時間的一個消費器,是classvisitor的一個子類實作,可以直接建構出類的二進制數組辨別形式,截取下例子:

classwriter cw = new classwriter(0);

// public interface comparable extends mesurable {

cw.visit(v1_5, acc_public + acc_abstract + acc_interface,

"pkg/comparable", null, "java/lang/object",

new string[] { "pkg/mesurable" });

// int less = -1;

cw.visitfield(acc_public + acc_final + acc_static, "less", "i",

null, new integer(-1)).visitend();

// int equal = 0;

cw.visitfield(acc_public + acc_final + acc_static, "equal", "i",

null, new integer(0)).visitend();

// int greater = 1;

cw.visitfield(acc_public + acc_final + acc_static, "greater", "i",

null, new integer(1)).visitend();

// int compareto(object o);

cw.visitmethod(acc_public + acc_abstract, "compareto",

"(ljava/lang/object;)i", null, null).visitend();

cw.visitend();

byte[] b = cw.tobytearray();

(3)classvisitor可以将接收到的所有監聽到事件的方法調用傳遞給另外一個classvisitor,可以看做一個事件過濾器,針對classvisitor添加自己的業務邏輯就可以實作神奇的位元組碼修改:

// 轉換前的位元組碼

byte[] b1 = ...;

classreader cr = new classreader(b);

classwriter cw = new classwriter(cr, 0);

// 在這裡面定義你想要的業務,一切皆有可能

classvisitor cv = new changeversionadapter(cw);

cr.accept(cv, 0);

// 轉換後的位元組碼

byte[] b2 = cw.tobytearray();

(4)classvisitor内可以實作删除類成員(如visitmethod傳回null删除方法)、添加類成員(visitend添加visitfield),classvisitor也可以是個調用鍊。

三、開發工具api:

(1)type提供了内部類型和java類型的轉換,如前面提到的string轉換内部類名可以這樣實作:

// 僅用于類或者接口,java/lang/string

string internalname = type.gettype(string.class).getinternalname();

string轉換為内部描述可以這樣:

// 轉換内部描述:ljava/lang/string;

string internaldesc = type.gettype(string.class).getdescriptor();

string intdesc = type.int_type.getdescriptor();

方法參數和傳回值示例:

// 傳回type.int_type

type.getargumenttypes("(i)v");

// type.void_type

type.getreturntype("(i)v")

(2)traceclassvisitor可以文本形式列印出轉換後的位元組碼到底是什麼樣子的:

// printwriter會輸出轉換後的位元組碼

traceclassvisitor cv = new traceclassvisitor(cw, printwriter);

cv.visit(...);

...

cv.visitend();

byte b[] = cw.tobytearray();

(3)轉換後的位元組碼是否格式合法可以用checkclassadapter校驗:

traceclassvisitor tcv = new traceclassvisitor(cw, printwriter);

// 發現問題會抛出異常

checkclassadapter cv = new checkclassadapter(tcv);

(4)如果有的類實在複雜到你很難寫出位元組碼來生成它,asm提供自動把複雜類列印出位元組碼的工具asmifier,通過指令行方式輸出,太強大了:

java -classpath asm.jar:asm-util.jar \

org.objectweb.asm.util.asmifier \

java.lang.runnable

核心類ap i- method

一、方法結構

(1)方法執行模型:java代碼線上程内執行,每個線程有自己響應的棧stack,每個棧由棧幀frame構成,每個frame代表一個方法調用。每個frame包括本地變量和操作棧,本地變量用序号通路,變量使用slot存儲,除了long和double需要2個slot外其他變量都有一個slot存儲。一個frame構成如下:

Java位元組碼修改架構ASM

(2)位元組碼指令由操作碼和參數構成,可以分為兩類:一類是将值在本地變量和操作數棧來回複制移動的,如iload\lload\fload\dload\aload\istore\lstore\fstore等。另一類是操作操作數棧上value的,包括一大批的指令,操作棧stack的、定義常量的、算術計算的、類型轉換的、操作類、操作字段、操作方法、操作數組、跳轉、傳回。

二、方法接口api群組件

(1)methodvisitor可以被classvisitor的visitmethod方法傳回,用于方法的生成和轉化。methodvisitor的内部方法嚴格按照如下順序調用:

Java位元組碼修改架構ASM

多個methodvisitor時,每個都是一個獨立的轉換方法執行個體,是以多個methodvisitor之間可以交替執行。類似類api,methodvisitor使用到classreader和classwriter。classwriter參數不同意義不同:

// asm不進行任何自動計算,需要我們自己計算棧幀、本地變量、操作數棧的大小

new classwriter(0);

(2)一個生産getf()方法的指令如下:

mv.visitcode();

mv.visitvarinsn(aload, 0);

mv.visitfieldinsn(getfield, "pkg/bean", "f", "i");

mv.visitinsn(ireturn);

mv.visitmaxs(1, 1);

mv.visitend();

(3)methodvisitor和classvisitor通常配合使用來轉換方法,可以用于删除方法指令、執行狀态無關的轉換、執行狀态相關的轉換(一般需要定義狀态屬性到methodvisitor内記憶)及其他更複雜的轉換。

三、方法開發工具api

(1)基本的type、traceclassvisitor、checkclassadapter、asmifier同樣适用。

(2)analyzeradapter記錄了visitframe調用後棧幀映射的資訊,必須要和classreader#expand_frames配合使用。每個visitx指令都被代理到調用鍊,并通過本地變量locals和棧stack模拟出隊棧幀映射的影響,這樣後面的visitor可以得到棧幀映射的目前狀态。

(3)localvariablessorter可以對方法内的變量自動編号,對于需要修改位元組碼添加變量的場景非常适用。還可以喝analyzeradapter配合構造成過濾鍊配合使用。

(4)需要在方法開始和結束為止添加指令的場景适合使用adviceadapter,而且使用與構造方法。