首页> 中国专利> 一种Java字节码修改方法与装置

一种Java字节码修改方法与装置

摘要

本发明提出了一种Java字节码修改方法与装置,既降低搭建编译环境的复杂度,又不需要开发人员了解字节码协议的细节,它的输入是两个字节码文件:待修改的字节码文件,目标字节码文件。工具内部会解析并比较两个字节码文件,然后将目标字节码文件的不同之处增量合并到待修改的字节码文件,最终输出修改后的字节码文件。

著录项

  • 公开/公告号CN113867733A

    专利类型发明专利

  • 公开/公告日2021-12-31

    原文格式PDF

  • 申请/专利权人 烽火通信科技股份有限公司;

    申请/专利号CN202111148367.4

  • 发明设计人 万金利;

    申请日2021-09-29

  • 分类号G06F8/41(20180101);G06F8/53(20180101);

  • 代理机构44372 深圳市六加知识产权代理有限公司;

  • 代理人许铨芬

  • 地址 430000 湖北省武汉市东湖高新技术开发区高新四路6号

  • 入库时间 2023-06-19 13:29:16

说明书

【技术领域】

本发明涉及字节码修改领域,具体为一种Java字节码修改方法与装置。

【背景技术】

Java开发人员在日常开发和维护过程中,需要对字节码进行修改,对于字节码的修改,目前主流的有两种方式。

第一种方式修改字节码文件对应的原始的完整的源码文件,将其编译生成字节码文件后进行替换,这种方式的需要开发人员能够获取原始的完整的源码文件及其所依赖的所有库文件,然后搭建编译环境进行编译。

第二种方式是使用一些字节码修改工具直接对字节码文件进行修改,Java作为开源语言,其字节码文件的协议也是公开的,可以在官方网站查到,故可以写工具参考协议直接对字节码文件的数据进行修改,这类工具目前常见的有ASM、BCEL、Javassist。这种方式搭建编译环境比较简单,只需要Java开发人员创建一个Java工程,在工程使用工具的API加载字节码文件,并对其进行修改,然后保存导出。

第一种方式是目前最常用的一种方式,缺点是如果字节码对应的源码文件比较大的话,搭建编译环境会比较繁琐,需要引用其所有的依赖文件才可编译。另外,字节码对应的源码文件有时无法获得,需要开发人员通过反编译工具从字节码文件反编译得到,反编译出来的源文件再编译又往往会有编译问题,需要凭经验进行修复,文件越大,出现编译问题的概率就越大。第二种方式搭建编译环境很简单,缺点是需要开发人员对字节码的协议和工具API非常熟悉,这个要求比较高。

因此,需要一种新的字节码修改方式,能够综合上述两种方式的优点,既降低搭建编译环境的复杂度,又不需要开发人员了解字节码协议的细节,从而提高修改字节码的效率。

【发明内容】

针对以上缺陷或改进需求,为了降低搭建编译环境的复杂度,提升开发人员维护修改的效率,本发明提供一种新的字节码修改方式,既降低搭建编译环境的复杂度,又不需要开发人员了解字节码协议的细节,它的输入是两个字节码文件:待修改的字节码文件,目标字节码文件。工具内部会解析并比较两个字节码文件,然后将目标字节码文件的不同之处增量合并到待修改的字节码文件,最终输出修改后的字节码文件。

为实现上述目的,本发明采用如下技术方案:

第一方面,一种Java字节码修改方法,如下:

将待修改的字节码文件与目标字节码文件按照字节码协议分别解析成待修改数据对象ClassFile与目标数据对象ClassFile,将目标数据对象ClassFile中的常量池添加到待修改数据对象ClassFile,将目标数据对象ClassFile中新增的成员对象依次添加到待修改数据对象ClassFile中,将目标数据对象ClassFile中新增的方法依次添加到待修改数据对象ClassFile中,导出修改后的待修改数据对象ClassFile并按照字节码协议保存为字节码文件。

优选的,遍历目标数据对象ClassFile的常量池,将所述常量池分批添加到待修改数据对象ClassFile中。

优选的,所述目标数据对象ClassFile中的常量池之间有引用关系,被引用的常量池需要先从目标数据对象ClassFile中添加到待修改数据对象ClassFile中,得到被引用常量池在待修改数据对象ClassFile中的位置,更新目标数据对象ClassFile中引用常量池的引用位置,再将所述更新后的引用常量池从目标数据对象ClassFile添加到待修改数据对象ClassFile中。

优选的,遍历目标数据对象ClassFile中的成员变量,将目标数据对象ClassFile中的成员变量与待修改数据对象ClassFile中的成员变量相比较,若需要添加的成员变量在待修改数据对象ClassFile中不存在,则直接将所述成员变量添加到待修改数据对象ClassFile中;若需要添加的成员变量在待修改数据对象ClassFile中存在相同成员变量,则将需要添加的成员变量覆盖到待修改数据对象ClassFile中。

优选的,遍历目标数据对象ClassFile的成员变量与待修改数据对象ClassFile的成员变量,逐一进行比较,比较依据是所述成员变量field_info的name_index对应的常量池的值是否相同。

优选的,遍历目标数据对象ClassFile中的方法,将目标数据对象ClassFile中的方法与待修改数据对象ClassFile中的方法相比较,若需要添加的方法在待修改数据对象ClassFile中不存在,并且所述方法名称以“wclassfile”开头,则将所述方法添加到待修改数据对象ClassFile中,若所述方法名称不以“wclassfile”开头,则不添加所述方法;若需要添加的方法在待修改数据对象ClassFile中存在相同方法,则将需要添加的方法覆盖到待修改数据对象ClassFile中。

优选的,所述目标数据对象ClassFile中新增的成员变量与新增的方法添加到待修改数据对象ClassFile后,修改所述成员变量内部属性中对常量池的引用索引与方法内部属性中对常量池的引用索引。

优选的,所述目标数据对象ClassFile中新增的方法添加到待修改数据对象ClassFile后,修改所述方法内部属性中对常量池的引用索引。

优选的,导出修改后的待修改数据对象ClassFile并按照字节码协议保存为字节码文件。

第二方面,一种Java字节码修改装置,包括至少一个处理器,以及,与所述至少一个处理器通信连接的存储器;其中,所述存储器存储有可被所述至少一个处理器执行的指令,所述指令被所述处理器执行,用于所述的Java字节码修改方法。

本发明提供了一种Java字节码修改方法,既降低搭建编译环境的复杂度,又不需要开发人员了解字节码协议的细节,极大地提高开发人员修改开发的速度与效率。

【附图说明】

为了更清楚地说明本发明实施例的技术方案,下面将对本发明实施例中所需要使用的附图作简单地介绍。显而易见地,下面所描述的附图仅仅是本发明的一些实施例,对于本领域普通技术人员来讲,在不付出创造性劳动的前提下,还可以根据这些附图获得其他的附图。

图1是本发明实施例提供的一种Java字节码修改方法的实施流程示意图;

图2是本发明实施例提供的一种Java字节码修改方法的流程示意图;

图3是本发明实施例提供的一种Java字节码修改方法的合并常量池流程示意图;

图4是本发明实施例提供的一种Java字节码修改方法的合并成员变量流程示意图;

图5是本发明实施例提供的一种Java字节码修改方法的合并方法流程示意图;

图6是本发明实施例提供的一种Java字节码修改装置的装置示意图。

【具体实施方式】

为了使本发明的目的、技术方案及优点更加清楚明白,以下结合附图及实施例,对本发明进行进一步详细说明。应当理解,此处所描述的具体实施例仅仅用以解释本发明,并不用于限定本发明。此外,下面所描述的本发明各个实施方式中所涉及到的技术特征只要彼此之间未构成冲突就可以相互组合。

实施例1:

本发明实施例提供了一种Java字节码修改方法,如图1所示。

本实施例中字节码修改读取待修改的字节码文件A1与目标字节码文件C1,并按照字节码协议分别解析成待修改数据对象ClassFile A2与目标数据对象ClassFile C2,本实施例将待修改数据对象ClassFile A2简称为A2,将目标数据对象ClassFile C2简称为C2。

如图2所示,一种Java字节码修改方法,如下:

步骤201,将待修改的字节码文件A1与目标字节码文件C1按照字节码协议分别解析成待修改数据对象ClassFileA2与目标数据对象ClassFileC2。

其中,所述数据对象ClassFile的数据结构在本发明中主要涉及以下属性类型:

cp_info、field_info与method_info;其中cp_info在本实施例中成为常量池,field_info在本实施例中称为成员变量,method_info在本实施例中称为方法。

步骤202,将目标数据对象ClassFileC2中的常量池添加到待修改数据对象ClassFileA2。

步骤203,将目标数据对象ClassFileC2中新增的成员对象依次添加到待修改数据对象ClassFileA2中。

步骤204,将目标数据对象ClassFileC2中新增的方法依次添加到待修改数据对象ClassFileA2中。

步骤205,导出修改后的待修改数据对象ClassFileA2并按照字节码协议保存为字节码文件。

其中,ClassFile数据结构如下:

所述常量池属性类型为cp_info,属性名为constant_pool;其中,cp_info数据结构如下:

所述常量池保存了数据对象ClassFile的所有常量,包括字符串与数值,所述字符串代表源文件里面的类名、变量名与方法名等,所述常量池用于被其他属性通过索引来引用,所述常量池是一个数组,里面有多个成员,成员之间也可相互引用,常量池有多种不同类型,用tag区分。

所述成员变量属性类型为field_info,属性名为fields,每个所述成员变量有名称、访问级别(private/protected/public)等属性;其中,field_info数据结构如下:

其中,所述方法属性类型为method_info,属性名为methods,每个方法有名称、访问级别(private/protected/public)等属性;其中,method_info数据结构如下:

如图3所示,所述一种Java字节码修改方法在合并常量池涉及以下设计:

步骤301,分三批遍历C2的常量池。

步骤302,将所述常量池分批添加到A2中。

由于常量池之间有引用关系,所以需要分批添加到A2,被引用者需要先添加,得到其在A2中新的位置,引用者需更新引用位置,再添加到A2。

步骤303,检查常量对象是否被引用,若常量对象被引用跳转至304,若常量对象未被引用,跳转至305。

步骤304,得到其在A2中新的位置,引用者需更新引用位置,跳转至步骤302。

由于每个常量池从C2加到A2之后,所述常量池的位置可能会发生变化,因此所述常量池引用者需要更新引用索引;所有从C2添加到A2的常量池,均需要保存所述常量池的新的位置,用于后续步骤使用。

步骤305,检查遍历是否结束,是则直接结束,否则跳转至301。

其中,常量池类型有如下种类,如表1所示:

表1

所述三批常量池如下:

第一批,如表2所示:

表2

第二批,如表3所示:

表3

第三批如表4所示:

表4

如图4所示,所述一种Java字节码修改方法在合并成员变量涉及以下设计:

步骤401,遍历C2中的成员变量。

步骤402,将C2中的成员变量与A2中的成员变量相比较,比较依据是所述成员变量field_info的name_index对应的常量池的值是否相同,若所述C2中的成员变量与A2中的成员变量进行对比,所述C2中的成员变量在A2中存在,跳转至403,若所述C2中的成员变量在A2中不存在,跳转至404。

步骤403,若C2中的成员变量在A2中存在相同成员变量,则将所述C2的成员变量覆盖A2中相同的成员变量。

步骤404,若C2中的成员变量在A2中不存在,则将所述C2中的成员变量添加到A2中。

步骤405,所述C2中新增的成员对象添加到A2后,需要更新对应的常量池的位置,修改新增成员变量所有常量池引用的值,保证A2的正确性。

步骤406,检查遍历是否结束,是则结束,否则跳转至401。

如图5所示,所述一种Java字节码修改方法在合并方法涉及以下设计:

步骤501,遍历C2中的方法。

步骤502,将C2中的方法与A2中的方法相比较,比较依据是所述方法method_info的name_index对应的常量池的值是否相同,若所述方法在A2中存在,则跳转至503,若所述方法不在A2中存在,则跳转504。

步骤503,若需要添加的方法在A2中存在相同方法,则将需要添加的方法覆盖到A2中。

步骤504,若需要添加的方法在A2中不存在,并且所述方法以“wclassfile”开头,跳转至505,若所述方法不以“wclassfile”开头,则不添加所述方法,并跳转至508;通过所述方法是否以“wclassfile”开头来判断是否需要该方法。

步骤506,C2中的方法放入A2后,需要更新对应的常量池的位置,修改新增方法所有常量池引用的值,如:name_index、descriptor_index,来保证A2的正确性。

步骤507,更新所述方法属性中Code的指令。

步骤508,检测遍历是否结束,是则直接结束,否则跳转至501。

其中,method_info里面还包括attribute,attribute_info里面根据attribute_name_index对应常量池的值决定所述attribute_info是什么类型,有下列三种类型attribute_info需要执行更新:

Code对应的数据结构如下:

其中,attribute_name_index、code[code_length]、start_pc、end_pc、handler_pc、exception_table与attribute_info attributes[attributes_count]在方法被添加后需要检查修改。

所述attribute_name_index需要修改为新的常量池位置,code属性经解析后是一系列序列,部分指令由于会引用常量池的位置,常量池位置的修改也会导致指令的修改,指令的修改会导致exception_table_length里面的所有内容的修改,因此需要对上述数据结构进行修改。

Signatrue info对应的数据结构如下:

其中attribute_name_index、signature_index需要修改为新的常量池索引。

Exceptions对应的数据结构如下:

其中,attribute_name_index、exception_index_table需要修改为新的常量池索引。

所述常量池合并、成员变量合并与方法合并均完成以后导出修改后的字节码文件D,将待修改数据对象ClassFileA2保存为字节码文件。

本Java字节码修改方法通过jar包形式发布,发布形式为:WClassfile.jar,其中WClassfile为jar包的名字,具体使用时可根据本领域技术人员需要更换名字,不应限制本发明保护范围。

可通过命令调用,使用方法为:

Java-jarWClassfile.jar-o=/filepath-n=/filepath-s=/dirpath

其中具体参数为:

-o:待修改字节码文件路径

-n:目标字节码文件路径

-s:修改后的字节码文件保存目录

实施例2:

本发明实施例提出了一种Java字节码修改方法,在实施例1的基础上,本实施例进一步使用一个具体的示例来阐述本发明的具体实施方式。

本实施例中现有一个待修改字节码文件Sample.class,所述待修改字节码文件Sample.class的源码文件和编译环境已无法获取,需要对其内部一个方法method_d增加调试信息,在日志文件里面记录入参i的值。

通过反编译工具反编译Sample.class,得到其源码片段如下:

此代码片段仅包括4个方法,参考上面的代码片段,由于目标是修改mehtod_d,因此目标源文件的重点是保证是此方法的完整性并保证编译成功,修改mehtod_d后代码片段如下:

与经过反编译的Sample.class的源代码片段相对比,可以看到以下区别:

1、mehtod_d增加了一行“Logger.getLogger(getClass()).info("method_c,i="+i);”来实现我们的修改需求,增加了调试信息。

2、method_a与method_b方法实现为空;这是因为这两个方法不是我们的修改目标,所以可以不实现;但由于method_d在调用它,因此能保证代码能够编译成功。

3、method_a与method_b方法访问级别private跟代码片段public的不一致,这是由于要避免这两个不完整的方法将我们待修改字节码文件的方法给覆盖。

其中,所述method_a与method_b两个方法虽然在待修改的字节码文件不存在,但它们的名称不是以“wclassfile_”开头,所以不会添加到修改后的字节码文件里面。

4、目标源文件未包含method_c,这是因为所述方法method_c跟method_d没有关联,缺少也不影响method_d的完整性。

使用JDK编译指令Javac将目标源文件(Sample.Java)编译生成目标字节码文件(Sample.class),具体操作如下:

在路径C:下打开命令行,运行:

Javac C:/new/Sample.Java

生成修改后的字节码文件;将待修改的字节码文件保存到C:/old,目标字节码文件保存到C:/new。在路径C:下打开命令行,运行:

Java-jar WClassfile.jar-o=C:/old/Sample.class-n=C:/new/Sample.class-s=C:/

运行成功后,在目录“C:/”下会生成一个新的Sample.class,通过反编译工具查看,其代码片段如下:

其中,mehtod_d增加了一行“Logger.getLogger(getClass()).info("method_c,i="+i);”来实现我们的修改需求,可见,本次修改成功。

实施例3:

本发明实施例提出了一种Java字节码修改方法,在实际应用中,可以根据不同应用场景结合不同技术工具使用,在实施例1的基础上,本实施例在配合JavaAgent技术一起使用场景下提供一个具体的实施方案。

配合JavaAgent技术一起使用,将第三方库或JRE的修改跟Java应用程序分开部署,在Java应用程序启动时按需加载,避免第三方库或JRE的改动影响部署环境下其他的Java应用程序。

具体流程如下:

在服务器部署JRE(Java Runtime Environment)和两个Java应用程序,分别位于C:/ProgramA和C:/ProgramB两个目录,ProgramA在运行过程中,可能会出现故障,开发人员为排查此故障,需要修改JDK自带的String类,在其构造函数增加某些特定代码,为了不影响ProgramB,要求不能直接在JRE上修改。

实施过程参考上述实施例2,目标源文件如下:

其中上述目标源文件中if(original.equals(“abc”)){throw RuntimeException("got it");}为目标源文件相比String源码新增的部分。

参考实施例2生成目标字节码文件String.class,放置到C:/ProgramA目录,然后基于本发明的方法写JavaAgent,命名为TestAgent,其代码如下:

以上代码可以看出TestAgent调用本发明提供的方法执行字节码修改。

本Java字节码修改方法的发布形式为:WClassfile.jar。

启动ProgramA,运行:

Java-Javaagent:TestAgent.jar-jar C:/ProgamA.jar

利用JavaAgent技术,调用上述TestAgent.jar,最终TestAgent调用本发明提供的方法完成字节码修改。

实施例4:

如图6所示,是本发明实施例的Java字节码修改装置的架构示意图。本实施例的Java字节码修改方法推荐装置包括一个或多个处理器601以及存储器602。其中,图6中以一个处理器601为例。

处理器601和存储器602可以通过总线或者其他方式连接,图6中以通过总线连接为例。

存储器602作为一种非易失性计算机可读存储介质,可用于存储非易失性软件程序和非易失性计算机可执行程序,如实施例1中的Java字节码修改方法。处理器601通过运行存储在存储器602中的非易失性软件程序和指令,从而执行Java字节码修改方法。

存储器602可以包括高速随机存取存储器,还可以包括非易失性存储器,例如至少一个磁盘存储器件、闪存器件、或其他非易失性固态存储器件。在一些实施例中,存储器602可选包括相对于处理器601远程设置的存储器,这些远程存储器可以通过网络连接至处理器601。上述网络的实例包括但不限于互联网、企业内部网、局域网、移动通信网及其组合。

所述程序指令/模块存储在所述存储器602中,当被所述一个或者多个处理器601执行时,执行上述实施例1中的Java字节码修改方法,例如,执行以上描述的图2至图5所示的各个步骤。

以上所述仅为本发明的较佳实施例而已,并不用以限制本发明,凡在本发明的精神和原则之内所作的任何修改、等同替换和改进等,均应包含在本发明的保护范围之内。

去获取专利,查看全文>

相似文献

  • 专利
  • 中文文献
  • 外文文献
获取专利

客服邮箱:kefu@zhangqiaokeyan.com

京公网安备:11010802029741号 ICP备案号:京ICP备15016152号-6 六维联合信息科技 (北京) 有限公司©版权所有
  • 客服微信

  • 服务号