btrace一些你不知道的事(源碼入手)

背景 

   週五下班回家,在公司班車上以爲無聊,看了下btrace的源碼(本身反編譯)。 一些關於btrace的基本內容,能夠看下我早起的一篇記錄:btrace記憶 html

 

   上一篇主要介紹的是btrace的一些基本使用以及api,這裏我想從btrace源碼自己進行下介紹。至於btrace的優點,能用來幹些什麼,本身上他的官網看下或者google一下,花個半小時就能明白了。 java

 

   至於爲何會去反編譯查看btrace源碼,主要是會在部門整個關於btrace的分享。同時btrace的相關技術文檔缺少,javadoc不少時候說的不明不白,做者也沒有提供源碼開源,因此就有了此次的分享。 web

Btrace涉及相關技術

  1. asm 
  2. instrument   http://download.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html
  3. JVM TI(java tool api)  http://download.oracle.com/javase/6/docs/jdk/api/attach/spec/com/sun/tools/attach/VirtualMachine.html
  4. Java Compiler Api   http://download.oracle.com/javase/6/docs/api/javax/tools/package-summary.html

你們能夠先去預備一下知識。 api

 

Btrace的大致設計

下面來看一個Btrace的設計圖: 數組


 

 

說明:  服務器

1. BtraceClient : 爲咱們使用的btrace的本地api,通常咱們使用的bin/btrace會在本地啓動一個btrace jvm,其內部使用了Java Complier Api, JVMTI技術,以及建立了一個socket。 oracle

 

  • Java Complier Api:動態的將咱們傳遞的監控的java源文件動態編譯成.class文件
  • JVMTI: 主要是利用了java 1.6以後的VirtaulMachine技術,動態的attach到一個已啓動的jvm上,爲他去啓動一個BtraceAgent。該Agent會爲BtraceClient啓動一個server socket進行通信。(多進程之間的通信)
  • 本地socket: BtraceClient和BtraceAgent之間的數據通信,好比生成的.class發送到BtraceAgent,還有一些Event事件等等。BtraceAgent一樣能夠將服務端print()的數據經過socket的方式回傳給BtraceClient進行打印
2. BtraceAgent:爲咱們在目標jvm上植入的btrace agent實現。主要是Instrumentation技術, asm字節碼處理技術。
  • BtraceAgent的啓動能夠有兩種方式: BtaceClient動態attach後進行啓動, 另外一種就是在目標jvm啓動以前添加agent參數進行啓動。
  • BtraceAgent會啓動一個server socket,與BtraceClient客戶端進行交互,客戶端能夠將監控的.class文件經過socket發送,一樣也能夠在jvm啓動時直接指定對應本地的.class作爲監控腳本。
  • BtraceAgent在接受到監控指令後,會遍歷當前全部已經加載的class類,挨個進行匹配檢查,並生成相應的監控字節碼(監控方法)。

 

 

btrace的包結構: app

  • btrace-client.jar
  • btrace-boot.jar
  • btrace-agent.jar

btrace-client 異步

   通常咱們一般直接使用的命令,好比: jvm

 

Java代碼    收藏代碼
  1. bin/btrace $pid Btrace.java  

 

都是直接調用了btrace-client包中的代碼。

 

幾個核心類介紹:

1. com.sun.btrace.client.Main (btrace的啓動入口)

  •     調用Client進行complier(Java Compiler Api)和attach(JVM TI)處理

2. com.sun.btrace.client.Client

  • compiler方法 : 調用了Java Complier Api進行動態編譯你的Btrace.java文件
    Java代碼    收藏代碼
    1. this.compiler = ToolProvider.getSystemJavaCompiler();    
    2. this.stdManager = this.compiler.getStandardFileManager(nullnullnull);    
    3.     
    4.     
    5. Verifier btraceVerifier = new Verifier(this.unsafe); //指定了btrace特定的語法校驗器   
     
  • attach方法: 調用VirtualMachine.attach(pid);vm.loadAgent(agentPath, agentArgs);動態加載btrace-agent.jar包
    Java代碼    收藏代碼
    1. 傳遞給agent程序的幾個參數:     
    2. debug=true    
    3. unsafe=true    
    4. dumpClass=true    
    5. dumpDir=xx    
    6. trackRetransforms=true  ##是否記錄instrument行爲    
    7. bootClassPath= xx ##agent.jar使用    
    8. systemClassPath =xx ##agent.jar使用    
    9. probeDescPath=xx    
     
  • submit方法: 調用提交對應的instrument指令,並傳遞對應code的byte[]
    Java代碼    收藏代碼
    1. this.sock = new Socket("localhost"this.port);    
    2. this.oos = new ObjectOutputStream(this.sock.getOutputStream());    
    3. ...    
    4.     
    5. WireIO.write(this.oos, new InstrumentCommand(code, args));    
     

幾點說明:

*   在調用了attach方法後,會經過btrace-agent.jar中的com.sun.btrace.agent.Main啓動一個ServerSocket

Java代碼    收藏代碼
  1. int port = 2020;    
  2. String p = (String)argMap.get("port");    
  3. ....    
  4. ServerSocket ss;    
  5.   try {    
  6.      (isDebug()) debugPrint(new StringBuilder().append("starting server at ").append(port).toString());    
  7.      System.setProperty("btrace.port", String.valueOf(port));    
  8.      if ((scriptOutputFile != null) && (scriptOutputFile.length() > 0)) {    
  9.           System.setProperty("btrace.output", scriptOutputFile);    
  10.      }    
  11.      ss = new ServerSocket(port);    
  12.    } catch(Exception e) ....    
  13.     
  14.     
  15. while (true)    
  16. {    
  17.    if (!isDebug()) continue; debugPrint("waiting for clients");    
  18.    Socket sock = ss.accept();    
  19.    if (!isDebug()) continue; debugPrint(new StringBuilder().append("client accepted ").append(sock).toString());    
  20.    Client client = new RemoteClient(inst, sock);    
  21.    handleNewClient(client);    
  22.    continue;    
  23. }    
 

 

*  因此在submit中,會經過一個本地socket進行鏈接server,並提交相應的Btrace.java中的監控代碼(這時應該是編譯後的字節碼).

 

3. com.sun.btrace.compiler.Verifier  btrace自定義的語法校驗器

 

  • Java代碼    收藏代碼
    1. Boolean value = this.unsafe ? Boolean.TRUE : (Boolean)ct.accept(new VerifierVisitor(this), null);  // 注意下unsafe的判斷    
     

 

4.  com.sun.btrace.compiler.VerifierVisitor (具體的一些檢查規則)

  •  visitBinary  String字符串的+限制
  •  visitClass  class的檢查,不容許有父類,不容許有接口類,不容許非static變量,必須有Btrace @標籤
  •  visitDoWhileLoop 不容許do while循環
  •  visitForLoop 不容許for循環
  •  visitMethod 必須爲static public ,不容許出現synchronized標記
  •  visitNewArray 不容許出現new Array
  •  visitNewClass 不容許出現new 對象
  •  visitReturn 不容許有返回值
  •  visitSynchronized 不容許有同步快
  •  visitThrow visitTry 不容許有try catch的動做
  •  visitOther 除上面容許以外的,不容許有其餘的

 說明:

*  看完Verifier和VerifierVisitor後,相信你們都應該明白了Btrace所謂的諸多限制,只是針對.java須要動態編譯。若是咱們預先生成.class文件,Btrace在1.2版本中並不會做類型合法性檢查。(在將code發送給btrace-agent後,會在目標的jvm內部進行一次簡單的Btrace語法檢查,具體見後面Btrace-agent介紹)

 

5. com.sun.btrace.comm.XXX  Btrace的各類command指令

 

btrace-agent

大體瞭解了Client類中的attach和submit方法後,相信也能猜到對應agent的一些設計。簡單的看一下

 

1. com.sun.btrace.agent.Main 爲attach上以後agent的總入口,會調用agentmain()方法

  • main方法: 首先解析參數,而後會啓動一個agentThread(Daemon線程)
  • parseArgs : 對應的參數解析,客戶端在attach時,提交給agent後的一些參數
    Java代碼    收藏代碼
    1. bootClassPath=xx.jar  //須要動態增長的jar    
    2. systemClassPath=xx.jar    
    3. noServer=true/false //是否啓動server socket    
    4. debug=true/false    
    5. unsafe=true/false    
    6. dumpClasses=true/false    
    7. dumpDir=路徑    
    8. trackRetransforms=true/false    
    9. probeDescPath=路徑    
    10. stdout=true/false     
    11. script=文件    
    12. scriptdir=路徑    
    13. scriptOutputFile=文件路徑特別注意下相比於Btrace-client提交的參數中,多了幾個script,scriptdir等參數,容許在Client調用服務端一個指定的Btrace script文件進行處理     
      
  • loadBTraceScript : 裝載指定的script(注意是script和scriptDir中指定的script),必須是.class文件,會調用FileClient進行處理,最後調用handleNewClient進行統一處理,最後調用handleNewClient進行統一instrument處理。
  • startServer : main啓動的agentThread會調用該方法,這裏會啓動一個serversocket,和btrace-client的客戶端socket進行通信,使用RemoteClient,最後調用handleNewClient進行統一instrument處理。
  • handleNewClient : 啓動一個異步線程進行class Transformer,根據提交的byte[] code進行類文件重寫

說明:

* 目前instrument進行字節碼重寫時,會從新load全部的class進行處理。(Btrace可使用正則,父類的方式進行匹配,只能是挨個Class進行處理,看下是否有匹配的OnMethod)

* 相比於btrace-client提交過來的參數中,btrace-agent支持的參數中多了幾個script,scriptdir等,容許在Client調用服務端一個指定的Btrace script文件進行處理,注意這裏的script必須是編譯後的.class文件。和經過socket提交的btrace在處理上沒有太大的差別。

 

2. com.sun.btrace.agent.RemoteClient / FileClient : (RemoteClient爲經過socket提交的script , FileClient爲script和scriptDir指定的script文件)

  • 二者的不一樣無非就是對應的結果輸出方式不一樣,一個是傳回給客戶端,另外一個是直接終端輸出
  • 會調用Client.loadClass()進行btrace數據解析,主要是解析對應的OnMethod和OnProbe數據。

3. com.sun.btrace.agent.ProbeDescriptorLoader

  • 會解析對應Btrace script中出現的@OnProbe,解析xml文件中對應的@OnMethod信息

4. com.sun.btrace.agent.Client:  (RemoteClient和FileClient的共同父類)

  • instument中的Transformer的實現類
  • loadClass()方法: btrace script腳本解析
  • verify 首先進行class的校驗, 調用Verity類進行檢查(可手工執行:java com.sun.btrace.runtime.Verifier <.class file>)
  • runtime.defineClass(codeBuf); 使用反射從新裝載class byte
  • transform: 核心的方法(isBTraceClass(cname)) || (isSensitiveClass(cname)) 過濾btrace內部類,已經一些Object,ThreadLocal,sun/reflect類)
  • instrument方法 : 方法中調用asm的ClassReader進行class對象解析,並設置Instrumentor進行Class對象處理

5.  com.sun.btrace.runtime.Instrumentor : 是Btrace實現代碼監控加強處理的核心邏輯

能夠直接調用:

Java代碼    收藏代碼
  1. java com.sun.btrace.runtime.Instrumentor <btrace-class> <target-class>]    
 

 

Btrace的幾點總結

1. btrace支持的監控方式

  • 本地jvm監控:目前大多數都是用的是btrace和監控的目標java是在同一機器上
  • 遠程jvm監控:須要在遠程服務器啓動時添加btrace-agent.jar,須要重寫btrace客戶端,完成和serversocket創建通信,完成btrace script發送監控。
    1. VirtualMachine動態attach不支持遠程操做,因此沒法動態的進行agent添加。
2. btrace支持的jdk版本
  • java 1.4以及以前 : 不支持,Instrument在jdk 1.5以後纔出現。
  • java 1.5 : 必須手動在jvm啓動時添加btrace-agent.jar,由於VirtualMachine是在jdk 1.6以後纔出現。
  • java 1.6 : 推薦使用
agent啓動: 
Java代碼    收藏代碼
  1. java -Xshare:off -javaagent:${BTRACE_HOME}/build/btrace-agent.jar=dumpClasses=false,debug=false,unsafe=false,probeDescPath=.,noServer=true,script=$1  
  2.   
  3. 具體的參數見上面的代碼分析  
 
 

3. btrace的支持的script方式有多種。

  • client上的.java文件
    會進行動態編譯,會有比較多的語法限制,btrace一堆的你不能作的事
  • client上的.class文件
    沒什麼好講的,本身寫Btrace script時導入btrace-client.jar,寫好後生成一個.class文件,再經過btrace pid Btrace.class進行啓動。 
  • remote上的.class文件
    1. 修改btrace-client中的Client類,支持script和scriptDir的一些參數提交。
    2. 在remote機器上放置對應的btrace.class文件

4. btrace的使用是否會對java進程形成影響?(影響是確定的,不過影響不大)

 

裝載時的影響:

  • btrace每次使用,都會從新load全部的class。固然若是OnMethod不匹配,是不會被從新裝載。因此跟你的OnMethod的匹配規則頗有關係,若是使用+java.lang.Object。那就死定了。
退出後的影響:
  • btrace監控每次退出後,原先全部的class都不會被恢復,你的全部的監控代碼依然一直在運行 
抓取了下btrace改寫事後的類:
Java代碼    收藏代碼
  1. public InstrumentServer(String ip, String port)  
  2.   {  
  3.     $btrace$com$agapple$btrace$Instrumentor$InstrumentTracer$bufferMonitor(this);  
  4.     this.ip = ip;  
  5.     this.port = port;  
  6.   }  
  7.   
  8. private static void $btrace$com$agapple$btrace$Instrumentor$InstrumentTracer$bufferMonitor(@Self  Object arg0)  
  9.   {  
  10.     if (!BTraceRuntime.enter(InstrumentTracer.runtime)) returntry { Field ipField = BTraceUtils.field("com.agapple.btrace.Instrumentor.InstrumentServer""ip");  
  11.       Field portField = BTraceUtils.field("com.agapple.btrace.Instrumentor.InstrumentServer""port");  
  12.   
  13.       String ip = (String)BTraceUtils.get(ipField, self);  
  14.       String port = (String)BTraceUtils.get(portField, self);  
  15.       BTraceUtils.println(BTraceUtils.strcat(BTraceUtils.strcat(BTraceUtils.strcat("ip : ", BTraceUtils.str(ip)), " port : "), BTraceUtils.str(port)));  
  16.       BTraceRuntime.leave(); return; } catch (Throwable localThrowable) { BTraceRuntime.handleException(localThrowable);  
  17.     }  
  18.   }  
注意其中的 if (!BTraceRuntime.enter(InstrumentTracer.runtime)) return;
再看一下 BTraceRuntime中對應方法的實現:

 

Java代碼    收藏代碼
  1. private volatile boolean disabled;  
  2.   
  3. public static boolean enter(BTraceRuntime current)  
  4.   {  
  5.     if (current.disabled) return false;  
  6.     return map.enter(current);  
  7.   }  
每次執行你的監控代碼以前會先進行一個判斷,判斷當前是否處於監控中。你的客戶端發起了exit指令後,該方法判斷false,直接return。
因此btrace使用退出後會讓你的代碼多走了一個方法調用+一個對象屬性判斷,因此說影響仍是很是的少

 



5. btrace諸多的使用限制,你必須得知道:

 

Java代碼    收藏代碼
  1. can not create new objects.  
  2. can not create new arrays.  
  3. can not throw exceptions.  
  4. can not catch exceptions.  
  5. can not make arbitrary instance or static method calls - only the public static methods of com.sun.btrace.BTraceUtils class or methods declared in the same program may be called from a BTrace program.  
  6. (pre 1.2) can not have instance fields and methods. Only static public void returning methods are allowed for a BTrace class. And all fields have to be static.  
  7. can not assign to static or instance fields of target program's classes and objects. But, BTrace class can assign to it's own static fields ("trace state" can be mutated).  
  8. can not have outer, inner, nested or local classes.  
  9. can not have synchronized blocks or synchronized methods.  
  10. can not have loops (forwhiledo..while)  
  11. can not extend arbitrary class (super class has to be java.lang.Object)  
  12. can not implement interfaces.  
  13. can not contains assert statements.  
  14. can not use class literals.  

 

說明: 

 

補充說明:

 

  • 正由於btrace有這諸多的限制,纔可讓咱們的監控代碼能夠更加的放心,這也正是btrace能普及的一個很重要的緣由。
  • 不得不說的一個點:對String的"+"限制使用,讓咱們使用起來很不爽,不過還好在btrace 1.2以後,做者提供了一個StringBuilder。相比於strcat已經好用多了

6. btrace對string字符串的處理

  • 能夠參看總結3,突破對應的限制。不是很是建議,由於總結4中提出即便btrace client退出後,服務端一直會運行btrace script。因此一旦有寫的動做,會是一個長期持續的過程
  • btrace 1.2 release說明中,已經提到增長了StringBuilder進行字符串處理,至少比先前的strcat使用上已經方便不少了。具體查看:http://kenai.com/jira/browse/BTRACE-38

7. btrace的相關源碼:

8. btrace中對OnMethod的Location使用上,以及一些annotation使用不明確,能夠查看:http://kenai.com/projects/btrace/sources/hg/content/src/share/classes/com/sun/btrace/runtime/Instrumentor.java

 

說明: self, ProbeClassName , ProbeMethodName 在任何的Kind中都支持,因此就不在每一個表格中贅述。

Kind Where.BEFORE Where.AFTER
ARRAY_GET 數組長度(int) , 數組類型(type) @return , 數組長度(int) , 數組類型(type)
ARRAY_SET 原始數組類型(type) , 數組長度(int) , 目標數組類型(type) @return,原始數組類型(type) , 數組長度(int) , 目標數組類型(type)
CALL 方法參數 , @TargetInstance , @TargetMethodOrField 方法參數, @return , @TargetInstance , @TargetMethodOrField
CATCH 異常類型(type) 異常類型(type)
CHECKCAST  轉型的目標類型 轉型的目標類型
ENTRY 方法參數 方法參數 
ERROR 異常類型(throwable type) 異常類型(throwable type)
FIELD_GET @TargetInstance,@TargetMethodOrField @TargetInstance,@TargetMethodOrField,@return 
FIELD_SET fldValueIndex,@TargetInstance,@TargetMethodOrField fldValueIndex,@TargetInstance,@TargetMethodOrField 
INSTANCEOF 轉型的目標類型 轉型的目標類型 
LINE 行數  行數 
NEW 對象類名  @return
NEWARRAY 數組內部對象類名,類名 數組內部對象類名,類名, @return 
RETURN 參數,@return , @Duration
SYNC_ENTRY sync對象 sync對象
SYNC_EXIT sync對象 sync對象
THROW 異常類型 異常類型

 

 

最後

花了多個小時時間整理了這份blog,但願能給你們理解btrace,掌握btrace的使用能帶來一些幫助!! 

 

有問題和交流,歡迎站內聯繫

相關文章
相關標籤/搜索