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 {
        @Override
        public 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")
ENDRULE

RULE trace read stacks
CLASS de.codepitbull.byteman.BrokenSingleton
METHOD get
AFTER READ BrokenSingleton.instance
IF true
DO traceln("READ:\n" + formatStack())
ENDRULE

Byteman脚本的核心构建模块是RULE。

它由几个组件组成(例如从Byteman-Docs中毫不客气地示例:

# rule skeleton
 RULE <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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值