わかる !Java バイトコード ―  30 分でわからない ?Java バイトコード入門 筑波大学大学院 システム情報工学研究科 博士後期課程 水島宏太
自己紹介とか Twitter: @kmizu はてな : id:kmizushima github: http://github.com/kmizu/ 大学院生 構文解析の研究とかやってます 特に Packrat Parsing Scala 好き Scala の布教活動をあちこちでやったり JVM 好き JVM 上で動作する言語処理系 Onion を開発
Agenda プログラミング言語としての Java バイトコード マシンモデル 型システム 命令セット クラスファイルベリファイア ベリファイアがはじく操作の例 簡単なプログラムを javap で逆アセンブルする 役に立つかもしれない javap のオプション解説 クラスファイル仕様については省略 時間が足りないので…
Javaバイトコードとは Java仮想マシン(JVM)の機械語命令 オペコード が1 バイト である事に由来 1命令 が必ずしも1バイトなわけではない(後述) 特徴: スタックマシン オブジェクト指向的(Java的)な型システム 安全でない操作が(基本的に)できない クラスファイルベリファイアによる事前チェック
Hello, World!を逆アセンブルする public class Hello { public static void main(String[] args){ System.out.println("Hello, World!"); } } ↓ public static void main(java.lang.String[]); Code: 0:  getstatic  #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3:  ldc  #3; //String Hello, World! 5:  invokevirtual  #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8:  return
Hello, World!を読む 0:  getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream; 0:  バイトコード配列の 0 番目を意味している getstatic:  オペコードのニーモニック java/lang/System.out: System.out の完全限定名 Ljava/io/PrintStream;: System.out の型 System.out をスタックにロード 3:  ldc   #3; //String Hello, World! //←1: ではなく 3: である点に注意 文字列定数 "Hello, World!" をスタックにロード 5:  invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V PrintStream のメソッド println を呼び出す 8:  return メソッドから return する
スタックマシン 演算対象( オペランド )がスタックに置かれる ←->レジスタマシン オペランドが置かれるスタック= オペランドスタック おおざっぱに言って、以下の繰り返し 1. 命令( オペコード )を取り出す 2. オペランドスタックから値をポップ 3. 命令を実行 4. 実行結果をオペランドスタックにプッシュ 5. 1.に戻る
スタックマシンの動作イメージ var pc: int var ops: byte[] ... while(pc < ops.length) { switch(ops[pc]) { case ADD: r = pop; l = pop; push(l + r); case SUB: r = pop; l = pop; push(l - r); case DIV: ... } pc++; }
型システム プリミティブ型 byte, short, int, long, char float, double void boolean 参照型 配列型 クラス型 インタフェース型 だいたい Java と同じだけど細かい所が違う
実際の型と計算上の型とカテゴリ 演算命令が直接サポートしていない型が存在 byte, short, char, boolean int 型の値として計算される カテゴリ おおざっぱに言うと、オペランドスタック上における値のサイズ boolean, byte, char, short, int, float,  参照型は 1 long, double は 2 命令のオペランドは特定のカテゴリでないと受け付けないものがある
実際の型と計算上の型とカテゴリ 2 double double 2 long long 1 returnAddress returnAddress 1 reference reference 1 float float 1 int int 1 int short 1 int char 1 int byte 1 int boolean カテゴリ 計算上の型 実際の型
命令の分類 大体 Java 仮想マシン仕様第 2 版に沿ってるが、一部独自に分類 ロード / ストア命令 算術命令 型変換命令 配列関係の命令 オブジェクトの生成・操作 制御命令 例外のスロー 同期化命令 メソッド呼び出し関係の命令 使われなくなった命令
ロード / ストア命令  (1) –  定数をオペランドスタックにロードする命令 bipush … byte の即値をプッシュ sipush … short の即値をプッシュ iconst_<i> … -1,0,...,5 をプッシュ aconst_null … null をプッシュ ldc, ldc_w, ldc2_w 文字列定数 , 整数 , 浮動小数点数を 実行時コンスタントプール からプッシュする など
bipush byte の即値をプッシュ フォーマット:  <0x10, byte> オペランドスタック:  ... ⇒ ..., value byte の即値が int の value へと符号拡張され、プッシュされる
ldc 実行時コンスタントプールから値をプッシュする フォーマット:  <0x18, index> index が示すエントリは、 int 型または float 型の実行時定数か、文字列リテラルへのシンボル参照でなければならない オペランドスタック:  ... ⇒ ..., value エントリが int 型または float 型の実行時定数の場合、該当する定数が int 型あるいは float 型としてプッシュされる エントリが文字列リテラルへのシンボル参照の場合、そのインスタンスへの参照がプッシュされる
ロード / ストア命令  (2) –  ローカル変数の値をオペランドスタックにロードする iload …  ローカル変数から int をロード iload_<n> …  ローカル変数から int をロード n = 0 ~ 3 fload …  ローカル変数から float をロード fload_<n> …  ローカル変数から float をロード n = 0 ~ 3 aload …  ローカル変数から参照型の値をロード など
iload ローカル変数から int をロードする フォーマット:  <0x15,index> オペランドスタック:  ..., ⇒ ..., value index 番目のローカル変数の値がスタックにプッシュされる index 番目のローカル変数には int 型の値が保持されていなければならない
fload ローカル変数から float をロードする フォーマット:  <0x17,index> オペランドスタック:  ..., ⇒ ..., value index 番目のローカル変数の値がスタックにプッシュされる index 番目のローカル変数には float 型の値が保持されていなければならない
ロード / ストア命令  (3) –  値をローカル変数にストアする istore …  ローカル変数に int をストア istore_<n> …  ローカル変数から int をロード n = 0 ~ 3 fstore …  ローカル変数に float をストア fstore_<n> …  ローカルに float をストア n = 0 ~ 3 astore …  ローカル変数に参照型の値をストア など
istore ローカル変数に int をストアする フォーマット:  <0x36,index> オペランドスタック:  ..., value ⇒ ..., スタックトップの値が、 index 番目のローカル変数にストアされる スタックトップの値は int 型 でなければならない
fstore ローカル変数に float をストアする フォーマット:  <0x38,index> オペランドスタック:  ..., value ⇒ ..., スタックトップの値が、 index 番目のローカル変数にストアされる スタックトップの値は float 型 でなければならない
算術命令 iadd, ladd, fadd, dadd …  加算 isub, lsub, fsub, dsub …  減算 imul, lmul, fmul, dmul …  乗算 idiv, ldiv, fdiv, ddiv …  除算 irem, lrem, frem, drem …  剰余 ineg, lneg, fneg, dneg …  符号反転 など
iadd int の加算を行う フォーマット:  <0x60> オペランドスタック:  ..., value1, value2 ⇒ ..., result value1 + value2 の結果がスタックにプッシュされる value1 と value2 は int 型 でなければならない
lsub long の加算を行う フォーマット:  <0x65> オペランドスタック:  ..., value1, value2 ⇒ ..., result value1 - value2 の結果がスタックにプッシュされる value1 と value2 は long 型 でなければならない
型変換命令 ワイドニング数値変換命令 i2l … int から long への型変換を行う i2f … int から float への型変換を行う … ナローイング数値変換命令 i2b … int から byte への変換を行う i2c … int から char への変換を行う …
i2l int を long に変換する フォーマット:  <0x85> オペランドスタック:  ..., value ⇒ ..., result value が long へと符号拡張された結果がプッシュされる value は int 型 でなければならない
i2b int を byte に変換する フォーマット:  <0x91> オペランドスタック:  ..., value ⇒ ..., result value が byte へ切り捨てられ、 int へと符号拡張された結果がプッシュされる value は int 型 でなければならない
配列関係の命令 配列を生成する命令 newarray … 1 次元配列を生成する … 配列の要素をロードする命令 baload … byte 型の配列要素をロードする … 配列の要素に値をストアする命令 bastore … byte 型の配列要素に値をストアする … arraylength …  配列の長さを取得する
newarray 配列を生成する フォーマット:  <0xbc,atype> atype は生成する配列の型 ( プリミティブ型 ) を表したコード 4,5,6,7,8,9,10,11 のいずれか オペランドスタック:  ..., count ⇒ ..., objectref count は int 型でなければならない 要素型が atype で長さが count の配列が生成されて、配列への参照がプッシュされる
arraylength 配列の長さを取得する フォーマット:  <0xbe> オペランドスタック:  ..., arrayref ⇒ ..., length arrayref は配列への参照でなければならない arrayref が参照する配列の長さがスタックにプッシュされる
オブジェクトの生成・操作 new …  オブジェクトを生成 getfield …  フィールドを取得 putfield …  フィールドに値をストア getstatic … static フィールドを取得 putstatic … static フィールドに値をストア instanceof … Java の instanceof 相当 checkcast …  参照型のキャストを行う
new オブジェクトを生成する フォーマット:  <0xbb,indexbyte1,indexbyte2> indexbyte はクラス型へのシンボル参照でなければならない オペランドスタック:  ..., ⇒ ..., objectref 指定された型のオブジェクトが生成されて、スタックにプッシュされる
getstatic static フィールドを取得する フォーマット:  <0xb2,indexbyte1,indexbyte2> indexbyte は該当するフィールドへのシンボル参照でなければならない オペランドスタック:  ..., ⇒ ..., value static フィールドの値がスタックにプッシュされる
スタック管理命令 スタック上の値の順番を入れ替えたりする pop, pop2 …  スタックから値をポップする dup, dup2 …  スタックトップの値を複製 dup_x1 …  スタックトップの値を複製して、トップの二つ下の値として挿入 dup_x2 …
pop スタックトップの値をポップする フォーマット:  <0x57> オペランドスタック:  ..., value ⇒ ... スタックトップの値がカテゴリ 1 の場合のみ使用可
dup スタックトップの値を複製する フォーマット:  <0x59> オペランドスタック:  ..., value ⇒ ..., value, value スタックトップの値がカテゴリ 1 の場合のみ使用可
制御命令 条件分岐命令 ifeq, iflt, ifne, ifgt, ifge, ... 複合条件分岐命令 tableswitch, lookupswitch switch 文に対応 無条件分岐命令 goto, goto_w,  jsr, jsr_w, ret
goto 無条件ジャンプを行う フォーマット:  <0xa7,branchbyte1,branchbyte2> branchbyte(1|2) から、分岐オフセットが生成される オペランドスタック:  ... ⇒ ...
ifeq スタックトップと 0 が等しい場合に該当アドレスにジャンプする 成功しない場合、次の命令があるアドレスから実行が再開 フォーマット:  <0x99,branchbyte1,branchbyte2> branchbyte(1|2) から、分岐オフセットが生成される オペランドスタック:  ..., value ⇒ ...
例外のスロー athrow …  例外を投げる命令 Java の throw 文に相当
athrow 例外を throw する 適合するハンドラが見つかるまでメソッドを巻き戻る throw された例外 フォーマット:  <0xbf> オペランドスタック:  ..., objectref ⇒ objectref ハンドラにジャンプする際に、現在のオペランドスタックがクリアされる ハンドラにジャンプする際に、 throw された例外オブジェクトへの参照がプッシュされる
同期化命令 要は synchronized 文を実装するための命令 monitorenter …  モニタに入る monitorexit …  モニタから抜ける monitor(enter/exit) を対応させるのはコンパイラ (javac など ) の責任 ベリファイアはチェックしない 例外や break/continue などで synchronized を抜けるときにもちゃんと対応する箇所で monitorexit を生成してあげないといけない…
メソッド呼び出し関係の命令 invokestatic … static メソッド呼び出し invokespecial …  コンストラクタ ( 等 ) 呼び出し invokevirtual …  インスタンスメソッド呼び出し invokeinterface …  インタフェースのメソッド呼び出し ireturn, lreturn, freturn, dreturn, areturn, return …  メソッドからの return
invokestatic static メソッドを呼び出す フォーマット:  <0xb8,indexbyte1,indexbyte2> indexbyte は、メソッドへのシンボル参照がある実行時コンスタントプールへのインデックス オペランドスタック:  ..., arg1, arg2, ..., arg n  ⇒ ... arg1, arg2, ... が引数 メソッドの返り値の型に応じて結果がスタックにプッシュされる
invokevirtual インスタンスメソッドを呼び出す フォーマット:  <0xb6,indexbyte1,indexbyte2> indexbyte は、メソッドへのシンボル参照がある実行時コンスタントプールへのインデックス オペランドスタック:  ..., objectref, arg1, arg2, ..., arg n  ⇒ ... objectref はオブジェクトへの参照 arg1, arg2, ... が引数 メソッドの返り値の型に応じて結果がスタックにプッシュされる interface 型の参照に対しては使えない 点に注意 invokeinterface を使う
使われなくなった命令 jsr, ret …  元々は finally の実装に使われていた JDK 1.4.2 から使われなくなった ベリファイア絡みで不具合があることが判明したため http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4381996   finally は code duplication によって実装されるように変更
クラスファイルベリファイア クラスファイル (.class) のフォーマットが Java 仮想マシン仕様に従っているかどうかを 該当クラスの初期化より前に チェック たとえば: 同じ pc におけるオペランド・スタックのサイズは常に一定でなければならない オペランド・スタックのオーバーフローやアンダーフローは原理的に発生しない 存在しないローカル変数に対するアクセスは無い 未初期化のローカル変数に対するアクセスは無い
jasmin 用のプログラム jasmin: Java バイトコードアセンブラ >java Overflow Exception in thread &quot;main&quot;  java.lang.VerifyError : (class: Overflow, method: main signature: ([Ljava/lang/String;)V)  Inconsistent stack height 1 != 0 ベリファイアがはねるプログラム (1) –  スタックオーバーフロー .class public Overflow .super java/lang/Object .method public static main([Ljava/lang/String;)V .limit stack 2 LABEL: ldc &quot;Hello, World!&quot; goto LABEL return .end method
>java Uninitialized Exception in thread &quot;main&quot;  java.lang.VerifyError : (class: Uninitialized, method: main signature: ([L java/lang/String;)V)  Accessing value from uninitialized register 1 ベリファイアがはねるプログラム (2) –  未初期化ローカル変数へのアクセス .class public Uninitialized .super java/lang/Object .method public static main([Ljava/lang/String;)V .limit stack 2 .limit locals 3 iload_1 return .end method
>java Incompatible Exception in thread &quot;main&quot;  java.lang.VerifyError : (class: Incompatible, method: main signature: ([Lj ava/lang/String;)V)  Expecting to find integer on stack ベリファイアがはねるプログラム (3)  -  型エラー .class public Incompatible .super java/lang/Object .method public static main([Ljava/lang/String;)V .limit stack 2 ldc 3.5 ldc 4.0 iadd return .end method
>java PopLong Exception in thread &quot;main&quot;  java.lang.VerifyError : (class: PopLong, method: main signature: ([Ljava/lang/String;)V)  Attempt to split long or double on the stack ベリファイアがはねるプログラム (4)  -  カテゴリ間違い .class public PopLong .super java/lang/Object .method public static main([Ljava/lang/String;)V .limit stack 3 invokestatic java/lang/System/currentTimeMillis()J pop  ; pop2 なら OK . pop はカテゴリ 1 の値しか pop できないが long はカテゴリ 2 return .end method
練習問題 ↓ のコンパイル結果を逆アセンブルしたものを読んでみる ',' で区切られた項目 ( 整数 ) の合計値を計算 import java.util.Scanner; public class CalcSum { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); while(scanner.hasNext()) { int result = 0; String[] line = scanner.nextLine().split(&quot;,&quot;); for(String element:line) result += Integer.parseInt(element); System.out.println(&quot;result:&quot; + result); } } }
練習問題 逆アセンブル (javap –c CalcSum) した結果 public class CalcSum { ↓ Compiled from &quot;CalcSum.java&quot; public class CalcSum extends java.lang.Object{ public CalcSum(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object.&quot;<init>&quot;:()V 4: return
練習問題 逆アセンブル (javap –c CalcSum) した結果 public static void main(String[] args) { Scanner scanner = new Scanner(System.in); ↓ public static void main(java.lang.String[]); Code: 0: new #2; //class java/util/Scanner 3: dup 4: getstatic #3; //Field java/lang/System.in:Ljava/io/InputStream; 7: invokespecial #4; //Method java/util/Scanner.&quot;<init>&quot;:(Ljava/io/InputStream;)V 10: astore_1
練習問題 逆アセンブル (javap –c CalcSum) した結果 while(scanner.hasNext()) { ↓ 11: aload_1 12: invokevirtual #5; //Method java/util/Scanner.hasNext:()Z 15: ifeq 97
練習問題 逆アセンブル (javap –c CalcSum) した結果 int result = 0; String[] line = scanner.nextLine().split(&quot;,&quot;); ↓ 18: iconst_0 19: istore_2 20: aload_1 21: invokevirtual #6; //Method java/util/Scanner.nextLine:()Ljava/lang/String; 24: ldc #7; //String , 26: invokevirtual #8; //Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String; 29: astore_3 30: aload_3 31: astore 4
練習問題 逆アセンブル (javap –c CalcSum) した結果 for(String element:line) result += Integer.parseInt(element); ↓ 33: aload 4 35: arraylength 36: istore 5 38: iconst_0 39: istore 6 41: iload 6 43: iload 5 45: if_icmpge 69 48: aload 4 50: iload 6 52: aaload 53: astore 7 55: iload_2 56: aload 7 58: invokestatic #9; //Method java/lang/Integer.parseInt:(Ljava/lang/String;)I 61: iadd 62: istore_2 63: iinc 6, 1 66: goto 41
練習問題 逆アセンブル (javap –c CalcSum) した結果 System.out.println(&quot;result:&quot; + result); } } } ↓ 69: getstatic #10; //Field java/lang/System.out:Ljava/io/PrintStream; 72: new #11; //class java/lang/StringBuilder 75: dup 76: invokespecial #12; //Method java/lang/StringBuilder.&quot;<init>&quot;:()V 79: ldc #13; //String result: 81: invokevirtual #14; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 84: iload_2 85: invokevirtual #15; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 88: invokevirtual #16; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 91: invokevirtual #17; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 94: goto 11 97: return }
知っていると役に立つ ( かもしれない )javap のオプション -c:  逆アセンブルする。一番よく使う -private: private なものを含む全てのメンバを表示 -c などと組み合わせて使う事が多い -verbose:  クラスファイルのバージョン、コンスタントプールなどの詳細な情報を表示 -s:  内部的な型シグネチャの情報を表示 public static void main(java.lang.String[]) ->  ([Ljava/lang/String;)V
何が嬉しいの? Java バイトコードにコンパイルする俺言語 / フレームワーク等を作れるようになる バイトコード変換を利用したツールやフレームワークを作れるようになる AOP とか バイトコードにコンパイルする言語処理系やツールのバグなどを発見できるようになる 発表者は、 scala が生成したコードをよく javap している 面白い
まとめ Java バイトコードの マシンモデル 命令セット 型システム について簡単に解説 クラスファイルベリファイアの概要を説明 簡単なサンプルコードを逆アセンブルしてみた 知っていると役に立つかもしれない javap オプション 参考文献:  Java 仮想マシン仕様第2版,ティム・リンドホルム、フランク・イェリン,ピアソン・エデュケーション Web で無料公開 http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html   されてるので、そちらでも可 ( ただし英語 )

More Related Content

PDF
Javaコードが速く実⾏される秘密 - JITコンパイラ⼊⾨(JJUG CCC 2020 Fall講演資料)
PPTX
フックを使ったPostgreSQLの拡張機能を作ってみよう!(第33回PostgreSQLアンカンファレンス@オンライン 発表資料)
PDF
12 分くらいで知るLuaVM
PDF
Pythonによる黒魔術入門
PPTX
BuildKitによる高速でセキュアなイメージビルド
PDF
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
PDF
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
PDF
例外設計における大罪
Javaコードが速く実⾏される秘密 - JITコンパイラ⼊⾨(JJUG CCC 2020 Fall講演資料)
フックを使ったPostgreSQLの拡張機能を作ってみよう!(第33回PostgreSQLアンカンファレンス@オンライン 発表資料)
12 分くらいで知るLuaVM
Pythonによる黒魔術入門
BuildKitによる高速でセキュアなイメージビルド
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
例外設計における大罪

What's hot (20)

PDF
【第26回Elasticsearch勉強会】Logstashとともに振り返る、やっちまった事例ごった煮
PDF
Elasticsearchを使うときの注意点 公開用スライド
PDF
10分で分かるLinuxブロックレイヤ
PDF
DockerとPodmanの比較
PDF
仕様起因の手戻りを減らして開発効率アップを目指すチャレンジ 【DeNA TechCon 2020 ライブ配信】
PDF
Dalvik仮想マシンのアーキテクチャ 改訂版
PDF
オブジェクト指向できていますか?
PDF
TLS, HTTP/2演習
PDF
モダン PHP テクニック 12 選 ―PsalmとPHP 8.1で今はこんなこともできる!―
PDF
Rust で RTOS を考える
PDF
CPU / GPU高速化セミナー!性能モデルの理論と実践:実践編
PDF
Twitterのsnowflakeについて
PDF
あなたのScalaを爆速にする7つの方法(日本語版)
PDF
組み込み関数(intrinsic)によるSIMD入門
PPTX
スマホゲームのチート手法とその対策 [DeNA TechCon 2019]
PPTX
ネットストーカー御用達OSINTツールBlackBirdを触ってみた.pptx
PPTX
データ履歴管理のためのテンポラルデータモデルとReladomoの紹介 #jjug_ccc #ccc_g3
PDF
トランザクションスクリプトのすすめ
PDF
ストリーム処理プラットフォームにおけるKafka導入事例 #kafkajp
PDF
オブジェクト指向の設計と実装の学び方のコツ
【第26回Elasticsearch勉強会】Logstashとともに振り返る、やっちまった事例ごった煮
Elasticsearchを使うときの注意点 公開用スライド
10分で分かるLinuxブロックレイヤ
DockerとPodmanの比較
仕様起因の手戻りを減らして開発効率アップを目指すチャレンジ 【DeNA TechCon 2020 ライブ配信】
Dalvik仮想マシンのアーキテクチャ 改訂版
オブジェクト指向できていますか?
TLS, HTTP/2演習
モダン PHP テクニック 12 選 ―PsalmとPHP 8.1で今はこんなこともできる!―
Rust で RTOS を考える
CPU / GPU高速化セミナー!性能モデルの理論と実践:実践編
Twitterのsnowflakeについて
あなたのScalaを爆速にする7つの方法(日本語版)
組み込み関数(intrinsic)によるSIMD入門
スマホゲームのチート手法とその対策 [DeNA TechCon 2019]
ネットストーカー御用達OSINTツールBlackBirdを触ってみた.pptx
データ履歴管理のためのテンポラルデータモデルとReladomoの紹介 #jjug_ccc #ccc_g3
トランザクションスクリプトのすすめ
ストリーム処理プラットフォームにおけるKafka導入事例 #kafkajp
オブジェクト指向の設計と実装の学び方のコツ
Ad

Viewers also liked (20)

PDF
JVMの中身を可視化してみた
PPTX
JJUG CCC 2015 Spring 「新人エンジニア奮闘記 - Javaって何?からwebサービスを公開するまで -」発表スライド
PDF
Javaはどのように動くのか~スライドでわかるJVMの仕組み
PDF
10のJava9で変わるJava8の嫌なとこ!
PDF
Stack on JavaVM
PPTX
先駆的研究テーマ mruby/c
PDF
クラスローダーについて
PDF
Vimから見たemacs
PDF
プログラミング超超超入門
PDF
情報編集 (web) 第4回:HTML入門 3 情報を整理する - リスト、テーブル
PPTX
Google検索だけで満足しない、一歩先をいく収集・整理術(1day)
PDF
Java の Collection 関連について整理してみました
PDF
僕のデスクトップ整理方法
PDF
データを整理するための基礎知識
PPTX
再入門、サーバープッシュ技術
PDF
201412ことばの理解とワーキングメモリ:基本概念の整理(公開)
PDF
再入門!RESTとSpringMVC
PPTX
SSL入門
PDF
Docker超入門
PPTX
HTTP/2入門
JVMの中身を可視化してみた
JJUG CCC 2015 Spring 「新人エンジニア奮闘記 - Javaって何?からwebサービスを公開するまで -」発表スライド
Javaはどのように動くのか~スライドでわかるJVMの仕組み
10のJava9で変わるJava8の嫌なとこ!
Stack on JavaVM
先駆的研究テーマ mruby/c
クラスローダーについて
Vimから見たemacs
プログラミング超超超入門
情報編集 (web) 第4回:HTML入門 3 情報を整理する - リスト、テーブル
Google検索だけで満足しない、一歩先をいく収集・整理術(1day)
Java の Collection 関連について整理してみました
僕のデスクトップ整理方法
データを整理するための基礎知識
再入門、サーバープッシュ技術
201412ことばの理解とワーキングメモリ:基本概念の整理(公開)
再入門!RESTとSpringMVC
SSL入門
Docker超入門
HTTP/2入門
Ad

Similar to Javaバイトコード入門 (8)

PDF
BOF1-Scala02.pdf
DOC
详细介绍什么是Java虚拟机(JVM)介绍资料
PPT
How did yarv2llvm fail
PDF
Boostのあるプログラミング生活
PPT
帰って来たNemerle
DOC
JVM 学习笔记
PPTX
C++模板与泛型编程
PPTX
C++模板与泛型编程
BOF1-Scala02.pdf
详细介绍什么是Java虚拟机(JVM)介绍资料
How did yarv2llvm fail
Boostのあるプログラミング生活
帰って来たNemerle
JVM 学习笔记
C++模板与泛型编程
C++模板与泛型编程

More from Kota Mizushima (20)

PDF
ドワンゴにおける新卒エンジニア向けScala研修について
PDF
kollectionの紹介
PDF
株式会社ドワンゴにおけるScala教育の現状
PDF
Macros in nemerle
PDF
Scala Daysに行ってみて - あるいはスイス旅行記 -
PDF
Introduction to PEG
PDF
Scalaの現状と今後
PPT
Power of Scala
PDF
Scala Performance Tuning Tips
PDF
こわくない型クラス
PDF
こわくないScala
PDF
Scala is-unscared
PDF
About Capabilities for Uniqueness and Borrowing
PDF
Scala Macros makes it easy to provide useful libraries
PDF
Scala + Finagleの魅力
PDF
Scalaの現状と課題
PDF
Scalaでのプログラム開発
PDF
日本Scalaユーザーズグループ発足
PDF
Implicit Implicit Scala
PDF
Implicit Explicit Scala
ドワンゴにおける新卒エンジニア向けScala研修について
kollectionの紹介
株式会社ドワンゴにおけるScala教育の現状
Macros in nemerle
Scala Daysに行ってみて - あるいはスイス旅行記 -
Introduction to PEG
Scalaの現状と今後
Power of Scala
Scala Performance Tuning Tips
こわくない型クラス
こわくないScala
Scala is-unscared
About Capabilities for Uniqueness and Borrowing
Scala Macros makes it easy to provide useful libraries
Scala + Finagleの魅力
Scalaの現状と課題
Scalaでのプログラム開発
日本Scalaユーザーズグループ発足
Implicit Implicit Scala
Implicit Explicit Scala

Javaバイトコード入門

  • 1. わかる !Java バイトコード ― 30 分でわからない ?Java バイトコード入門 筑波大学大学院 システム情報工学研究科 博士後期課程 水島宏太
  • 2. 自己紹介とか Twitter: @kmizu はてな : id:kmizushima github: http://github.com/kmizu/ 大学院生 構文解析の研究とかやってます 特に Packrat Parsing Scala 好き Scala の布教活動をあちこちでやったり JVM 好き JVM 上で動作する言語処理系 Onion を開発
  • 3. Agenda プログラミング言語としての Java バイトコード マシンモデル 型システム 命令セット クラスファイルベリファイア ベリファイアがはじく操作の例 簡単なプログラムを javap で逆アセンブルする 役に立つかもしれない javap のオプション解説 クラスファイル仕様については省略 時間が足りないので…
  • 4. Javaバイトコードとは Java仮想マシン(JVM)の機械語命令 オペコード が1 バイト である事に由来 1命令 が必ずしも1バイトなわけではない(後述) 特徴: スタックマシン オブジェクト指向的(Java的)な型システム 安全でない操作が(基本的に)できない クラスファイルベリファイアによる事前チェック
  • 5. Hello, World!を逆アセンブルする public class Hello { public static void main(String[] args){ System.out.println(&quot;Hello, World!&quot;); } } ↓ public static void main(java.lang.String[]); Code: 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3; //String Hello, World! 5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return
  • 6. Hello, World!を読む 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 0: バイトコード配列の 0 番目を意味している getstatic: オペコードのニーモニック java/lang/System.out: System.out の完全限定名 Ljava/io/PrintStream;: System.out の型 System.out をスタックにロード 3: ldc #3; //String Hello, World! //←1: ではなく 3: である点に注意 文字列定数 &quot;Hello, World!&quot; をスタックにロード 5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V PrintStream のメソッド println を呼び出す 8: return メソッドから return する
  • 7. スタックマシン 演算対象( オペランド )がスタックに置かれる ←->レジスタマシン オペランドが置かれるスタック= オペランドスタック おおざっぱに言って、以下の繰り返し 1. 命令( オペコード )を取り出す 2. オペランドスタックから値をポップ 3. 命令を実行 4. 実行結果をオペランドスタックにプッシュ 5. 1.に戻る
  • 8. スタックマシンの動作イメージ var pc: int var ops: byte[] ... while(pc < ops.length) { switch(ops[pc]) { case ADD: r = pop; l = pop; push(l + r); case SUB: r = pop; l = pop; push(l - r); case DIV: ... } pc++; }
  • 9. 型システム プリミティブ型 byte, short, int, long, char float, double void boolean 参照型 配列型 クラス型 インタフェース型 だいたい Java と同じだけど細かい所が違う
  • 10. 実際の型と計算上の型とカテゴリ 演算命令が直接サポートしていない型が存在 byte, short, char, boolean int 型の値として計算される カテゴリ おおざっぱに言うと、オペランドスタック上における値のサイズ boolean, byte, char, short, int, float, 参照型は 1 long, double は 2 命令のオペランドは特定のカテゴリでないと受け付けないものがある
  • 11. 実際の型と計算上の型とカテゴリ 2 double double 2 long long 1 returnAddress returnAddress 1 reference reference 1 float float 1 int int 1 int short 1 int char 1 int byte 1 int boolean カテゴリ 計算上の型 実際の型
  • 12. 命令の分類 大体 Java 仮想マシン仕様第 2 版に沿ってるが、一部独自に分類 ロード / ストア命令 算術命令 型変換命令 配列関係の命令 オブジェクトの生成・操作 制御命令 例外のスロー 同期化命令 メソッド呼び出し関係の命令 使われなくなった命令
  • 13. ロード / ストア命令 (1) – 定数をオペランドスタックにロードする命令 bipush … byte の即値をプッシュ sipush … short の即値をプッシュ iconst_<i> … -1,0,...,5 をプッシュ aconst_null … null をプッシュ ldc, ldc_w, ldc2_w 文字列定数 , 整数 , 浮動小数点数を 実行時コンスタントプール からプッシュする など
  • 14. bipush byte の即値をプッシュ フォーマット: <0x10, byte> オペランドスタック: ... ⇒ ..., value byte の即値が int の value へと符号拡張され、プッシュされる
  • 15. ldc 実行時コンスタントプールから値をプッシュする フォーマット: <0x18, index> index が示すエントリは、 int 型または float 型の実行時定数か、文字列リテラルへのシンボル参照でなければならない オペランドスタック: ... ⇒ ..., value エントリが int 型または float 型の実行時定数の場合、該当する定数が int 型あるいは float 型としてプッシュされる エントリが文字列リテラルへのシンボル参照の場合、そのインスタンスへの参照がプッシュされる
  • 16. ロード / ストア命令 (2) – ローカル変数の値をオペランドスタックにロードする iload … ローカル変数から int をロード iload_<n> … ローカル変数から int をロード n = 0 ~ 3 fload … ローカル変数から float をロード fload_<n> … ローカル変数から float をロード n = 0 ~ 3 aload … ローカル変数から参照型の値をロード など
  • 17. iload ローカル変数から int をロードする フォーマット: <0x15,index> オペランドスタック: ..., ⇒ ..., value index 番目のローカル変数の値がスタックにプッシュされる index 番目のローカル変数には int 型の値が保持されていなければならない
  • 18. fload ローカル変数から float をロードする フォーマット: <0x17,index> オペランドスタック: ..., ⇒ ..., value index 番目のローカル変数の値がスタックにプッシュされる index 番目のローカル変数には float 型の値が保持されていなければならない
  • 19. ロード / ストア命令 (3) – 値をローカル変数にストアする istore … ローカル変数に int をストア istore_<n> … ローカル変数から int をロード n = 0 ~ 3 fstore … ローカル変数に float をストア fstore_<n> … ローカルに float をストア n = 0 ~ 3 astore … ローカル変数に参照型の値をストア など
  • 20. istore ローカル変数に int をストアする フォーマット: <0x36,index> オペランドスタック: ..., value ⇒ ..., スタックトップの値が、 index 番目のローカル変数にストアされる スタックトップの値は int 型 でなければならない
  • 21. fstore ローカル変数に float をストアする フォーマット: <0x38,index> オペランドスタック: ..., value ⇒ ..., スタックトップの値が、 index 番目のローカル変数にストアされる スタックトップの値は float 型 でなければならない
  • 22. 算術命令 iadd, ladd, fadd, dadd … 加算 isub, lsub, fsub, dsub … 減算 imul, lmul, fmul, dmul … 乗算 idiv, ldiv, fdiv, ddiv … 除算 irem, lrem, frem, drem … 剰余 ineg, lneg, fneg, dneg … 符号反転 など
  • 23. iadd int の加算を行う フォーマット: <0x60> オペランドスタック: ..., value1, value2 ⇒ ..., result value1 + value2 の結果がスタックにプッシュされる value1 と value2 は int 型 でなければならない
  • 24. lsub long の加算を行う フォーマット: <0x65> オペランドスタック: ..., value1, value2 ⇒ ..., result value1 - value2 の結果がスタックにプッシュされる value1 と value2 は long 型 でなければならない
  • 25. 型変換命令 ワイドニング数値変換命令 i2l … int から long への型変換を行う i2f … int から float への型変換を行う … ナローイング数値変換命令 i2b … int から byte への変換を行う i2c … int から char への変換を行う …
  • 26. i2l int を long に変換する フォーマット: <0x85> オペランドスタック: ..., value ⇒ ..., result value が long へと符号拡張された結果がプッシュされる value は int 型 でなければならない
  • 27. i2b int を byte に変換する フォーマット: <0x91> オペランドスタック: ..., value ⇒ ..., result value が byte へ切り捨てられ、 int へと符号拡張された結果がプッシュされる value は int 型 でなければならない
  • 28. 配列関係の命令 配列を生成する命令 newarray … 1 次元配列を生成する … 配列の要素をロードする命令 baload … byte 型の配列要素をロードする … 配列の要素に値をストアする命令 bastore … byte 型の配列要素に値をストアする … arraylength … 配列の長さを取得する
  • 29. newarray 配列を生成する フォーマット: <0xbc,atype> atype は生成する配列の型 ( プリミティブ型 ) を表したコード 4,5,6,7,8,9,10,11 のいずれか オペランドスタック: ..., count ⇒ ..., objectref count は int 型でなければならない 要素型が atype で長さが count の配列が生成されて、配列への参照がプッシュされる
  • 30. arraylength 配列の長さを取得する フォーマット: <0xbe> オペランドスタック: ..., arrayref ⇒ ..., length arrayref は配列への参照でなければならない arrayref が参照する配列の長さがスタックにプッシュされる
  • 31. オブジェクトの生成・操作 new … オブジェクトを生成 getfield … フィールドを取得 putfield … フィールドに値をストア getstatic … static フィールドを取得 putstatic … static フィールドに値をストア instanceof … Java の instanceof 相当 checkcast … 参照型のキャストを行う
  • 32. new オブジェクトを生成する フォーマット: <0xbb,indexbyte1,indexbyte2> indexbyte はクラス型へのシンボル参照でなければならない オペランドスタック: ..., ⇒ ..., objectref 指定された型のオブジェクトが生成されて、スタックにプッシュされる
  • 33. getstatic static フィールドを取得する フォーマット: <0xb2,indexbyte1,indexbyte2> indexbyte は該当するフィールドへのシンボル参照でなければならない オペランドスタック: ..., ⇒ ..., value static フィールドの値がスタックにプッシュされる
  • 34. スタック管理命令 スタック上の値の順番を入れ替えたりする pop, pop2 … スタックから値をポップする dup, dup2 … スタックトップの値を複製 dup_x1 … スタックトップの値を複製して、トップの二つ下の値として挿入 dup_x2 …
  • 35. pop スタックトップの値をポップする フォーマット: <0x57> オペランドスタック: ..., value ⇒ ... スタックトップの値がカテゴリ 1 の場合のみ使用可
  • 36. dup スタックトップの値を複製する フォーマット: <0x59> オペランドスタック: ..., value ⇒ ..., value, value スタックトップの値がカテゴリ 1 の場合のみ使用可
  • 37. 制御命令 条件分岐命令 ifeq, iflt, ifne, ifgt, ifge, ... 複合条件分岐命令 tableswitch, lookupswitch switch 文に対応 無条件分岐命令 goto, goto_w, jsr, jsr_w, ret
  • 38. goto 無条件ジャンプを行う フォーマット: <0xa7,branchbyte1,branchbyte2> branchbyte(1|2) から、分岐オフセットが生成される オペランドスタック: ... ⇒ ...
  • 39. ifeq スタックトップと 0 が等しい場合に該当アドレスにジャンプする 成功しない場合、次の命令があるアドレスから実行が再開 フォーマット: <0x99,branchbyte1,branchbyte2> branchbyte(1|2) から、分岐オフセットが生成される オペランドスタック: ..., value ⇒ ...
  • 40. 例外のスロー athrow … 例外を投げる命令 Java の throw 文に相当
  • 41. athrow 例外を throw する 適合するハンドラが見つかるまでメソッドを巻き戻る throw された例外 フォーマット: <0xbf> オペランドスタック: ..., objectref ⇒ objectref ハンドラにジャンプする際に、現在のオペランドスタックがクリアされる ハンドラにジャンプする際に、 throw された例外オブジェクトへの参照がプッシュされる
  • 42. 同期化命令 要は synchronized 文を実装するための命令 monitorenter … モニタに入る monitorexit … モニタから抜ける monitor(enter/exit) を対応させるのはコンパイラ (javac など ) の責任 ベリファイアはチェックしない 例外や break/continue などで synchronized を抜けるときにもちゃんと対応する箇所で monitorexit を生成してあげないといけない…
  • 43. メソッド呼び出し関係の命令 invokestatic … static メソッド呼び出し invokespecial … コンストラクタ ( 等 ) 呼び出し invokevirtual … インスタンスメソッド呼び出し invokeinterface … インタフェースのメソッド呼び出し ireturn, lreturn, freturn, dreturn, areturn, return … メソッドからの return
  • 44. invokestatic static メソッドを呼び出す フォーマット: <0xb8,indexbyte1,indexbyte2> indexbyte は、メソッドへのシンボル参照がある実行時コンスタントプールへのインデックス オペランドスタック: ..., arg1, arg2, ..., arg n ⇒ ... arg1, arg2, ... が引数 メソッドの返り値の型に応じて結果がスタックにプッシュされる
  • 45. invokevirtual インスタンスメソッドを呼び出す フォーマット: <0xb6,indexbyte1,indexbyte2> indexbyte は、メソッドへのシンボル参照がある実行時コンスタントプールへのインデックス オペランドスタック: ..., objectref, arg1, arg2, ..., arg n ⇒ ... objectref はオブジェクトへの参照 arg1, arg2, ... が引数 メソッドの返り値の型に応じて結果がスタックにプッシュされる interface 型の参照に対しては使えない 点に注意 invokeinterface を使う
  • 46. 使われなくなった命令 jsr, ret … 元々は finally の実装に使われていた JDK 1.4.2 から使われなくなった ベリファイア絡みで不具合があることが判明したため http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4381996 finally は code duplication によって実装されるように変更
  • 47. クラスファイルベリファイア クラスファイル (.class) のフォーマットが Java 仮想マシン仕様に従っているかどうかを 該当クラスの初期化より前に チェック たとえば: 同じ pc におけるオペランド・スタックのサイズは常に一定でなければならない オペランド・スタックのオーバーフローやアンダーフローは原理的に発生しない 存在しないローカル変数に対するアクセスは無い 未初期化のローカル変数に対するアクセスは無い
  • 48. jasmin 用のプログラム jasmin: Java バイトコードアセンブラ >java Overflow Exception in thread &quot;main&quot; java.lang.VerifyError : (class: Overflow, method: main signature: ([Ljava/lang/String;)V) Inconsistent stack height 1 != 0 ベリファイアがはねるプログラム (1) – スタックオーバーフロー .class public Overflow .super java/lang/Object .method public static main([Ljava/lang/String;)V .limit stack 2 LABEL: ldc &quot;Hello, World!&quot; goto LABEL return .end method
  • 49. >java Uninitialized Exception in thread &quot;main&quot; java.lang.VerifyError : (class: Uninitialized, method: main signature: ([L java/lang/String;)V) Accessing value from uninitialized register 1 ベリファイアがはねるプログラム (2) – 未初期化ローカル変数へのアクセス .class public Uninitialized .super java/lang/Object .method public static main([Ljava/lang/String;)V .limit stack 2 .limit locals 3 iload_1 return .end method
  • 50. >java Incompatible Exception in thread &quot;main&quot; java.lang.VerifyError : (class: Incompatible, method: main signature: ([Lj ava/lang/String;)V) Expecting to find integer on stack ベリファイアがはねるプログラム (3) - 型エラー .class public Incompatible .super java/lang/Object .method public static main([Ljava/lang/String;)V .limit stack 2 ldc 3.5 ldc 4.0 iadd return .end method
  • 51. >java PopLong Exception in thread &quot;main&quot; java.lang.VerifyError : (class: PopLong, method: main signature: ([Ljava/lang/String;)V) Attempt to split long or double on the stack ベリファイアがはねるプログラム (4) - カテゴリ間違い .class public PopLong .super java/lang/Object .method public static main([Ljava/lang/String;)V .limit stack 3 invokestatic java/lang/System/currentTimeMillis()J pop ; pop2 なら OK . pop はカテゴリ 1 の値しか pop できないが long はカテゴリ 2 return .end method
  • 52. 練習問題 ↓ のコンパイル結果を逆アセンブルしたものを読んでみる ',' で区切られた項目 ( 整数 ) の合計値を計算 import java.util.Scanner; public class CalcSum { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); while(scanner.hasNext()) { int result = 0; String[] line = scanner.nextLine().split(&quot;,&quot;); for(String element:line) result += Integer.parseInt(element); System.out.println(&quot;result:&quot; + result); } } }
  • 53. 練習問題 逆アセンブル (javap –c CalcSum) した結果 public class CalcSum { ↓ Compiled from &quot;CalcSum.java&quot; public class CalcSum extends java.lang.Object{ public CalcSum(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object.&quot;<init>&quot;:()V 4: return
  • 54. 練習問題 逆アセンブル (javap –c CalcSum) した結果 public static void main(String[] args) { Scanner scanner = new Scanner(System.in); ↓ public static void main(java.lang.String[]); Code: 0: new #2; //class java/util/Scanner 3: dup 4: getstatic #3; //Field java/lang/System.in:Ljava/io/InputStream; 7: invokespecial #4; //Method java/util/Scanner.&quot;<init>&quot;:(Ljava/io/InputStream;)V 10: astore_1
  • 55. 練習問題 逆アセンブル (javap –c CalcSum) した結果 while(scanner.hasNext()) { ↓ 11: aload_1 12: invokevirtual #5; //Method java/util/Scanner.hasNext:()Z 15: ifeq 97
  • 56. 練習問題 逆アセンブル (javap –c CalcSum) した結果 int result = 0; String[] line = scanner.nextLine().split(&quot;,&quot;); ↓ 18: iconst_0 19: istore_2 20: aload_1 21: invokevirtual #6; //Method java/util/Scanner.nextLine:()Ljava/lang/String; 24: ldc #7; //String , 26: invokevirtual #8; //Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String; 29: astore_3 30: aload_3 31: astore 4
  • 57. 練習問題 逆アセンブル (javap –c CalcSum) した結果 for(String element:line) result += Integer.parseInt(element); ↓ 33: aload 4 35: arraylength 36: istore 5 38: iconst_0 39: istore 6 41: iload 6 43: iload 5 45: if_icmpge 69 48: aload 4 50: iload 6 52: aaload 53: astore 7 55: iload_2 56: aload 7 58: invokestatic #9; //Method java/lang/Integer.parseInt:(Ljava/lang/String;)I 61: iadd 62: istore_2 63: iinc 6, 1 66: goto 41
  • 58. 練習問題 逆アセンブル (javap –c CalcSum) した結果 System.out.println(&quot;result:&quot; + result); } } } ↓ 69: getstatic #10; //Field java/lang/System.out:Ljava/io/PrintStream; 72: new #11; //class java/lang/StringBuilder 75: dup 76: invokespecial #12; //Method java/lang/StringBuilder.&quot;<init>&quot;:()V 79: ldc #13; //String result: 81: invokevirtual #14; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 84: iload_2 85: invokevirtual #15; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 88: invokevirtual #16; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 91: invokevirtual #17; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 94: goto 11 97: return }
  • 59. 知っていると役に立つ ( かもしれない )javap のオプション -c: 逆アセンブルする。一番よく使う -private: private なものを含む全てのメンバを表示 -c などと組み合わせて使う事が多い -verbose: クラスファイルのバージョン、コンスタントプールなどの詳細な情報を表示 -s: 内部的な型シグネチャの情報を表示 public static void main(java.lang.String[]) -> ([Ljava/lang/String;)V
  • 60. 何が嬉しいの? Java バイトコードにコンパイルする俺言語 / フレームワーク等を作れるようになる バイトコード変換を利用したツールやフレームワークを作れるようになる AOP とか バイトコードにコンパイルする言語処理系やツールのバグなどを発見できるようになる 発表者は、 scala が生成したコードをよく javap している 面白い
  • 61. まとめ Java バイトコードの マシンモデル 命令セット 型システム について簡単に解説 クラスファイルベリファイアの概要を説明 簡単なサンプルコードを逆アセンブルしてみた 知っていると役に立つかもしれない javap オプション 参考文献: Java 仮想マシン仕様第2版,ティム・リンドホルム、フランク・イェリン,ピアソン・エデュケーション Web で無料公開 http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html されてるので、そちらでも可 ( ただし英語 )