zoukankan      html  css  js  c++  java
  • 基于BTrace监控调试Java代码

    BTrace是Java的一个动态代码追踪工具,通过编写btrace脚本,它可以动态的向目标应用程序的字节码注入追踪代码,通过修改字节码的方式,达到监控调试和定位问题的目的,是解决线上问题的利器。

    BTrace项目的Github主页 https://github.com/btraceio/btrace,本文中示例代码在 https://github.com/cellei/btrace-practice

    快速开始

    我们使用 BTrace User's Guide 中的示例来做一个快速开始:

    import com.sun.btrace.annotations.*;
    
    @BTrace
    public class HelloWorld {
        @OnMethod(
            clazz="java.lang.Thread",
            method="start",
            location = @Location(Kind.ENTRY)
        )
        public static void func() {
            BTraceUtils.println("about to start a thread!");
        }
    }
    

    BTrace包下载解压后,把bin目录加入环境变量,执行命令btrace <pid> HelloWorld.java,pid是Java目标程序的进程号,可以使用jps或者ps命令查询到。这样就执行了BTrace脚本程序,每启动一个线程,就会打印出 "about to start a thread!",BTrace脚本已经正常执行了,不需要重启目标Java程序。

    若想要退出BTrace的话,按下ctrl+c,再选择1就可以了,也可以在脚本中使用代码BTraceUtils.Sys.exit(0)来退出。

    简单分析一下这段代码,@BTrace注解表明这是一段BTrace脚本,@OnMethod注解以及func方法的参数列表(重载时),指定了要跟踪的目标代码,称为探测点(Probe Point),func方法体内的代码就是跟踪行为(Trace Action),即跟踪到目标代码后所执行的代码。

    btrace 命令格式:btrace <pid> <btrace-script>,会直接打印结果到命令行下,也可使用转向命令输出结果到文件,例如btrace <pid> HelloWorld.java > btrace.log

    如果执行的BTrace脚本是".java"源文件,btrace命令执行时会首先编译为字节码,建议预编译为".class"字节码文件后,再执行编译后的BTrace脚本。

    写BTrace脚本并且预编译时,需要引入BTrace的jar包,以Maven本地jar包形式引入如下:

    <dependency>
        <groupId>com.sun.btrace</groupId>
        <artifactId>btrace-client</artifactId>
        <version>1.3.11.2</version>
        <type>jar</type>
        <scope>system</scope>
        <systemPath>${basedir}/src/main/resources/lib/btrace-client.jar</systemPath>
    </dependency>
    

    使用限制

    由于BTrace脚本会向线上代码中注入字节码,即使退出后,注入的字节码也不会恢复,注入的监控代码依然会执行,所以使用BTrace时有一些限制必须遵守,正因为这些限制,BTrace才能更放心的在线上调试中使用。

    • BTrace脚本中不能创建对象、创建数组、捕获和抛出异常。
    • 不能调用实例或静态方法,只能调用com.sun.btrace.BTraceUtils中的静态方法。
    • 不能把目标程序的类或对象赋值给静态或实例字段。
    • 不能定义外部, 内部, 匿名, 本地类。
    • 不能使用同步synchronized代码块和同步方法。
    • 不能使用forwhile等循环语句。
    • 不能扩展类,父类必须是Object,不允许实现接口。
    • 不能使用assert语句,不能使用Class字面值,例如Class<String> c = String.class;

    BTrace注解

    注解都在com.sun.btrace.annotations包下,分为类注解、方法注解、方法参数注解、属性注解。

    类注解

    • @BTrace:用来指明这个Java类是个BTrace脚本程序,被BTrace编译器和BTrace代理执行。
    • @DTrace及@DTraceRef:是单独给Solaris系统使用的,一般使用不到。

    方法注解

    • @OnMethod:用来指定监控的目标代码,它有三个常用属性,clazzmethodlocation
    1. clazz 属性用来指定目标代码的类名,支持全限定名/正则/父类/注解形式。全限定名,例如java.lang.Thread;也可以使用正则表达式匹配,例如/java\.lang\..+/;父类或接口的形式,例如+java.lang.Runnable,注解形式,例如@javax.annotation.Resource。内部类使用$符号连接。
    2. method 属性用来指定目标代码的方法名,支持全限定名/正则/注解形式,特殊的,指定构造方法时使用<init>表示,目标类有重载的方法时,根据@OnMethod修饰的方法的参数列表具体指明哪个目标方法。
    3. location 属性指定目标代码的拦截时机,比如是进入目标方法时拦截(默认),还是目标方法返回值的时候拦截,后面会详细讲到。
    • @OnTimer:用来周期性的、每隔N毫秒定期执行该注解修饰的方法,例如@OnTimer(4000)表示每间隔4000毫秒执行一次。
    • @OnError:当BTrace脚本中其他任何跟踪行为发生异常时,该注解修饰的方法会被执行。
    • @OnExit:当BTrace脚本使用代码BTraceUtils.Sys.exit(0)来退出BTrace命令行会话时,该注解修饰的方法会被执行。
    • @OnEvent:当在BTrace客户端命令行下发生外部事件时,该注解修饰的方法会被执行,此注解的值为事件名称,默认值为"SIGINT"。目前,当执行ctrl+c操作时,该注解修饰的方法就会被执行。
    • @OnLowMemory:用来监测JVM中堆内存是否达到阈值,它有两个属性poolthresholdpool指定监测年轻代还是老年代,GC算法不同也会有所不同,threshold指定阈值大小,举例@OnLowMemory(pool = "Tenured Gen",threshold=6000000)
    • @OnProbe:该注解可以用来避免使用BTrace脚本的内部类,需要和xml映射文件配合使用,详见User's Guide.

    拦截时机

    @OnMethod注解的location属性指明拦截目标代码的拦截时机,经常使用的有如下几个:

    • Kind.ENTRY:刚进入目标方法时就执行,当没有指明location时,此为默认值。
    • Kind.RETURN:目标方法返回时执行,配合方法参数注解@Duration可以获取目标方法执行时间,单位纳秒。
    • Kind.THROW:目标方法有异常被抛出时。
    • Kind.CATCH:目标方法有异常被捕获时。
    • Kind.ERROR:目标方法有异常没被捕获抛出了方法之外。
    • Kind.CALL:目标方法被调用时。
    • Kind.LINE:目标方法具体某一行被执行时,值为-1表示方法内所有代码行。

    方法参数注解

    • @ProbeClassName:目标代码的类名,全限定名,@OnMethod的clazz中定义。
    • @ProbeMethodName:目标代码的方法名,@OnMethod的method中定义。
    • @Duration:目标方法执行时间,与Kind.RETURN配合使用。
    • @Return:目标方法返回值,与Kind.RETURN配合使用。。
    • @Self:目标实例,指向this关键字的值。
    • @TargetInstance:与Kind.CALL一起使用,代表location中定义的监控到的调用实例。
    • @TargetMethodOrField:与Kind.CALL一起使用,代表location中定义的监控到的调用方法与属性。

    属性注解

    • @TLS:ThreadLocal变量,可用于在多个拦截方法间通信、共享变量,只能在使用@OnMethod注解的拦截方法中访问。
    • @Export:该注解标注的字段可以被映射到一个jvmstat计数器上,用以暴露给外部的jvmstat客户端(例如jstat)。

    使用示例

    获取执行时间超过100ms的方法

    @BTrace
    public class DurationDemo {
        @OnMethod(
                clazz="/com\.cellei\.btrace\.practice\.controller\..*/",
                method="/.*/",
                location = @Location(Kind.RETURN)
        )
        public static void duration(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long duration) {
            //duration单位纳秒
            if(duration > 1000000 * 100){
                BTraceUtils.println("DurationDemo.duration: " + pcn + "." + pmn +  ":" + (duration/1000000) + "ms");
            }
        }
    }
    

    目标方法的某一行是否被执行

    @BTrace
    public class AllLines {
        @OnMethod(
                clazz="/com\.cellei\.btrace\.practice\.controller\..*/",
                method = "createUser",
                location=@Location(value=Kind.LINE, line=24)
        )
        public static void applines(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) {
            BTraceUtils.println("AllLines.applines: " + pcn + "." + pmn +  ":" + line);
        }
    }
    

    获取目标方法的参数

    @BTrace
    public class ArgArray {
        @OnMethod(
                clazz="/com\.cellei\.btrace\.practice\.controller\..*/",
                method="updateUser",
                location = @Location(Kind.ENTRY)
        )
        public static void userArg(@ProbeClassName String pcn, @ProbeMethodName String pmn, String id, User user) {
            //只能使用BTraceUtils中的反射
            Field filed = BTraceUtils.field("com.cellei.btrace.practice.model.User", "name");
            BTraceUtils.println("ArgArray.userArg<id>: " + id);
            BTraceUtils.println("ArgArray.userArg<user>: " + user);
            BTraceUtils.printFields(user);
            BTraceUtils.println("ArgArray.userArg<user.name>:" + BTraceUtils.get(filed, user));
        }
    }
    

    获取构造方法的参数及调用栈

    @BTrace
    public class Constructor {
        @OnMethod(
                clazz="com.cellei.btrace.practice.model.User",
                method="<init>"
        )
        public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {
            BTraceUtils.println(pcn+","+pmn);
            BTraceUtils.printArray(args);
            BTraceUtils.jstack();
        }
    }
    

    目标方法重载时

    @BTrace
    public class Overload {
    
        @OnMethod(
                clazz = "com.cellei.btrace.practice.controller.AppController",
                method = "getUser"
        )
        public static void oneArg(@ProbeClassName String pcn, @ProbeMethodName String pmn, String id) {
            BTraceUtils.println("Overload.oneArg: " + id);
        }
    
        @OnMethod(
                clazz = "com.cellei.btrace.practice.controller.AppController",
                method = "getUser"
        )
        public static void twoArg(@ProbeClassName String pcn, @ProbeMethodName String pmn, String name, Integer age) {
            BTraceUtils.println("Overload.twoArg: " + name + ":" + age);
        }
    }
    

    是否有死锁

    @BTrace
    public class Deadlock {
        @OnTimer(4000)
        public static void print() {
            BTraceUtils.deadlocks();
        }
    }
    

    获取环境变量及JVM参数

    @BTrace
    public class JInfo {
        static {
            BTraceUtils.println("System Properties:");
            BTraceUtils.printProperties();
            BTraceUtils.println("VM Flags:");
            BTraceUtils.printVmArguments();
            BTraceUtils.println("OS Enviroment:");
            BTraceUtils.printEnv();
            BTraceUtils.exit(0);
        }
    }
    

    捕获异常堆栈

    @BTrace
    public class OnThrow {
        @TLS
        static Throwable currentException;
    
        @OnMethod(
                clazz="java.lang.Throwable",
                method="<init>"
        )
        public static void onthrow(@Self Throwable self) {
            currentException = self;
        }
    
        @OnMethod(
                clazz="java.lang.Throwable",
                method="<init>"
        )
        public static void onthrow1(@Self Throwable self, String s) {
            currentException = self;
        }
    
        @OnMethod(
                clazz="java.lang.Throwable",
                method="<init>"
        )
        public static void onthrow1(@Self Throwable self, String s, Throwable cause) {
            currentException = self;
        }
    
        @OnMethod(
                clazz="java.lang.Throwable",
                method="<init>"
        )
        public static void onthrow2(@Self Throwable self, Throwable cause) {
            currentException = self;
        }
    
        @OnMethod(
                clazz="java.lang.Throwable",
                method="<init>",
                location=@Location(Kind.RETURN)
        )
        public static void onthrowreturn() {
            if (currentException != null) {
                BTraceUtils.Threads.jstack(currentException);
                BTraceUtils.println("=====================");
                currentException = null;
            }
        }
    }
    

    5秒后退出

    @BTrace
    public class ProbeExit {
        private static volatile int i;
    
        @OnExit
        public static void onexit(int code) {
            BTraceUtils.println("BTrace program exits!");
        }
        
        @OnTimer(1000)
        public static void ontime() {
            BTraceUtils.println("hello");
            i++;
            if (i == 5) {
                BTraceUtils.Sys.exit(0);
            }
        }
    }
    
  • 相关阅读:
    如何使用命令行备份SAP HANA数据库
    Rootkit介绍
    web渗透测试基本步骤
    IIS服务器的安全保护措施
    渗透测试的流程
    Kali Linux之常见后门工具介绍
    中间人攻击(MITM)之数据截获原理
    口令破解工具
    常见渗透测试工具集成系统简介
    Kali Linux之使用SET快捷生成钓鱼网站方法
  • 原文地址:https://www.cnblogs.com/cellei/p/12370636.html
Copyright © 2011-2022 走看看