Byteman –用于字节码操纵的瑞士军刀
我正在JBoss的許多社區中工作,有很多有趣的事情要談論,以至于我自己無法將自己的每一分都纏住。 這就是為什么我非常感謝有機會不時地歡迎客座博客的主要原因。 今天是Jochen Mader,他是以代碼為中心的書呆子群的一部分。 目前,他花費大量的時間編寫基于Vert.x的中間件解決方案的代碼,為不同的出版物撰寫文章并在會議上發表演講。 他的業余時間屬于他的家人,山地車和桌面游戲。 您可以在Twitter @codepitbull上關注他。
有些工具通常是您不希望使用的,但是很高興在需要時了解它們。 至少對我來說,Byteman屬于這一類。 這是我個人的瑞士軍刀,用來處理一個大泥巴球或那些可怕的黑森貝格蟲之一。 因此,獲取當前的Byteman發行版 ,將其解壓縮到您計算機上的某個位置,我們可以進行一些繁瑣的工作。
它是什么
Byteman是字節碼操作和注入工具套件。 它允許我們攔截和替換Java代碼的任意部分,以使其表現不同或(故意)破壞它:
- 將所有線程卡在某個位置,并讓它們同時繼續(hello race條件)
- 在意外的位置拋出異常
- 在執行過程中跟蹤代碼
- 更改返回值
還有更多的東西。
一個例子
讓我們直接看一些代碼來說明Byteman可以為您做些什么。
在這里,我們有一個很棒的Singleton和一個(可悲的)很好的示例代碼,您可能在很多地方都可以找到。
public class BrokenSingleton {private static volatile BrokenSingleton instance;private BrokenSingleton() {}public static BrokenSingleton get() {if (instance == null) {instance = new BrokenSingleton();}return instance;} }我們假裝自己是可憐的人,負責調試一些遺留代碼,這些代碼顯示了生產中的怪異行為。 一段時間后,我們發現了這顆寶石,我們的直覺表明這里有問題。
首先,我們可以嘗試如下操作:
public class BrokenSingletonMain {public static void main(String[] args) throws Exception {Thread thread1 = new Thread(new SingletonAccessRunnable());Thread thread2 = new Thread(new SingletonAccessRunnable());thread1.start();thread2.start();thread1.join();thread2.join();}public static class SingletonAccessRunnable implements Runnable {@Overridepublic void run() {System.out.println(BrokenSingleton.get());}} }運行此命令,很少有機會看到實際的問題發生。 但是最有可能我們不會看到任何異常情況。 Singleton初始化一次,應用程序按預期執行。 很多時候,人們開始通過增加線程數來進行暴力破解,以期使問題得以解決。 但是我更喜歡一種結構化的方法。
輸入Byteman。
DSL
Byteman提供了方便的DSL來修改和跟蹤應用程序的行為。 在我的小示例中,我們將從跟蹤調用開始。 看一下這段代碼。
RULE trace entering CLASS de.codepitbull.byteman.BrokenSingleton METHOD get AT ENTRY IF true DO traceln("entered get-Method") ENDRULERULE trace read stacks CLASS de.codepitbull.byteman.BrokenSingleton METHOD get AFTER READ BrokenSingleton.instance IF true DO traceln("READ:\n" + formatStack()) ENDRULEByteman腳本的核心構建模塊是RULE。
它由幾個組件組成(例如從Byteman-Docs中毫不客氣地示例:
# rule skeletonRULE <rule name>CLASS <class name>METHOD <method name>BIND <bindings>IF <condition>DO <actions>ENDRULE每個規則都必須具有唯一的__規則名稱__。 CLASS和METHOD的組合定義了我們希望將修改應用到的位置。 BIND允許我們將變量綁定到可以在IF和DO中使用的名稱。 使用IF,我們可以添加觸發規則的條件。 在DO中,實際的魔術發生了。
ENDRULE,它結束規則。
知道這一點,我的第一條規則很容易轉換為:
當有人調用_de.codepitbull.byteman.BrokenSingleton.get()_時,我想在調用方法主體之前(即__AT ENTRY__轉換為)打印字符串“ entered get-Method”。
我的第二條規則可以轉換為:
讀取(__AFTER READ__)之后,我想查看當前的調用堆棧。
抓取代碼并將其放入名為_check.btm_的文件中。 Byteman提供了一個不錯的工具來驗證您的腳本。 使用__ <bytemanhome> /bin/bmcheck.sh -cp文件夾/包含/已編譯/類/至/測試check.btm__來查看腳本是否可以編譯。 每次更改它時都要這樣做,很容易弄錯細節并花很長時間弄清楚它。
現在,腳本已保存并經過測試,現在可以在我們的應用程序中使用它了。
中介
腳本通過代理應用于運行代碼。 打開__BrokenSingletonMain-class__的運行配置并添加
__-javaagent:<BYTEMAN_HOME>/lib/byteman.jar=script:check.btm__到您的JVM參數。 這將注冊代理并告訴它運行_check.btm_。
而當我們在這里時,還有更多選擇:
如果您需要操縱一些核心Java東西,請使用
__-javaagent:<BYTEMAN_HOME>/lib/byteman.jar=script:appmain.btm,boot:<BYTEMAN_HOME>/lib/byteman.jar__這會將Byteman添加到引導類路徑中,并允許我們操縱_Thread _,_ String_之類的類……我的意思是,如果您想處理如此討厭的事情……
也可以將代理附加到正在運行的進程。 我們__jps__查找您要附加并運行的進程ID
__<bytemanhome>/bin/bminstall.sh <pid>__安裝代理。 之后運行
__<bytemanhome>/bin/bmsubmit.sh check.btm__回到我們眼前的問題。
使用修改后的run-Configuration運行我們的應用程序,應導致類似以下的輸出
entered get-Method entered get-Method READ: Stack trace for thread Thread-0 de.codepitbull.byteman.BrokenSingleton.get(BrokenSingleton.java:14) de.codepitbull.byteman.BrokenSingletonMain$SingletonAccessRunnable.run(BrokenSingletonMain.java:20) java.lang.Thread.run(Thread.java:745)READ: Stack trace for thread Thread-1 de.codepitbull.byteman.BrokenSingleton.get(BrokenSingleton.java:14) de.codepitbull.byteman.BrokenSingletonMain$SingletonAccessRunnable.run(BrokenSingletonMain.java:20) java.lang.Thread.run(Thread.java:745)恭喜,您剛剛操作了字節碼。 輸出還不是很有幫助,但這是我們要更改的東西。
線程混亂
現在,隨著我們的基礎架構的建立,我們可以開始更深入地挖掘。 我們非常確定我們的問題與某些多線程問題有關。 為了檢驗我們的假設,我們必須同時將多個線程放入關鍵部分。 使用純Java,這幾乎是不可能的,至少在不對我們要調試的代碼進行大量修改的情況下。
使用Byteman可以輕松實現。
RULE define rendezvous CLASS de.codepitbull.byteman.BrokenSingleton METHOD get AT ENTRY IF NOT isRendezvous("rendezvous", 2) DO createRendezvous("rendezvous", 2, true); traceln("rendezvous created"); ENDRULE該規則定義了一個所謂的集合點。 它允許我們指定多個線程必須到達的位置,直到允許它們繼續前進(也稱為aa障礙)。
這是規則的翻譯:
調用_BrokenSingleton.get()_時,創建一個新的集合點,當2個線程到達時將允許進度。 使集合點可重用,并僅在它不存在時才創建它(IF NOT部分至關重要),否則,我們將在每次對_BrokenSingleton.get()_的調用上創建一個障礙。
定義此障礙后,我們仍然需要顯式使用它。
RULE catch threads CLASS de.codepitbull.byteman.BrokenSingleton METHOD get AFTER READ BrokenSingleton.instance IF isRendezvous("rendezvous", 2) DO rendezvous("rendezvous"); ENDRULE翻譯:讀取_BrokenSingleton.get()_中的_instance_-member之后,在集合點等待,直到第二個線程到達并繼續。
在實例空檢查之后,我們現在停止來自同一花邊中_BrokenSingletonMain_的兩個線程。 這就是使比賽條件可再現的方法。 兩個線程將繼續認為_instance_為null,從而導致構造函數觸發兩次。
我將這個問題的解決方案留給您……
單元測試
我在撰寫此博客文章時發現,有可能在我的單元測試中運行Byteman腳本。 它們的JUNit和TestNG集成很容易集成。
將以下依賴項添加到_pom.xml_
<dependency><groupId>org.jboss.byteman</groupId> ? <artifactId>byteman-submit</artifactId><scope>test</scope><version>${byteman.version}</version> </dependency>現在,Byteman腳本可以在您的單元測試中執行,如下所示:
@RunWith(BMUnitRunner.class) public class BrokenSingletonTest {@Test@BMScript("check.btm")public void testForRaceCondition() {...} }將此類測試添加到您的西裝中會大大提高Byteman的有用性。 沒有更好的方法來防止其他人將這些腳本作為構建過程的一部分來重復您的錯誤。
結束語
博客文章中只有這么多空間,我也不想開始重寫他們的文檔。 寫這篇文章是一件很有趣的事情,因為我已經有一段時間沒有使用Byteman了。 我不知道我如何忽略了單元測試的集成。 這將使我將來更多地使用它。
現在,我建議瀏覽他們的文檔并開始進行注入,有很多事情要做。
翻譯自: https://www.javacodegeeks.com/2015/02/byteman-swiss-army-knife-byte-code-manipulation.html
總結
以上是生活随笔為你收集整理的Byteman –用于字节码操纵的瑞士军刀的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring项目中的Netflix Ar
- 下一篇: 快速浏览JAX-RS请求与方法匹配