java插桩-javaassist
目录
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对象的容器。
- getDefault (): 返回默认的
ClassPool
,单例模式,一般通过该方法创建我们的ClassPool; - appendClassPath(ClassPath cp), insertClassPath(ClassPath cp) : 将一个
ClassPath
加到类搜索路径的末尾位置或插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类问题; - importPackage(String packageName):导入包;
- makeClass(String classname):创建一个空类,没有变量和方法,后序通过CtClass的函数进行添加;
- get(String classname)、getCtClass(String classname) : 根据类路径名获取该类的CtClass对象,用于后续的编辑。
CtClass
类
- debugDump;String类型,如果生成。class文件,保存在这个目录下。
- setName(String name):给类重命名;
- setSuperclass(CtClass clazz):设置父类;
- addField(CtField f, Initializer init):添加字段(属性),初始值见CtField;
- addMethod(CtMethod m):添加方法(函数);
- toBytecode(): 返回修改后的字节码。需要注意的是一旦调用该方法,则无法继续修改CtClass;
- toClass(): 将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的
toClass
方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的CtClass; - writeFile(String directoryName):根据CtClass生成
.class
文件; - defrost():解冻类,用于使用了toclass()、toBytecode、writeFile(),类已经被JVM加载,Javassist冻结CtClass后;
- detach():避免内存溢出,从ClassPool中移除一些不需要的CtClass。
Loader
类加载器
- loadClass(String name):加载类
CtField
字段
- CtField(CtClass type, String name, CtClass declaring) :构造函数,添加字段类型,名称,所属的类;
- CtField.Initializer constant():CtClass使用addField时初始值的设置;
- setModifiers(int mod):设置访问级别,一般使用Modifier调用常量。
CtMethod
方法
- insertBefore(String src):在方法的起始位置插入代码;
- insertAfter(String src):在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;
- insertAt(int lineNum, String src):在指定的位置插入代码;
- addCatch(String src, CtClass exceptionType):将方法内语句作为try的代码块,插入catch代码块src;
- setBody(String src):将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除;
- setModifiers(int mod):设置访问级别,一般使用Modifier调用常量;
- 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方法插桩,演示了添加变量,访问变量,修改变量,方法前插,后插,任意位置插。
调用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
类
- getFields():返回字段列表;
- addField(FieldInfo finfo):添加字段;
- addMethod(MethodInfo minfo) :添加方法;
- getMethod(String name):根据方法名返回MethodInfo对象;
FieldInfo
字段
- setAccessFlags(int acc):设置访问级别,通过类AccessFlag调用它的常量PUBLIC等。
MethodInfo
方法
- getCodeAttribute():返回CodeAttribute对象。
CodeAttribute
代码属性
- iterator():返回代码迭代器
CodeIterator
代码指令迭代器
void begin()
移到第一个指令处.void move(int index)
移到指定索引处boolean hasNext()
如果存在指令的话,返回trueint next()
返回下一个指令的索引
需要注意的是,此方法并不会返回下一个指令的操作码int byteAt(int index)
返回指定索引处的无符号8bit位长值.int u16bitAt(int index)
返回指定索引处的无符号16bit位长值.int write(byte[] code, int index)
在指定索引处写入字节数组.void insert(int index, byte[] code)
在指定索引处写入字节数组,其他字节码的offset等将会自适应更改。
举例
参考
javassist官网
javassist的Github
javassist在线API手册
javassist参考博客
更多内容查看:网络安全-自学笔记
喜欢本文的请动动小手点个赞,收藏一下,有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。如果您感觉有所收获,自愿打赏,可选择支付宝18833895206(小于),您的支持是我不断更新的动力。
java插桩-javaassist相关推荐
- java 插桩 工具_一个基于Eclipse的通用Java程序插桩工具.pdf
第38卷第7期 计算机科学 V01.38NO.7 Science 2011 2011年7月 Computer July 一个基于Eclipse的通用Java程序插桩工具 郑晓梅 (南京中医药大学信息技 ...
- java ast 添加注解_基于AST的组件化自动插桩方案
本文将带你实现一个一百多行代码实现的自动化插桩方案,解决组件化子模块的初始化和路由器的自动注册,支持多种类型的插桩.支持前插后插.支持插入代码的优先级设置.我们将使用编辑器的API来操作AST实现代码 ...
- JVM插桩之二:Java agent基础原理
Javaagent只要作用在class被加载之前对其加载,插入我们需要添加的字节码. Javaagent面向的是我们java程序员,而且agent都是用java编写的,不需要太多的c/c++编程基础, ...
- Java成神之路——javaAgent(插桩,attach)
javaAgent Javaagent 是什么? javaAgent运行类加载器在加载类之前对类做出动态的修改. 运行java命令执行时添加参数 -javaagent指定打包好的agent的jar即可 ...
- sonar覆盖率怎么统计的_实战|Java 测试覆盖率 Jacoco插桩的不同形式总结和踩坑记录(上)...
本文为霍格沃兹测试学院优秀学员关于 Jacoco 的小结和踩坑记录.测试开发进阶学习,文末加群. 一.概述 测试覆盖率是老生常谈的话题.因为我测试理论基础不是很好,这里就不提需求.覆盖率等内容,直奔主 ...
- mvn exec: java_实战|Java 测试覆盖率 Jacoco插桩的不同形式总结和踩坑记录(下)
本文为霍格沃兹测试学院优秀学员关于 Jacoco 的小结和踩坑记录.测试开发进阶学习,文末加群. 六.注意事项汇总 修改 JAVA_OPTS 参数时,如果位置不对,可能造成代理无法启动. java - ...
- 语法树的Java代码自动化插桩
代码插桩是测试和定位问题的常用手段,通过在代码对应位置插入相应的代码("桩"),来打印或收集我们所需要的数据. 自动化插桩,也就是在代码的特定位置,自动的插入我们需要的一行或几行代 ...
- Java ASM框架与字节码插桩的常见用法(生成类,修改类,方法插桩,方法注入)
前言 ASM 是一款读写Java字节码的工具,可以达到跳过源码编写,编译,直接以字节码的形式创建类,修改已经存在类(或者jar中的class)的属性,方法等. 通常用来开发一些Java开发的辅助框架, ...
- 字节码插桩之Java Agent
字节码插桩之Java Agent 本篇文章将详细讲解有关Java Agent的知识,揭开它神秘的面纱,帮助开发人员了解它的黑魔法,帮助我们完成更多业务需求 What is Java Agent Jav ...
- Android字节码插桩
什么是字节码插桩 字节码插桩就是在构建的过程中,通过修改已经编译完成的字节码文件,也就是class文件,来实现功能的添加. 简单来讲,我们要实现无埋点对客户端的全量统计.这里的统计概括的范围比较广泛, ...
最新文章
- NanoPi NEO Air使用十二:使用自带的fbtft驱动点亮SPI接口TFT屏幕,ST7789V
- SQL like 模糊查询
- python_ re模块学习
- [转] ios学习--openURL的使用方法
- javascript select option对象总结
- 阿里巴巴如何改善开发人员在 K8s 上的体验?
- MATLAB字符串和ASCII码的转换
- thinkjs——空对象判断
- 5. 公元二OO七年
- [Deep Learning] 神经网络基础
- 用 IIS 进行ASP.NET 成员/角色管理(1):安全和配置概述
- JS库之Highlight.js高亮代码
- H5静态网页设计与制作_川西旅游网设计作品(HTML+CSS+jQuery)
- java爬取新浪微博带有“展开全文”的完整微博文本
- stc15系列c语言pwm编程,分享一下stc15单片机的PWM波程序
- iphone版 天行skyline_Mega Zipline 3D Skyline Adventure Simulator加速器
- 编码:8421 BCD码(彻底弄懂+6是什么意思,为什么要加6)
- css系列-ol,ul
- 如何下载原生纯净的Windows系统?(MSDN)
- 2018-11-16学习笔记 读论文Advantages of high quality SWIR bands for ocean colour processing: Examples from