BTrace 原理淺析

以前在看agentzh的此篇博文動態追蹤技術漫談時,領會到了動態追蹤技術的強大之處,也一直因爲沒法在不重啓線上服務器的狀況下排查線上問題在尋找Java中的動態追蹤工具。在公司內部的JavaEE性能檢測框架中,咱們使用了asm作字節碼注入來作線上性能的監測,沿着這個思路,若是要作到動態追蹤應該是須要作字節碼注入的,可是額外的一點是須要動態加載字節碼替換掉原有的類的。此外,性能監測框架是須要耦合到業務應用中的,沒法作到一個監測工具的靈活性。html

後來聽同事提到了BTrace這個工具,因而去嘗試了一下。BTrace是SUN Kenai雲計算開發平臺下的一個開源項目,旨在爲java提供安全可靠的動態跟蹤分析工具。江南白衣的這篇文章calvin1978.blogcn.com/articles/bt…作了比較詳細的描述。java

那麼,BTrace這麼神奇的功能是如何實現的呢?既然這是個開源的代碼,那麼直接從代碼找原理。BTrace代碼開源在github.com/btraceio/bt…git

整體來講,BTrace是基於動態字節碼修改技術(Hotswap)來實現運行時java程序的跟蹤和替換。大致的原理能夠用下面的公式描述:github

Client(Java compile api + attach api) + Agent(腳本解析引擎 + ASM + JDK6 Instumentation) + Socket複製代碼

BTrace的入口類在github.com/btraceio/bt…中。在其main方法中,能夠看到起最終的核心邏輯是在github.com/btraceio/bt…中。方法調用以下:api

  • client.compile
  • client.attach
  • client.submit

Client

首先是client.compile方法,使用的是Java compile api,將咱們傳遞的java源文件編譯爲.class文件,固然你若是使用btracec提早編譯了源代碼,那麼這裏就不會有這一步。安全

針對官方腳本的一個例子:服務器

import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class HelloWorld {
    @OnMethod(
        clazz="java.lang.Thread",
        method="start"
    )
    public static void func() {
        println("about to start a thread!");
    }
}複製代碼

@OnMethod告訴Btrace解析引擎須要代理的類和方法。
這個例子的做用是當java.lang.Thread類的任意一個對象調用 start 方法後,會調用func方法。app

client端在編譯完腳本以後,進行了一次字節碼修改,可是僅僅是作了一些兼容性,例如域訪問控制器、簡寫等。框架

接着client.attach中使用java的attach api將agent動態attach到目標jvm進程中(ava agent,一般有兩種方式添加到jvm進程中:動態attach;在目標jvm啓動以前添加agent參數)。異步

VirtualMachine vm  = VirtualMachine.attach(pid);
...
vm.loadAgent(agentPath, agentArgs);複製代碼

最後client的submit方法,會向agent發送監控命令以及傳遞對應code的字節碼。

Agent

BTrace的agent實現類就在github.com/btraceio/bt…中,具體的實現能夠看其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.

...
inst.appendToBootstrapClassLoaderSearch(jf);
...
inst.appendToSystemClassLoaderSearch(jf);複製代碼

接着開啓一個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見github.com/btraceio/bt…。以下:

new ClassFileTransformer() {
    public byte[] transform(ClassLoader l, String className, Class c, ProtectionDomain pd, byte[] b) throws IllegalClassFormatException {
        // BTrace解析腳本,利用asm重寫bytecode,而後classLoader加載
    }
}, true);複製代碼

其中,在agent的agentmain中經過handleNewClient方法啓動一個異步線程進行class transformer,而在這個異步線程中最終是經過調用github.com/btraceio/bt…中的retransformLoaded()來進行的。

總結

其實BTrace就是使用了java attach api附加agent.jar,而後使用腳本解析引擎+asm來重寫指定類的字節碼,再使用instrument實現對原有類的替換。借鑑這些,咱們也徹底能夠實現本身的動態追蹤工具。

閱讀原文

相關文章
相關標籤/搜索