Btrace 插桩前后的类变换分析

btrace 插桩过程

20141028200001 67060

btrace-client通过attach api附加到远程的目标进程上,建立socket通信连接,然后Btrace-client编译

btrace script脚本并验证,将编译好的class,通过socket传递到目标进程,btrace-agent对其class类变换,

对监测的类进行代码插桩,监测函数的信息,通过socket返回给btrace-client,显示监测的信息。

testSubCharles2函数插桩前

1. 插桩前的com.charles.test.CaseObject 的testSubCharles2方法代码如下:

public  Entity testSubCharles2(Long m1, Map<String, Object> map) {
  Entity entity =  new Entity("zhang","cheng",150);
  return entity;
}

2. 生成的MethodReturn的btrace脚本代码如下

对于类com.charles.test.CaseObject类,监测testSubCharles的第(1,2)个参数,和函数的返回值的命令如下。

mreturn com.charles.test.CaseObject testSubCharles2(1,2) com.charles.test.Entity

charles-btrace会根据mreturn命令,合并模板和参数生成MethodReturn的btrace脚本代码如下所示。

import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.AnyType;

import com.charles.org.codehaus.jackson.map.ObjectMapper;
import com.charles.org.codehaus.jackson.map.SerializationConfig;
import java.util.Map;
import java.util.HashMap;

@BTrace(unsafe=true)
public class MethodReturn {

    //存储函数参数和返回值
    @TLS
    private static String outputStr = "";
    //函数调用的起始时间
    @TLS
    private static long time;

    @OnMethod(clazz="com.charles.test.CaseObject", method="testSubCharles2")
    public static void charlesParam(AnyType[] args) {
        //outputStr 拼装函数参数,
        outputStr += "param\n" ;
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
        String params = "1,2";
        if (params.equalsIgnoreCase("")) {
        } else {
            String[] methodParams = params.split(",");
            Map<Integer,Boolean> paramMap = new HashMap<Integer,Boolean>();
            for (String param : methodParams) {
                paramMap.put(Integer.parseInt(param), true);
            }
            for (int n = 0; n <args.length; n++) {
                if (paramMap.get(n + 1) != null &amp;&amp; paramMap.get(n + 1) == true) {
                    outputStr += "[param" + (n + 1) + "]\n";
                    try {
                        outputStr += mapper.writerWithDefaultPrettyPrinter().writeValueAsString(args[n]) + "\n";
                    } catch (Exception e) {
                    }
                }
            }
        }
        //time函数调用的起始时间
        time = timeMillis();
    }

   @OnMethod(
        clazz="com.charles.test.CaseObject",
        method="testSubCharles2",
        location=@Location(value=Kind.RETURN))
    public static void charlesReturn(@Return {returnType} ret) {
        //添加函数的返回值
        outputStr += "\nreturn\n" ;
        try {
            ObjectMapper mapper = new ObjectMapper();
            mapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
            outputStr += mapper.writerWithDefaultPrettyPrinter().writeValueAsString(ret);
        } catch (Exception e) {
        }
        outputStr += "\n-\n" ;
        //添加函数的执行时间
        outputStr += " testSubCharles call time(ms) : " + (timeMillis() - time) + "\n";
        outputStr += "-\n" ;
        //和客户端通信,打印函数的相关信息
        println(outputStr);
    }
}

testSubCharles2函数插桩后

1. testSubCharles2函数插桩过程

(1) 抽取出MethodReturn脚本类中的@TLS的ThreadLocal变量,加上com.sun.btrace.BTraceRuntime的

runtime变量,以及BTraceRuntime的start代码,生成新的MethodReturn类,

(2) 抽取出MethodReturn脚本类中的@OnMethod方法,插桩到estSubCharles2函数中进行数据监测。

2. 插桩后类的提取

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

其中,通过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>

3.MethodReturn类,采用jd-gui,未能完整的反编译

使用 javap -verbose MethodReturn ,反编译类

public class MethodReturn extends java.lang.Object {

    public static java.lang.ThreadLocal $outputStr;
    public static java.lang.ThreadLocal $time;
    public static com.sun.btrace.BTraceRuntime runtime;

    public MethodReturn();
      Code:
       Stack=1, Locals=1, Args_size=1
       0:   aload_0
       1:   invokespecial   #21; //Method java/lang/Object."<init>":()V
       4:   return

    static {};
      Code:
       Stack=2, Locals=3, Args_size=0
       0:   ldc #2; //class MethodReturn
       2:   invokestatic    #30; //Method com/sun/btrace/BTraceRuntime.forClass:(Ljava/lang/Class;)Lcom/sun/btrace/BTraceRuntime;
       5:   putstatic   #32; //Field runtime:Lcom/sun/btrace/BTraceRuntime;
       8:   getstatic   #32; //Field runtime:Lcom/sun/btrace/BTraceRuntime;
       11:  invokestatic    #36; //Method com/sun/btrace/BTraceRuntime.enter:(Lcom/sun/btrace/BTraceRuntime;)Z
       14:  ifne    18
       17:  return
       18:  ldc #38; //String
       20:  astore_2
       21:  lconst_0
       22:  invokestatic    #44; //Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
       25:  invokestatic    #48; //Method com/sun/btrace/BTraceRuntime.newThreadLocal:(Ljava/lang/Object;)Ljava/lang/ThreadLocal;
       28:  putstatic   =50; //Field $time:Ljava/lang/ThreadLocal;
       31:  aload_2
       32:  invokestatic    #48; //Method com/sun/btrace/BTraceRuntime.newThreadLocal:(Ljava/lang/Object;)Ljava/lang/ThreadLocal;
       35:  putstatic   #52; //Field $outputStr:Ljava/lang/ThreadLocal;
       38:  invokestatic    #55; //Method com/sun/btrace/BTraceRuntime.start:()V
       41:  return
       42:  return
      Exception table:
       from   to  target type
        18    42    42   Class java/lang/Throwable

}

关于Java指令集信息,请点击,手工通过java指令集,转换成java的代码如下

public class MethodReturn {

    public static ThreadLocal $outputStr;
    public static ThreadLocal $time;
    public static BTraceRuntime runtime;

    static {
        runtime= BTraceRuntime.forClass(MethodReturn.class);
        if (runtime != null) {
            try {
                String local2 = "";
                $time = BTraceRuntime.newThreadLocal(Long.valueOf(0));
                $outputStr = BTraceRuntime.newThreadLocal(local2);
                BTraceRuntime.start();
            }catch (Throwable e) {
            }
        }
        return;
    }

}

MethodReturn主要负责初始化,线程本地存储ThreadLocal函数调用时间变量$time

和字符串输出$outputStr,以及启动BTraceRuntime.start()线程的监测。

4.jd-gui查看插桩后的com.charles.test.CaseObject类

查看testSubCharles2函数,发现在原有的代码前增加$btrace$MethodReturn$charlesParam,

原有的代码后增加$btrace$MethodReturn$charlesReturn。

public Entity testSubCharles2(Long paramLong, Map<String, Object> paramMap) {
    $btrace$MethodReturn$charlesParam(new Object[] { paramLong, paramMap });
    Entity localEntity1 = new Entity("zhang", "cheng", Integer.valueOf(150));
    Entity localEntity2;
    $btrace$MethodReturn$charlesReturn(localEntity2);
    return localEntity2 = localEntity1;
}

其中$btrace$MethodReturn$charlesParam函数获取函数的参数信息和执行的初始时间

$btrace$MethodReturn$charlesReturn函数, 获取函数的返回信息,以及执行时间,

然后通过socket将函数信息传给客户端显示。

$btrace$MethodReturn$charlesReturn函数, ThreadLocal变量$outputStr 增加函数的返回值信息,

使用BTaceUtils.println和客户端通信,在客户端输出函数的参数,返回值,以及调用时间信息。

private static void $btrace$MethodReturn$charlesReturn(Entity paramEntity) {
    if (!BTraceRuntime.enter(MethodReturn.runtime)) {
        return;
    }
    try {
        MethodReturn.$outputStr.set((String)MethodReturn.$outputStr.get()
              + "\nreturn\n");
        try {
            ObjectMapper localObjectMapper = new ObjectMapper();
            localObjectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
            MethodReturn.$outputStr.set((String)MethodReturn.$outputStr.get()
                + localObjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(paramEntity));
        } catch (Exception localException) {
        }
        MethodReturn.$outputStr.set((String)MethodReturn.$outputStr.get()
            + "\n-\n");
        MethodReturn.$outputStr.set((String)MethodReturn.$outputStr.get()
            + " testSubCharles2 call time(ms) : "
            + (BTraceUtils.timeMillis() - ((Number)MethodReturn.$time.get()).longValue()) + "\n");
        MethodReturn.$outputStr.set((String)MethodReturn.$outputStr.get() + "\n");
        BTraceUtils.println((String)MethodReturn.$outputStr.get());
        BTraceRuntime.leave();
        return;
    } catch (Throwable localThrowable) {
        BTraceRuntime.handleException(localThrowable);
    }
}

在函数被插桩后,就能够通过socket通信的方式,在客户端显示监测的信息,实现对函数的动态监测。

文章目录
  1. btrace 插桩过程
    1. testSubCharles2函数插桩前
      1. 1. 插桩前的com.charles.test.CaseObject 的testSubCharles2方法代码如下:
      2. 2. 生成的MethodReturn的btrace脚本代码如下
    2. testSubCharles2函数插桩后
      1. 1. testSubCharles2函数插桩过程
      2. 2. 插桩后类的提取
      3. 3.MethodReturn类,采用jd-gui,未能完整的反编译
      4. 4.jd-gui查看插桩后的com.charles.test.CaseObject类