Java動態,安全追蹤工具
Java動態,安全追蹤工具
在我們日常的開發中,總是難以避免的要解決線上的問題.如果線上的問題我們在本地除錯的時候無論除錯多少次發現明明本地呼叫了這個方法呀,怎麼線上就是沒調呢?還有就是出了問題的時候由於沒有打日誌,所以不得不去價格logger,然後換個包,然後再重啟,然後再呼叫,如果在使用者很多的時候這麼搞,無疑面臨著巨大的風險,還不得不去處理使用者的大量的投訴,在領導面前也只能默默的低著頭承受著批評
那麼有沒有一種方法可以不用重啟應用,又可以在線上追蹤程式碼呢? 答案當然是有的,就是使用Btrace這個工具了
BTrace是sun公司推出的一款Java 動態、安全追蹤(監控)工具,可以在不用重啟的情況下監控系統執行情況,方便的獲取程式執行時的資料資訊,如方法引數、返回值、全域性變數和堆疊資訊等,並且做到最少的侵入,佔用最少的系統資源。
應用場景:
- 服務慢,但是不知道慢在哪一步,哪個函式慢
- 誰呼叫了System.gc(),呼叫棧如何?
- 誰構造了一個超大的ArrayList?
- 執行某個方法丟擲異常時,分析執行時引數
......
快速開始
btrace的官方的網址是http://github.com/btraceio/btrace , 可以從裡面下載最新的版本, 目前版本是1.3.11.2
下載好之後doc裡面有很多例子, 我們這裡來跑一個UserGuide的例子
import com.sun.btrace.annotations.BTrace; import com.sun.btrace.annotations.OnMethod; import static com.sun.deploy.trace.Trace.println; /** * @author luozhiyun on 2019-01-06. */ @BTrace public class HelloWorld { @OnMethod(clazz="java.lang.Thread", method="start") public static void onThreadStart() { println("thread start!"); } }
然後在要監控的機器上打jps, 找到應用的pid, 然後 btrace $pid HelloWorld.java就可以跑起來了
如果還想要監控其他內容,直接修改HelloWorld.java的內容,然後再執行一次btrace就可以了,完全不需要重啟應用!!!
攔截
1.直接指出類和方法 ,如HelloWorld的例子
2.正則表示式攔截
如下, 攔截所有的有關InputStream類的readXXX方法,正則表示式要寫在兩個/中間
package com.sun.btrace.samples; import com.sun.btrace.annotations.*; import static com.sun.btrace.BTraceUtils.*; /** * This BTrace class demonstrates that we can * probe into multiple classes and methods by a * single probe specification using regular * expressions for class and/or method names as * given below. In the example, we put probe into * all readXXX methods of all InputStream classes. */ @BTrace public class MultiClass { @OnMethod( clazz="/java\\.io\\..*Input.*/", method="/read.*/" ) public static void onread(@ProbeClassName String pcn) { println("read on " + pcn); } }
3.攔截所有有這個註解的類或方法
如: @OnMethod( clazz="@javax.jws.WebService", method="@javax.jws.WebMethod" )
4.攔截介面的實現類
如:@OnMethod( clazz="+java.lang.Runnable", method="run" )
5.攔截構造器
@OnMethod(clazz="java.net.ServerSocket", method="<init>")
6.攔截靜態內部類
@OnMethod(clazz="com.vip.MyServer$MyInnerClass", method="hello")
只要在類和內部類前面加上$
攔截時機
Kind.ENTRY
在@onMethod這個註解裡面可以加上一個location引數,表示攔截的這個方法的攔截時機,如果不加,預設就是Kind.ENTRY
Kind.RETURN
如果要獲得返回結果或執行時間, 那麼就要加上Kind.RETURN.
OnMethod(clazz = "java.net.ServerSocket", method = "getLocalPort", location = @Location(Kind.RETURN))
public static void onGetPort(@Return int port, @Duration long duration)
duration的單位是納秒,要除以 1,000,000 才是毫秒。
Kind.Error, Kind.Throw和 Kind.Catch
這幾個主要用於異常情況的跟蹤
在攔截函式的引數定義裡注入一個Throwable的引數,代表異常。
@OnMethod(clazz = "java.net.ServerSocket", method = "bind", location = @Location(Kind.ERROR))
public static void onBind(Throwable exception, @Duration long duration)
Kind.Line
下例監控程式碼是否到達了Socket類的第363行
@OnMethod(clazz = "java.net.ServerSocket", location = @Location(value = Kind.LINE, line = 363))
public static void onBind4() {
println("socket bind reach line:363");
}
Kind.Call
下例就是打印出run()方法裡面呼叫的所有其他方法
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.println;
/**
* @author luozhiyun on 2019-01-06.
*/
@BTrace
public class HelloWorld {
@OnMethod(clazz="BtraceCase", method="run",
location [email protected](value = Kind.CALL, clazz = "/.*/", method = "/.*/", where = Where.AFTER))
public static void onThreadStart(@Self Object self, @TargetInstance Object instance,
@TargetMethodOrField String method, @Duration long duration) {
println("self: " + self);
println("instance: " + instance);
println("method: " + method);
println("duration: " + duration);
}
}
BtraceCase
import java.util.Random;
/**
* @author luozhiyun on 2019-01-06.
*/
public class BtraceCase {
public static Random random = new Random();
public int size;
public static void main(String[] args) throws Exception {
Thread.sleep(1000*15);
new BtraceCase().run();
}
public void run() throws Exception {
while (true) {
add(random.nextInt(10), random.nextInt(10));
}
}
public int add(int a, int b) throws Exception {
Thread.sleep(random.nextInt(10) * 100);
return a + b;
}
}
列印結果:
self: [email protected]
instance: [email protected]
method: add
duration: 402211413
self: [email protected]
instance: [email protected]
method: nextInt
duration: 2602
所呼叫的類及方法名所注入到@TargetInstance與 @TargetMethodOrField中.如果想獲得執行時間,必須把Where定義成AFTER
列印this,引數與返回值
import com.sun.btrace.AnyType;
@OnMethod(clazz = "java.io.File", method = "createTempFile", location = @Location(value = Kind.RETURN))
public static void o(@Self Object self, String prefix, String suffix, @Return AnyType result)
Self:
如果是靜態函式,self為空 , 用@Self可以表示當前方法,也就是this
引數:
引數列表要麼不要定義, 要定義就要定義完整,否則BTrace無法處理不同引數的同名函式
結果:
結果的型別用AnyType來定義,特別是用正則表示式匹配多個函式的時候,連void都可以表示
一點經驗
用於匹配方法入參或返回型別時,因嫌麻煩不想引入外部依賴(一般也沒有必要),外部型別請用AnyType代替,而不是Object!因為你可能用Object來準確匹配方法返回引數或返回型別。
啟動跟蹤指令碼時,請使用和啟動Java程序相同的Linux賬號,不然會因為許可權問題而attach失敗。
另一個和BTrace類似的Java診斷工具greys-anatomy,由阿里釋出,感興趣的也可以學習一下。
若報錯"Port 2020 unavailable.",則使用
btrace -p 2021 ...
來指定其它埠。Linux下已經有個命令也叫btrace,注意別用混了。
由於Btrace會把指令碼邏輯直接侵入到執行的程式碼中,所以在使用上做很多限制:
1、不能建立物件
2、不能使用陣列
3、不能丟擲或捕獲異常
4、不能使用迴圈
5、不能使用synchronized關鍵字
6、屬性和方法必須使用static修飾根據官方宣告,不恰當的使用BTrace可能導致JVM崩潰,如在BTrace指令碼使用錯誤的class檔案,所以在上生產環境之前,務必在本地充分的驗證指令碼的正確性。