ホームページ  >  記事  >  Java  >  Javaでクラスパーサを実装する方法の例

Javaでクラスパーサを実装する方法の例

黄舟
黄舟オリジナル
2017-09-15 10:16:001307ブラウズ

この記事では主に、クラスファイルの分析を通じてJavaクラスパーサーの実装方法の例を紹介します。これは一定の参考価値があり、必要な友人がそれについて学ぶことができます。

私は現在、ClassAnalyzer というプライベート プロジェクトを書いています。ClassAnalyzer の目的は、Java クラス ファイルの設計と構造を深く理解することです。主要な枠組みと基本的な機能は完成していますが、今後、いくつかの詳細な機能が追加される予定です。実際、JDK は Class ファイルを逆コンパイルするためのコマンド ライン ツール javap をすでに提供していますが、この記事ではパーサーの実装に関する私の考えを明確にします。

クラス ファイル

クラスまたはインターフェイス情報のキャリアとして、各クラス ファイルはクラスを完全に定義します。 Java プログラムを「一度書けばどこでも実行できる」ようにするために、Java 仮想マシンの仕様では Class ファイルに厳しい規制が設けられています。 Class ファイルを構成する基本的なデータ単位はバイトです。このため、クラス ファイル全体に格納される内容は、1 バイトでは表現できないデータとなります。複数 連続したバイトで表されます。

Java 仮想マシンの仕様によれば、クラス ファイルは C 言語の構造に似た擬似構造を使用してデータを保存します。この擬似構造には符号なし数値とテーブルの 2 つのデータ型のみがあります。 Java 仮想マシンの仕様では、u1、u2、u4、および u8 がそれぞれ 1 バイト、2 バイト、4 バイト、および 8 バイトの符号なし数値を表すように定義されています。符号なし数値は、参照、数量、または文字列を記述するために使用できます。テーブルは、データ項目として複数の符号なし数値または他のテーブルで構成される複合データ型であり、階層複合構造でデータを記述するために使用されるため、クラス ファイル全体が本質的にテーブルになります。 ClassAnalyzer では、byte、short、int、long がそれぞれ u1、u2、u4、u8 のデータ型に対応します。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
}

クラスファイルを構成する各データ項目(マジックナンバー、クラスファイルのバージョン、その他のデータ項目、アクセスフラグ、クラスインデックス、親クラスインデックスなど)の解析方法

は各クラス ファイル内にあり、それぞれが固定バイト数を占有し、解析中に対応するバイト数のみを読み取る必要があります。また、柔軟に対応する必要がある部分は主に、定数プール、フィールドテーブルコレクション、メソッドテーブルコレクション、属性テーブルコレクションの4つです。フィールドとメソッドは独自の属性を持つことができ、クラス自体にも対応する属性があるため、フィールド テーブル コレクションとメソッド テーブル コレクションの解析には属性テーブルの解析も含まれます。

定数プールはクラス ファイル内のデータの大部分を占め、数値定数、文字列定数、クラス名、インターフェイス名、フィールド名、メソッド名などを含むすべての定数情報を保存するために使用されます。 Java 仮想マシンの仕様では複数の定数タイプが定義されており、それぞれが独自の構造を持っています。定数プール自体はテーブルであり、解析する際に注意すべき点がいくつかあります。

各定数型は、u1 型タグによって識別されます。

ヘッダーに指定された定数プール サイズ (constantPoolCount) は、実際の値より 1 大きくなります。たとえば、constantPoolCount が 47 に等しい場合、定数プールには 46 個の定数があります。

定数プールのインデックス範囲は 1 から始まります。たとえば、constantPoolCount が 47 の場合、定数プールのインデックス範囲は 1 ~ 46 です。項目 0 を空のままにする設計者の目的は、「定数プール項目を参照しない」ことを表現することです。

CONSTANT_Utf8_info 定数の構造には、タイプ u1 のタグ、タイプ u2 の length バイト、およびタイプ u1 の length バイトの連続データが MUTF-8 (Modified UTF-8) 文字列を使用してエンコードされます。 MUTF-8 は UTF-8 と互換性がありません。主な違いは 2 つあります。1 つ目は、NULL 文字が 2 バイト (0xC0 および 0x80) にエンコードされることです。2 つ目は、補助文字がサロゲート ペアに分割され、UTF に従って個別にエンコードされることです。 -16 、関連する詳細はここにあります (バリアント UTF-8)。

属性テーブルは、特定のシナリオに固有の情報を記述するために使用されます。クラス ファイル、フィールド テーブル、メソッド テーブルにはすべて、対応する属性テーブル セットがあります。 Java 仮想マシン仕様ではさまざまな属性が定義されており、現在 ClassAnalyzer は一般的に使用される属性の分析を実装しています。定数タイプのデータ項目とは異なり、属性には属性のタイプを識別するためのタグがありませんが、各属性にはタイプ 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_countattributes_infoタイプの属性を含む。属性テーブルの解析についてはすでに紹介しましたが、属性の解析方法は属性テーブルの解析方法と一致しています。

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文件的每个字节进行了分析,希望对大家的理解有所帮助。

以上がJavaでクラスパーサを実装する方法の例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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