前段時間做動態無侵入攔截的工作,對於“即時載入”新類有了一些較深入的理解,已經寫出兩篇文章在這裡。
我們已經解決了“如何修改”的問題,但是另一個問題是“能修改成什麼樣子”。
利用Instrumentation來動態redefine的類,只能修改方法,即在原有的方法代碼插入代碼來實現我們需要的邏輯。
卻無法增加,刪除方法和欄位,即修改類定義的結構。
目前真正能做到“即時”載入結構已經被修改的新的class的工具就是javarebel.但這個工具是一個收費的(免費的功能有限)
工具,其它還沒見到有相同功能的工具,其實這種需求是很強烈的,特別是在開發階段,新修改一個類的實現,特別是類的結構。
希望能在不重啟的情況下能即時看到效果。而實際上只javarebel能做到,說明這個技術還是有些小難度的。
經過非人的折磨(看混淆過的反編解碼還不如直接看位元組碼更明白),基本弄懂了某個工具的實現原理(傳說為了研究目的進行
某些工作可以原諒的,也正是因為這個原因可能會引起不必要的麻煩,所以不寫具體的代碼,只做原理性的記錄在這裡)。
當然,對於從class讀懂了它的位元組碼,crack是相當簡單滴了。
首先,把JDK中幾個基本類的源碼拿過來修改。
ClassLoader,Class,Enum,Constructor,Field,Method,Proxy.這幾個類,其實還有其它幾個ObjectStreamClass,URLClassLoader,PropertyResourceBundle,ResourceBundle$Control這幾個類都需要修改。但是
從原理上講對於最小模型我們先動ClassLoader,Class,Enum,Constructor,Field,Method,Proxy.
現在假設有一個class:
class A{
int x = 0;
}
要載入這個類,一定不能被JRE的預設ClassLoader載入,因為類一旦在JVM中露頭,你在redefin時只能修改它的方法體而不能
修改它的結構了。所以我們要修改ClassLoader,當載入類A的時候,我們利用位元組碼產生器產生一個A$$ver1.class,其中A的
所有方法都在A$$ver1中實現,並反回這個A$$ver1的Class引用。
這裡我們就需要修改Class,Constructor,Field,Method這幾個基礎類了。比如Class的getName(),當A$$ver1.class.getName()時
在return name前需要
int index = name.indexOf("$$ver");
if(index != -1) name = name.subString(0,index);
而Class.forName(String name)時我們則需要
修改成name = name + "$$verx";//這個x可以用一個全域的map儲存原始類和目前的版本的對應表。
還有Constructor,Method,Field這些反射方法,都要修改,即load A時,我實際是動態產生了A$$ver1.class,所以這些類中對應的方法中
要修改成實際上找A$$ver1來對應A的邏輯。
這時如果我們修改了A:
class A{
int x = 0;
int y = 0;
}
當掃描發現類結構被修改後,被crack過的ClassLoader又動態產生一個A$$ver2.class,當然修改後的方法和欄位都產生在A$$ver2.class中,這裡映射表中把A的對應類換成A$$ver2,所以Class類,Constructor,Field,Method,Proxy這些基礎類訪問A.class時都知道直接去訪問A$$ver2.class,原來的A$$ver1.class就放棄了。
這樣實際上每次修改A時底層實際上是產生了不同版本的它的替身,然後通過被crack過的基礎類ClassLoader,Class,Enum,Constructor,Field,Method,Proxy就能達到動態“載入”目的。
前提是這幾個被crack過的基礎類一定要在Boot-Classpath中,先於JRE的原始的基礎類工作。