解读Java Class文件格式

1目的

大型软件系统开发时,某些Java组件可能涉及到多种数据库或中间件系统的连接和应用,例如一个数据传递组件需要从DB2中读取数据,并将数据通过中间件WebSphere MQ发送到其他系统,这类组件功能单一,但却需要连接多种第三方产品,使得程序员的单元测试变的非常不便,程序员不得不注视或修改部分源代码,或者在本地安装所需第三方产品。无疑这两种选择都是痛苦的。

基于以上的不便,本文开发了解析Java Class文件程序,目的是将第三方产品APIClass文件转换为Java源文件(不包括Java类的方法实现),在源文件的各种程序所需的方法里实现一些简单的语句,例如数据库连接方法永远返回true,获得数据方法永远返回 ”Hello world” 等,用JDK重新编译转换后的Java源文件,来替换真正的API 文件,这样程序员在UT测试时,无需修改源代码,也无需安装任何产品,并且能通过修改替换的API Java源文件实施各种UT测试。

为了实现以上需求,必须先要了解Java Class文件格式。Java虚拟机识别的class文件格式包含Java虚拟机指令(或者bytecodes)和一个符号表以及其他的辅助信息。本文将使用VC++语言解析Java Class文件符号表,逆向生成Java源代码结构。如图1

解读Java Class文件格式

图1

之所以使用VC++而不使用Java的主要是因为VC++界面开发简单;运行速度快,不需要虚拟机;需要用指针建立复杂的数据结构。

2实现

实现该工具的过程如下:

1.解析Class文件,从Class文件中读取数据并保存到称为ClassFile结构体中;

2.解析ClassFile结构体,生成源代码字符串;

3.将字符串显示到视图中。

2.1 解析Class文件

为实现第1步,首先需要了解Class文件格式规范,参考《Java虚拟机规范》第四章class文件格式,总结class文件的数据结构如图2

<chsdate w:st="on" isrocdate="False" islunardate="False" day="30" month="12" year="1899"><span lang="EN-US" style="mso-fareast-font-family: SimHei">2.1.1</span></chsdate> Class文件格式

Class文件格式ClassFile结构体的C语言描述如下:

struct ClassFile

{

u4 magic; //识别Class文件格式,具体值为0xCAFEBABE

u2 minor_version; // Class文件格式副版本号,

u2 major_version; // Class文件格式主版本号,

u2 constant_pool_count; // 常数表项个数,

cp_info **constant_pool;// 常数表,又称变长符号表,

u2 access_flags; //Class的声明中使用的修饰符掩码,

u2 this_class; //常数表索引,索引内保存类名或接口名,

u2 super_class; //常数表索引,索引内保存父类名,

u2 interfaces_count; //超接口个数,

u2 *interfaces; //常数表索引,各超接口名称,

u2 fields_count; //类的域个数,

field_info **fields; //域数据,包括属性名称索引,

//域修饰符掩码等,

u2 methods_count; //方法个数,

method_info **methods;//方法数据,包括方法名称索引,方法修饰符掩码等,

u2 attributes_count; //类附加属性个数,

attribute_info **attributes; //类附加属性数据,包括源文件名等。

};

其中u2unsigned shortu4unsigned long

typedef unsigned char u1;

typedef unsigned short u2;

typedef unsigned long u4;

cp_info **constant_pool是常量表的指针数组,指针数组个数为constant_pool_count,结构体cp_info

struct cp_info

{

u1 tag; //常数表数据类型

u1 *info; //常数表数据

};

常数表数据类型Tag定义如下:

#define CONSTANT_Class 7

#define CONSTANT_Fieldref 9

#define CONSTANT_Methodref 10

#define CONSTANT_InterfaceMethodref 11

#define CONSTANT_String 8

#define CONSTANT_Integer 3

#define CONSTANT_Float 4

#define CONSTANT_Long 5

#define CONSTANT_Double 6

#define CONSTANT_NameAndType 12

#define CONSTANT_Utf8 1

每种类型对应一个结构体保存该类型数据,例如CONSTANT_Class info指针指向的数据类型应为CONSTANT_Class_info

struct CONSTANT_Class_info

{

u1 tag;

u2 name_index;

};

解读Java Class文件格式

2

CONSTANT_Utf8info指针指向的数据类型应为CONSTANT_Utf8_info

struct CONSTANT_Utf8_info

{

u1 tag;

u2 length;

u1 *bytes;

};

Taginfo的详细说明参考《Java虚拟机规范》第四章4.4节。

access_flags为类修饰符掩码,域与方法都有各自的修饰符掩码。

#define ACC_PUBLIC 0x0001

#define ACC_PRIVATE 0x0002

#define ACC_PROTECTED 0x0004

#define ACC_STATIC 0x0008

#define ACC_FINAL 0x0010

#define ACC_SYNCHRONIZED 0x0020

#define ACC_SUPER 0x0020

#define ACC_VOLATILE 0x0040

#define ACC_TRANSIENT 0x0080

#define ACC_NATIVE 0x0100

#define ACC_INTERFACE 0x0200

#define ACC_ABSTRACT 0x0400

#define ACC_STRICT 0x0800

例如类的修饰符为public abstractaccess_flags的值为ACC_PUBLIC | ACC_ABSTRACT=0x0401

this_class的值是常数表的索引,索引的info内保存类或接口名。例如类名为com.sum.java.swing.SwingUtitlities2info保存为com/sum/java/swing/SwingUtitlities2

super_class的值是常数表的索引,索引的info内保存超类名,在info内保存形式和类名相同。

interfaces是数组,数组个数为interfaces_count,数组内的元素为常数表的索引,索引的info内保存超接口名,在info内保存形式和类名相同。

field_info **fields是类域数据的指针数组,指针数组个数为fields_count,结构体field_info定义如下:

struct field_info

{

u2 access_flags; //域修饰符掩码

u2 name_index; //域名在常数表内的索引

u2 descriptor_index; //域的描述符,其值是常数表内的索引

u2 attributes_count; //域的属性个数

attribute_info **attributes; //域的属性数据,即域的值

};

例如一个域定义如下:

private final static byte UNSET=127;

则该域的修饰符掩码值为:ACC_PRIVATE | ACC_STATIC | ACC_FINAL=0x<chmetcnv w:st="on" tcsc="0" numbertype="1" negative="False" hasspace="False" sourcevalue="1" unitname="a">001A</chmetcnv>

常数表内name_index索引内保存数据为UNSET,常数表内descriptor_index索引内保存的数据为BB表示byte, 其他类型参考《Java虚拟机规范》第四章<chsdate w:st="on" isrocdate="False" islunardate="False" day="30" month="12" year="1899"><span lang="EN-US" style="mso-fareast-font-family: SimHei"><font face="Times New Roman">4.3.2</font></span></chsdate>节)。attributes_count的值为1,其中attributes是指针数组。指针数组个数为attributes_count,在此为1attribute_info结构体如下:

struct attribute_info

{

u2 attribute_name_index; //常数表内索引

u4 attribute_length; //属性长度

u1 *info; //根据属性类型不同而值不同

};

attribute_info可以转换(cast)为多种类型ConstantValue_attributeExceptions_attributeLineNumberTable_attributeLocalVariableTable_attributeCode_attribute等。

因为域的属性只有一种:ConstantValue_attribute,因此此结构体转换为

struct ConstantValue_attribute

{

u2 attribute_name_index; //常数表内索引

u4 attribute_length; //属性长度值,永远为2

u2 constantvalue_index; //常数表内索引,保存域的值

//在此例中,常数表内保存的值为127

};

method_info **methods是方法数据的指针数组,指针数组个数为methods_count,结构体method_info定义如下:

struct method_info

{

u2 access_flags; //方法修饰符掩码

u2 name_index; //方法名在常数表内的索引

u2 descriptor_index; //方法描述符,其值是常数表内的索引

u2 attributes_count; //方法的属性个数

attribute_info **attributes; //方法的属性数据,

//保存方法实现的Bytecode和异常处理

};

例如一个方法定义如下:

public static boolean canAccessSystemClipboard(){

...

}

access_flags的值为 ACC_PUBLIC | ACC_STATIC =0x0009,常数表内name_index索引内保存数据为canAccessSystemClipboard,常数表内descriptor_index索引内保存数据为()Z(括号表示方法参数,Z表示返回值为布尔型,详细说明参照《Java虚拟机规范》第四章<chsdate w:st="on" isrocdate="False" islunardate="False" day="30" month="12" year="1899"><span lang="EN-US" style="mso-fareast-font-family: SimHei"><font face="Times New Roman">4.3.2</font></span></chsdate>)attribute_info **attributes是方法的属性指针数组,个数为attributes_count,数组内保存的是常数表索引,infoCode_attributeExceptions_attribute

本文不解析方法内容,因此忽略Code_attributeExceptions_attribute的内容。

ClassFile结构体中的attribute_info **attributes是附加属性数组指针,个数为attributes_count,本文只识别SourceFile属性。

struct SourceFile_attribute

{

u2 attribute_name_index; //常数表内索引

u4 attribute_length; //属性长度值,永远为2

u2 sourcefile_index; //常数表内索引,info保存源文件名

};

例如com.sum.java.swing.SwingUtitlities2类的源文件名为SwingUtitlities2.java

以上是本文需要解析的Class文件格式。

<chsdate w:st="on" isrocdate="False" islunardate="False" day="30" month="12" year="1899"><span lang="EN-US" style="mso-fareast-font-family: SimHei">2.1.2</span></chsdate> 读取数据

定义CJavaClass类完成解析Class文件,生成Java源程序字符串。使用VC++MFCCFileClass文件读取数据。例如:用16进制编辑器打开Class文件,如图3,前4byte分别是CA FE BA BE,使用CFile::Read(tmp,sizeof(u4))读取后,tmp的值为0xBEBAFECA,所以需要位转换。定义以下方法从文件读取定长数据:

void readu1(u1 *buff);

void readu2(u2 *buff);

void readu4(u4 *buff);

定义如下方法读取变长数据。

void readun(void *buff,u4 len)

读取的u2u4的数据需要位转换:

<group id="_x0000_s1038" style="WIDTH: 387pt; HEIGHT: 54pt; mso-position-horizontal-relative: char; mso-position-vertical-relative: line" coordsize="6450,926" coordorigin="2527,6847" editas="canvas"><lock aspectratio="t" v:ext="edit"></lock><shapetype id="_x0000_t75" coordsize="21600,21600" stroked="f" filled="f" path="[email protected]@[email protected]@[email protected]@[email protected]@5xe" o:preferrelative="t" o:spt="75"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_s1039" style="LEFT: 2527px; WIDTH: 6450px; POSITION: absolute; TOP: 6847px; HEIGHT: 926px" o:preferrelative="f" type="#_x0000_t75"><font size="3"><fill o:detectmouseclick="t"></fill><path o:connecttype="none" o:extrusionok="t"></path><lock v:ext="edit" text="t"></lock></font></shape><shapetype id="_x0000_t202" coordsize="21600,21600" path="m,l,21600r21600,l21600,xe" o:spt="202"><stroke joinstyle="miter"></stroke><path o:connecttype="rect" gradientshapeok="t"></path></shapetype><shape id="_x0000_s1040" style="LEFT: 2677px; WIDTH: 1200px; POSITION: absolute; TOP: 7464px; HEIGHT: 309px" type="#_x0000_t202"><textbox style="mso-next-textbox: #_x0000_s1040" inset="5.85pt,.7pt,5.85pt,.7pt"><table cellspacing="0" cellpadding="0" width="100%"><tbody><tr> <td style="BORDER-RIGHT: #ece9d8; BORDER-TOP: #ece9d8; BORDER-LEFT: #ece9d8; BORDER-BOTTOM: #ece9d8; BACKGROUND-COLOR: transparent"> <div> <p class="MsoNormal" style="MARGIN: 0mm 0mm 0pt"><span lang="EN-US"><font face="Times New Roman" size="3">U1<span style="mso-spacerun: yes"> </span>[0]</font></span></p> </div> </td> </tr></tbody></table></textbox></shape><shape id="_x0000_s1041" style="LEFT: 3877px; WIDTH: 1200px; POSITION: absolute; TOP: 7464px; HEIGHT: 309px" type="#_x0000_t202"><textbox style="mso-next-textbox: #_x0000_s1041" inset="5.85pt,.7pt,5.85pt,.7pt"><table cellspacing="0" cellpadding="0" width="100%"><tbody><tr> <td style="BORDER-RIGHT: #ece9d8; BORDER-TOP: #ece9d8; BORDER-LEFT: #ece9d8; BORDER-BOTTOM: #ece9d8; BACKGROUND-COLOR: transparent"> <div> <p class="MsoNormal" style="MARGIN: 0mm 0mm 0pt"><span lang="EN-US"><font face="Times New Roman" size="3">U1 [1]</font></span></p> </div> </td> </tr></tbody></table></textbox></shape><line id="_x0000_s1042" style="POSITION: absolute" from="5227,7619" to="5977,7620"><stroke endarrow="block"><font face="Times New Roman" size="3"></font></stroke></line><shape id="_x0000_s1043" style="LEFT: 6127px; WIDTH: 1200px; POSITION: absolute; TOP: 7464px; HEIGHT: 309px" type="#_x0000_t202"><textbox style="mso-next-textbox: #_x0000_s1043" inset="5.85pt,.7pt,5.85pt,.7pt"><table cellspacing="0" cellpadding="0" width="100%"><tbody><tr> <td style="BORDER-RIGHT: #ece9d8; BORDER-TOP: #ece9d8; BORDER-LEFT: #ece9d8; BORDER-BOTTOM: #ece9d8; BACKGROUND-COLOR: transparent"> <div> <p class="MsoNormal" style="MARGIN: 0mm 0mm 0pt"><span lang="EN-US"><font face="Times New Roman" size="3">U1<span style="mso-spacerun: yes"> </span>[1]</font></span></p> </div> </td> </tr></tbody></table></textbox></shape><shape id="_x0000_s1044" style="LEFT: 7327px; WIDTH: 1200px; POSITION: absolute; TOP: 7464px; HEIGHT: 309px" type="#_x0000_t202"><textbox style="mso-next-textbox: #_x0000_s1044" inset="5.85pt,.7pt,5.85pt,.7pt"><table cellspacing="0" cellpadding="0" width="100%"><tbody><tr> <td style="BORDER-RIGHT: #ece9d8; BORDER-TOP: #ece9d8; BORDER-LEFT: #ece9d8; BORDER-BOTTOM: #ece9d8; BACKGROUND-COLOR: transparent"> <div> <p class="MsoNormal" style="MARGIN: 0mm 0mm 0pt"><span lang="EN-US"><font face="Times New Roman" size="3">U1 [0]</font></span></p> </div> </td> </tr></tbody></table></textbox></shape><shape id="_x0000_s1045" style="LEFT: 2677px; WIDTH: 1350px; POSITION: absolute; TOP: 7156px; HEIGHT: 245px" stroked="f" type="#_x0000_t202"><textbox style="mso-next-textbox: #_x0000_s1045" inset="5.85pt,.7pt,5.85pt,.7pt"><table cellspacing="0" cellpadding="0" width="100%"><tbody><tr> <td style="BORDER-RIGHT: #ece9d8; BORDER-TOP: #ece9d8; BORDER-LEFT: #ece9d8; BORDER-BOTTOM: #ece9d8; BACKGROUND-COLOR: transparent"> <div> <p class="MsoNormal" style="MARGIN: 0mm 0mm 0pt"><font size="3"><span lang="EN-US"><font face="Times New Roman">U2</font></span><span lang="ZH-CN" style="FONT-FAMILY: SimSun; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">:</span></font></p> </div> </td> </tr></tbody></table></textbox></shape><wrap type="none"></wrap><anchorlock></anchorlock></group>