.class文件格式(java字节码文件的格式)
一個.class文件對應一個類(Class)
包含字節碼(虛擬機指令)?
1 . 目的
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 的值為 0x CAFEBABE ,該值為 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 方法內容需要程序員手動實現。
?
總結
以上是生活随笔為你收集整理的.class文件格式(java字节码文件的格式)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jar包的分析
- 下一篇: iPhone4 FaceTime 联通官