ホームページ  >  記事  >  Java  >  Java クラス パーサーを実装するための強力なコードの共有

Java クラス パーサーを実装するための強力なコードの共有

黄舟
黄舟オリジナル
2017-03-18 10:41:322597ブラウズ

最近、私は ClassAnalyzer というプライベート プロジェクトを書いています。ClassAnalyzer の目的は、<span class="wp_keywordlink">Java クラスを理解できるようにすることです。 span ></span>ファイルの設計と構造を深く理解します。本文のフレームワークと基本的な機能は完成しており、いくつかの詳細な機能が追加されています。将来的には追加されます。実際、JDK は、Class ファイルを逆コンパイルするためのコマンド ライン ツール javap をすでに提供していますが、この記事では、私の実装のアイデアを明確にします。パーサー。 ClassAnalyzerClassAnalyzer的目的是能让我们对<span class="wp_keywordlink">Java Class</span>文件的设计与结构能够有一个深入的理解。主体框架与基本功能已经完成,还有一些细节功能日后再增加。实际上JDK已经提供了命令行工具javap来反编译Class文件,但本篇文章将阐明我实现解析器的思路。

Class文件

作为类或者接口信息的载体,每个Class文件都完整的定义了一个类。为了使Java程序可以“编写一次,处处运行”,Java虚拟机规范对Class文件进行了严格的规定。构成Class文件的基本数据单位是字节,这些字节之间不存在任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,单个字节无法表示的数据由多个连续的字节来表示。

根据Java虚拟机规范,Class文件采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表。Java虚拟机规范定义了u1u2u4u8来分别表示1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者是字符串。表是由多个无符号数或者其它表作为数据项构成的符合数据类型,表用于描述有层次关系的符合结构的数据,因此整个Class文件本质上就是一张表。在ClassAnalyzeru1u2u4u8分别对应于byteshortintlongClass文件被描述为如下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类型的tagu2类型的length和由lengthu1类型组成的bytes,这length字节的连续数据是一个使用MUTF-8Modified UTF-8)编码的字符串。MUTF-8UTF-8并不兼容,主要区别有两点:一是null字符会被编码成2字节(0xC00x80);二是补充字符是按照UTF-16

    クラス ファイル

    クラスまたはインターフェイス情報のキャリアとして, 各 Class ファイルはクラスを完全に定義します。 Java プログラムを「一度書けばどこでも実行できる」ようにするために、Java 仮想マシン仕様では Class ファイルに対して厳格な規制を設けています。 Class ファイルを構成する基本的なデータ単位はバイトです。これにより、Class ファイル全体に格納される内容がほぼすべてのプログラムの実行になります。必要なデータは、1 バイトで表現できないデータを連続した複数バイトで表現します。 🎜🎜 Java 仮想マシンの仕様に従って、Class ファイルは、C 言語構造に似た疑似構造を使用してデータを保存します。構造体には、符号なし数値とテーブルという 2 つのデータ型しかありません。 Java 仮想マシン仕様では、u1u2u4、および u8 をそれぞれ定義します。 1 バイト、2 バイト、4 バイト、および 8 バイトを表すなし 符号付き数値、符号なし数値を使用可能数値を説明するには、インデックス見積書、数量値、または文字列。 。テーブルは、データ項目として複数の符号なし数値またはその他のテーブルで構成される準拠したデータ型であり、したがって、 Class ファイル全体が本質的にテーブルになります。 ClassAnalyzer では、u1u2u4u8 がそれぞれ に対応します。 >byteshortint、および long の場合、Class ファイルは次のように記述されます 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;
              ...
        }
    }

    解析方法

    🎜 Class ファイルを構成するさまざまなデータ項目のうち、マジック ナンバー、Class ファイルのバージョン、他のデータ項目、アクセス フラグ、クラス インデックス、および親クラス インデックスは、各 Class ファイル内で固定数のバイトを占有し、解析中に対応するバイト数のみを読み取る必要があります。さらに、柔軟に処理する必要がある主要な部分には、4 が含まれます: 定数プール、フィールド テーブル コレクション、メソッド テーブル コレクション、および属性テーブル コレクション。フィールドとメソッドは独自の属性を持つことができ、Class 自体にも対応する属性があります。したがって、フィールド テーブル コレクションとメソッド テーブル コレクションの解析には属性テーブルの解析も含まれます。 🎜🎜定数プールは、Class ファイル内のデータの大部分を占め、数値および文字列定数、クラス名、インターフェイス名、フィールド名、メソッド名、等Java 仮想マシン仕様では複数の定数タイプが定義されており、それぞれが独自の構造を持っています。定数プール自体はテーブルであり、解析する際に注意すべき点がいくつかあります。 🎜
    • 🎜 各定数タイプは、タイプ u1 のタグによって識別されます。 🎜
    • 🎜ヘッダーに指定された定数プール サイズ (constantPoolCount) は、実際のサイズより 1 大きくなります。たとえば、constantPoolCount の場合 47 に等しい場合、定数プールには 46 個の定数が存在します。 🎜
    • 🎜 定数プールのインデックス範囲は、1 から始まります。たとえば、constantPoolCount47 に等しい場合です。 、次に定数プール。インデックスの範囲は 1 ~ 46 です。設計者は、「定数プール項目を参照しない」ことを表現するために、0 項目を空のままにしました。 🎜
    • 🎜 CONSTANT_Utf8_info 型定数の構造には、u1型の tagu2 が含まれています> タイプの length と、u1 タイプの length 部分で構成される bytes 。 > 連続するバイトデータは、MUTF-8 (Modified UTF-8) を使用してエンコードされた文字列です。 MUTF-8UTF-8 と互換性がありません。主な違いは 2 つあります。 まず、null 文字は にエンコードされます。 2 バイト (0xC0 および 0x80); 次に、補助文字がサロゲート ペアに分割され、UTF-16 に従って個別にエンコードされます。 > 、関連する詳細はここにあります (バリアント UTF-8)。 🎜

    属性表用于描述某些场景专有的信息,Class文件、字段表和方法表都有相应的属性表集合。Java虚拟机规范定义了多种属性,ClassAnalyzer目前实现了对常用属性的解析。和常量类型的数据项不同,属性并没有一个tag来标识属性的类型,但是每个属性都包含有一个u2类型的attribute_name_indexattribute_name_index指向常量池中的一个CONSTANT_Utf8_info类型的常量,该常量包含着属性的名称。在解析属性时,ClassAnalyzer正是通过attribute_name_index指向的常量对应的属性名称来得知属性的类型。

    字段表用于描述类或者接口中声明的变量,字段包括类级变量以及实例级变量。字段表的结构包含一个u2类型的access_flags、一个u2类型的name_index、一个u2类型的descriptor_index、一个u2类型的attributes_countattributes_countattribute_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属性中,codeLengthcode分别用于存储字节码长度和字节码指令,每条指令即一个字节(u1类型)。在虚拟机执行时,通过读取code中的一个个字节码,并将字节码翻译成相应的指令。另外,虽然codeLength是一个u4类型的值,但是实际上一个方法不允许超过65535条字节码指令。

    代码实现

    ClassAnalyzer的源码已放在了GitHub上。在ClassAnalyzer的README中,我以一个类的Class文件为例,对该Class文件的每个字节进行了分析,希望对大家的理解有所帮助。

以上がJava クラス パーサーを実装するための強力なコードの共有の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。