字节码操作的手术刀-Javassist

Java字节码操作:从原理到实战应用 Java字节码作为JVM执行的中间指令集,是理解Java程序运行机制的关键。通过操作字节码可以实现代码增强、性能监控等高级功能,常见的工具包括ASM和Javassist。ASM提供指令级控制适合高性能场景,而Javassist的源码级API更易于快速开发。字节码技术在AOP、动态代理、测试框架等领域有广泛应用,例如实现无侵入的方法耗时监控。掌握字节码操作需要熟悉class文件格式和JVM指令集,同时要注意版本兼容性和性能优化。 阅读详情

Javassist

前面文章介绍的 ASM 入门门槛还是挺高的,需要跟底层的字节码指令打交道,优点是小巧、性能好。Javassist 是一个性能比 ASM 稍差但是使用起来简单很多的字节码操作库,不需要了解字节码指令,由东京工业大学的数学和计算机科学系的教授 Shigeru Chiba 开发.

Javassist是可以动态编辑Java字节码的类库。它可以在Java程序运行时定义一个新的类,并加载到JVM中;还可以在JVM加载时修改一个类文件。Javassist使用户不必关心字节码相关的规范也是可以编辑类文件的。

Javassist作用

  • 动态代理 Javassist可以在运行时生成代理类,从而实现AOP编程,比如在方法调用前后增加日志、权限控制等功能。

  • 动态生成类 Javassist可以在运行时动态地生成新的类,这个特性在一些框架中被广泛使用。

  • 类文件编辑 Javassist可以在运行时修改类的字节码,从而实现一些功能,比如动态修改类的字段、方法等。

  • 字节码分析 Javassist可以对字节码进行分析,提取类的结构信息,比如类名、字段、方法等。

核心 API

alt 在 Javassist 中每个需要编辑的 class 都对应一个 CtClass,CtClass 的含义是编译时的类("compile time class"),这些类会存储在 ClassPool 中,ClassPool 是一个容器,存储了一系列 CtClass 对象。

Javassist 的 API 与 Java 反射 API 比较相似,Java 类包含的字段、方法在 Javassist 中分别对应 CtField 和 CtMethod,通过 CtClass 对象就可以给类新增字段、修改方法了。

alt

CtMethod

对应一个 class 文件,CtMethod 对应一个方法,ClassPool 是 CtClass 的集合。

CtClass 的 writeFile 可以将生成的 class 文件输出到指定目录中。新建一个 Hello 类可以用下面的代码:

ClassPool cp = ClassPool.getDefault();
CtClass ct = cp.makeClass("ya.me.Hello");
ct.writeFile("./out");

运行上面的代码,out 目录下就生成了一个 Hello 类,内容如下:

package ya.me;

public class Hello {
    public Hello() {
    }
}

给已有类新增方法 有一个空的 MyMain 类,如下所示:

public class MyMain {

}

接下来用 Javassist 给它新增一个 foo 方法。

ClassPool cp = ClassPool.getDefault();
cp.insertClassPath("/path/to/MyMain.class");
CtClass ct = cp.get("MyMain");
CtMethod method = new CtMethod(
        CtClass.voidType,
        "foo",
        new CtClass[]{CtClass.intType, CtClass.intType},
        ct);
method.setModifiers(Modifier.PUBLIC);
ct.addMethod(method);
ct.writeFile("./out");

查看生成 MyMain 类可以看到生成了 foo 方法

public void foo(int var1, int var2) {
}

修改方法体

CtMethod 提供了几个实用的方法来修改方法体:

  • setBody 方法来替换整个方法体,setBody 方法接收一段源代码字符串,Javassist 会将这段源码字符串编译为字节码,替换原有的方法体。
  • insertBefore、insertAfter:在方法开始和结束插入语句。
public int foo(int a, int b) {
    return a + b;
}

比如想把 foo 方法体修改为 "return 0;",可以这么修改:

...
CtMethod method = ct.getMethod("foo", "(II)I");
method.setBody("return 0;");
...

生成的修改过的 foo 方法如下:

public int foo(int var1, int var2) {
    return 0;
}   

如果想把 foo 方法体的 a + b; 修改为 "a * b",是不是可以用下面的代码:

method.setBody("return a * b;")

运行报错,提示找不到字段 a:

Exception in thread "main" javassist.CannotCompileException: [source error] no such field: a
 at javassist.CtBehavior.setBody(CtBehavior.java:474)
 at javassist.CtBehavior.setBody(CtBehavior.java:440)
 at JavassistTest2.main(JavassistTest2.java:17)
Caused by: compile error: no such field: a

这是因为源代码在 javac 编译以后,局部变量名字都被抹去了,只留下了类型和局部变量表的位置,比如上面的 a 和 b 对应局部变量表 1 和 2 的位置,位置 0 由 this 占用。

在 Javassist 中访问方法参数使用 $0 $1 ...,而不是直接使用变量名,把上面的代码改为:

method.setBody("return $1 * $2;");

生成新的 MyMain 类中 foo 方法已经变

public int foo(int var1, int var2) {
    return var1 * var2;
}

除了方法的参数,Javassist 定义了以 $ 开头的特殊标识符,如下表所示:

alt

下面来逐一介绍

  • $0 $1 $2 ... 方法参数
  • 0 等价于 this,如果是静态方法 $0 不可用,从 $1 开始依次表示方法参数
  • $args 参数
  • $args变量表示所有参数的数组,它是一个 Object 类型的数组,如果参数中有原始类型,会被转为对应的对象类型,比如上面 foo(int a, int b) 对应的 $args 为
new Object[]{ new Integer(a), new Integer(b) }
  • ?参数 ? 参数表示所有的参数的展开,参数直接用逗号分隔,
foo(?)

相当于:

foo($1, $2, ...)

  • $cflow $cflow 是 "control flow" 的缩写,这是一个只读的属性,表示某方法递归调用的深度。一个典型的使用场景是监控某递归方法执行的时间,只想记录一次最顶层调用的时间,可以使用 $cflow 来判断当前递归调用的深度,如果不是最顶层调用忽略记录时间。

比如下面的计算 fibonacci 的方法:

public long fibonacci(int n) {
    if (n <= 1) return n;
    else return fibonacci(n-1) + fibonacci(n-2);
}

如果只想在第一次调用的时候执行打印,可以用下面的的代码:

CtMethod method = ct.getMethod("fibonacci", "(I)J");
method.useCflow("fibonacci");
method.insertBefore(
        "if ($cflow(fibonacci) == 0) {" +
            "System.out.println(\"fibonacci init \" + $1);" +
        "}"
);

执行生成的 MyMain,可以看到只输出了一次打印:

java -cp /path/to/javassist.jar:. MyMain
fibonacci init 10

  • $_ 参数 CtMethod 的 insertAfter() 方法在目标方法的末尾插入一段代码。 $_ 来表示方法的返回值,在 insertAfter 方法可以引用。比如下面的代码:
method.insertAfter("System.out.println(\"result: \"  + $_);");

查看反编译生成的 class 文件:

public int foo(int a, int b) {
    int var4 = a + b;
    System.out.println(var4);
    return var4;
}

细心的读者看到这里会有疑问,如果是方法异常退出,它的方法返回值是什么呢?假如 foo 代码如下:

public int foo(int a, int b) {
    int c = 1 / 0;
    return a + b;
}

执行上的改写后,反编译以后代码如下:

public int foo(int a, int b) {
    int c = 1 / 0;
    int var5 = a + b;
    System.out.println(var5);
    return var5;
}

这种情况下,代码块抛出异常,是无法执行插入的语句的。如果想代码抛出异常的时候也能执行,就需要把 insertAfter 的第二个参数 asFinally 设置为 true:

method.insertAfter("System.out.println(\"result: \"  + $_);", true);

执行输出结果如下,可以看到已经输出了 "result: 0"

result: 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at MyMain.foo(MyMain.java:9)
        at MyMain.main(MyMain.java:6)

还有几个 Javassist 提供的内置变量($r等),用的非常少,这里不再介绍,具体可以查看 javassist 的官网。

小结

本文的内容主要介绍了 Javassist 这个非常广泛的字节码改写工具,详细介绍了它们的 API 和常见使用场景,后续我们将看到更多这两个工具的应用。

本文由 mdnice 多平台发布

Aspose Java 25.10 版本深度破解:基于字节码修改的实战指南 本文提供了Aspose Java 25.10版本基于字节码修改的实战指南。详细介绍了如何通过Javassist工具逆向分析并修改Aspose Cells、Words、Slides及PDF组件的许可证验证逻辑,从而实现绕过限制,并完成最终的打包与功能验证。 阅读详情

相关推荐

Java字节码操作指南:ASM核心原理与实战应用

Java字节码是Java虚拟机(JVM)执行的核心中间表示,它作为Java源代码与机器指令之间的桥梁,是实现'一次编写,到处运行'的关键。字节码操作技术允许开发者在运行时动态修改或生成类文件,突破Java语言层面的限制,为框架开发、性能监控和代码增强提供了底层支持。其技术价值在于能够实现AOP、动态代理、热部署等高级特性,广泛应用于性能监控工具、APM系统和各类开发框架中。本文以ASM这一高性能字节码操作库为例,深入解析其事件驱动的访问者模式设计,并通过方法注入、动态类生成等实战案例,展示如何利用字节码技术

weixin_30216561的博客 414

使用javassist修改类字节码时关于范型一个小坑

javassist作为一款低门槛的java字节码修改工具,允许用户以java代码的方式修改java类文件,无需对类文件的结构有深入的了解。 最近,在公司项目中使用了javassist,使用过程中遇到了一个和范型有关的小坑,特此记录。 代码如下: StringBuffer stringBuffer = new StringBuffer(); stringBuf...

kakaweb的博客 8249

Aspose.words 20.12破解实战:JDK17环境下的Word转PDF无水印解决方案

本文详细介绍了在JDK17环境下,针对Aspose.words 20.12版本实现Word转PDF无水印的实战解决方案。通过使用Javassist字节码工具,定位并修改许可证验证的核心方法,从而从根源上移除评估水印,生成纯净的PDF文档。该方法避免了使用不安全的破解版,提供了安全可控的技术路径。

weixin_29253711的博客 684

错误:javassist.CannotCompileException: [source error] no such class

错误:javassist.CannotCompileException: [source error] no such class 解决方案:可以尝试使用类的全限定名。原因可能是该类与你所修改的类不在一个jar包下

wu可达的博客 1万+

Java Agent与内存马技术解析:Agenst工具原理与实战应用

Java Agent是Java平台提供的一种强大机制,允许开发者在JVM运行时动态修改字节码,实现应用行为的监控与增强。其核心原理基于JVMTI(JVM Tool Interface)和Instrumentation API,通过ClassFileTransformer接口在类加载时进行字节码转换。这项技术在性能监控、故障诊断、热部署等领域具有重要价值,尤其在安全攻防场景中,它为实现无文件、高隐蔽性的内存驻留技术提供了基础。内存马(Memory Shell)正是基于此原理,将恶意逻辑以Filter、Serv

weixin_33841503的博客 408

【javassist】javassist.CannotCompileException: [source error] no such class

【javassist】使用javassist修改kafka源码AbstractCoordinator类原始的方法如下,中间的try catch是我加的。// 九师兄 if(this . coordinator!= null) {try {throw new IllegalArgumentException("消费组死掉");log . error("消费组死掉异常栈:" , e);

九师兄 1162

异常:javassist.CannotCompileException: [source error] getEveryUserNum

添加接口后dubbo启动失败,抛出的异常。各种调试、测试,没有发现代码的问题。 于是百度了一下,说可能是由于提供的接口以getXXX 开头,而且接口没有返回值,从而出现此种异常。因此尝试修改方法名称,再次启动,成功! 果然因为如此! 但是这是为什么呐? 原因在于dubbo暴露服务,将getxxx开头的方法,当做获取属性方法来进行编译,然后方法没有返回值,所以抛出异常。 贴上dubbo的动...

云兮莫愁的博客 1545

【大二组培训】2017年电子设计大赛H题总结(软件部分)

文章目录前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结 前言 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。 一、pandas是什么? 示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。 二、使用步骤 1.引入库 代码如下(示例): import numpy as np import pandas as pd import matplotlib.pyplot as plt im

HAIBARA的博客 3021

框架手写系列---javassist修改字节码方式,实现美团Robust热修复框架

本文用javassist方式,模拟美团Robust插件的前置处理:用插入代码的方式,针对apk中的每个方法都插入一段静态代码判断语句,用于控制是否启用热修复fix(也就是动态加载patch包到原apk中)。 一、插件的生成与依赖导入 1、用该文章中的方法:Android中Plugin插件工程的自动生成自动生成一个android的插件模板; 2、加入javassist依赖,加入依赖后的插件build的格式如下: dependencies{ compile gradleApi() ..

双斧狂暴战专栏 709

【UE 反射】反射的原理是什么?如何使用机制?

静态类型检查确保了类型的早期一致性和高效的代码执行,但减少了灵活性。动态类型检查提供了更大的灵活性,但增加了运行时的开销和潜在的错误。反射作为一种动态特性,无论是在静态类型语言还是动态类型语言中,都提供了强大的运行时类型操作能力,但也伴随着性能和安全性方面的权衡。通过理解这些联系,开发者可以在不同的编程语言和应用场景中选择适合的类型检查方式和反射机制,以实现所需的动态行为和灵活性,同时保证性能和安全性。

hhw_hhw的博客 6032

【实战案例】IBM V3500存储控制器故障诊断与更换全流程解析

本文详细解析了IBM Storwize V3500存储控制器发生故障后的完整处理流程。通过还原真实故障现场,介绍了如何利用服务助手精准定位缓存模块损坏的根源,并逐步指导完成控制器备件准备、核心部件移植更换、系统上线同步及全面功能验证的全过程,为存储运维人员提供了一份实用的实战指南。

weixin_29269441的博客 547

Java Agent 字节码增强技术到底有多野?

Java Agent(Java 代理)不是什么第三方框架,而是 JVM 亲生的底层机制。它的本质是一个特殊的 Jar 包。当你启动 Java 程序时,JVM 会在执行你写的main()方法之前,先去执行这个 Agent 里的特殊方法。这就好比在程序的“受精卵”阶段,你就把一个“间谍”植入了进去。Java Agent 字节码增强技术,赋予了架构师“运行时上帝视角”。优势:绝对的零侵入,业务团队毫无感知;能力通天,可以监控、诊断、甚至热修复线上 Bug(Arthasredefine指令)。代价。

郑龙飞 677

保姆级教程:在RK3568上搞定PR2100K+GC2385双摄(从DTS配置到HAL层补丁)

本文详细介绍了在RK3568平台上调试PR2100K+GC2385双摄系统的完整流程,从DTS配置到HAL层补丁。涵盖硬件连接、媒体控制器拓扑验证、Camera3配置、HAL层特殊补丁及性能优化等关键步骤,帮助开发者快速解决双摄调试中的常见问题,提升嵌入式视觉系统开发效率。

weixin_28724661的博客 195

Android进阶宝典 --- 从字节码插桩技术了解美团热修复

手写美团热修复robust框架

qq_33235287的博客 814

【计算机组成原理】课程设计四:控制器设计(二)

本文主要是介绍了计算机组成原理课程设计四其二,即控制器设计(二),属于计算机课程设计专栏

superiony的博客 1428

十一种室内定位传感器方案汇总介绍与对比(机器人、物联网领域)

室内定位传感器方案汇总 目录 室内定位传感器方案汇总 1、定位方案概述 1.1、内定位系统有最基本的5种算法: 1.2、常用的室内定位技术主要包括以下几种: 1.3、定位理论 1.4、不同的定位方案对比 2、各种定位方案详细介绍 2.1、单线激光定位 2.2、多线激光定位: 2.3、视觉定位: 2.4、基于射频识别(RFID)技术室内定位 2.4.1、RFID标签(不涉及...

JK-Cool的博客 3万+

你真以为 Java 只能“写代码跑起来”?那字节码为什么还能被你改到“换了个人”?

纯注解处理器(Annotation Processor)本职是生成源码/资源,它并不擅长直接“改已经编译好的 .class”。方案:注解处理器负责“收集信息”,构建阶段任务负责“字节码注入”。这样既符合 Java 编译链的习惯,也不容易跟编译器打架。@Target(ElementType . METHOD) @Retention(RetentionPolicy . CLASS) // 只要进到 class 文件即可 public @interface Timed {

欢迎来到菜鸟不学编程的博客 149

码农新宠大对决:GitHub Copilot、通义灵码和CodeLlama谁主沉浮?

本文对三款主流AI编程工具GitHub Copilot、通义灵码和CodeLlama进行了全面对比分析。GitHub Copilot在多语言支持和代码质量上表现突出;通义灵码在中文理解和阿里云生态集成方面优势明显;CodeLlama则以开源可定制见长。文章从代码生成、理解能力、多语言支持、使用体验等维度展开评测,并结合实际案例说明不同开发者群体的适用选择。建议新手可优先考虑通义灵码,经验开发者适合GitHub Copilot,而有定制需求的企业团队则可选择CodeLlama。随着AI技术发展,这些工具将持续

远程部署调试 运行安装 项目调试 二次开发 项目技术新 持续迭代 部分源码免费分享 675

Java字节码逆向工程实战:从.class文件结构到动态代理与Lambda底层原理

字节码是Java虚拟机(JVM)执行的指令集,是连接Java源代码与机器执行的中间层。其原理基于栈帧结构,通过操作数栈和局部变量表完成计算与方法调用,这种设计实现了跨平台的可移植性。掌握字节码分析的技术价值在于,它能帮助开发者进行深度调试、性能优化与安全审计,例如精准定位线上环境中的NoSuchMethodError等依赖冲突问题。在应用场景上,无论是理解Spring框架的动态代理机制,还是剖析Java 8中Lambda表达式通过invokedynamic指令的实现,都离不开对字节码的逆向分析。本文将以.c

weixin_30938149的博客 348

24、虚拟地理信息系统:技术与应用的全面解析

本文全面解析了虚拟地理信息系统(虚拟GIS)的技术架构与应用前景。文章从地理空间数据的增长出发,探讨了虚拟GIS在地形、建筑、天气等多维度数据的交互式可视化技术,重点介绍了全球层次结构、多分辨率模型、缓存分页机制及交互导航方法。系统已应用于天气分析、城市规划、应急响应等领域,并展望了未来在数据处理、模拟操作、移动可视化及智能城市、文化遗产保护等方向的发展路径与技术挑战。

v4w5x6的博客 88

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

深入解析LinkAgent:Java无侵入监控与全链路压测的字节码增强实践

Java Agent技术是实现应用无侵入监控和性能分析的关键底层机制,它通过JVM字节码增强技术,在类加载阶段动态修改程序行为,从而实现对运行时数据的采集和动态控制。其核心原理基于Java Instrumentation API,允许开发者在无需修改源代码的情况下,向目标方法插入自定义逻辑,如性能指标采集、调用链追踪和故障注入。这一技术为应用性能监控(APM)、全链路压测和混沌工程等场景提供了基础支撑,显著降低了系统可观测性的接入成本。在实际工程中,字节码增强框架如LinkAgent,通过模块化设计将核心通

weixin_30561425的博客 313

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

JavaAgent插桩技术:从字节码操作到运行时安全防线的实战演进

在Java应用性能监控与故障排查领域,字节码操作是实现深度可观测性的核心技术。其原理基于Java Instrumentation API,允许在类加载时动态修改字节码,从而实现对运行时行为的无侵入式监控与增强。这项技术的核心价值在于,它能够将传统的“黑盒”应用转变为“白盒”,为性能分析、链路追踪和安全防护提供底层支持。在应用场景上,它最初聚焦于性能监控,通过方法耗时统计和调用链追踪解决系统可见性问题;随后演进至运行时诊断,支持动态热修复和问题定位;如今更拓展至运行时应用安全防护(RASP),在SQL注入、命

weixin_34122810的博客 388

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

如何无需解压快速编辑JAR文件?JarEditor插件完整使用指南

**JarEditor**是一款专为IntelliJ IDEA开发的高效插件,它允许开发者直接编辑JAR文件内的类和资源文件,无需繁琐的解压和重新打包流程。无论是修改Java代码、调整资源配置,还是处理SpringBoot项目中的嵌套JAR,这款工具都能让你的开发效率提升3倍以上! ## 为什么选择JarEditor? 传统JAR文件编辑需要经历"解压→修改→重新打包"的冗长流程,而JarEd...

gitblog_01011的博客 1041

Gradle Receipes (AGP-7.3) & AGP 使用指南

本文所有代码是基于 https://github.com/android/gradle-recipes agp-7.3分支版本的官方 sample。由于缺乏清晰易懂的说明文档 ,有些sample代码即便能够跑通,但在细节方便仍然不是很容易让人理解。从 AGP 7.0 开始,AGP的API相较AGP 7.0 以前的变化较大,且迭代更新比较频繁,目前官方最新已迭代至8.0,请注意对照你所使用的AGP版本。但是据官方的说法是, 另外Gradle插件和Android Studio的版本之间存在着兼容要求,请参考这里

川峰的博客 2347
上一篇: 教你用 Java 字节码做点有趣的事 ——无痛破解 Java 系软件
下一篇: 从Mysql架构看一条查询sql的执行过程
yue_stack
博客等级 码龄3年 6粉丝 43原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值