クラスファイルは、JVM が使用するビッグエンディアンのバイナリー構造です。先頭は 0xCAFEBABE で、その後に u2 のマイナーバージョンとメジャーバージョンが続きます。JVM が受け入れるのは、その実装に対して定義されたバージョンだけです。クラスファイルパーサーは、新しいファイルに後方互換性があると仮定せず、サポート対象外のバージョンを拒否する必要があります。
最上位構造 #
マジック値とバージョンの後に、ファイルには次の要素が含まれます。
constant_pool_countとインデックス付きの定数プールエントリー。- クラスの
access_flags、this_class、super_class。 - インターフェースのインデックス。
field_info構造体。method_info構造体。- クラスレベルの
attribute_info構造体。
カウントは符号なし u2 値ですが、属性の attribute_length は u4 です。アクセスフラグはコンテキストを記述します。ACC_PUBLIC、ACC_FINAL、ACC_SUPER、ACC_INTERFACE、ACC_ABSTRACT、ACC_SYNTHETIC、ACC_ANNOTATION、ACC_ENUM、ACC_MODULE のすべてが、あらゆる種類のクラスファイルで意味を持つ、または正当であるわけではありません。
定数プールと名前 #
定数プールのインデックスは 1 から始まります。特定の構造で予約されている場合を除き、インデックス 0 は無効です。CONSTANT_Long と CONSTANT_Double は連続する 2 つのインデックスを使用し、2 番目は使用できません。エントリーには、プリミティブ定数と文字列定数、Class、String、フィールド参照、メソッド参照、インターフェースメソッド参照、名前と型のペア、メソッドハンドルとメソッド型、dynamic および invoke-dynamic コールサイト、モジュール、パッケージが含まれます。参照はシンボル形式です。たとえば、CONSTANT_Methodref はメモリー内のメソッドではなく、クラスエントリーおよび名前と型のエントリーを介して参照します。
クラスファイルの名前には、java/lang/String のようにスラッシュで区切られた内部名を使用します。ソース指向 API のバイナリー名はドットを使用します。フィールドおよびメソッドのディスクリプターは、Ljava/lang/String;、[I、(I[Ljava/lang/String;)V などの形式で型をエンコードします。CONSTANT_Utf8 はクラスファイルの修正 UTF-8 エンコーディングを使用します。このエンコーディングは null 文字を C0 80 としてエンコードし、UTF-16 のサロゲートコード単位を個別に表現します。汎用 UTF-8 デコーダーと交換可能ではありません。
メンバーと属性 #
各 field_info と method_info には、アクセスフラグ、名前およびディスクリプターのインデックス、属性テーブルがあります。メソッドの Code 属性には、max_stack、max_locals、バイトコード、例外ハンドラー、および行番号やスタックマップフレームなどのネストされた属性が格納されます。フィールドは ConstantValue を持つ場合があります。クラスとメンバーは、アノテーション、シグネチャー、nest 情報、レコード、モジュール、デバッグデータなどの属性を通じてメタデータを保持します。未知の属性をスキップできるのは、宣言された長さを使用する場合だけです。名前からセマンティクスを推測することはできません。
安全な解析 #
ビルドから生成された場合でも、クラスファイルを敵対的な入力として扱ってください。読み取りのたびに残りのバイト数を検証し、すべてのカウント、インデックス、タグ、フラグの組み合わせ、ディスクリプター、属性長を検証し、オフセットと長さの加算には検査付き算術演算を使用し、末尾の構造的不整合を拒否してください。シンボル参照を解決する前に、形式のバージョン規則とコンテキスト規則を適用します。ベリファイアは実行安全性の検査を追加しますが、有界なバイナリーパーサーの代わりにはなりません。
javap -v などのツールはデコード済みのビューを表示しますが、変換には、対象のクラスファイルバージョンをサポートし、必要に応じて未知の属性を保持する、保守されたライブラリを使用すべきです。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)