自己動手實現一個Java Class解析器

NoeDowns 7年前發布 | 13K 次閱讀 Java Java開發

最近在寫一個私人項目,名字叫做 ClassAnalyzer , ClassAnalyzer 的目的是能讓我們對 Java Class 文件的設計與結構能夠有一個深入的理解。主體框架與基本功能已經完成,還有一些細節功能日后再增加。實際上 JDK 已經提供了命令行工具 javap 來反編譯 Class 文件,但本篇文章將闡明我實現解析器的思路。

Class文件

作為類或者接口信息的載體,每個 Class 文件都完整的定義了一個類。為了使 Java 程序可以“編寫一次,處處運行”,Java虛擬機規范對 Class 文件進行了嚴格的規定。構成 Class 文件的基本數據單位是字節,這些字節之間不存在任何分隔符,這使得整個 Class 文件中存儲的內容幾乎全部是程序運行的必要數據,單個字節無法表示的數據由多個連續的字節來表示。

根據 Java 虛擬機規范, Class 文件采用一種類似于 C 語言結構體的偽結構來存儲數據,這種偽結構中只有兩種數據類型:無符號數和表。 Java 虛擬機規范定義了 u1 、 u2 、 u4 和 u8 來分別表示 1 個字節、 2 個字節、 4 個字節和 8 個字節的無符號數,無符號數可以用來描述數字、索引引用、數量值或者是字符串。表是由多個無符號數或者其它表作為數據項構成的符合數據類型,表用于描述有層次關系的符合結構的數據,因此整個 Class 文件本質上就是一張表。在 ClassAnalyzer 中 u1 、 u2 、 u4 和 u8 分別對應于 byte 、 short 、 int 和 long , Class 文件被描述為如下 Java 類。

public class ClassFile {

    public U4 magic;                            // magic
    public U2 minorVersion;                     // minor_version
    public U2 majorVersion;                     // major_version
    public U2 constantPoolCount;                // constant_pool_count
    public ConstantPoolInfo[] cpInfo;           // cp_info
    public U2 accessFlags;                      // access_flags
    public U2 thisClass;                        // this_class
    public U2 superClass;                       // super_class
    public U2 interfacesCount;                  // interfaces_count
    public U2[] interfaces;                     // interfaces
    public U2 fieldsCount;                      // fields_count
    public FieldInfo[] fields;                  // fields
    public U2 methodsCount;                     // methods_count
    public MethodInfo[] methods;                // methods
    public U2 attributesCount;                  // attributes_count
    public BasicAttributeInfo[] attributes;     // attributes

}

如何解析

組成 Class 文件的各個數據項中,例如魔數、 Class 文件的版本等數據項、訪問標志、類索引、父類索引,它們在每個 Class 文件中都占用固定數量的字節,在解析時只需要讀取相應數量的字節。除此之外,需要靈活處理的主要包括 4 部分:常量池、字段表集合、方法表集合和屬性表集合。字段和方法都可以具備自己的屬性, Class 本身也有相應的屬性,因此,在解析字段表集合和方法表集合的同時也包含了屬性表的解析。

常量池占據了 Class 文件很大一部分的數據,用于存儲所有的常量信息,包括數字和字符串常量、類名、接口名、字段名和方法名等。 Java 虛擬機規范定義了多種常量類型,每一種常量類型都有自己的結構。常量池本身是一個表,在解析時有幾點需要注意。

  • 每個常量類型都通過一個 u1 類型的tag來標識。
  • 表頭給出的常量池大小( constantPoolCount )比實際大 1 ,例如,如果 constantPoolCount 等于 47 ,那么常量池中有 46 項常量。
  • 常量池的索引范圍從 1 開始,例如,如果 constantPoolCount 等于 47 ,那么常量池的索引范圍為 1~46 。設計者將第 0 項空出來的目的是用于表達“不引用任何一個常量池項目”。
  • CONSTANT_Utf8_info 型常量的結構中包含 u1 類型的 tag 、 u2 類型的 length 和由 length 個 u1 類型組成的 bytes ,這 length 字節的連續數據是一個使用 MUTF-8 ( Modified UTF-8) 編碼的字符串。 MUTF-8 與 UTF-8 并不兼容,主要區別有兩點:一是 null 字符會被編碼成 2 字節( 0xC0 和 0x80 );二是補充字符是按照 UTF-16 拆分為代理對分別編碼的,相關細節可以看這里(變種UTF-8)。

屬性表用于描述某些場景專有的信息, Class 文件、字段表和方法表都有相應的屬性表集合。 Java 虛擬機規范定義了多種屬性, ClassAnalyzer 目前實現了對常用屬性的解析。和常量類型的數據項不同,屬性并沒有一個 tag 來標識屬性的類型,但是每個屬性都包含有一個 u2 類型的 attribute_name_index , attribute_name_index 指向常量池中的一個 CONSTANT_Utf8_info 類型的常量,該常量包含著屬性的名稱。在解析屬性時, ClassAnalyzer 正是通過 attribute_name_index 指向的常量對應的屬性名稱來得知屬性的類型。

字段表用于描述類或者接口中聲明的變量,字段包括類級變量以及實例級變量。字段表的結構包含一個 u2 類型的 access_flags 、一個 u2 類型的 name_index 、一個 u2 類型的 descriptor_index 、一個 u2 類型的 attributes_count 和 attributes_count 個 attribute_info 類型的 attributes 。我們已經介紹了屬性表的解析, attributes 的解析方式與屬性表的解析方式一致。

Class 的文件方法表采用了和字段表相同的存儲格式,只是 access_flags 對應的含義有所不同。方法表包含著一個重要的屬性: Code 屬性。 Code 屬性存儲了 Java 代碼編譯成的字節碼指令,在 ClassAnalyzer 中, Code 對應的 Java 類如下所示(僅列出了類屬性)。

public class Code extends BasicAttributeInfo {

    private short maxStack;
    private short maxLocals;
    private long codeLength;
    private byte[] code;
    private short exceptionTableLength;
    private ExceptionInfo[] exceptionTable;
    private short attributesCount;
    private BasicAttributeInfo[] attributes;
    ...

    private class ExceptionInfo {
        public short startPc;
        public short endPc;
        public short handlerPc;
        public short catchType;
          ...
    }
}

在 Code 屬性中, codeLength 和 code 分別用于存儲字節碼長度和字節碼指令,每條指令即一個字節( u1 類型)。在虛擬機執行時,通過讀取 code 中的一個個字節碼,并將字節碼翻譯成相應的指令。另外,雖然 codeLength 是一個 u4 類型的值,但是實際上一個方法不允許超過 65535 條字節碼指令。

代碼實現

ClassAnalyzer 的源碼已放在了 GitHub 上。在 ClassAnalyzer 的 README 中,我以一個類的 Class 文件為例,對該 Class 文件的每個字節進行了分析,希望對大家的理解有所幫助。

 

來自:http://developer.51cto.com/art/201702/531014.htm

 

 本文由用戶 NoeDowns 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!