btrace扩展小工具

背景

在不能调试的情况下,需要定位问题,比较难,排除问题费时费力。
1、 编写写btrace脚本,实现对数据的监控,但是脚本调试起来需要时间
2、 对于没有重写toString方法的复杂的对象,不能够打印出具体的属性值
3、 对于函数返回值,需要找到返回类型所在路径,然后将其添加到btrace脚本编译路径下比较麻烦。
4、 对于没有做异常捕获的程序,没有打印栈信息。

目标

通过命令行交互的方式,实现程序监测。
1、 实现函数和返回参数的打印
2、 将复杂对象转成json格式化字符串显示
3、 查看函数的调用时间
4、 死锁检测
5、 打印未处理的异常信息
6、 支持打印函数的某几个特定的参数
7、 输出虚拟机中加载的类的所有类路径、父加载器等信息
8、 支持Java进程dump出Class文件

整体结构图

1、 类信息加载模块,classAgent从目标进程中读取加载类的路径,父加载器classloader等其他信息,classInpect输出所有的类目的信息到文件。
2、 btrace扩展模块,Jline2负责REPL命令交互,btrace命令扩展已有的功能,进行数据监测,btrace脚本生成负责合并脚本模板和参数,生成btrace脚本文件。
3、 类字节码输出,调用jdk提供ServiceabilityAgent,完成类的字节码输出,便于查看运行过程中的类的动态插桩后的字节码。
4、 CharlesJackson模块,主要是重命名已有的jackson类库,防止强制加载的类与已有的类冲突,,造成目标进程VM崩溃,异常退出。
5、 Btace模块是已有的开源实现,使用btraceEngine+AttachAPI+Intrument+ASM完成,附加到目标程序上,使用插桩的方式,完成对目标程序的动态监测和诊断。

整体思路


解析命令行输入命令,进行命令处理,如果指定函数的返回类型,会附加ClassAgent到目标进程,建立socket通信,返回特定类在目标进程中的加载路径,设置编译的函数返回类型的路径,合并脚本模板生成btrace脚本文件,编译后使用socket通信的方式加载到目标进程,代码插桩,实时返回监测信息,完成对目标程序的监测。

charles-btrace 使用说明

以linux下为例,使用jps查看运行的java进程

charles@ubuntu:~/charles-btrace/bin$ jps 16327 CaseTest 16346 Jps 

btrace 附加到远程进程

charles@ubuntu:~/charles-btrace/bin$ ./btrace 16327 

查看支持的扩展命令 

charles> help
----------------------------------------------
| time
| help
| showsql
| onthrow
| mreturn
| mparam
| quit
| deadlock
| subparam
---------------------------------------------
use 'help <command>' to get detail usage!
---------------------------------------------

使用help 查看命令的使用方法

charles> help mreturn
---------------------------------------------------------------------
  mparam  <clazzname> <method>[(1,2,...,n)] <type> [file] 
  打印函数的参数信息、返回值和调用时间 
  <clazzname>            函数所在的类 
  <method>[(1,2,...,n)]  方法名,指定方法参数可选,(1,2,5),打印参数1,2,5
  <type>                 函数的返回值 
  [file]                 输出重定向文件(可选),默认输出到控制台 
----------------------------------------------------------------------

监测com.charles.test.CaseObject类中的的testSubCharles2函数的第一个和第二个参数,以及返回值

关于函数插桩后的类变换对比,请点击

charles> mreturn com.charles.test.CaseObject testSubCharles2(1,2) com.charles.test.Entity
----------------param----------------
---[param1]---
100
---[param2]---
{
  "entity" : {
    "firstName" : "charles",
    "lastName" : "zhang",
    "age" : 88
  },
  "name" : "charles.zhang",
  "list" : [ "charles1", "charles2" ]
}

----------------return----------------
{
  "firstName" : "charles",
  "lastName" : "zhang",
  "age" : 150
}
-------------------------------------
 testSubCharles2 call time(ms) : 10
-------------------------------------

查看进程是否死锁,如果发生死锁,打印相关信息

charles> deadlock 
-------------------------------------
"Thread-1" Id=9 in BLOCKED on lock=java.lang.Object@1bc74f37     owned by Thread-0 Id=8
    at com.charles.test.DeadLockTest.run(DeadLockTest.java:58)
      - locked java.lang.Object@65faba46
    at java.lang.Thread.run(Thread.java:662)

    Locked synchronizers: count = 0

"Thread-0" Id=8 in BLOCKED on lock=java.lang.Object@65faba46     owned by Thread-1 Id=9
    at com.charles.test.DeadLockTest.run(DeadLockTest.java:46)
      - locked java.lang.Object@1bc74f37
    at java.lang.Thread.run(Thread.java:662)

    Locked synchronizers: count = 0
-------------------------------------

显示程序中调用的sql语句

charles> showsql 
--------------------
---[sql]---
    select
        id as id,
        gmt_create as gmtCreate,
        gmt_modified as gmtModified,
        field_type as fieldType,
        dimension as dimension,
        ds_field_name as dsFieldName,
        os_field_name as osFieldName,
        show_name as showName,
        os_filed_type as osFiledType,
        show_type as showType,
        show_conf as showConf,
        group_info as groupInfo,
        order_num as orderNum,
        is_order_field as isOrderField,
        update_type as updateType,
        field_source as fieldSource,
        search_name_type as searchNameType,
        block_name as blockName 
    from
        XXX 
    WHERE
        1 = 1 
        and field_type = ? 
    order by
        order_num desc 
---[params]---
[ "s" ]
--------------------

使用ctrl + c 终止当前命令的监测

Please enter charles option:
    1. charles exit
    2. send an event
    3. send a named event

使用quit退出命令行交互

charles> quit
Bye charles~

关于其他的命令可以尝试使用 help 了解其使用方法。

dump java进程内加载的类的所有类路径、父加载器等信息

charles@ubuntu:~/charles-btrace/bin$ sh inspect.sh <pid> 

会在当前目录下生成inpsect.txt

2014-09-18  [info] class: com.charles.test.CaseObject @classLoader: sun.misc.Launcher$AppClassLoader @from: file:/home/charles/workspace/charles-all/charles-test/target/classes/

dump java进程内中的Class文件

Java进程dump出Class文件的方式有以下两种:

(1) 使用JVMTI系的API

(2) 使用Serviceability Agent

charles@ubuntu:~/charles-btrace/bin$  classdump.sh <pid> <className>

其中,classdump脚本中,SA导出编译后的类的命令为

java ".:${JAVA_HOME}/lib/sa-jdi.jar" -Dsun.jvm.hotspot.tools.jcore.filter=sun.jvm.hotspot.tools.jcore.NameFilter -Dsun.jvm.hotspot.tools.jcore.NameFilter.pattern= <className> sun.jvm.hotspot.tools.jcore.ClassDump  <pid>

对于导出的class,可以使用jd-gui等java反编译工具,查看java代码。