解读Java Class文件格式
1.目的
大型軟件系統開發時,某些Java組件可能涉及到多種數據庫或中間件系統的連接和應用,例如一個數據傳遞組件需要從DB2中讀取數據,并將數據通過中間件WebSphere MQ發送到其他系統,這類組件功能單一,但卻需要連接多種第三方產品,使得程序員的單元測試變的非常不便,程序員不得不注視或修改部分源代碼,或者在本地安裝所需第三方產品。無疑這兩種選擇都是痛苦的。
基于以上的不便,本文開發了解析Java Class文件程序,目的是將第三方產品API的Class文件轉換為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;?//類附加屬性數據,包括源文件名等。
};
?
其中u2為unsigned short,u4為unsigned 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_Utf8的info指針指向的數據類型應為CONSTANT_Utf8_info
struct?CONSTANT_Utf8_info
{
??????????????u1 tag;
??????????????u2 length;
??????????????u1 *bytes;
};
Tag和info的詳細說明參考《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 abstract則access_flags的值為ACC_PUBLIC | ACC_ABSTRACT=0x0401。
this_class的值是常數表的索引,索引的info內保存類或接口名。例如類名為com.sum.java.swing.SwingUtitlities2在info保存為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索引內保存的數據為B(B表示byte,?其他類型參考《Java虛擬機規范》第四章4.3.2節)。attributes_count的值為1,其中attributes是指針數組。指針數組個數為attributes_count,在此為1,attribute_info結構體如下:
struct?attribute_info
{
??????????????u2 attribute_name_index;???//常數表內索引
??????????????u4 attribute_length;????????????//屬性長度
??????????????u1 *info;?????????????????????????????//根據屬性類型不同而值不同
};?
attribute_info可以轉換(cast)為多種類型ConstantValue_attribute,Exceptions_attribute,LineNumberTable_attribute,LocalVariableTable_attribute,Code_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,數組內保存的是常數表索引,info為Code_attribute或Exceptions_attribute。
本文不解析方法內容,因此忽略Code_attribute和Exceptions_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++的MFC類CFile從Class文件讀取數據。例如:用16進制編輯器打開Class文件,如圖3,前4個byte分別是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);
讀取的u2和u4的數據需要位轉換:
| 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,該值為ClassFile的magic,識別該文件是Java Class文件。
圖3
??????????????magic的后面是Class格式的版本號,圖3的版本為0x00000030=0.48。版本后面是常數表的元素個數,圖3的常數表的元素個數為0xD2=210個。常數表的元素個數之后如ClassFile結構體定義的常數表,類信息,接口信息,域信息,方法信息和附加屬性等。
2.2?生成Java源文件
??????????????解析Class文件后,生產ClassFile結構體。遍歷該結構體數據,則可根據Java語言規范生成Java源文件。例如根據ClassFile的access_flags值獲得Java類的修飾符,其中access是CArray<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源代碼顯示在MFC的CScrollView視圖非常簡單,可以添加一些關鍵字顏色,例如注釋顯示為草綠色等,如圖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方法內容需要程序員手動實現
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的解读Java Class文件格式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java过滤特殊字符的正则表达式
- 下一篇: 深入Java虚拟机:Class文件实例解