java class文件格式解析

转载 2007年09月25日 17:07:00

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

 

                                                                                          图1

 

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

2实现

实现该工具的过程如下:

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

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

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

2.1 解析Class文件

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

2.1.1 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;

};

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=0x001A

常数表内name_index索引内保存数据为UNSET,常数表内descriptor_index索引内保存的数据为BB表示byte, 其他类型参考《Java虚拟机规范》第四章4.3.2节)。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虚拟机规范》第四章4.3.2)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文件格式。

2.1.2 读取数据

定义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的数据需要位转换:

U1  [0]

U1 [1]

U1   [1]

U1 [0]

U2

U1  [0]

U1 [1]

U1   [3]

U4

U1 [2]

U1  [3]

U1 [2]

U1   [0]

U1 [1]

调用void readu4(u4 *buff);buff的值为0xCAFEBABE,该值为ClassFilemagic,识别该文件是Java Class文件。

3

              magic的后面是Class格式的版本号,图3的版本为0x00000030=0.48。版本后面是常数表的元素个数,图3的常数表的元素个数为0xD2=210个。常数表的元素个数之后如ClassFile结构体定义的常数表,类信息,接口信息,域信息,方法信息和附加属性等。

2.2 生成Java源文件

              解析Class文件后,生产ClassFile结构体。遍历该结构体数据,则可根据Java语言规范生成Java源文件。例如根据ClassFileaccess_flags值获得Java类的修饰符,其中accessCArray<CString,CString&>,保存类所有的修饰符

              if((flag & ACC_PUBLIC )==ACC_PUBLIC)

              {

                                          access.Add(CString("public"));

              }

              if((flag & ACC_PRIVATE)==ACC_PRIVATE)

              {

                                          access.Add(CString("private"));

 

              }

              if((flag & ACC_PROTECTED)==ACC_PROTECTED)

              {

                                          access.Add(CString("protected"));

 

              }

2.3显示视图

              将获得的Java源代码显示在MFCCScrollView视图非常简单,可以添加一些关键字颜色,例如注释显示为草绿色等,如图4

4

3.总结

              本文根据《Java虚拟机规范》开发了解析Java Class文件格式,并生成Java源代码结构的工具。其优点是:

1.脱离Java 虚拟机或Java开发环境;

2.可查阅没有Java源代码的Class文件的内容;

3.为一些复杂的Java Jar包生成相同类名的替代类,方便开发调试。例如,用返回固定字符串的java源文件更换需要网络链接的相同java类,有助于本地运行与调试。

缺点是:

1.由于没有反编译Bytecode,工具生成的部分Java源文件需要手动添加一些Java属性值;

2.Java源文件内的所需要使用的Java方法内容需要程序员手动实现。

 

相关文章推荐

Java Class文件格式解析及实例

JAVA无关性概述 Java语言从刚诞生开始曾提出一个非常著名的宣言:“一次编写,到处运行(Write Once, Run Anywhere)”。Sun公司和其他虚拟机公司发布了许多可以运行在不同操...

深入Java class文件格式

.class文件中的内容,可以分为两部分,一是结构描述,二是代码指令。这其实跟我们写的java文件是非常相似的,只不过它为了性能考虑,生成了二进制文件。下面以HelloWorld来举例说明。 首先我...

深入理解Java Class文件格式(九)

经过前八篇关于class文件的博客, 关于class文件格式的内容也基本上讲完了。 本文是关于class文件格式的最后一篇。 在这篇博客中, 将会讲解关于方法的几个属性。 理解这篇博客的内容, 对于理...

深入理解Java Class文件格式(一)

在上一篇博客中, 大致讲解了Java虚拟机的体系结构和执行原理。 本篇博客主要讲解能够被JVM识别, 加载并执行的class文件的格式。 对于理解JVM和深入理解Java语言, 学习并了解class...

深入理解Java Class文件格式(六)

经过前几篇文章, 终于将常量池介绍完了, 之所以花这么大的功夫介绍常量池, 是因为对于理解class文件格式,常量池是必须要了解的, 因为class文件中其他地方,大量引用了常量池中的数据项。  对于...

深入理解Java Class文件格式(二)

在上一篇文章 深入理解Java Class文件格式(一) 中, 介绍了class文件在整个java体系结构中的位置和作用, 并对class文件的整体格式做了说明, 介绍了其中的魔数和版本号的相关内容,...

深入理解Java Class文件格式(六)

经过前几篇文章, 终于将常量池介绍完了, 之所以花这么大的功夫介绍常量池, 是因为对于理解class文件格式,常量池是必须要了解的, 因为class文件中其他地方,大量引用了常量池中的数据项。  对于...

深入理解Java Class文件格式(三)

首先, 让我们回顾一下关于class文件格式的之前两篇博客的主要内容。 在 深入理解Java Class文件格式(一) 中, 讲解了class文件在整个java体系结构中的位置和作用, 讲解了cl...

深入理解Java Class文件格式(三)

首先, 让我们回顾一下关于class文件格式的之前两篇博客的主要内容。 在 深入理解Java Class文件格式(一) 中, 讲解了class文件在整个java体系结构中的位置和作用, 讲解了clas...

深入理解Java Class文件格式(九)

经过前八篇关于class文件的博客, 关于class文件格式的内容也基本上讲完了。 本文是关于class文件格式的最后一篇。 在这篇博客中, 将会讲解关于方法的几个属性。 理解这篇博客的内容, 对于理...
  • cq1982
  • cq1982
  • 2015年06月02日 11:52
  • 272
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:java class文件格式解析
举报原因:
原因补充:

(最多只允许输入30个字)