《九江租车全攻略:多样选择,畅享便捷出行》
873
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(数组类型)来存储类方法信息,目的是为了对
比两种不同存储类型的存取速度,在实际应用中只要选择一个即可.以下是如何得到类中已声明方法的部分
代码:
/* 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小时内删除侵权内容。