之前在看agentzh的此篇博文動态追蹤技術漫談時,領會到了動态追蹤技術的強大之處,也一直由于無法在不重新開機線上伺服器的情況下排查線上問題在尋找Java中的動态追蹤工具。在公司内部的JavaEE性能檢測架構中,我們使用了asm做位元組碼注入來做線上性能的監測,沿着這個思路,如果要做到動态追蹤應該是需要做位元組碼注入的,但是額外的一點是需要動态加載位元組碼替換掉原有的類的。此外,性能監測架構是需要耦合到業務應用中的,無法做到一個監測工具的靈活性。
後來聽同僚提到了BTrace這個工具,于是去嘗試了一下。BTrace是SUN Kenai雲計算開發平台下的一個開源項目,旨在為java提供安全可靠的動态跟蹤分析工具。江南白衣的這篇文章http://calvin1978.blogcn.com/articles/btrace1.html做了比較詳細的描述。
那麼,BTrace這麼神奇的功能是如何實作的呢?既然這是個開源的代碼,那麼直接從代碼找原理。BTrace代碼開源在https://github.com/btraceio/btrace。
總體來說,BTrace是基于動态位元組碼修改技術(Hotswap)來實作運作時java程式的跟蹤和替換。大體的原理可以用下面的公式描述:
BTrace的入口類在https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/client/Main.java中。在其main方法中,可以看到起最終的核心邏輯是在https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/client/Client.java中。方法調用如下:
client.compile
client.attach
client.submit
首先是client.compile方法,使用的是Java compile api,将我們傳遞的java源檔案編譯為.class檔案,當然你如果使用btracec提前編譯了源代碼,那麼這裡就不會有這一步。
針對官方腳本的一個例子:
@OnMethod告訴Btrace解析引擎需要代理的類和方法。 這個例子的作用是當java.lang.Thread類的任意一個對象調用 start 方法後,會調用func方法。
client端在編譯完腳本之後,進行了一次位元組碼修改,但是僅僅是做了一些相容性,例如域通路控制器、簡寫等。
接着client.attach中使用java的attach api将agent動态attach到目标jvm程序中(ava agent,通常有兩種方式添加到jvm程序中:動态attach;在目标jvm啟動之前添加agent參數)。
最後client的submit方法,會向agent發送監控指令以及傳遞對應code的位元組碼。
BTrace的agent實作類就在https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/agent/Main.java中,具體的實作可以看其main方法,此agent的premain和agentmain方法都是調用了這個方法。這裡需要注意的一點:必須要上jdk6,因為jdk5雖然已經有了instrument api,但是其僅僅支援premain方法,也就是僅僅支援在main方法運作之前執行一些動作,而jdk6後加入了agentmain方法和VirtualMachine,是可以在main方法運作後執行的(如果是通過指令行啟動的,那麼agentmain方法不會被調用)。此外,在jdk6之前,程式啟動之後是無法再設定boot class加載路徑和system class加載路徑的。而jdk6之後,instrument新增的appendToBootstrapClassLoaderSearch和appendToSystemClassLoaderSearch是可以動态添加classpath的。
agent被送出到目标jvm程序後,首先會添加boot classpath.
接着開啟一個serversocket等待client的連接配接。之後client和agent之間的資料通訊,比如生成.class發送到agent,agent将線上程式列印的資料回傳給 client都是通過socket來進行的。當agent接收到監控指令後,主要有以下兩部分工作:
重寫類:周遊目前所有的class,根據正則找到比對的類,用asm重寫
替換類:替換掉原來的class
agent接受到client發來的監控指令以及對應的參數後,會load所有的class,根據正則去比對指定的類和方法,并使用腳本解析引擎去處理發送過來的位元組碼然後使用ASM将腳本裡标注的類java.lang.Thread的位元組碼重寫,植入跟蹤代碼或新的邏輯。在上面那個例子中,Java.lang.Thread這個類的位元組碼被重寫并在start方法體尾部植入了func方法的調用。
BTrace的agent利用instrumentation的retransformClasses方法将原始位元組碼替換掉,使用的transfomer見https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/runtime/BTraceTransformer.java。如下:
其中,在agent的agentmain中通過handleNewClient方法啟動一個異步線程進行class transformer,而在這個異步線程中最終是通過調用https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/agent/Client.java中的retransformLoaded()來進行的。
其實BTrace就是使用了java attach api附加agent.jar,然後使用腳本解析引擎+asm來重寫指定類的位元組碼,再使用instrument實作對原有類的替換。借鑒這些,我們也完全可以實作自己的動态追蹤工具。
原文出處:後端技術雜談
<a href="http://www.rowkey.me/blog/2016/09/20/btrace/" target="_blank">原文連結</a>
轉載請與作者聯系,同時請務必标明文章原始出處和原文連結及本聲明。