Byteman 简单使用
Byteman 简单使用
简介
byteman 是jboss一个java 字节码注入工具,它易于跟踪,可以监控和测试Java应用程序和JDK运行时代码的行为。它将Java代码注入到应用程序方法或java运行时方法,而无需您重新编译,重新编写或甚至重新部署应用程序
安装
下载
curl -L https://github.com/chaos-mesh/byteman/releases/download/4.0.14-cm/byteman.tar.gz -o /tmp/byteman.tar.gz
tar -zxvf byteman.tar.gz
解压后会生成一个byteman 文件夹,我们主要需要使用的东西都在 /tmp/byteman/bin
文件夹中
设置环境变量
export BYTEMAN_HOME="/tmp/byteman"
export PATH=${PATH}:${BYTEMAN_HOME}/bin
简单使用
byteman 可以在进程启动时,进程运行时,也可以注入到 jvm runtime class 中
进程启动时注入
使用Byteman对Java程序注入故障有几种不同的方法。最基本的方式是在Java命令行上使用-javaAgent
选项
针对方法进行代码注入
package org.my;
class AppMain {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
接下来可以注入一些代码来跟踪main方法的进入和退出。
我们创建一个 Byteman
规则 appmain.btm
RULE trace main entry
CLASS AppMain
METHOD main
AT ENTRY
IF true
DO traceln("entering main")
ENDRULE
RULE trace main exit
CLASS AppMain
METHOD main
AT EXIT
IF true
DO traceln("exiting main")
ENDRULE
以上脚本包含两个规则,该规则的含义是
字段 | 含义 |
---|---|
RULE | 指定规则的名字 |
CLASS | 需要被注入代码的类名 |
METHOD | 需要被注入的方法名,可以不带返回值和入参,其中<init> 表示构造方法,<clinit> 表示类初始化方法 |
AT ENTRY | 指定代码注入在方法的哪个地方,ENTRY表示方法的入口 |
IF true | 条件判断,这里简单直接使用true |
DO | 注入需要被执行的代码,这里的traceln 相当于 System.out.println |
ENDRULE | 代表规则结束 |
java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:appmain.btm org.my.AppMain foo bar baz
输出
entering main
foo
bar
baz
exiting main
jvm runtime class
想要将代码注入到jvm runtime class 中需要启动时新增两个参数
boot:${BYTEMAN_HOME}/lib/byteman.jar
: 将 byteman.jar 安装到bootstrap classpath
中-Dorg.jboss.byteman.transform.all
: 通常Byteman 为了安全起见是不会将代码注入到类似java.lang
这样的包中的,如果一定要注入,则要加此参数
想要对以下代码中的java.lang.Thread
类中的start
方法注入规则
package org.my;
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
RULE trace thread start
CLASS java.lang.Thread
METHOD start()
IF true
DO traceln("*** start for thread: "+ $0.getName())
ENDRULE
这里 $0
表示的是调用该方法的当前对象
执行启动命令
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
进程运行时
大多数情况下启动的进程肯定是不会加载byteman 代理的,但是当需要对进程进行调试的时候,有需要有个工具可以将调试代码注入进去,byteman 也支持对正在运行的程序进行代码注入
在目录 /tmp/byteman/bin/
下存在两个脚本文件 bminstall.sh
和 bmsubmit.sh
有以下程序代码
public class Main {
public static void main(String []args) {
for(int x = 0; x < 1000; x = x+1) {
try {
Thread.sleep(1000);
Main.sayhello(x);
} catch (Exception e) {
System.out.println("Got an exception!" + e);
}
}
}
public static void sayhello(int num) throws Exception {
try {
num = getnum(num);
String s=String.valueOf(num);
System.out.println(s + ". Hello World");
} catch (Exception e) {
throw e;
}
}
public static int getnum(int num) {
return num;
}
}
执行下面代码编译并执行。假设获取到的 pid 为 1000
javac Main.java
java Main 1>out.log 2>&1 &
注入打印语句的代码
编写规则 trace.btm
RULE trace sayhello
CLASS Main
METHOD sayhello
AT ENTRY
IF true
DO traceln("hello byteman")
ENDRULE
将byteman attach 到1000 进程上,并启动一个6000的端口来接受哦规则
bminstall.sh -p 6000 1000
将规则提交给 byteman
bmsubmit.sh -p 6000 -l trace.btm
此时可以查看 out.log 中是否有 hello byteman
的内容输出
卸载规则
bmsubmit.sh -p 6000 -u trace.btm
注入抛出异常的语句
这里需要注意的是,由于sayhello 方法是有抛出异常的,因此可以注入throw new java.io.IOException("BOOM");
这段代码,否则会报错
RULE throw an exception at sayhello
CLASS Main
METHOD sayhello
AT ENTRY
IF true
DO throw new java.io.IOException("BOOM");
ENDRULE
注入指定返回值
让 getnum 方法直接返回固定值
RULE modify return value
CLASS Main
METHOD getnum
AT ENTRY
IF true
DO return 9999
ENDRULE
扩展
除了以上简单的使用之外,byteman 还有很多丰富的功能,比如针对接口方法进行插入,对所有子类的方法进行插入,自定义对象等
针对接口
实际应用场景中存在很多RPC 调用的场景,由于RPC调用中通过接口对象进行远程调用,这个时候通过类名方法名的方式来执行故障注入就比较繁琐。因此我们可以通过接口类型的方式进行注入
规则 interface.btm
RULE Dubbo RPC request
INTERFACE org.eggjs.dubbo.UserService
METHOD echoUser
IF true
DO System.out.println("Finalizing " )
ENDRULE
只需要将class改为interface,该规则适用于实现该interface 的任何class。
对所有子类方法进行插入
有时候需要只定义一个父类,或者接口 ,集成了该类的所有子类重写了父类的方法,想对所有这些子类也进行故障注入,如果一个个class 的写的话会比较麻烦,这个时候可以使用 ^
Main.java
public class GFG {
public static void main(String[] args) {
for(int x = 0; x < 1000; x = x+1) {
try {
Thread.sleep(1000);
Complex c1 = new Complex(10, 15);
System.out.println(c1);
} catch (Exception e) {
System.out.println("Got an exception!" + e);
}
}
}
}
Complex.java
class Complex {
private double re, im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
@Override
public String toString() {
return this.re + " + " + this.im + "i";
}
}
注入以下规则
RULE overriding methods
CLASS ^java.lang.Object
METHOD toString
IF true
DO System.out.println("Finalizing " )
ENDRULE
像 overriding tostring 方法的类有很多,比如 java.nio.file.attribute.FileTime、java.nio.file.attribute.FileTime、java.nio.ShortBuffer、java.nio.IntBuffe
等等。这个时候我们通过前面加上 ^ 前缀符号,就能针对这些所有子类都进行代码注入。
同理 interface 也可以通过此种 ^ 前缀方式来匹配。
自定义对象
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
Iterator foo = list.iterator();
System.out.println(foo.hasNext() );
System.out.println("start");
for(int i=0; i<1000; i++){
for(String val : list){
System.out.println(val);
}
}
System.out.println("stop");
}
可以通过BIND 参数来生成对象
bind.btm
RULE Iterator_init
INTERFACE ^java.util.Iterator
METHOD <init>
AT EXIT
BIND down : java.util.Iterator = $0;
IF down.hasNext()
DO System.out.println("Triggered!!!!");
ENDRULE
这个规则的含义就是会对 java.util.Iterator 这个类的所有子类中的构造方法进行代码注入,
其中
METHOD <init>
表示构造方法,AT EXIT
表示在构造方法退出的时候执行BIND down : java.util.Iterator = $0;
表示从当前类返回一个对象,对象的类型为java.util.Iterator,对象名为 down
由于byteman的一些机制,知道这个 java.util.Iterator 类是jvm 运行时的,安装之前说的,jvm 运行时的类需要加上-Dorg.jboss.byteman.transform.all -Dorg.jboss.byteman.verbose
.
且继承这个类的子类有很多,byteman在运行是可能会出现 stackoverflow error
.因此我们在执行的时候需要加上 -Dorg.jboss.byteman.compile.to.bytecode
bminstall.sh -p 6000 -b -Dorg.jboss.byteman.transform.all -Dorg.jboss.byteman.verbose -Dorg.jboss.byteman.compile.to.bytecode 1000
./bmsubmit.sh -p 6000 bind.btm
参考
A Byteman Tutorial
StackOverflow for Iterator.
The Byteman Rule Language
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!