Upgrade to Pro — share decks privately, control downloads, hide ads and more …

javapを使ってクラスファイルを読んでみよう!

 javapを使ってクラスファイルを読んでみよう!

(JJUG CCC 2023 Spring 登壇資料)

みなさんは、Java のクラスファイルを読んだことがありますか。

そこには、Java を実行する上で必要なあらゆる情報が格納されています。
そう、Java について詳しく知ろうと思ったら、クラスファイルを読むことは避けて通れないのです。

「でも、クラスファイルってあくまで JVM が読むためのもので、人間には読めないんじゃないの?」
そんなことはありません!
Java のクラスファイルは、 javap というツールを使えば、わかりやすい形式で読むことができるのです。

今回は、そんな javap を使ってクラスファイルのどのような情報を知ることができるのか、どのように読んでいけばいいのかについて解説します。

このセッションで、あなたも JVM になれる!(かも)

YujiSoftware

June 04, 2023
Tweet

More Decks by YujiSoftware

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. 使い方
    • 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

    View Slide

  7. 実行例
    • シンプルな 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[]);
    }
    フィールドとメソッドの一覧が出力される

    View Slide

  8. よく使うオプション
    • -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

    View Slide

  9. 実行例 (-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

    View Slide

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

    View Slide

  11. 順番に解説していきます

    View Slide

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

    View Slide

  13. ファイルのプロパティ
    • クラスファイルのパス
    • 最終更新日
    • 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"
    ディスク上の情報
    (中身ではない)

    View Slide

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

    View Slide

  15. クラス定義
    • アイテム : 値 という形式で出力される
    • // 以降は定数プールの値
    • 読みやすくなるように、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

    View Slide

  16. クラス定義:バージョン
    • 実行対象の 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

    View Slide

  17. クラス定義:アクセスフラグ
    • アクセス許可とプロパティの指定
    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 モジュール

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. 定数プールとは
    • クラスで使用する定数の集合
    • 文字列
    • 数値
    • メソッド定義
    • 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
    ...

    View Slide

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

    View Slide

  24. • 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

    View Slide

  25. • 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

    View Slide

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

    View Slide

  27. メソッド情報
    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;
    コード属性
    行番号テーブル属性
    ローカル変数テーブル属性
    メソッド定義

    View Slide

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

    View Slide

  29. メソッド情報:ディスクリプタ
    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次元分)

    View Slide

  30. メソッド情報:アクセスフラグ
    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 合成メソッド(ソースコードに存在しない)

    View Slide

  31. メソッド情報:バイトコード
    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 : 引数の数

    View Slide

  32. メソッド情報:バイトコード
    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) )を呼び出す
    • 終了

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  41. 参考資料
    • 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 時代の仕様
    • 基本的な構造は変わっていないので、
    この本 + 上記のページを読むのがおすすめ

    View Slide

  42. 参考資料
    • 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 の出力については、ソースコードがドキュメント

    View Slide

  43. 補足
    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

    View Slide

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

    View Slide