Javassist进行方法插桩

javassist官网  http://jboss-javassist.github.io/javassist/

javassist API网  http://jboss-javassist.github.io/javassist/html/index.html

javassist参考博客  https://www.ibm.com/developerworks/cn/java/j-dyn0916/

 Ⅰ插桩

自动用例生成(使用Randoop)

评价(对用例筛选冗余)>功能覆盖、语句覆盖(一般用后者)

           >插桩  (插入语句)

              用Javassist实现自动插入语句,即插桩

方法层次的插桩:

  在方法前插入进入方法的语句,方法后插入退出方法的语句

Ⅱ用Javassist对方法插桩

1‘  首先在官网下Javassist的jar包,放置在web项目的lib下,在eclipse里右击项目选择build path的最后一个,导入jar包。

2’  新建一个相对要插桩类a.java的被插桩程序aJssistTiming.java

弄明白这个对方法的插桩,还是得看程序,我对测试第二个博客里的Triangle.java代码进行方法插桩,在方法前后都插入一个输出语句,TriangleJssistTiming.java代码示例如下:

 1 import java.io.IOException;
 2 import javassist.*;
 3
 4 public class TriangleJssistTiming {
 5     public static void main(String argv[]){
 6              try {
 7                      String classname="Triangle";
 8                      //获取class文件
 9                     CtClass clas =ClassPool.getDefault().get(classname);
10
11                     if(clas==null){
12                         System.out.println("classname"+clas+"  not found.");
13                     }else{
14                         //调用方法
15                         addTiming(clas,"isTriangle");
16                         addTiming(clas,"getType");
17                         addTiming(clas,"diffOfBorders");
18                         addTiming(clas,"getBorders");
19                         clas.writeFile();
20                     }
21                 } catch (CannotCompileException ex) {
22                     ex.printStackTrace();
23                 } catch (NotFoundException ex) {
24                     ex.printStackTrace();
25                 } catch (IOException ex) {
26                     ex.printStackTrace();
27                 }
28
29
30     }
31
32     private static void addTiming(CtClass cct,String method) throws NotFoundException, CannotCompileException{
33          //获取方法信息,如果方法不存在,则抛出异常
34         CtMethod ctMethod = cct.getDeclaredMethod(method);
35          //将旧的方法名称进行重新命名
36         String nname = method + "$impl";
37         ctMethod.setName(nname);
38         //方法的副本,采用过滤器的方式
39         CtMethod newCtMethod = CtNewMethod.copy(ctMethod, method, cct, null);
40
41         //为该方法添加时间过滤器来计算时间,并判断获取时间的方法是否有返回值
42         String type = ctMethod.getReturnType().getName();
43         StringBuffer body = new StringBuffer();
44         body.append("{\n long start = System.currentTimeMillis();\n");
45
46         body.append("System.out.println(\" "+ nname +"_in_11\");");
47         //返回值类型不同
48         if(!"void".equals(type)) {
49             body.append(type + " result = ");
50         }
51       //可以通过$$将传递给拦截器的参数,传递给原来的方法
52         body.append(nname + "($$);\n");
53        //输出方法运行时间差
54       //  body.append("System.out.println(\"Call to method " + nname + " took \" + \n (System.currentTimeMillis()-start) + " +  "\" ms.\");\n");
55         body.append("System.out.println(\" "+ nname +"_out_11\");");
56         if(!"void".equals(type)) {
57             body.append("return result;\n");
58         }
59
60         body.append("}");
61         //替换拦截器方法的主体内容,并将该方法添加到class之中
62         newCtMethod.setBody(body.toString());
63         cct.addMethod(newCtMethod);
64
65         //输出拦截器的代码块
66         System.out.println("拦截器方法的主体:");
67         System.out.println(body.toString());
68     }
69 }

  ▲1‘  body.append()插入字符串,里面若是输出语句需要注意用转义字符  “ “ 改成/“    /"

   2’  构造一个addtiming(0方法,方法里构造拦截器时使用一个 java.lang.StringBuffer 来累积正文文本(这显示了处理 String的构造的正确方法。这种变化取决于原来的方法是否有返回值。如果它 有返回值,那么构造的代码就将这个值保存在局部变量中,这样在拦截器方法结束时就可以返回它。如果原来的方法类型为 void ,那么就什么也不需要保存,也不用在拦截器方法中返回任何内容。

   3‘  body.append(nname + "($$);\n") 中 nname 是原来方法修改后的名字。在调用中使用的 $$ 标识符是 Javassist 表示正在构造的方法的一系列参数的方式。通过在对原来方法的调用中使用这个标识符,在调用拦截器方法时提供的参数就可以传递给原来的方法。

3‘  运行

  在eclipse里运行插桩程序和被插桩程序,然后看当地目录下又生成一个Triangle.class文件,大小比原来的大一点。

  在cmd代码目录下运行该文件  java   Triangle

▲原Triangle里没有main方法,需自己插入main方法并创建一个对象引用第一个方法isTriangle()

比较插桩前后Triangle.class,在当地目录找到Triangle.class(后生成的,不在bin目录里,较大的文件),然后用,导入该文件,可以查看原java代码,会发现多了四个类似的方法:

  1 import java.io.PrintStream;
  2
  3 public class Triangle
  4 {
  5   public static void main(String[] args)
  6   {
  7     Triangle tr = new Triangle(2L, 3L, 4L);
  8     tr.isTriangle(tr);
  9   }
 10
 11   protected long lborderA = 0L;
 12   protected long lborderB = 0L;
 13   protected long lborderC = 0L;
 14
 15   public Triangle(long lborderA, long lborderB, long lborderC)
 16   {
 17     this.lborderA = lborderA;
 18
 19     this.lborderB = lborderB;
 20
 21     this.lborderC = lborderC;
 22   }
 23
 24   public boolean isTriangle$impl(Triangle triangle)
 25   {
 26     boolean isTriangle = false;
 27     if ((triangle.lborderA > 0L) && (triangle.lborderA <= 9223372036854775807L) &&
 28       (triangle.lborderB > 0L) && (triangle.lborderB <= 9223372036854775807L) &&
 29       (triangle.lborderC > 0L) && (triangle.lborderC <= 9223372036854775807L)) {
 30       if ((diffOfBorders(triangle.lborderA, triangle.lborderB) < triangle.lborderC) &&
 31         (diffOfBorders(triangle.lborderB, triangle.lborderC) < triangle.lborderA) &&
 32         (diffOfBorders(triangle.lborderC, triangle.lborderA) < triangle.lborderB)) {
 33         isTriangle = true;
 34       }
 35     }
 36     return isTriangle;
 37   }
 38
 39   public String getType$impl(Triangle triangle)
 40   {
 41     String strType = "Illegal";
 42     if (isTriangle(triangle)) {
 43       if ((triangle.lborderA == triangle.lborderB) &&
 44         (triangle.lborderB == triangle.lborderC)) {
 45         strType = "Regular";
 46       } else if ((triangle.lborderA != triangle.lborderB) &&
 47         (triangle.lborderB != triangle.lborderC) &&
 48         (triangle.lborderA != triangle.lborderC)) {
 49         strType = "Scalene";
 50       } else {
 51         strType = "Isosceles";
 52       }
 53     }
 54     return strType;
 55   }
 56
 57   public long diffOfBorders$impl(long a, long b)
 58   {
 59     return a > b ? a - b : b - a;
 60   }
 61
 62   public long[] getBorders$impl()
 63   {
 64     long[] borders = new long[3];
 65     borders[0] = this.lborderA;
 66     borders[1] = this.lborderB;
 67     borders[2] = this.lborderC;
 68     return borders;
 69   }
 70
 71   public boolean isTriangle(Triangle paramTriangle)
 72   {
 73     long l = System.currentTimeMillis();
 74     System.out.println(" isTriangle$impl_in_11");
 75     boolean bool = isTriangle$impl(paramTriangle);
 76     System.out.println(" isTriangle$impl_out_11");
 77     return bool;
 78   }
 79
 80   public String getType(Triangle paramTriangle)
 81   {
 82     long l = System.currentTimeMillis();
 83     System.out.println(" getType$impl_in_11");
 84     String str = getType$impl(paramTriangle);
 85     System.out.println(" getType$impl_out_11");
 86     return str;
 87   }
 88
 89   public long diffOfBorders(long paramLong1, long paramLong2)
 90   {
 91     long l1 = System.currentTimeMillis();
 92     System.out.println(" diffOfBorders$impl_in_11");
 93     long l2 = diffOfBorders$impl(paramLong1, paramLong2);
 94     System.out.println(" diffOfBorders$impl_out_11");
 95     return l2;
 96   }
 97
 98   public long[] getBorders()
 99   {
100     long l = System.currentTimeMillis();
101     System.out.println(" getBorders$impl_in_11");
102     long[] arrayOfLong = getBorders$impl();
103     System.out.println(" getBorders$impl_out_11");
104     return arrayOfLong;
105   }
106 }

 总结:

 Javassist 不仅是一个处理字节码的库,而且更因为它的另一项功能使得它成为试验 classworking 的很好的起点。这一项功能就是:可以用 Javassist 改变 Java 类的字节码,而无需真正了解关于字节码或者 Java 虚拟机(Java virtual machine JVM)结构的任何内容。

用Javassist  API的方法,对插桩程序用ctclass、classpool、CtMethod等方法进行方法插桩,然后生成第二个插桩程序的class文件,此时run这个class文件就会显示插入的语句要输出的内容。

时间: 2025-01-18 08:31:12

Javassist进行方法插桩的相关文章

Javsssist用InsertAt()方法对语句插桩

基于上一篇的方法插桩,这一篇则是进一步的对每行的语句进行插桩. 对于存在分支的方法(例如if(){}else{}),对方法插桩的方法是不能够全部涉及到的.所以要对程序的每条语句进行插桩. 插入什么语句呢?可以插入包括以下的内容: 1‘ classname 2’ linenumber 此时需要用到javassist  api里的类CtMethod的方法insertAt(),对程序的每条语句进行插桩. 逻辑思想就是:读取class文件,对类的方法进行获取,读取每个类的行号范围,在每个行号前用inse

zorka源码解读之通过beanshell进行插桩的流程

zorka中插桩流程概述 1.在SpyDefinition中配置插桩属性,将SpyDefinition实例提交给插桩引擎.2.SpyDefinition实例中包含了插桩探针probes,probe插入到方法中,对方法的执行进行监控.方法的插入阶段主要包括三个:开始阶段(entry),返回阶段(return),异常阶段(error).每个probe会根据其指定的阶段对方法插桩,捕获其中的数据,比如当前时间.方法参数.方法内部的变量等.probe捕获的数据会封装为SpyRecord实例(Symbol

介绍自己的一个Android插桩热修复框架项目QuickPatch

QuickPatch项目地址:https://gitee.com/egg90/QuickPatch 和 https://github.com/eggfly/QuickPatch 同步更新 类似于美团的Robust插桩热修复,但是代码可读性比较强,还在继续完善,todo list在项目README里 特性:基于函数插桩,兼容性好(Android版本升级不需要做修改),支持热更新无需重启app,参考了美团的Robust插桩热修复框架,精简了很多实现细节,代码可读性高 一句话原理 简单地讲,就是通过编

ASM字节码插桩

个人博客 http://www.milovetingting.cn ASM字节码插桩 前言 热修复的多Dex加载方案中,对于5.0以下的系统存在CLASS_ISPREVERIFIED的问题,而解决这个问题的一个方案是:通过ASM插桩,在类的构造方法里引入一个其它dex里的类,从而避免被打上CLASS_ISPREVERIFIED标签.热修复可以参考其它资料或者前面写的一篇文章.本文主要介绍ASM插桩,主要参考 https://juejin.im/post/5c6eaa066fb9a049fc042

关于smali插桩

????虽说是老生常谈的东西了,稍微记录一下. ????我觉得最重要的就是寄存器的问题了,如果需要额外的寄存器,要在smali函数的最前面将寄存器数量增加到需要的数量. ????在smali代码中,寄存器有两种表示方式,一种是v命名法,一种是p命名法.在v命名法中,从v0开始依次表示局部变量和参数.在p命名法中,从p0开始表示参数,从v0开始表示局部变量.以p命名法为例,在smali函数的开头,.locals n则表示可以使用v0到vn-1表示局部变量,因此如果插桩的时候需要额外的寄存器,则将.

javassist获得方法参数名称

举例,方法get(String name, long id),目的想获得name,id这两个名称: 初衷:做日志拦截的时候,比如拦截了方法get(String name, long id),想在日志表中记录成姓名:admin,主键:123, 就需要获取方法的参数名称,以便将参数名称翻译成对应的中文名,比如name对应姓名,id对应主键 package cn.sniper.reflect.utils; import java.util.ArrayList; import java.util.Has

Smali插桩打日志

一.smali目录下新建crack.smali,内容如下: .class public Lcrack; .super Ljava/lang/Object; .source "crack.java" .method public static log1(Ljava/lang/String;)V #打印出info 1字符串 .locals 1 .prologue const-string v0, "crack_log_str" invoke-static {v0, p0

红米3 Flyme5.1.7.4插桩适配更新

ROM介绍: 1.基于xda论坛CM12.1 20160612版本适配. 2.更新最新Flyme源码至2016年7月4日. 3.修复本地音乐FC,视频FC,手机管家FC,频繁的随机重启. 4.精简部分无用自带,未添加任何推广. 5.暂无其他BUG. ROM下载地址: 链接: http://pan.baidu.com/s/1i5qCUrj 密码: wgbi 第三方刷机REC(TWRP 3.0.2): http://pan.baidu.com/s/1qXX1NCo ROOT单刷卡刷包(SuperSU

Java学习-025-类名或方法名应用之二 -- 统计分析基础

前文讲述了类名或方法的应用之一调试源码,具体请参阅:Java学习-025-类名或方法名应用之一 -- 调试源码 此文主要讲述类名或方法应用之二统计分析,通过在各个方法中插桩(调用桩方法),获取方法的调用关系.通过调用关系,我们可以统计出被调用次数比较多的方法,同时也可以构建全系统调用关系链:通过操作重要业务流程,可以统计组成重要业务流程的主要方法,加强相应的单元测试.功能.安全.性能等方面的测试.对于软件产品质量控制存在非凡的意义. 下面构建的演示示例调用关系如下所示: GetClassMeth