Btrace 原理分析

来源:互联网 发布:红三兵炒股软件评价 编辑:程序博客网 时间:2024/05/18 02:12

http://www.iteye.com/topic/1005918

概述

Btrace是由sundararajan在2009年6月开发的一个开源项目,是一种动态跟踪分析一个运行中的Java应用程序的工具。
BTrace是一个为Java平台开发的安全、动态的追踪工具。BTrace动态地向目标应用程序的字节码注入追踪代码(字节码追踪),这些追踪字节码追踪代码使用Java语言表达,也就是BTrace的脚本。

组件说明

说明:
1. BtraceClient : 为我们使用的btrace的本地api,一般我们使用的bin/btrace会在本地启动一个btrace jvm,其内部使用了Java Complier Api, JVMTI技术,以及创建了一个socket。

Java Complier Api:动态的将我们传递的监控的java源文件动态编译成.class文件
JVMTI: 主要是利用了java 1.6之后的VirtaulMachine技术,动态的attach到一个已启动的jvm上,为他去启动一个BtraceAgent。该Agent会为BtraceClient启动一个server socket进行通讯。(多进程之间的通讯)
本地socket: BtraceClient和BtraceAgent之间的数据通讯,比如生成的.class发送到BtraceAgent,还有一些Event事件等等。BtraceAgent同样可以将服务端print()的数据通过socket的方式回传给BtraceClient进行打印
2. BtraceAgent:为我们在目标jvm上植入的btrace agent实现。主要是Instrumentation技术, asm字节码处理技术。
BtraceAgent的启动可以有两种方式: BtaceClient动态attach后进行启动, 另一种就是在目标jvm启动之前添加agent参数进行启动。
BtraceAgent会启动一个server socket,与BtraceClient客户端进行交互,客户端可以将监控的.class文件通过socket发送,同样也可以在jvm启动时直接指定对应本地的.class做为监控脚本。
BtraceAgent在接受到监控指令后,会遍历当前所有已经加载的class类,挨个进行匹配检查,并生成相应的监控字节码(监控方法)。

btrace的包结构:
btrace-client.jar
btrace-boot.jar
btrace-agent.jar

btrace-client:
一般我们通常直接使用的命令,比如:

bin/btrace $pid Btrace.java

都是直接调用了btrace-client包中的代码。

核心类介绍:

com.sun.btrace.client.Main (btrace的启动入口)

调用Client进行complier(Java Compiler Api)和attach(JVM TI)处理

com.sun.btrace.client.Client

compiler方法 : 调用了Java Complier Api进行动态编译你的Btrace.java文件

Verifier btraceVerifier = new Verifier(this.unsafe); //指定了btrace特定的语法校验器 

attach方法: 调用VirtualMachine.attach(pid); vm.loadAgent(agentPath, agentArgs);动态加载btrace-agent.jar包

动态加载agent可传递给agent程序的几个参数:
debug=true
unsafe=true
dumpClass=true
dumpDir=xx
trackRetransforms=true ##是否记录instrument行为
bootClassPath= xx ##agent.jar使用
systemClassPath =xx ##agent.jar使用
probeDescPath=xx

submit方法: 调用提交对应的instrument指令,并传递对应code的byte[]

this.sock = new Socket("localhost", this.port);    this.oos = new ObjectOutputStream(this.sock.getOutputStream());    WireIO.write(this.oos, new InstrumentCommand(code, args));    

几点说明:
* 在调用了attach方法后,会通过btrace-agent.jar中的com.sun.btrace.agent.Main启动一个ServerSocket

int port = 2020;    String p = (String)argMap.get("port");    ServerSocket ss;      try {         (isDebug()) debugPrint(new StringBuilder().append("starting server at ").append(port).toString());         System.setProperty("btrace.port", String.valueOf(port));         if ((scriptOutputFile != null) && (scriptOutputFile.length() > 0)) {              System.setProperty("btrace.output", scriptOutputFile);         }         ss = new ServerSocket(port);       } catch(Exception e) ....    while (true)    {       if (!isDebug()) continue; debugPrint("waiting for clients");       Socket sock = ss.accept();       if (!isDebug()) continue; debugPrint(new StringBuilder().append("client accepted ").append(sock).toString());       Client client = new RemoteClient(inst, sock);       handleNewClient(client);       continue;    }    
  • 所以在submit中,会通过一个本地socket进行连接server,并提交相应的Btrace.java中的监控代码(这时应该是编译后的字节码).

com.sun.btrace.compiler.Verifier btrace自定义的语法校验器

Boolean value = this.unsafe ? Boolean.TRUE : (Boolean)ct.accept(new
VerifierVisitor(this), null); // 注意下unsafe的判断

###com.sun.btrace.compiler.VerifierVisitor (具体的一些检查规则)
visitBinary String字符串的+限制
visitClass class的检查,不允许有父类,不允许有接口类,不允许非static变量,必须有Btrace @标签
visitDoWhileLoop 不允许do while循环
visitForLoop 不允许for循环
visitMethod 必须为static public ,不允许出现synchronized标记
visitNewArray 不允许出现new Array
visitNewClass 不允许出现new 对象
visitReturn 不允许有返回值
visitSynchronized 不允许有同步快
visitThrow visitTry 不允许有try catch的动作
visitOther 除上面允许之外的,不允许有其他的
说明:
* 看完Verifier和VerifierVisitor后,相信大家都应该明白了Btrace所谓的诸多限制,只是针对.java需要动态编译。如果我们预先生成.class文件,Btrace在1.2版本中并不会作类型合法性检查。(在将code发送给btrace-agent后,会在目标的jvm内部进行一次简单的Btrace语法检查,具体见后面Btrace-agent介绍)

  1. com.sun.btrace.comm.XXX Btrace的各种command指令

btrace-agent

大致了解了Client类中的attach和submit方法后,相信也能猜到对应agent的一些设计。简单的看一下:
com.sun.btrace.agent.Main 为attach上之后agent的总入口,会调用agentmain()方法
main方法: 首先解析参数,然后会启动一个agentThread(Daemon线程)
parseArgs : 对应的参数解析,客户端在attach时,提交给agent后的一些参数

bootClassPath=xx.jar //需要动态增加的jar systemClassPath=xx.jar
noServer=true/false //是否启动server socket debug=true/false
unsafe=true/false dumpClasses=true/false dumpDir=路径
trackRetransforms=true/false probeDescPath=路径
stdout=true/false script=文件 scriptdir=路径
scriptOutputFile=文件路径特别注意下相比于Btrace-client提交的参数中,多了几个script,scriptdir等参数,允许在Client调用服务端一个指定的Btrace
script文件进行处理

loadBTraceScript : 装载指定的script(注意是script和scriptDir中指定的script),必须是.class文件,会调用FileClient进行处理,最后调用handleNewClient进行统一处理,最后调用handleNewClient进行统一instrument处理。
startServer : main启动的agentThread会调用该方法,这里会启动一个serversocket,和btrace-client的客户端socket进行通讯,使用RemoteClient,最后调用handleNewClient进行统一instrument处理。
handleNewClient : 启动一个异步线程进行class Transformer,根据提交的byte[] code进行类文件重写

说明:
* 目前instrument进行字节码重写时,会重新load所有的class进行处理。(Btrace可以使用正则,父类的方式进行匹配,只能是挨个Class进行处理,看下是否有匹配的OnMethod)

Btrace总结

监控方式

本地jvm监控:目前大多数都是用的是btrace和监控的目标java是在同一机器上
远程jvm监控:需要在远程服务器启动时添加btrace-agent.jar,需要重写btrace客户端,完成和serversocket建立通讯,完成btrace script发送监控。
1. VirtualMachine动态attach不支持远程操作,所以无法动态的进行agent添加。
2. btrace支持的jdk版本
java 1.4以及之前 : 不支持,Instrument在jdk 1.5之后才出现。
java 1.5 : 必须手动在jvm启动时添加btrace-agent.jar,因为VirtualMachine是在jdk 1.6之后才出现。
java 1.6 : 推荐使用

btrace的支持的script方式有多种。

1.client上的.java文件
会进行动态编译,会有比较多的语法限制,btrace一堆的你不能做的事
2.client上的.class文件
没什么好讲的,自己写Btrace script时导入btrace-client.jar,写好后生成一个.class文件,再通过btrace pid Btrace.class进行启动。
3.remote上的.class文件
修改btrace-client中的Client类,支持script和scriptDir的一些参数提交。
在remote机器上放置对应的btrace.class文件

btrace的使用是否会对java进程造成影响?(影响是肯定的,不过影响不大)

1.装载时的影响:
btrace每次使用,都会重新load所有的class。当然如果OnMethod不匹配,是不会被重新装载。所以跟你的OnMethod的匹配规则很有关系,如果使用**+**java.lang.Object。那就死定了。
2.退出后的影响:
btrace监控每次退出后,原先所有的class都不会被恢复,你的所有的监控代码依然一直在运行 。

抓取了下btrace改写过后的类(也可以通过jvisualvm的btrace插件dump出修改类):public InstrumentServer(String ip, String port)    {      $btrace$com$agapple$btrace$Instrumentor$InstrumentTracer$bufferMonitor(this);      this.ip = ip;      this.port = port;    }  private static void $btrace$com$agapple$btrace$Instrumentor$InstrumentTracer$bufferMonitor(@Self Object arg0)    {      if (!BTraceRuntime.enter(InstrumentTracer.runtime)) return; try { Field ipField = BTraceUtils.field("com.agapple.btrace.Instrumentor.InstrumentServer", "ip");        Field portField = BTraceUtils.field("com.agapple.btrace.Instrumentor.InstrumentServer", "port");        String ip = (String)BTraceUtils.get(ipField, self);        String port = (String)BTraceUtils.get(portField, self);        BTraceUtils.println(BTraceUtils.strcat(BTraceUtils.strcat(BTraceUtils.strcat("ip : ", BTraceUtils.str(ip)), " port : "), BTraceUtils.str(port)));        BTraceRuntime.leave(); return; } catch (Throwable localThrowable) { BTraceRuntime.handleException(localThrowable);      }    }  注意其中的if (!BTraceRuntime.enter(InstrumentTracer.runtime)) return; 再看一下BTraceRuntime中对应方法的实现:private volatile boolean disabled;  public static boolean enter(BTraceRuntime current)    {      if (current.disabled) return false;      return map.enter(current);    }  每次执行你的监控代码之前会先进行一个判断,判断当前是否处于监控中。你的客户端发起了exit指令后,该方法判断false,直接return。所以btrace使用退出后会让你的代码多走了一个方法调用+一个对象属性判断,所以说影响还是非常的少。

btrace诸多的使用限制(设置unsafe=true可突破限制):

can not create new objects.
can not create new arrays.
can not throw exceptions.
can not catch exceptions.
can not make arbitrary instance or static method calls - only the public static methods of com.sun.btrace.BTraceUtils class or methods declared in the same program may be called from a BTrace program.
(pre 1.2) can not have instance fields and methods. Only static public void returning methods are allowed for a BTrace class. And all fields have to be static.
can not assign to static or instance fields of target program’s classes and objects. But, BTrace class can assign to it’s own static fields (“trace state” can be mutated).
can not have outer, inner, nested or local classes.
can not have synchronized blocks or synchronized methods.
can not have loops (for, while, do..while)
can not extend arbitrary class (super class has to be java.lang.Object)
can not implement interfaces.
can not contains assert statements.
can not use class literals.

说明:

在btrace-client和btrace-agent分别都有对诸多限制的检查。
btrace-client代码类: http://kenai.com/projects/btrace/sources/hg/content/src/share/classes/com/sun/btrace/compiler/VerifierVisitor.java
btrace-agent代码类: http://kenai.com/projects/btrace/sources/hg/content/src/share/classes/com/sun/btrace/runtime/MethodInstrumentor.java
补充说明:

正因为btrace有这诸多的限制,才可以让我们的监控代码可以更加的放心,这也正是btrace能普及的一个很重要的原因。
不得不说的一个点:对String的”+”限制使用,让我们使用起来很不爽,不过还好在btrace 1.2之后,作者提供了一个StringBuilder,相比于strcat已经好用多了。
btrace可突破对应的限制。不是非常建议,因为总结4中提出即使btrace client退出后,服务端一直会运行btrace script。所以一旦有写的动作,会是一个长期持续的过程。

btrace的相关源码:

官网上找到一个,不过是web版本: http://kenai.com/projects/btrace/sources/hg/show/src/share/classes/com/sun/btrace
BTrace是GPLv2开源的。源码用Mercurial管理着。
要源码的话这样就好了: hg clone https://hg.kenai.com/hg/btrace~hg

0 0