Slide 1

Slide 1 text

javap を使ってクラスファイル を読んでみよう! @YujiSoftware https://yuji.software/javap/

Slide 2

Slide 2 text

目次 • javap の概要 • javap の使い方 • 引数の指定方法 • オプションについて • –verbose オプションについて • 出力内容の解説 • まとめ

Slide 3

Slide 3 text

javap って何? • JDK に付属しているツールの一つ • クラスファイルを逆アセンブル(人間に読みやすい形式で出力)するツール • できること • メソッドやフィールドの一覧の出力 • 定数プールの出力 • バイトコード(実行命令)の出力 • 属性の出力 etc… • できないこと • クラスファイルをもとのソースコードに戻す

Slide 4

Slide 4 text

javap の位置づけ .java (ソースコード) .class (クラスファイル) JVM (Java仮想マシン) javac java javap コンパイル 実行 逆アセンブル結果 (テキストファイル)

Slide 5

Slide 5 text

補足:クラスファイルって何? • 0xCAFEBABE で始まるバイナリーファイル • バイトコード(実行命令)など、実行に必要な情報が含まれている • “クラス”ファイルという名前だが、クラス以外にも使う • インタフェース • モジュールの情報 etc… • バイナリなので… • コンピューターが扱いやすい • 人間には読みにくい • 読めなくはない https://bit.ly/3Ls1r70 • javap は、これを人間に読みやすいフォーマットに変換してくれる

Slide 6

Slide 6 text

使い方 • javap [options] classes... • classes の指定方法 • ファイル名 • Foo.class • クラス名(完全限定名) • java.lang.Object • java/lang/Object • URL • jar:file:///path/log4j-api.jar!/org/apache/logging/log4j/simple/SimpleLogger.class • https://github.com/openjdk/jdk/raw/master/test/jdk/sun/misc/Hello.class • javac と同様に -cp (-classpath) の指定もできる • javap -cp log4j-api.jar org.apache.logging.log4j.simple.SimpleLogger

Slide 7

Slide 7 text

実行例 • シンプルな Hello world を用意 (com/example/Main.java) • これを javac でコンパイルして、javap で逆アセンブル package com.example; public class Main { public static void main(String[] args) { System.out.println("Hello world."); } } Compiled from "Main.java" public class com.example.Main { public com.example.Main(); public static void main(java.lang.String[]); } フィールドとメソッドの一覧が出力される

Slide 8

Slide 8 text

よく使うオプション • -constants • static final 定数値を表示 • -private または -p • すべてのクラスとメンバーを表示 • デフォルトは、protected および public のみ • -verboseまたは-v • クラスの詳細情報を出力 • すべてのオプションは、ドキュメントまたはヘルプ(-h)を参照 • https://docs.oracle.com/javase/jp/20/docs/specs/man/javap.html • 記載のない隠しオプションがある • https://qiita.com/YujiSoftware/items/f734cf656e6c01195f92

Slide 9

Slide 9 text

実行例 (-verbose) • なんかいっぱい表示される! Classfile /home/yuji/sample/com/example/Main.class Last modified 2023/05/27; size 426 bytes SHA-256 checksum 2887486397f85a098cd8908463d9499351497938089eafc72988a5cbaf4b55e0 Compiled from "Main.java" public class com.example.Main minor version: 0 major version: 64 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #21 // com/example/Main super_class: #2 // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 1 Constant pool: #1 = Methodref #2.#3 // java/lang/Object."":()V … 全体:https://gist.github.com/YujiSoftware/9fef6bf6abd22e0cec6b32ea0d1151c8

Slide 10

Slide 10 text

-verbose で表示される内容 • ファイルのプロパティ • パス、サイズ、日付、SHA-256ハッシュ • クラス定義 • バージョン、アクセスフラグ、クラス名、親クラス、etc... • 定数プール • フィールド情報 • メソッド情報 • 定義、バイトコード、変数テーブル、etc... • 属性 etc...

Slide 11

Slide 11 text

順番に解説していきます

Slide 12

Slide 12 text

-verbose で表示される内容 • ファイルのプロパティ • パス、サイズ、日付、SHA-256ハッシュ • クラス定義 • バージョン、アクセスフラグ、クラス名、親クラス、etc... • 定数プール • フィールド情報 • メソッド情報 • 定義、バイトコード、変数テーブル、etc... • 属性 etc...

Slide 13

Slide 13 text

ファイルのプロパティ • クラスファイルのパス • 最終更新日 • SHA-256 ハッシュ • Java 12 までは MD5 ハッシュ • Compiled from • コンパイルしたソースコードのファイル名(パスは含まない) • ファイルの中に書き込まれている属性の情報 Classfile /home/yuji/sample/com/example/Main.class Last modified 2023/05/27; size 426 bytes SHA-256 checksum 2887486397f85a098cd8908463d9499351497938089eafc72988a5cbaf4b55e0 Compiled from "Main.java" ディスク上の情報 (中身ではない)

Slide 14

Slide 14 text

-verbose で表示される内容(再掲) • ファイルのプロパティ • パス、サイズ、日付、SHA-256ハッシュ • クラス定義 • バージョン、アクセスフラグ、クラス名、親クラス、etc... • 定数プール • フィールド情報 • メソッド情報 • 定義、バイトコード、変数テーブル、etc... • 属性 etc...

Slide 15

Slide 15 text

クラス定義 • アイテム : 値 という形式で出力される • // 以降は定数プールの値 • 読みやすくなるように、javap がコメントとして出力してくれている public class Main minor version: 0 major version: 64 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #21 // com/example/Main super_class: #2 // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 1

Slide 16

Slide 16 text

クラス定義:バージョン • 実行対象の Java のバージョン • コンパイル時に -target オプションで指定したバージョン • デフォルトは、コンパイルした Java のバージョン • 実行環境よりも新しいと、UnsupportedClassVersionError が発生 • Main has been compiled by a more recent version of the Java Runtime (class file version 64.0), this version of the Java Runtime only recognizes class file versions up to 61.0 • minor version • 基本的に "0" • コンパイル時に --enable-preview を指定した場合は "65535" • major version • Java 1.1 = "45" で、バージョンが上がるたびに1ずつ増える (Java 20 = "64") • 一覧 : https://javaalmanac.io/jdk/ minor version: 0 major version: 64

Slide 17

Slide 17 text

クラス定義:アクセスフラグ • アクセス許可とプロパティの指定 flags: (0x0021) ACC_PUBLIC, ACC_SUPER 名称 値 解説 ACC_PUBLIC 0x0001 公開(パッケージの外部からアクセスできる) ACC_FINAL 0x0010 継承不可 ACC_SUPER 0x0020 invokespecial 命令を実行した際に、スーパークラスメソッドを特別に扱う ※歴史的経緯によるもので、クラスには強制的に付与される ACC_INTERFACE 0x0200 インタフェース ACC_ABSTRACT 0x0400 インスタンス化できない ACC_SYNTHETIC 0x1000 ソースコードに存在しない(コンパイラが生成したクラス) ACC_ANNOTATION 0x2000 アノテーションインタフェース ACC_ENUM 0x4000 ENUMクラス ACC_MODULE 0x8000 モジュール

Slide 18

Slide 18 text

クラス定義:クラス名 • クラス名 と 親クラス名 • 完全限定名の内部形式 • 歴史的な理由により、. (ドット)を / (スラッシュ)に置き換えている • #番号は定数プール(後述)の番号 • コメントがその定数値 this_class: #21 // com/example/Main super_class: #2 // java/lang/Object

Slide 19

Slide 19 text

クラス定義:その他 • インタフェースの個数 • フィールドの数 • メソッドの個数 • 属性の個数 • 属性? • ソースコードのアノテーション(@Deprecated など)ではない • (詳細は次のページ) interfaces: 0, fields: 0, methods: 2, attributes: 1

Slide 20

Slide 20 text

クラスファイルの構造 • 定義 + 属性(0個以上)の入れ子になっている フィールド •定義 • アクセスフラグ • 名前 • ディスクリプター(型) •属性(0個以上) • シグネチャ(ジェネリクス型情報) • 実行時アノテーション ... etc メソッド •定義 • アクセスフラグ • 名前 • ディスクリプター(型) •属性(0個以上) • コード • シグネチャ(ジェネリクス型情報)… etc クラス(=クラスファイル) • 定義 • バージョン • 定数プール • アクセスフラグ • … • フィールド(0個以上) • メソッド(0個以上) • 属性(0個以上) • ソースファイル名 • 内部クラス ...etc コード •定義 • 最大スタック • 最大ローカル • バイトコード • 例外テーブル • 属性(0個以上) • 行番号テーブル… etc

Slide 21

Slide 21 text

-verbose で表示される内容(再掲) • ファイルのプロパティ • パス、サイズ、日付、SHA-256ハッシュ • クラス定義 • バージョン、アクセスフラグ、クラス名、親クラス、etc... • 定数プール • フィールド情報 • メソッド情報 • 定義、バイトコード、変数テーブル、etc... • 属性 etc...

Slide 22

Slide 22 text

定数プールとは • クラスで使用する定数の集合 • 文字列 • 数値 • メソッド定義 • etc... • #1 から順に、番号が振られている • #0 は null を意味する Constant pool: #1 = Methodref #2.#3 // java/lang/Object."":()V #2 = Class #4 // java/lang/Object #3 = NameAndType #5:#6 // "":()V #4 = Utf8 java/lang/Object #5 = Utf8 ...

Slide 23

Slide 23 text

定数プールのタグ • それぞれの定数には、内容を表す「タグ」がついている 分類 タグ 文字列 Utf8 数値定数 Integer, Float, Long, Double 文字列定数 String クラス Class 定義 Fieldref, Methodref, InterfaceMethodref 型 NameAndType 動的呼び出し MethodHandle, MethodType, InvokeDynamic, Dynamic その他 Module, Package

Slide 24

Slide 24 text

• Invokevirtual(インスタンスメソッド呼び出し)のパラメータ • パラメータで指定されたメソッドを呼び出す 定数プールの使用例 #15 = Methodref #16.#17 #16 = Class #18 #17 = NameAndType #19:#20 #18 = Utf8 java/io/PrintStream #19 = Utf8 println #20 = Utf8 (Ljava/lang/String;)V Code: stack=2, locals=1, args_size=1 (中略) 5: invokevirtual #15 8: return

Slide 25

Slide 25 text

• Methodref(メソッド定義) • クラス定義(#16)、名前とディスクリプタ(#17)を参照 • Class(クラス定義) • クラス名(#18)を参照 • NameAndType(名前とディスクリプタ) • 名前(#19)、ディスクリプタ(#20)を参照 • つまり #15 は、 java.io.PrintStream クラスの void println(java.langString) メソッド 定数プールの使用例 #15 = Methodref #16.#17 #16 = Class #18 #17 = NameAndType #19:#20 #18 = Utf8 java/io/PrintStream #19 = Utf8 println #20 = Utf8 (Ljava/lang/String;)V

Slide 26

Slide 26 text

-verbose で表示される内容(再掲) • ファイルのプロパティ • パス、サイズ、日付、SHA-256ハッシュ • クラス定義 • バージョン、アクセスフラグ、クラス名、親クラス、etc... • 定数プール • フィールド情報 • メソッド情報 • 定義、バイトコード、変数テーブル、etc... • 属性 etc... フィールド情報は解説をスキップ (メソッド情報とほぼ同じなので)

Slide 27

Slide 27 text

メソッド情報 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #13 // String Hello world. 5: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 3: 0 line 4: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; コード属性 行番号テーブル属性 ローカル変数テーブル属性 メソッド定義

Slide 28

Slide 28 text

メソッド定義 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC • 定義 • 内部表現(ディスクリプタ、アクセスフラグ、など)を分かりやすく出力し たもの • とりあえず、ここだけ見ればOK • ディスクリプタ • フィールドやメソッドの型を表現した文字列 • メソッドの場合は、引数と戻り値の型を示している • アクセスフラグ • アクセス許可とメソッド属性を表現したフラグ

Slide 29

Slide 29 text

メソッド情報:ディスクリプタ descriptor: ([Ljava/lang/String;)V • 引数と戻り値 • カッコ内が、引数 • 表記例 • void (String[] foo) • ([Ljava/lang/String;)V • byte (char c, long[][] map) • (C[[J)B FieldType 文字 型 意味 B byte 符号付きバイト C char 基本多言語面の Unicode 文字コード ポイント (UTF-16 エンコード) D double 倍精度浮動小数点数値 F float 単精度浮動小数点数地 I int 符号付き32​ビット整数 J long 符号付き64ビット整数 L ClassName; reference クラスのインスタンス S short 符号付き16ビット整数 V void (戻り値がない) Z boolean true または false [ reference 配列次元(1次元分)

Slide 30

Slide 30 text

メソッド情報:アクセスフラグ flags: (0x0009) ACC_PUBLIC, ACC_STATIC フラグ名 値 意味 ACC_PUBLIC 0x0001 public と宣言されている ACC_PRIVATE 0x0002 private と宣言されている ACC_PROTECTED 0x0004 protected と宣言されている ACC_STATIC 0x0008 static と宣言されている ACC_FINAL 0x0010 final と宣言されている(オーバーライドできない) ACC_SYNCHRONIZED 0x0020 synchronized と宣言されている(起動時はモニタ・ロックで囲まれる) ACC_BRIDGE 0x0040 ブリッジメソッド(コンパイラが生成した) ACC_VARARGS 0x0080 ... (可変長引数)と宣言されている ACC_NATIVE 0x0100 nativeと宣言されている(Java 以外の言語で実装されている) ACC_ABSTRACT 0x0400 abstract と宣言されている(実装が提供されていない) ACC_STRICT 0x0800 srictfp と宣言されている(浮動小数点が FP-strict である) ACC_SYNTHETIC 0x1000 合成メソッド(ソースコードに存在しない)

Slide 31

Slide 31 text

メソッド情報:バイトコード Code: stack=2, locals=1, args_size=1 0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #13 // String Hello world. 5: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return • stack : オペランド・スタックの最大の深さ • locals : 割り当てられるローカル変数の数 • ソースコード上のローカル変数の数とは必ずしも一致しない • インスタンスメソッドの場合、this で1つ消費 • long, double は2つ分を消費 • args_size : 引数の数

Slide 32

Slide 32 text

メソッド情報:バイトコード Code: stack=2, locals=1, args_size=1 0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #13 // String Hello world. 5: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return • ニーモニック(命令)とオペランドの組み合わせ • 先頭の数字はバイトコード内のオフセット • このコードの内容 • #7 の static フィールド(System.out)をスタックに積む • #13 の定数("Hello world.")をスタックに積む • #15 のメソッド( void PrintStream#println(String) )を呼び出す • 終了

Slide 33

Slide 33 text

補足:ニーモニック一覧 • 全113個 • わかりやすく読みやすい • Java 1.0 以降、ほとんど変化がない • Java 7 で invokedynamic が追加になっただけ • 詳しくは、JVM仕様を参照 • https://docs.oracle.com/javase/ specs/jvms/se20/html/jvms-6.html

Slide 34

Slide 34 text

メソッド情報: 行番号テーブル、ローカル変数テーブル • LineNumberTable • バイトコードとソースコードの行数の対応表 • LocalVariableTable • ローカル変数のスコープと変数名の対応表 • デバッグに必要な情報 • コンパイル時に -g:none を指定すると出力されない LineNumberTable: line 5: 0 line 6: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String;

Slide 35

Slide 35 text

クラス属性 • SourceFile属性 • コンパイルしたソースコードのファイル名(パスは含まない) • プロパティの "Compiled From" はこの属性の情報と同じ • デバッグに必要な情報 • コンパイル時に -g:none を指定すると出力されない SourceFile: "Main.java"

Slide 36

Slide 36 text

主なクラスの属性 • InnerClasses • インナークラスの定義 • Signature • ジェネリクス型の定義 InnerClasses: private static #41= #22 of #26; // Inner=class Main$Inner of class Main Signature: #51 // Ljava/lang/Enum;

Slide 37

Slide 37 text

主なクラスの属性 • PermittedSubclasses • シール・クラス(sealed class)で継承を許可されたクラス PermittedSubclasses: Circle Square Rectangle public sealed class Shape permits Circle, Square, Rectangle { }

Slide 38

Slide 38 text

-verbose で表示される内容(再掲) • ファイルのプロパティ • パス、サイズ、日付、SHA-256ハッシュ • クラス定義 • バージョン、アクセスフラグ、クラス名、親クラス、etc... • 定数プール • フィールド情報 • メソッド情報 • 定義、バイトコード、変数テーブル、etc... • 属性 etc...

Slide 39

Slide 39 text

まとめ • javap は、JDK に付属しているツールの一つ • クラスファイルの情報を、人間に読みやすい形式で出力してくれる • これを使うことで、クラスファイルの詳細を把握できる • ぜひ、いろんなファイルを javap してみてください!

Slide 40

Slide 40 text

javap を使ってクラスファイル を読んでみよう! @YujiSoftware https://yuji.software/javap/

Slide 41

Slide 41 text

参考資料 • Java Virtual Machine Specification • Chapter 4. The class File Format • https://docs.oracle.com/javase/specs/jvms/se20/html/jvms-4.html • Java 仮想マシン仕様(第二版) • 上記の日本語訳 • すでに絶版 (´・ω・`) • 最新の仕様が載っていない • 2001年出版、Java 1.2 時代の仕様 • 基本的な構造は変わっていないので、 この本 + 上記のページを読むのがおすすめ

Slide 42

Slide 42 text

参考資料 • javap コマンドのドキュメント • https://docs.oracle.com/javase/jp/19/docs/specs/man/javap.html • javap のソースコード • https://github.com/openjdk/jdk/blob/master/src/jdk.jdeps/share/classes/co m/sun/tools/javap/Main.java • -verbose の出力については、ソースコードがドキュメント

Slide 43

Slide 43 text

補足 Q: javap –verbose の出力をパースして、ツールを作ると面白そう! A: やめたほうがいいです • javap の出力は構造化されていません • パースしづらい • バージョンによって変わる • 代わりに、クラスリーダーを使いましょう! • Object Web ASM の ClassReader • https://asm.ow2.io/javadoc/org/objectweb/asm/ClassReader.html • 標準APIも準備中 • JEP draft: Class-File API (Preview) https://openjdk.org/jeps/8280389

Slide 44

Slide 44 text

補足:便利なツール • Visual Studio Code に JVM Bytecode Viewer を 入れておくと便利 • https://marketplace.visualstudio .com/items?itemName=mnxn.jv m-bytecode-viewer • クラスファイルを開いた際、自 動で javap してくれる • シンタックスハイライトしてく れる