@joshsulin
2017-07-28T12:34:22.000000Z
字数 11333
阅读 2372
byteman
英文原文: https://developer.jboss.org/wiki/ABytemanTutorial#top
我们只是在这里入门。 。
本文是一个简单的教程,向您展示如何开始使用Byteman。它解释了如何安装Byteman并将其用于将简单的Java程序进行注入。本教程的重点是从命令行驱动Byteman(这就是为什么需要下载zip版本)。第二个教程说明如何使用Byteman在单元和集成测试中执行故障注入测试。它描述了如何从ant和maven驱动Byteman(n.b.maven集成避免了下载Byteman版本的需要)。建议您在尝试第2部分之前完成本教程,因为它将基于您将从本教程中获得的理解
如果您需要有关如何使用Byteman的完整信息,“Byteman程序员指南”提供了Byteman的概述,以及Byteman如何操作以及如何定义规定您要注入的副作用的规则的详细说明。它还包括如何安装Byteman以及您可以运行Byteman的各种方式的综合说明。
本教程的结构是一个FAQ,包括以下问题的答案。如果你是Byteman的新人,你应该从头开始读完这些例子。如果您以前使用过Byteman,并希望刷新您的记忆或回答具体问题,您可以使用以下链接之一跳过。然而,在后一种情况下,您可能会发现有必要对前面的部分进行简要说明,以便充分掌握正在呈现的内容。
- 为什么要用Byteman?
- 如何下载并安装Byteman?
- 使用Byteman如何运行程序?
- 如何将代码注入JVM类?
- 有更简单的方法来运行Byteman吗?
- 如何将规则加载到正在运行的程序中?
- 如何查看哪些规则被加载和编译?
- 如何卸载规则?
- 如何将代理安装到正在运行的程序中?
- 如何使用Byteman运行JBoss AS?
- 我如何知道我的规则是否正确?
- 如何判断我的规则是否正在运行?
- 如何让我的规则运行得很快?
- 哪里可以下载教程来源
您可以使用Byteman来更改Java程序的运行方式,而无需编辑源代码并重新编译它。实际上,您甚至可以使用Byteman修改正在运行的应用程序,而无需停止并重新启动它。 Byteman将乐意重新定义应用程序类和JVM运行时类(如String,Thread等)的行为。
Byteman最简单的使用是将print语句插入到代码中,以便您可以看到程序正在做什么。 Byteman可以读取和打印公共,私人和受保护的字段或局部变量。甚至可以调用应用程序的方法来计算要显示的值。 Byteman做出非常具体的,高度本地化的变化,并且在这样做时引起了很少的开销。调试与时序相关的代码非常有用,特别是多线程应用程序,同时在多个线程中可能会发生有趣的事件。它还允许您调试或监视已部署的应用程序,因为使用调试器停止程序是不可接受的。
Byteman也可以改变程序控制流程。您可以调用应用程序或JVM方法来修改应用程序或运行时状态。您还可以重新分配静态和实例字段,方法参数和局部变量,强制返回或抛出异常。通常在测试过程中使用此功能来模拟错误情况。使用仪器代码或使用虚拟类来实现错误行为,而不是用您的应用程序来混淆应用程序,您只需在准确的位置将代码注入到应用程序中即可。
请注意,Byteman要求您在Java 6或更高版本的JVM上运行应用程序。如果您的代码是使用较早版本的Java版本编译的,那么您不需要担心它仍然可以工作。但是,Byteman推出了仅在JDK6中完全支持的JVM功能。如果您真的需要运行Java 5 JVM,您可以下载旧版1.0.3 Byteman版本。它提供了这里描述的所有基本功能,但不支持动态加载Byteman代理和Byteman规则(有关详细信息,请参阅1.0.3用户指南)。
在运行Byteman之前,您将需要安装Java 6或更高版本的JVM。为了执行本教程中的程序,您将需要编译源代码,以便您需要安装包含javac编译器和Java工具jar的完整JDK。但是,可以在仅安装JRE的系统上使用Byteman。需要安装完整JDK的唯一功能是Byteman代理的动态加载。当然,如果您正在开发Java软件,那么您将已经安装了JDK。
Byteman项目下载页面提供了最新的Byteman二进制版本。下载二进制zip版本并将其解压缩到本地机器上的目录中。现在设置环境变量BYTEMAN_HOME来引用这个目录。在Linux上,您执行这样的命令
export BYTEMAN_HOME = {PATH}:$ {BYTEMAN_HOME} / bin
如果您在Windows上安装Byteman,那么您将需要使用java命令代替shell脚本(请注意,从2.0.1版本开始,bin目录包含等效的Windows脚本 - 以.bat文件提供 - 它们使用与Linux脚本相同的命令行语法)。
无论如何,就是这样,安装完成。
使用Byteman有几种不同的方式来运行Java程序。最基本的方法是在java命令行中使用-javaagent选项
这是一个简单的应用程序类,将用于显示Byteman的动作
包org.my;
AppMain类
{public static void main(String [] args){for(int i = 0; i <args.length; i ++){的System.out.println(参数[I]);}}}
你通常会编译并运行这个程序如下:
javac org/my/AppMain.java
java org.my.AppMain foo bar baz
foo
bar
baz
我们注入一些代码来跟踪进入和退出方法main。
首先,我们将创建一个Byteman规则脚本文件。只需在名为appmain.btm的工作目录中打开一个文件,并插入以下文本
RULE trace main entryCLASS AppMainMETHOD mainAT ENTRYIF trueDO traceln("entering main")ENDRULERULE trace main exitCLASS AppMainMETHOD mainAT EXITIF trueDO traceln("exiting main")ENDRULE
该脚本包含两个规则,两个规则都指定要注入到CLASS AppMain的METHOD主体中的代码。第一个规则被注入AT ENTRY,即在该方法的开始处。要注入的代码是IF DO部分。在第一个规则中,它调用内建操作traceln(String),将其参数打印到System.out()后跟换行。第二个规则被注入AT EXIT,即控制从方法main返回的点。两个规则都指定条件IF为真,这意味着始终执行DO部分。
如果要计算运行DO之后的操作是否可以提供不同的Java表达式。
所有主要的桌面和服务器JVM都支持用于使用这些Byteman规则运行progarm的-javaagent选项。
语法为-javaagent:path_to_agent_jar = agent_option1,agent_option_2,。 。 。
用Byteman和这个规则集运行程序的Linux命令是
java -javaagent: {BYTEMAN_HOME} /lib/byteman.jar。我们只提供一个代理选项,跟随= sign脚本:appmain.btm,指定Byteman规则脚本的位置。 Byteman代理读取此选项,然后从文件appmain.btm加载并注入规则。如果我们要加载多个脚本,我们可以提供多个脚本:文件代理选项,用逗号分隔。
那么我们来试试跟踪一些JVM操作。将以下规则文本插入名为thread.btm的新脚本文件。
RULE trace thread startCLASS java.lang.ThreadMETHOD start()IF trueDO traceln("*** start for thread: "+ $0.getName())ENDRULE
该规则被注入到JVM运行时CLASS java.lang.Thread的METHOD start()中。 它使用String +运算符打印粘贴在一起的跟踪消息。 现在,start()是一个实例方法,所以调用它时有一个特定的Thread实例,它是方法调用的目标。 特殊变量 1,$ 2等引用。
Byteman知道 0.getName(),并验证结果类型是String。注入的代码调用此方法,将结果追加到常量String,并将结果传递给要写入System.out的方法traceln()。
我们将这个规则注入到我们原始类AppMain2的变体中,AppMain2创建一些线程来进行打印
class AppMain2{public static void main(String[] args){for (int i = 0; i < args.length; i++) {final String arg = args[i];Thread thread = new Thread(arg) {public void run() {System.out.println(arg);}};thread.start();try {thread.join();} catch (Exception e) {}}}}
要使用新脚本运行此程序,我们修改脚本:代理选项以引用文件thread.btm。但是,如果我们要将代码注入到JVM类中,这还不够。我们需要提供一些额外的选项/参数。在Linux上
> java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:thread.btm,boot:${BYTEMAN_HOME}/lib/byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain2 foo bar baz
or on Windows
> java -javaagent:%BYTEMAN_HOME%\lib\byteman.jar=script:thread.btm,boot:%BYTEMAN_HOME%\lib\byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain2 foo bar baz
Class Thread是一个JVM类,它意味着它被引导类加载器加载。如果Byteman代理类也由引导类加载器加载,Byteman只能将代码注入此类。额外的代理选项boot:$ {BYTEMAN_HOME} /lib/byteman.jar附加在脚本选项之后(注意用作分隔符的逗号)。这样可以确保将byteman代理jar安装到引导类路径中。
Class Thread也恰好在java.lang包中。 通常,Byteman是非常谨慎的,不会将代码注入到这个包中,以防止它挂起JVM。 如果你真的想改变这个包中的类的方法,你需要定义系统属性org.jboss.byteman.transform.all。
当我们运行该程序时,我们得到这个输出
在Linux上,您可以使用命令脚本bmjava.sh来包装-javaagent选项。 它看起来非常像java命令,但它在命令行上接受Byteman规则脚本,并将其捆绑为-javaagent script:options。 它也使用boot:agent选项自动捆绑在byteman jar中。 所以,使用bmjava.sh,以前的命令行简化为
- bmjava.sh -l thread.btm org.my.AppMain2 foo bar baz
请注意,使用的标志是-l用于加载。
在Windows上,如果您使用的是byteman 2.0.1或更新版本,则有一个名为bmjava.bat的等效脚本,您可以使用命令bjava执行该脚本。 所以以前的命令就简化了
- bmjava -l thread.btm org.my.AppMain2 foo bar baz
请注意,您需要将安装的bin目录$ {BYTEMAN_HOME} / bin添加到路径中,以便能够按名称执行脚本(在Windows上使用%BYETMAN_HOME%/ bin)
如果您有一个长时间运行的程序,您可能希望在程序开始运行后加载规则,甚至卸载规则并加载新的规则。 如果你告诉Byteman来启动代理监听器,你可以这样做。 监听器还允许您检查加载的规则的状态。 为了显示在这里使用的听众是我们的程序AppMain3的另一个变体。
package org.my;import java.io.DataInputStream;class AppMain3{public static void main(String[] args){try {DataInputStream in = new DataInputStream(System.in);String next = in.readLine();while (next != null && next.length() > 0 && !next.contains("end")) {final String arg = next;Thread thread = new Thread(arg) {public void run() {System.out.println(arg);}};thread.start();try {thread.join();} catch (Exception e){}next = in.readLine();}} catch (Exception e) {}}}
监听器打开服务器套接字,然后等待传入命令。 请注意,没有将规则脚本指定为代理选项,所以最初在输入时只是回显
在Linux上,我们使用命令脚本bmsubmit.sh与代理监听器通信。 调用它没有参数列出所有安装的规则的状态
再次运行bmsubmit.sh,我们可以看到规则已经成功编译。
> bmsubmit.sh# File thread.btm line 4RULE trace thread startCLASS java.lang.ThreadMETHOD start()AT ENTRYIF trueDO traceln("*** start for thread: "+ $0.getName())ENDRULETransformed in:loader: sun.misc.Launcher$AppClassLoader@5acac268trigger method: java.lang.Thread.start() voidcompiled successfully>
这一次,输出结束处有一个额外的线,编译成功。 这意味着规则已被类型检查并执行。 类型检查仅在规则首次触发时发生 - 在这种情况下,当在包含单词baz的行中键入后,调用Thread.start()。 如果类型检查失败,则执行注入的代码将被禁止,并且列表将包括类型错误的详细信息。 注释代码只有在卸载规则时才会被删除。
我们也可以使用bmsubmit.sh(或Windows上的Submit Submit)来卸载规则,恢复Thread.start()的原始行为。
> bmsubmit.sh -u thread.btmuninstall RULE trace thread start> bmsubmit.shno rules installed
-u标志后跟规则脚本文件的名称。 文件中提到的所有规则都将被卸载,从他们注入的任何方法中删除其IF DO代码。 如果我们再次切换到该程序,它现在将恢复为仅回显输入文本
. . .mumble*** start for thread: mumblemumblegrumblegrumblebletchbletchend>
注: 如果要更改注入的规则代码,则不必卸载,然后重新加载规则。 如果您提交修改版本的规则脚本,Byteman将删除任何现有注入的代码并重新注入新代码。
您有时会发现,您启动您的程序,而不加载Byteman代理选项,只能意识到您希望使用Byteman来检查其行为。
您有时会发现,您启动您的程序,而不加载Byteman代理选项,只能意识到您希望使用Byteman来检查其行为。 这通常发生在长时间运行的程序,如JBoss应用程序服务器,当您注意到一个日志消息指示某些事情已经开始行为不正。 您不必重新启动程序,以便能够使用Byteman,至少不是如果您在Hotspot,JRockit或OpenJDK JVM上运行代码。 这些JVM都允许您将代理程序安装到正在运行的程序中。
如果您在Linux上运行,则可以使用命令脚本bminstall.sh安装代理程序
> bminstall.sh 13101
n.b.不是所有JVM都允许代理被动态加载。 在使用Oracle JVM,OpenJDK和JRockit JVM以及在使用Oracle JVM和OpenJDK JVM时在OSX上,已知可在Linux上运行。 尚未发现在IBM JVM或Windows上的任何JVM上都可以工作。
在Windows上,如果您使用2.0.1或更高版本,您可以使用等效的脚本bminstall.bat。否则,您需要执行Install.main()传递相关参数。
数字参数是要安装代理程序的进程的id - 显然,您需要提供一个适合您要检查的Java程序的值。或者,您可以提供程序的Java主类的名称(如果您使用java -jar myapp.jar启动它的话,也可以提供该名称)。如果要将Byteman代理安装到正在运行的JBoss Application Server实例中,则此选项非常有用。让我们再次运行AppMain3,但没有-javaagent选项,然后加载代理和一些规则,它已经开始运行。
现在在另一个命令shell中,我们将安装代理。在Linux上。
bminstall.sh不加载任何规则脚本。但是,它会自动启用代理侦听器,允许您使用bmsubmit.sh提交规则。如果您尝试使用上述示例中给出的命令提交和取消提交在thread.btm中定义的规则,您将看到它们修改AppMain3的行为。
如果要在启动时将Byteman安装到JBoss应用服务器中,则需要使用-javaagent选项。 但是JBoss AS是通过在命令脚本中执行java命令来运行的,对于AS 4,5或6执行run.sh,对于AS 7,standalone.sh或domain.sh。那么,如何确保这些脚本通过必要的 -javaagent选项到JVM?
With AS 4, 5 and 6 you can pass the required argument to the scripts by setting the environment variable JAVA_OPTS. On Linux you might use the following command
set JAVA_OPTS="${JAVA_OPTS} -Dorg.jboss.byteman.transform.all -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:thread.btm,boot:${BYTEMAN_HOME}/lib/byteman.jar,listener:true"
or on Windows
set JAVA_OPTS="%JAVA_OPTS% -Dorg.jboss.byteman.transform.all -javaagent:%BYTEMAN_HOME%\lib\byteman.jar=script:thread.btm,boot:%BYTEMAN_HOME%\lib\byteman.jar,listener:true"
请注意,该命令插入JAVA_OPTS的当前值,保留可能已设置的任何其他java选项。
使用AS 7,您通常会在位于AS7安装的bin子目录中的配置文件中插入诸如JAVA_OPTS之类的变量的配置设置。 在Linux上,您需要编辑standalone.conf或domain.conf文件。 在Windows上,您可以编辑standalone.conf.bat或domain.conf.bat文件。 如果编辑这些文件,您将看到有一个明显的地方可以将-javaagent选项插入到JAVA_OPTS中。 您将使用一个设置,就像上面给出的示例一样。
加载规则后,重新运行bmsubmit.sh并没有参数是有用的,看看他们是否遇到了解析或类型错误。不过,提前确定这些问题通常更好。 Byteman允许您在将其安装到Java程序之前解析并键入检查规则。在Linux上运行命令脚本bmcheck.sh
> bmcheck.sh thread.btmchecking rule trace thread startparsed rule "trace thread start" for class java.lang.Threadtype checked rule "trace thread start"TestScript: no errors>
如果规则脚本引用应用程序类,那么您需要使用-cp标志来告诉bmcheck.sh找到它们。 所以,我们需要使用这个参数来检查我们的第一个脚本appmain.btm。 但这还不够。 这是第一条规则
有时您可能不确定您的规则是否被正确注入。 也许目标方法从不执行。 也许规则条件总是假的。 或者可能是解析或类型错误正在阻止它们被执行。 如果您以详细模式运行Byteman,您将看到跟踪输出告诉您Byteman正在做什么。 通过设置系统属性启用详细模式
> java -Dorg.jboss.byteman.verbose -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:appmain.btm org.my.AppMain foo bar bazorg.jboss.byteman.agent.Transformer : possible trigger for rule trace main entry in class org.my.AppMainRuleTriggerMethodAdapter.injectTriggerPoint : inserting trigger into org.my.AppMain.main(java.lang.String[]) void for rule trace main entryorg.jboss.byteman.agent.Transformer : inserted trigger for trace main entry in class org.my.AppMainorg.jboss.byteman.agent.Transformer : possible trigger for rule trace main exit in class org.my.AppMainRuleTriggerMethodAdapter.injectTriggerPoint : inserting trigger into org.my.AppMain.main(java.lang.String[]) void for rule trace main exitorg.jboss.byteman.agent.Transformer : inserted trigger for trace main exit in class org.my.AppMainRule.execute called for trace main entry_0HelperManager.install for helper classorg.jboss.byteman.rule.helper.Helpercalling activated() for helper classorg.jboss.byteman.rule.helper.HelperDefault helper activatedcalling installed(trace main entry) for helper classorg.jboss.byteman.rule.helper.HelperInstalled rule using default helper : trace main entrytrace main entry executeentering mainfoobarbazRule.execute called for trace main exit_1HelperManager.install for helper classorg.jboss.byteman.rule.helper.Helpercalling installed(trace main exit) for helper classorg.jboss.byteman.rule.helper.HelperInstalled rule using default helper : trace main exittrace main exit executeexiting main>
跟踪是相当冗长的,但是您需要的所有信息都在那里,如果你寻找它。
当加载类AppMain时,会识别每个规则的触发器位置,并注入规则代码Next主方法被调用,您可以看到条目规则的执行消息(您可以立即忽略有关助手激活和规则安装的消息 或者如果要解释,请阅读Ptrogrammer指南)执行消息后面是输入规则的跟踪消息。
主要方法打印它的所有ouptut
在它返回之前,还有一个执行消息的退出规则(再次,只是忽略有关规则安装的消息)
第二个执行消息后面是退出规则的跟踪消息。
通常,Byteman通过解释解析的规则代码来执行规则。 这对于大多数使用来说足够快,但是如果您将代码注入紧密循环或被称为频繁的方法,那么这可能会减慢您的程序。 您可以通过要求Byteman将规则代码转换为JIT编译器可以优化的字节码来加快速度。 这使得规则的第一次执行需要更长的时间,但是后续的执行应该更快。
通过在安装代理程序时设置系统属性org.jboss.byteman.compile.to.bytecode来启用规则编译。 您可以在java命令行或使用bminstall.sh安装代理程序时执行此操作