解讀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

                                                                                          圖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方法內容需要程序員手動實現。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章