基于Java Reflection自动逆向生成类间 方法关系图的解析器

网友投稿 847 2022-05-27

0 引言

在一个用Java语言开发的大型软件系统中,一旦发生软件的错误,往往会导致整个系统产生故障.只有

找到故障发生的真正原因,才能从根本上排除故障.定位大型系统中的故障来源,需要耗费较多的时间和人

力.如果能为系统建立起一个软件错误传递图,辅助分析和定位故障的来源,就能帮助操作员、系统分析员快

速定位故障的来源,找到解决故障的办法[1-2].

Java作为一门面向对象的语言,其程序由类、类成员、方法和方法的参数等构成.类方法之间存在互相依

赖、互相调用的关系.建立起一个类方法关系图,并将其扩展成为软件的错误传递图,如果故障发生在软件的

某个模块或类方法中,在传递图上就会显示故障的位置,结合一定的算法,就能找出故障产生的来源.建立类

方法的关系图,还需要分析出类方法的名称、包含的参数、返回类型及方法之间的关系.

1 逆向分析Java文件的现状与实践

在有源代码的情况下,要分析出类方法之间的关系,前提就是去阅读和理解源代码.现实中往往因商业

机密得不到源代码,比如只有Java类文件(.class的扩展名).这类文件经编译器编译过,用户看不到源代码,

只能看到字节码(byte code),即二进制代码,难以理解,无从下手,必须另寻途径.

有一种办法是通过逆向分析Java文件,得到类结构和信息,人们在这方面做过不少有益的尝试.比如说

第36卷 第5期广东第二师范学院学报Vol.36 No.5

2016年10月Journal of Guangdong University of Education Oct.2016

有一种方法是在一个类中,通过识别类方法的行号,来找出该类及其方法[3].这种方法需要逐行读取程序.

还有一种是利用Javassist(Java Programming Assistant)工具来打开Java类文件,得到类包含的方法、

构造函数等.Javassist是一个Java字节代码类库,允许编辑Java字节代码,使Java程序在运行时能定义一

个新类,当JVM(Java虚拟机)载入类文件时可以修改这个类文件.它提供了两种类型的API供调用,一种是

源代码API,另外一种是字节码API.利用源代码API,无需懂得Java字节码,就可编辑一个类文件,且源代

码API也是用Java语言来设计的,支持在源代码窗口中插入字节码,再经Javaassit在运行时编译.字节码

API则允许用户像使用其他编辑器一样,来编辑一个类文件[4].这种方法通过打开类文件,从字节码得出类

方法,还可以编辑这个文件.

类方法的定义语句和调用语句格式不同,类方法还存在有重载现象,需要区分出名字相同,但参数不同

的方法.Java Reflection是Java自带的类,允许使用一个类或对象,去分析另一个Java源程序或者.class文

件,从而得到该程序或文件所包含的类名称、方法和方法的参数,最终可得到整个类结构.Java Reflection提

供了许多详细的工具集来操作Java代码,能提供Visual Basic用户熟悉的那些工具.新的类可以在设计和运

行时被加入,使用快速开发工具还可以动态查询新增加类的容量[5].Java Reflection的API(Application

图1 实现思路

Program Interface)存在于Java.lang.reflect类内.譬

如,调用java.lang.reflect.Field,java.lang.reflect.

Method,java.lang.reflect.Constructor[6],就能得到类

成员、方法和构造函数.本文探讨了如何使用Java

Reflection设计逆向解析Java源程序或者.class文件

的解析器,实现思路可分为以下三方面,如图1所示:

1)使用Java Reflection创建一个解析器程序,

具备识别和分析Java源程序或者类(class)文件的功

能,能搜索到类的构造函数、类方法名称和定义、类方法的返回类型和作用范围.

2)导入一个Java样例程序文件,分析文件的类结构.

3)对搜索到的类及类方法信息,予以整理,并以特定格式的动态数组保存,为分析类方法之间的关系和

生成类关系图奠定基础.

2 用Java Reflection逆向分析Java程序文件的具体步骤

Java Reflection存在于Java.lang.reflect类中,导入了这个类后,才能调用其中的代码,步骤如图2所示:

图2 逆向分析Java程序文件的具体步骤

2.1 获取类名称

分析一个Java文件(以.java或者.class为扩展名),先

要分析该文件包含了哪些类.通过调用方法“Class.

forName”可以得到.java或者.class文件中包含的类名

称[7],如以下代码所示:

/* CVSClass.java*/

System.out.println(" 寻找类" +className);

this.className=className;

2.2 获取类方法

当找出Java文件中的类之后,调用getMethod()可以

2016年第5期       薛 亮,等:基于Java Reflection自动逆向生成类间方法关系图的解析器      · 9 3 ·

得到类中全部的公有(public)方法,以及继承(inherited)方法.如果调用getDeclaredMethods(),可以得到类

中已声明的公有和非公有方法,但不能得到从superclasses继承来的方法.代码中methodSet使用了

HashSet(哈希集合),methodList存储类型使用了ArrayList(数组类型)来存储类方法信息,目的是为了对

比两种不同存储类型的存取速度,在实际应用中只要选择一个即可.以下是如何得到类中已声明方法的部分

代码:

基于Java Reflection自动逆向生成类间 方法关系图的解析器

/* CVSClass.java*/

Method methods=cls.getDeclaredMethods();//得到所有已声明的方法

cvsMethodArray=new CVSMethod[methods.length];//初始化数组

/* 找到的类方法名存放在methodSet和methodList中*/

if(methods!=null){for(int i=0;i< methods.length;i++){

cvsMethodArray[i]=new CVSMethod(methods[i],className);

methodSet.add(new CVSMethod(methods[i],className));

methodList.add(new CVSMethod(methods[i],className));}

2.3 获取构造函数

为得到类中的构造函数,调用getDeclaredConstructors()方法可以得到构造函数的名字.面向对象编程

中,构造函数有时可以被重载(Overloading),因此一个类可以包含不止一个构造函数.如以下代码所示:

/* CVSClass.java*/

//创建数组来存储构造函数的名字

Constructor[]constructors=cls.getDeclaredConstructors();

cvsConstructorArray=new CVSConstructor[constructors.length];

if(constructors!=null){for(int i=0;i<constructors.length;i++){

/*给constructorSet和cvsConstructorArray数组添加搜寻到的构造函数名称*/

constructorSet.add(new CVSConstructor(constructors[i]));

cvsConstructorArray[i]=new CVSConstructor(constructors[i]);}

2.4 获取类方法的返回类型

在Java中除了构造函数之外,每个类方法均有一个返回类型.返回类型可以是空值、整数型、字符串型、

双精度型等.调用method.getReturnType().getName()可以得到类方法返回类型的名称,如以下所示:

/* CVSMethod.java*/

this.refMethod=refMethod;

this.cvsMethodName=refMethod.getName();//得到类名称

this.cvsMethodReturnType=refMethod.getReturnType().getName();//得到返回类型

this.className=className;

2.5 获取类方法的参数

一个类方法通常带有一个或多个参数的声明.面向对象编程中,类方法允许被重载,即多个类方法可以

有相同名字,但返回参数可以不一样.逆向分析时,需要知道类方法的名字、参数名称和数量,才能区别出哪

些是重载的方法.读取类方法的参数部分代码如下:

/* CVSMethod.java*/

public CVSMethod(Method refMethod,String className){

/* 创建methodParameterClasses类数据来存放参数的类型*/

· 94· 广东第二师范学院学报第36卷

Class[]methodParameterClasses=refMethod.getParameterTypes();

/* 把类方法的参数存储进数组*/

for(int i=0;i<methodParameterClasses.length;i++){

cvsMethodParameterSet.add (CVSUtil.toShortName(methodParameterClasses[i].getName()));

methodParameters.append(CVSUtil.toShortName(methodParameterClasses[i].getName()));}

/* MethInfo.java

/* MethodInfo类用于存取类方法的名称、类型、起止行信息*/

this.shortMethodName=CVSUtil.getShortMethodInfoName(methodName);/* 类方法名字*/

this.methodNameArgu=CVSUtil.getMethodNameArgu(methodName);/*类方法参数声明*/

2.6 在类方法内读取括号类型、所在行号,识别方法的定义行和作用域

Java类方法的定义语句,由一对小括号“(”和“)”表示开始和结束.用一对花括号”{”和“}”表示一个类或

方法的作用域开始和结束.通过识别这些成对括号的开始和结束的行号,就能知道哪些语句属于类方法的定

义语句,哪些语句属于作用域语句,以及哪些语句才是类方法的内容.Bracket类定义了存储括号类型和行

号,CVSUtil类识别类方法定义和作用域,部分代码如下所示:

/* Bracket.java*/

this.bracketType=bracketType;//存放括号类型

this.lineNumber=lineNumber;//存放行号}

public String getBracketType(){return bracketType;}//返回括号类型

public int getLineNumber(){return lineNumber;}//返回行号

/*CVSUtil.java*/

/*判断是否含类方法定义和作用域语句*/

{//探测读入的语句是否存在“(”,“)”,“{”,“}”,以及表示方法语句结束的符号“;”

if((containMethodLines.indexOf(cvsMethod.toPartString())!= -1)

&& (containMethodLines.indexOf(" (" )!= -1)&& (containMethodLines.indexOf(" )" )!= -1)

&& ((containMethodLines.indexOf(" {" )!= -1)||(containMethodLines.indexOf(" ;" )!= -1)))......}

2.7 寻找注释语句

注释语句在程序运行时不会被编译,Java语言用两种注释符号,一种是以“//”开始,另一种是“/*”和

“*/”来表示注释的部分.在逆向分析时,必须识别出这些注释语句,并将其忽略掉.在Bracket.java代码中,

isCommentBracket方法使用字符串函数IndexOf来搜索注释符号“//”,如下:

readLineString.indexOf("//" );//在字符串中寻找”//”符号

2.8 搜索类方法之间的关系

在类中的一个方法内,往往需要调用其他的方法.类方法之间存在着相互调用和依赖的关系.在搜索一

个类方法内的语句时,解析器并不知道具体在哪行代码调用其他方法,所以需要读取类方法内的每一行代

码,保证调用其他方法的语句不会被遗漏.在SechDep.java中,readline方法用于读取行并找出方法之间的

关系.MethArr数组储存了类中所有声明的方法.代码请参照SechDep.Java文件.

2.9 保存搜索到的类方法信息到数组

当类方法的名称、返回类型、参数、参数的数量以及类方法之间的关系被识别出来后,这些信息就可以储

2016年第5期       薛 亮,等:基于Java Reflection自动逆向生成类间方法关系图的解析器      · 9 5 ·

存在一定格式的数组中.在SechDep.java的readline方法中,定义了一个名为finalResult的数组存放这些信

息.代码请参照SechDep.Java文件.

3 导入样例文件进行测试

导入一个样例文件SafeExp.java,然后运行逆向分析解析器程序,搜索到类方法信息并存储到数组中.

类方法数组存储的信息如表1所示.

表1 搜索到SafeExp.java中所包含类方法的信息的存储数组

类名方法名方法返回类型类成员类型是否构造函数参数数量

SafeExp SafeExp void public 是0

SafeExp fun_A void private 否0

SafeExp fun_B void public 否0

SafeExp fun_C void public 否0

SafeExp fun_D void public 否0

SafeExp fun_E void public 否0

SafeExp Main void public static 否1

在存储完类方法数组后,将存储类方法之间相互调用的关系,用另一个名为“finalResult”的数组来存

放,存储信息如表2所示.

表2 SafeExp.java中类方法之间的关系存储在finalResult数组

方法名称调用的方法方法之间的关系

main SafeExp main→SafeExp

SafeExp fun_A,fun_E SafeExp→fun_A,SafeExp→fun_E

Fun_A fun_B,fun_C,fun_D fun_A→fun_B,fun_A→fun_B,fun_A→fun_C

Fun_B 无无

Fun_C fun_D fun_C→fun_D

Fun_D 无无

Fun_E fun_B,fun_D fun_E→fun_B,fun_E→fun_D

利用类方法和方法之间关系的数组,使用Java图形编程,可生成类方法关系图,如图3中由节点和有向

边构成的图形.

4 结语

对于Java源程序或者.class文件,使用Java Reflection设计逆向分析解析器程序,获得类的名称、方法

名、方法参数、构造函数等信息,并搜索出每个类方法与其他方法之间的关系.然后,将这些信息用特定格式

的数组存储起来.该数组存储的信息就可作为生成类方法关系图所用.

类方法关系图可进一步扩展成为软件错误传递图,供操作员或系统分析员监测软件运行的状况,帮助

· 96· 广东第二师范学院学报第36卷

图3 分析SafeExp.java后生成的类

方法关系图

他们在故障发生时,分析和确定故障产生的来源,及时

采取相应措施.目前,对于大型的软件系统,逆向分析类

方法之间的关系还存在一定的困难,需要更多地研究、

实践和改进解析器程序.比如,当系统的文件内部结构

(逻辑结构)非常复杂,存在各种不同类型的类(子类、抽

象类、内部类等等)时,类方法之间互相调用的关系更加

错综复杂,分析工作更为艰巨.

文中使用Java Reflection进行逆向分析的解析器

源代码,限于篇幅无法详尽列出,完整的代码可通过访

问网盘链接“http://pan.baidu.com/s/1c1H7Ksg”下

载.源代码在JDK1.6和NetBeans中调试通过.

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:超轻量级网红软件定时器multi_timer(51+stm32小熊派双平台实战)
下一篇:TOP级CG行业云渲染服务的演进之路
相关文章