目录

Java插桩工具

简介

目录

源代码级别

ClassPool

CtClass

Loader

CtField

CtMethod

$符号含义

使用步骤

举例

项目结构1

代码1

项目结构2

代码2

字节码级别

ClassFile

FieldInfo

MethodInfo

CodeAttribute

CodeIterator

举例

参考


Java插桩工具

字节码的抽象级别 描述 例子
低级 库需要直接在字节码级别上进行操作。通常,它们提供大多数功能丰富的功能,但与其他字节码操作工具相比,它们的使用也最复杂。

ASM(ASM –主页)

BCEL(https://commons.apache.org/proper/commons-bcel/ )

中级水平 库提供了字节码的某种抽象级别,并简化了其修改。例如,代替修改字节码,可以使用类似于Java的语法进行更改,然后将其编译为字节码,然后由使用的库修改为原始字节码。通常,它们缺少修改后的代码验证的功能-这意味着,错误可能在修改准备过程中被忽略,然后在运行时被发现。 Javassist(jboss-javassist的Javassist)
高水平 库使用高级指令进行操作,并且通常配备有用于语法验证的工具集。不幸的是,从修改后的字节码进行的最高抽象化通常会导致某些功能的丧失,这些功能仅在直接修改字节码时可用。

AspectJ(AspectJ项目)

CGLib(CGLib GitHub,基于ASM)

简介

Javassist (JAVA programming Assistant,Java编程助手) 是一个用Java编辑字节码的类库。它使Java程序可以在运行时定义新类,并在JVM加载它时修改类文件。

与其他类似的字节码编辑器不同,Javassist提供两个级别的API:源代码级别和字节码级别。

如果使用源代码级API,则可以在不了解Java字节码规范的情况下编辑类文件。整个API仅使用Java语言的词汇表进行设计。甚至可以以源文本的形式指定插入的字节码。Javassist可以即时对其进行编译。

另一方面,字节码级API允许用户像其他编辑器一样直接编辑类文件。

我们常用到的动态特性主要是反射,在运行时查找对象属性、方法,修改作用域,通过方法名称调用方法等。在线的应用不会频繁使用反射,因为反射的性能开销较大。其实还有一种和反射一样强大的特性,但是开销却很低,它就是Javassist。

类似的技术还有:bcel,asm等,他们相对于Javassit,偏向底层,效率较高,但编码难度更高(需要了解JVM指令)。

Javassist是Jboss的一个子项目,其特点是简单:不需要了解底层JVM指令,直接用Java代码编写,容易理解,并且现在生成代码效率和以上两种技术相差已经很小。目前,最新版本 3.27.0-GA (2020年03月19日).

目录

.
├── build.xml
├── javassist.jar
├── License.html
├── pom.xml
├── Readme.html
├── README.md
├── sample
│   ├── duplicate
│   ├── evolve
│   ├── hotswap
│   ├── preproc
│   ├── reflect
│   ├── rmi
│   ├── Test.java
│   └── vector
├── src
│   ├── main
│   └── test
└── tutorial
    ├── brown.css
    ├── tutorial2.html
    ├── tutorial3.html
    └── tutorial.html

  • sample  样例
  • src         源代码
  • tutorial   教程
  • Readme.html 自述文件。
  • License.html  许可证文件
  • tutorial 教程目录
  • javassist.jar  jar文件(类文件)
  • src        源代码目录

源代码级别

ClassPool

CtClass对象的容器。

  1. getDefault (): 返回默认的ClassPool ,单例模式,一般通过该方法创建我们的ClassPool;
  2. appendClassPath(ClassPath cp), insertClassPath(ClassPath cp) : 将一个ClassPath加到类搜索路径的末尾位置或插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类问题;
  3. importPackage(String packageName):导入包;
  4. makeClass(String classname):创建一个空类,没有变量和方法,后序通过CtClass的函数进行添加;
  5. get(String classname)、getCtClass(String classname) : 根据类路径名获取该类的CtClass对象,用于后续的编辑。

CtClass

  1. debugDump;String类型,如果生成。class文件,保存在这个目录下。
  2. setName(String name):给类重命名;
  3. setSuperclass(CtClass clazz):设置父类;
  4. addField(CtField f, Initializer init):添加字段(属性),初始值见CtField;
  5. addMethod(CtMethod m):添加方法(函数);
  6. toBytecode(): 返回修改后的字节码。需要注意的是一旦调用该方法,则无法继续修改CtClass
  7. toClass(): 将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的CtClass
  8. writeFile(String directoryName):根据CtClass生成 .class 文件;
  9. defrost():解冻类,用于使用了toclass()、toBytecode、writeFile(),类已经被JVM加载,Javassist冻结CtClass后;
  10. detach():避免内存溢出,从ClassPool中移除一些不需要的CtClass。

Loader

类加载器

  1. loadClass(String name):加载类

CtField

字段

  1. CtField(CtClass type, String name, CtClass declaring) :构造函数,添加字段类型,名称,所属的类;
  2. CtField.Initializer constant():CtClass使用addField时初始值的设置;
  3. setModifiers(int mod):设置访问级别,一般使用Modifier调用常量。

CtMethod

方法

  1. insertBefore(String src):在方法的起始位置插入代码;
  2. insertAfter(String src):在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;
  3. insertAt(int lineNum, String src):在指定的位置插入代码;
  4. addCatch(String src, CtClass exceptionType):将方法内语句作为try的代码块,插入catch代码块src;
  5. setBody(String src):将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除;
  6. setModifiers(int mod):设置访问级别,一般使用Modifier调用常量;
  7. invoke(Object obj, Object... args):反射调用字节码生成类的方法。

对于setBody $0代表this $1、$2、...代表方法的第几个参数

setBody("{$0.name = $1;}");

$符号含义

$符号含义
符号 含义
$0, $1, $2, ...   this,第几个参数
$args 参数列表. $args的类型是Object[].
$$ 所有实参.例如, m($$) 等价于 m($1,$2,...)
$cflow(...) cflow变量
$r 结果类型. 用于表达式转换.
$w 包装类型. 用于表达式转换.
$_ 结果值
$sig java.lang.Class列表,代表正式入参类型
$type java.lang.Class对象,代表正式入参值.
$class java.lang.Class对象,代表传入的代码段.

使用步骤

使用步骤

举例

新建Java项目,导入javassist.jar。

项目结构1

项目结构

代码1

Main无所谓,是我建项目时选择了Hello World模板

Test.java

package bupt.edu.cn;
import javassist.*;import java.lang.reflect.Method;public class Test {public static void createStudent() throws  Exception {ClassPool pool = ClassPool.getDefault();CtClass cc = pool.makeClass("E:\\Workspace\\IDEA_workspace\\JavassistTest\\src\\bupt.edu.cn.Student");// 字段名为nameCtField param = new CtField(pool.get("java.lang.String"),"name", cc);// 访问级别是 privateparam.setModifiers(Modifier.PRIVATE);// 初始值是 "Frankyu"cc.addField(param, CtField.Initializer.constant("Frankyu"));// 生成 getter、setter 方法cc.addMethod(CtNewMethod.setter("setName", param));cc.addMethod(CtNewMethod.getter("getName", param));// 添加无参的构造函数CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);cons.setBody("{name = \"yubo\";}");cc.addConstructor(cons);// 5. 添加有参的构造函数cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);// $0=this / $1,$2,$3... 代表方法参数cons.setBody("{$0.name = $1;}");cc.addConstructor(cons);// 6. 创建一个名为printName方法,无参数,无返回值,输出name值CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);ctMethod.setModifiers(Modifier.PUBLIC);ctMethod.setBody("{System.out.println(name);}");cc.addMethod(ctMethod);//这里会将这个创建的类对象编译为.class文件cc.writeFile("");}public static void usingStudent() throws Exception{ClassPool pool = ClassPool.getDefault();CtClass cc = pool.getCtClass("E:\\Workspace\\IDEA_workspace\\JavassistTest\\src\\bupt.edu.cn.Student");// 实例化Object student = cc.toClass().newInstance();// 设置值Method setName = student.getClass().getMethod("setName", String.class);setName.invoke(student, "junjie");// 输出值Method execute = student.getClass().getMethod("printName");execute.invoke(student);}public static void main(String[] args) throws Exception{createStudent();usingStudent();}
}

注意:.class文件的绝对路径需要修改

Student.class

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//package E:\Workspace\IDEA_workspace\JavassistTest\src\bupt.edu.cn;public class Student {private String name = "Frankyu";public void setName(String var1) {this.name = var1;}public String getName() {return this.name;}public Student() {this.name = "yubo";}public Student(String var1) {this.name = var1;}public void printName() {System.out.println(this.name);}
}

结果

项目结构2

项目结构

代码2

Student.java

package bupt.edu.cn;import java.util.Random;public class Student {public int getAge() {return age;}public void setAge(int age) {this.age = age;}private int age;public int getGrade() {return grade;}public void setGrade(int grade) {this.grade = grade;}private int grade;@Overridepublic String toString() {return "Student{" +"age=" + age +", grade=" + grade +'}';}public void display() {System.out.println(this.toString());}public void learn() throws Exception{
//        long start = System.currentTimeMillis();Random rand = new Random();int time =  rand.nextInt(1000)+3000;Thread.sleep(time);
//        long end = System.currentTimeMillis();
//        System.out.println(end-start);}public static void main(String[] args) {Student s  = new Student();s.display();try{s.learn();}catch (Exception e){}}
}

Monitor.java

import bupt.edu.cn.Student;
import javassist.*;public class Monitor {final static ClassPool pool = ClassPool.getDefault();final static String classname = "bupt.edu.cn.Student";public void studentLearnMonitor() throws Exception{CtClass ss = pool.getCtClass(classname);CtClass.debugDump="./dump";String methodname = "learn";CtMethod learn_ori = ss.getDeclaredMethod(methodname);//拷贝一份learn方法CtMethod learn_cp = CtNewMethod.copy(learn_ori,learn_ori.getName()+"_cp",ss,null);//添加拷贝后的方法ss.addMethod(learn_cp);//修改learn方法:原代码前后添加时间String src = "{"+"long start = System.currentTimeMillis();" +learn_ori.getName()+"_cp($$);"+"long end = System.currentTimeMillis();"+"System.out.println(end-start);"+"}";learn_ori.setBody(src);ss.toClass();//生成.class文件,主要用于调试,查看是否有代码片段被忽略//ss.writeFile();Student s = new Student();s.learn();}public void studentDisplayMonitor() throws Exception{CtClass ss = pool.getCtClass(classname);CtClass.debugDump="./dump";//添加字段nameCtField param = new CtField(pool.get("java.lang.String"),"name", ss);// 访问级别是 privateparam.setModifiers(Modifier.PRIVATE);// 初始值是 "Frankyu"ss.addField(param, CtField.Initializer.constant("Frankyu"));String methodname = "display";CtMethod display = ss.getDeclaredMethod(methodname);String src = "{"+"System.out.println($0.age);" +"$0.name=\"frankyu\";" +"System.out.println($0.name);"+"}";display.insertBefore(src);if(true){System.out.println("Hello javassist");}src ="if(true){" +"System.out.println(\"Hello javassist\");" +"}";display.insertAt(7,src);src = "{"+"System.out.println($0.grade);"+"}";display.insertAfter(src);ss.toClass();//ss.writeFile();Student s = new Student();s.display();}public static void main(String[] args) throws Exception{Monitor m = new Monitor();// 由于类冻结问题,两个方法不可同时调用
//            m.studentLearnMonitor();m.studentDisplayMonitor();}}

studentLearnMonitor对learn方法插桩,通过拷贝的方式,使start和end属于同一个代码块,解决不同代码块之间变量无法使用问题。

studentDisplayMonitor对display方法插桩,演示了添加变量,访问变量,修改变量,方法前插,后插,任意位置插。

learn插桩结果

display插桩结果

调用studentDisplayMonitor时,dump目录下,Student.class部分代码

    public void display() {System.out.println(this.age);this.name = "frankyu";System.out.println(this.name);if (true) {System.out.println("Hello javassist");}System.out.println(this.toString());Object var2 = null;System.out.println(this.grade);}

调用studentLearnMonitor时,dump目录下,Student.class部分代码

    public void learn() throws Exception {long var1 = System.currentTimeMillis();this.learn_cp();long var3 = System.currentTimeMillis();System.out.println(var3 - var1);}

字节码级别

ClassFile

  1. getFields():返回字段列表;
  2. addField(FieldInfo finfo):添加字段;
  3. addMethod(MethodInfo minfo) :添加方法;
  4. getMethod(String name):根据方法名返回MethodInfo对象;

FieldInfo

字段

  1. setAccessFlags(int acc):设置访问级别,通过类AccessFlag调用它的常量PUBLIC等。

MethodInfo

方法

  1. getCodeAttribute():返回CodeAttribute对象。

CodeAttribute

代码属性

  1. iterator():返回代码迭代器

CodeIterator

代码指令迭代器

  1. void begin()
    移到第一个指令处.
  2. void move(int index)
    移到指定索引处
  3. boolean hasNext()
    如果存在指令的话,返回true
  4. int next()
    返回下一个指令的索引
    需要注意的是,此方法并不会返回下一个指令的操作码
  5. int byteAt(int index)
    返回指定索引处的无符号8bit位长值.
  6. int u16bitAt(int index)
    返回指定索引处的无符号16bit位长值.
  7. int write(byte[] code, int index)
    在指定索引处写入字节数组.
  8. void insert(int index, byte[] code)
    在指定索引处写入字节数组,其他字节码的offset等将会自适应更改。

举例

参考

javassist官网

javassist的Github

javassist在线API手册

javassist参考博客

更多内容查看:网络安全-自学笔记

喜欢本文的请动动小手点个赞,收藏一下,有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。如果您感觉有所收获,自愿打赏,可选择支付宝18833895206(小于),您的支持是我不断更新的动力。

java插桩-javaassist相关推荐

  1. java 插桩 工具_一个基于Eclipse的通用Java程序插桩工具.pdf

    第38卷第7期 计算机科学 V01.38NO.7 Science 2011 2011年7月 Computer July 一个基于Eclipse的通用Java程序插桩工具 郑晓梅 (南京中医药大学信息技 ...

  2. java ast 添加注解_基于AST的组件化自动插桩方案

    本文将带你实现一个一百多行代码实现的自动化插桩方案,解决组件化子模块的初始化和路由器的自动注册,支持多种类型的插桩.支持前插后插.支持插入代码的优先级设置.我们将使用编辑器的API来操作AST实现代码 ...

  3. JVM插桩之二:Java agent基础原理

    Javaagent只要作用在class被加载之前对其加载,插入我们需要添加的字节码. Javaagent面向的是我们java程序员,而且agent都是用java编写的,不需要太多的c/c++编程基础, ...

  4. Java成神之路——javaAgent(插桩,attach)

    javaAgent Javaagent 是什么? javaAgent运行类加载器在加载类之前对类做出动态的修改. 运行java命令执行时添加参数 -javaagent指定打包好的agent的jar即可 ...

  5. sonar覆盖率怎么统计的_实战|Java 测试覆盖率 Jacoco插桩的不同形式总结和踩坑记录(上)...

    本文为霍格沃兹测试学院优秀学员关于 Jacoco 的小结和踩坑记录.测试开发进阶学习,文末加群. 一.概述 测试覆盖率是老生常谈的话题.因为我测试理论基础不是很好,这里就不提需求.覆盖率等内容,直奔主 ...

  6. mvn exec: java_实战|Java 测试覆盖率 Jacoco插桩的不同形式总结和踩坑记录(下)

    本文为霍格沃兹测试学院优秀学员关于 Jacoco 的小结和踩坑记录.测试开发进阶学习,文末加群. 六.注意事项汇总 修改 JAVA_OPTS 参数时,如果位置不对,可能造成代理无法启动. java - ...

  7. 语法树的Java代码自动化插桩

    代码插桩是测试和定位问题的常用手段,通过在代码对应位置插入相应的代码("桩"),来打印或收集我们所需要的数据. 自动化插桩,也就是在代码的特定位置,自动的插入我们需要的一行或几行代 ...

  8. Java ASM框架与字节码插桩的常见用法(生成类,修改类,方法插桩,方法注入)

    前言 ASM 是一款读写Java字节码的工具,可以达到跳过源码编写,编译,直接以字节码的形式创建类,修改已经存在类(或者jar中的class)的属性,方法等. 通常用来开发一些Java开发的辅助框架, ...

  9. 字节码插桩之Java Agent

    字节码插桩之Java Agent 本篇文章将详细讲解有关Java Agent的知识,揭开它神秘的面纱,帮助开发人员了解它的黑魔法,帮助我们完成更多业务需求 What is Java Agent Jav ...

  10. Android字节码插桩

    什么是字节码插桩 字节码插桩就是在构建的过程中,通过修改已经编译完成的字节码文件,也就是class文件,来实现功能的添加. 简单来讲,我们要实现无埋点对客户端的全量统计.这里的统计概括的范围比较广泛, ...

最新文章

  1. NanoPi NEO Air使用十二:使用自带的fbtft驱动点亮SPI接口TFT屏幕,ST7789V
  2. SQL like 模糊查询
  3. python_ re模块学习
  4. [转] ios学习--openURL的使用方法
  5. javascript select option对象总结
  6. 阿里巴巴如何改善开发人员在 K8s 上的体验?
  7. MATLAB字符串和ASCII码的转换
  8. thinkjs——空对象判断
  9. 5. 公元二OO七年
  10. [Deep Learning] 神经网络基础
  11. 用 IIS 进行ASP.NET 成员/角色管理(1):安全和配置概述
  12. JS库之Highlight.js高亮代码
  13. H5静态网页设计与制作_川西旅游网设计作品(HTML+CSS+jQuery)
  14. java爬取新浪微博带有“展开全文”的完整微博文本
  15. stc15系列c语言pwm编程,分享一下stc15单片机的PWM波程序
  16. iphone版 天行skyline_Mega Zipline 3D Skyline Adventure Simulator加速器
  17. 编码:8421 BCD码(彻底弄懂+6是什么意思,为什么要加6)
  18. css系列-ol,ul
  19. 如何下载原生纯净的Windows系统?(MSDN)
  20. 2018-11-16学习笔记 读论文Advantages of high quality SWIR bands for ocean colour processing: Examples from

热门文章

  1. 免费会员管理管理系统
  2. 简单实现根据excel表生成sql 建表语句
  3. 英语语音识别软件_识别英语语音的软件_英文语音识别软件 - 云+社区 - 腾讯云...
  4. 精心整理的10套最美Web前端新年特效---提前祝大家新年快乐(文末送书)
  5. 射击类游戏html代码,超简单射击游戏
  6. MODIS,Himwari-8遥感数据介绍
  7. HashMap底层原理实现源码分析
  8. JAVA 正则表达式(大全)
  9. usb驱动设备该设备无法启动 代码10
  10. java如何动态添加数组数据_Java动态数组添加数据的方法与应用示例