from:http://agapple.iteye.com/blog/1005918

背景 

   周五下班回家,在公司班車上覺得無聊,看了下btrace的源碼(自己反編譯)。 一些關于btrace的基本內容,可以看下我早起的一篇記錄:btrace記憶

 

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

 

   至于為什么會去反編譯查看btrace源碼,主要是會在部門整個關于btrace的分享。同時btrace的相關技術文檔缺乏,javadoc很多時候說的不明不白,作者也沒有提供源碼開源,所以就有了這次的分享。

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

大家可以先去預備一下知識。

 

Btrace的大體設計

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


 

 

說明: 

1. BtraceClient : 為我們使用的btrace的本地api,一般我們使用的bin/btrace會在本地啟動一個btrace jvm,其內部使用了Java Complier Api, JVMTI技術,以及創建了一個socket。

 

  • 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的包結構:

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

btrace-client

   一般我們通常直接使用的命令,比如:

 

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中都支持,所以就不在每個表格中贅述。

KindWhere.BEFOREWhere.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_SETfldValueIndex,@TargetInstance,@TargetMethodOrFieldfldValueIndex,@TargetInstance,@TargetMethodOrField 
INSTANCEOF轉型的目標類型轉型的目標類型 
LINE行數 行數 
NEW對象類名 @return
NEWARRAY數組內部對象類名,類名數組內部對象類名,類名, @return 
RETURN參數,@return , @Duration
SYNC_ENTRYsync對象sync對象
SYNC_EXITsync對象sync對象
THROW異常類型異常類型

 

 

最后

花了多個小時時間整理了這份blog,希望能給大家理解btrace,掌握btrace的使用能帶來一些幫助!! 

 

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

分享到:  
評論
10 樓 agapple 2014-03-03  
xiuyuandashi 寫道
博主你好,
    我想請教下,你是如何分析btrace,透徹了解btrace的工作機制的。


多看看btrace的源碼
9 樓 xiuyuandashi 2014-03-02  
博主你好,
    我想請教下,你是如何分析btrace,透徹了解btrace的工作機制的。
8 樓 agapple 2013-09-23  
命中注定1314 寫道
確實阿 ,現在暫時想的不用BTrace的遠程agent, 自己用Socket來傳輸, 監控那邊還是像本地那樣干, 你改過他的Agent和Client咯?


暫時沒,都是直接在服務器上運行btrace
7 樓 命中注定1314 2013-09-23  
確實阿 ,現在暫時想的不用BTrace的遠程agent, 自己用Socket來傳輸, 監控那邊還是像本地那樣干, 你改過他的Agent和Client咯?
6 樓 agapple 2013-09-17  
命中注定1314 寫道
師兄你修改過遠程的Btrace嗎


你需要做遠程監控?
5 樓 命中注定1314 2013-09-17  
師兄你修改過遠程的Btrace嗎
4 樓 khotyn 2013-08-27  
anxiety 是曹帥寫的嗎?求源碼地址~
3 樓 agapple 2011-06-24  
RednaxelaFX 寫道
agapple 寫道
至于為什么會去反編譯查看btrace源碼,主要是會在部門整個關于btrace的分享。同時btrace的相關技術文檔缺乏,javadoc很多時候說的不明不白,作者也沒有提供源碼開源,所以就有了這次的分享。 

7. btrace的相關源碼: 


呃…BTrace明明是GPLv2開源的。源碼用Mercurial管理著。 
要源碼的話這樣就好了: 
Command prompt代碼  收藏代碼
  1. hg clone https://hg.kenai.com/hg/btrace~hg  

正常的話會看到: 
Command prompt代碼  收藏代碼
  1. $ hg clone https://hg.kenai.com/hg/btrace~hg  
  2. destination directory: btrace~hg  
  3. requesting all changes  
  4. adding changesets  
  5. adding manifests  
  6. adding file changes  
  7. added 421 changesets with 2192 changes to 692 files (+4 heads)  
  8. updating working directory  
  9. 299 files updated, 0 files merged, 0 files removed, 0 files unresolved  

代碼就抓下來了…


呵呵,我土了, 先前不了解mercurial工具。 只用過svn和git,長見識了   
2 樓 RednaxelaFX 2011-06-23  
agapple 寫道
至于為什么會去反編譯查看btrace源碼,主要是會在部門整個關于btrace的分享。同時btrace的相關技術文檔缺乏,javadoc很多時候說的不明不白,作者也沒有提供源碼開源,所以就有了這次的分享。 

7. btrace的相關源碼: 


呃…BTrace明明是GPLv2開源的。源碼用Mercurial管理著。 
要源碼的話這樣就好了: 
Command prompt代碼  收藏代碼
  1. hg clone https://hg.kenai.com/hg/btrace~hg  

正常的話會看到: 
Command prompt代碼  收藏代碼
  1. $ hg clone https://hg.kenai.com/hg/btrace~hg  
  2. destination directory: btrace~hg  
  3. requesting all changes  
  4. adding changesets  
  5. adding manifests  
  6. adding file changes  
  7. added 421 changesets with 2192 changes to 692 files (+4 heads)  
  8. updating working directory  
  9. 299 files updated, 0 files merged, 0 files removed, 0 files unresolved  

代碼就抓下來了…
1 樓 agapple 2011-06-23  
貌似關注btrace的人挺少,呵呵。 

用btrace最大的好處就是監控業務數據,而且允許在jvm運行之后動態attach,完全是一種無嵌入的監控模式。你不需要像jwebap一樣,丟個jar包到你的運行容器中。 

一般常用:  
1. 某方法調用的性能監控 
2. 某方法調用的參數內容監控,記錄等。(排查問題特別有用) 
3. ......