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