1. 程式人生 > >開發工具系列(一):Btrace——線上Debug工具

開發工具系列(一):Btrace——線上Debug工具

Btrace

Btrace用於除錯正在執行的系統,並且在除錯時不會暫停系統。特別適用於跟蹤線上問題。你可以實時監控一個系統中任何一個方法的呼叫,你可以知道這些方法的引數、返回值是什麼,還可以知道方法呼叫消耗了多少時間。

Btrace不需要安裝,只要下載一個包,解壓即可。

Btrace用法為bin/btrace <pid> <trace-script>。其中pid是正在執行的java程序,trace-script是跟蹤指令碼,它其實就是一段java程式碼。

Hello World

首先我們模擬一個正在執行的程式,它僅有一個迴圈。

packagecom.caipeichao
;
publicclassNullApp{publicstaticvoidmain(String[] argv){newNullApp().run();}publicvoidrun(){for(int i =0; i <100000; i++){sleep(1000);newMyObj().life(i);}}privatestaticclassMyObj{publicvoidlife(intn){System.out.println(n);}}privatevoidsleep(intn){try{Thread.sleep(n);}catch(InterruptedException e){}}}

然後開啟這個程式: java com.caipeichao.NullApp

通過jps命令得到這個程式的PID,這裡為13348。

> jps3034 RemoteMavenServer2902 Main15147 Jps13348 NullApp

準備工作做完了,現在編寫最重要的跟蹤指令碼。

import staticcom.sun.btrace.BTraceUtils.*;importcom.sun.btrace.annotations.*;@BTracepublicclassHelloBtrace{// 當com.caipeichao.NullApp.sleep方法返回時,執行該方法@OnMethod(clazz="com.caipeichao.NullApp
"
,
method="sleep",location=@Location(Kind.RETURN))publicstaticvoidonSleep(){println("Hello world");}}

執行btrace,得到如下輸出。

> btrace 13348 HelloBtrace.javaHello worldHello worldHello worldHello worldHello worldHello world

常用註解

名稱 作用域 作用
@BTrace 宣告跟蹤指令碼
@OnMethod(clazz,method,location) 方法 當指定方法被呼叫時
@OnMethod(method="<init>") 方法 當建構函式被呼叫時
@OnMethod(clazz="/java\\.io\\..*Input/")) 方法 方法名稱正則匹配
@Location(kind) @OnMethod 指定監控方法呼叫前還是呼叫後
@Location(value=Kind.NEWARRAY, clazz="char") @OnMethod 監控新增陣列
@Self 引數 表示被監控的物件
@ProbeMethodName 引數 被監控的方法名稱
@ProbeClassName 引數 被監控的類名
@OnTimer(interval) 方法 定時呼叫某個方法
@OnLowMemory(pool,threshold) 方法 當記憶體不足時
@OnExit 方法 當程式退出時
@OnProbe(namespace="java.net.socket",name="bind") 方法 監控socket中的bind方法

常用方法

方法 作用
println 在本地控制檯輸出一行
print 在本地控制檯輸出
printArray 在本地控制檯輸出陣列
jstack 列印遠端方法的呼叫呼叫棧
jstackAll 輸出所有執行緒的呼叫棧
exit 退出跟蹤指令碼
Strings.strcat 連線字串
Reflactive.name 獲取類名
Threads.name 執行緒名
Threads.currentThread 當前執行緒
deadlocks 打出死鎖執行緒
sizeof 獲取物件的大小,比如List物件就返回List.size()
Sys.Env.property 獲取系統變數

原理

BTrace利用了java.lang.instrument包實現程式碼注入。首先通過VirtualMachine.attach(pid)連線遠端JVM,然後通過VirtualMachine.loadAgent("*.jar")載入一個btrace的jar包。這個jar包最重要的程式碼如下。

publicstaticvoidpremain(String args,Instrumentation inst){main(args, inst);}publicstaticvoidagentmain(String args,Instrumentation inst){main(args, inst);}// 將btrace的jar包新增到ClassLoader搜尋目錄privatestaticsynchronizedvoidmain(finalString args,finalInstrumentation inst){  ...  inst.appendToBootstrapClassLoaderSearch(newJarFile(newFile(path)));  ...  inst.appendToSystemClassLoaderSearch(newJarFile(newFile(path)));  ...startServer();}// 開啟服務privatestaticvoidstartServer(){  ...while(true){try{      ...handleNewClient(client);}catch(RuntimeException re){if(isDebug())debugPrint(re);}catch(IOException ioexp){if(isDebug())debugPrint(ioexp);}}}// 修改記憶體中的類定義privatestaticvoidhandleNewClient(finalClient client){  ...  inst.addTransformer(client,true);  ...  inst.retransformClasses(classes);}// 用ASM動態生成位元組碼abstractclassClientimplementsClassFileTransformerCommandListener{static{ClassFilter.class.getClass();ClassReader.class.getClass();ClassWriter.class.getClass();    ...}privatebyte[] instrument(ClassclazzStringcnamebyte[] target){byte[] instrumentedCode;try{ClassWriter writer =InstrumentUtils.newClassWriter(target);ClassReader reader =newClassReader(target);Instrumentor i =newInstrumentor(clazz, className,  btraceCode, onMethods, writer);    ...}}

一句話總結,btrace利用instrument工具修改JVM記憶體中的類位元組碼,達到注入程式碼的目的。