Dalvikバイトコードリファレンスの読み方 改訂版

3,907 views

Published on

Published in: Technology
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,907
On SlideShare
0
From Embeds
0
Number of Embeds
89
Actions
Shares
0
Downloads
53
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide

Dalvikバイトコードリファレンスの読み方 改訂版

  1. 1. Dalvikバイトコードリファレンスの読み方 僻地からの出稼ぎプログラマ kmt-t
  2. 2. 自己紹介 上での活動Web上での活動・ハンドルネーム : kmt-t・はてなダイアリ ID : kmt-t2・Twitter ID : kmt_t 属性 属性・鳥取県から大阪に出稼ぎ中です・組み込みプログラマらしい・組み込みプログラマらしい・ミドルウェアが得意です・ミドルウェアが得意です→画像処理(2D/3D)、ファイルシステム、仮想マシンが専門です→画像処理(2D/3D)、ファイルシステム、仮想マシンが専門です・使用言語はC++(not C)/C#/Python・使用言語はC++(not C)/C#/Python→C++11とかC#の最新の仕様がキャッチアップできていません→C++11とかC#の最新の仕様がキャッチアップできていません
  3. 3. 発表の構成 仮想マシン3部作Dalvik仮想マシン 部作 仮想マシンDalvik仮想マシンの発表を以下の3回にわけて行います1. Dalvik仮想マシンのアーキテクチャ2. Dalvikバイトコードのリファレンスの読み方 ←今回はここの発表 バイトコードのリファレンスの読み方3. DEXファイルフォーマット 発表の目的 仮想マシンのソースコードが誰でも読めるようにする1. Dalvik仮想マシンのソースコードが誰でも読めるようにする 仮想マシンに対するみんなのリテラシを上げる2. Dalvik仮想マシンに対するみんなのリテラシを上げる3. より深い部分の発表をするための下地をつくる
  4. 4. 本日の発表の概要 バイトコードリファレンスの読み方 Dalvikバイトコードリファレンスの読み方・Dalvikバイトコードの命令バイナリフォーマット・Dalvikバイトコード命令に対応する実装→リファレンスから実装をトレースできるように リファレンスから実装をトレースできるように リファレンス・dalvik/docs/dalvik-bytecode.html (概略リファレンス ←注目 概略リファレンス) 概略リファレンス・dalvik/docs/instruction-formats.html (バイナリフォーマット)・dalvik/docs/opcodeディレクトリ以下 (セマンティクス) 実装今回はC言語バージョンの実装を参照・dalvik/vm/mterp/out/InterpC-portstd.c
  5. 5. dalvik/docs/dalvik-bytecode.htmlを開いてみる 注目点「Summary of Instruction Set」の章に注目
  6. 6. 記述されている内容 表の列注目する章の表には以下の列がある・Op & Format→命令のオペコードとバイナリフォーマット・Mnemonic / Syntax→命令のオペランドとそのサイズと並び・Arguments→オペランド一覧・Description→命令の概要
  7. 7. Op & Format 列の意味・「01 12x」とは何か?→「01」は命令のオペコード (16進数)→「12x」は命令のバイナリフォーマットID・命令のバイナリフォーマットの詳細→この命令の場合は命令のバイナリフォーマットIDは「12x」→dalvik/docs/instruction-formats.htmlの対応する行を参照
  8. 8. Mnemonic / Syntax 列の意味・「move vA, vB」とは何か?→「move」は命令の名前→「vA」、「vB」はオペランドにレジスタ番号「A」、「B」を持つことを示す・レジスタ番号の表記は「vA」、「vAA」、「vAAAA」のバリエーションを持つ→「vA」の場合4bit幅のレジスタ番号(0~15)を持つ→「vAA」の場合8bit幅のレジスタ番号(0~255)を持つ→「vAAAA」の場合16bit幅のレジスタ番号(0~65535)を持つ→レジスタ番号MAX値65535にアクセスできない命令がほとんど
  9. 9. Arguments 列の意味・オペランドとしてレジスタAとレジスタBを持つ・レジスタAはデストネーションレジスタ (レジスタ番号は4bit幅)・レジスタAはソースレジスタ(レジスタ番号は4bit幅)
  10. 10. Description 列の意味命令の概要→「オブジェクトではないレジスタの内容を他のレジスタにコピーする」→命令ごとの覚書が書かれている→命令の挙動はあまり書かれていない場合も多い→詳細はdalvik/docs/opcodeディレクトリ以下参照
  11. 11. dalvik/docs/instruction-formats.htmlを開いてみる 表の読み方ID「12x」に対応するFormat「B | A | op」とは何か?→「op」はオペコード (すべての命令で8bit長)→「A」はオペランドA (Aの数が1個なので4bit長)→「B」はオペランドB (Bの数が1個なので4bit長)
  12. 12. 命令のバイナリフォーマットの制限から導き出す命令のバイナリフォーマットの制限・命令は16bitアライメントされている・最初の16bitの下位8bitは必ずオペコード・16bit境界の下位ビットからオペランドはアサインされる以上からmove命令のバイナリフォーマットは以下のようになるレジスタ番号 レジスタ番号 オペコード = 0x01 (8bit) vB (4bit) vA (4bit) 命令長 = 16bit
  13. 13. リファレンスを読む上でその他ハマリどころ ハマリどころ・レジスタに格納される値は32bit幅・Javaのlong/double型(64bit値)はレジスタ番号NとN+1が使われる・発生した例外を保持する専用領域がスレッド毎にある・メソッドの戻り値を保存する専用領域がある→一部命令でこの領域をテンポラリで使用する・定数文字列は定数文字列プール上の32bitインデックスでロードされる・クラスやメソッドのようなメタ情報はID(16bit整数)で管理されている
  14. 14. バイトコード命令の実装を読むsparse-switch命令の例 (1) 命令の概略Javaのswitch文に対応するバイトコード命令は以下の二種類・packed-switch命令→switch文の比較値(case文の値)が連続している場合の命令・sparse-switch命令→switch文の比較値(case文の値)が飛び飛びの場合の命令※パフォーマンスとしてはpacked-switchの方が高速な分岐が可能
  15. 15. バイトコード命令の実装を読むsparse-switch命令の例 (2) リファレンスの内容以下のオペランドを持つ・分岐に使う値を格納したレジスタvAA (8bit幅)・不明のデータへのオフセット+BBBBBBBB (32bit幅)
  16. 16. バイトコード命令の実装を読むsparse-switch命令の例 (3)不明のデータの中身+BBBBBBBBは以下のデータへのオフセット→オフセットの原点は現在実行中のバイトコードの位置
  17. 17. バイトコード命令の実装を読むsparse-switch命令の例 (4)sparse-switch-payload不明の構造体(sparse-switch-payload)の要素・ident→ここから先はバイトコード命令ではないことを示すマーク・size→case文の数・keys→case文の値の配列・targets→case文のジャンプ先バイトコード命令へのオフセットの配列
  18. 18. バイトコード命令の実装を読むsparse-switch命令の例 (5)// dalvik/vm/mterp/out/InterpC-portstd.c// 該当部分の実装コードを簡略化すると以下のとおりHANDLE_OPCODE(OP_SPARSE_SWITCH /*vAA, +BBBB*/){ u2 vsrc1 = INST_AA(inst); u4 testVal = GET_REGISTER(vsrc1); s4 offset = FETCH(1) | (((s4) FETCH(2)) << 16); const u2* switchData = pc + offset; offset = dvmInterpHandleSparseSwitch(switchData, testVal); FINISH(offset);}OP_END
  19. 19. バイトコード命令の実装を読むinvoke-virtual命令の例 (1) リファレンスの内容仮想関数の呼び出し詳細は今までの説明から推測してください (説明略)
  20. 20. バイトコード命令の実装を読むinvoke-virtual命令の例 (2) 対応する実装・dalvik/vm/mterp/out/InterpC-portstd.c・GOTO_TARGET(invokeVirtual, bool methodCallRange) 処理内容1. 引数の数を命令から取得2. メソッドIDを命令から取得3. メソッド引数のレジスタ番号を命令から取得4. クラスインスタンスポインタのNULLチェック5. メソッドIDから基底メソッドの「Method構造体 構造体※1」 を取得 構造体 5.1. クラスIDとメソッドIDの組をキーにキャッシュを取得 5.2. キャッシュミスの場合はDEXファイルを検索して取得6. 基底メソッドのMethod構造体からメソッドのvtableインデックスを取得
  21. 21. バイトコード命令の実装を読むinvoke-virtual命令の例 (3) 続き) 処理内容 (続き 続き7. 派生メソッドのMethod構造体をクラスインスタンスのvtableから取得8. 抽象メソッドの呼び出しでないかチェック9. 引数レジスタの値をひとつずつ取り出しスタックに積む10. 新しいフレームポインタを計算11. 「vm-specific-internal-goop※2」の位置を計算12. スタックあふれが発生していないかチェック13. vm-specific-internal-goopにメソッド呼び出し元情報を保存 フレームポインタ※3」を保存 13.1. 呼び出し元の「フレームポインタ フレームポインタ 13.2. 呼び出し元の「プログラムカウンタ プログラムカウンタ※4」を保存 プログラムカウンタ 13.3. 現在のメソッド情報構造体のポインタを保存
  22. 22. バイトコード命令の実装を読むinvoke-virtual命令の例 (4) 続き) 処理内容 (続き 続き14. (以下ネイティブメソッドでない場合)15. 「InterpSaveState構造体 構造体※4」の書き換え 構造体 15.1. 実行中のメソッドを呼び出し側に変更 15.2. プログラムカウンタを呼び出し先のものに変更 15.3. フレームポインタを呼び出し先のものに変更
  23. 23. バイトコード命令の実装を読む invoke-virtual命令の例 (5) 構造体 Method構造体仮想マシンの実行に必要なメソッドの情報を保存する構造体struct Method { ClassObject* clazz; // 所属するクラス u4 accessFlags; // アクセス権限フラグ u2 methodIndex; // vtableのインデックス u2 registersSize; // レジスタ数 u2 outsSize; // 出力引数の数 u2 insSize; // バイトコード長 (16bit単位) const char* name; // メソッド名 DexProto prototype; // メソッドの型 const char* shorty; // メソッドの型(文字列形式) const u2* insns; // バイトコードのポインタ // 以下省略};
  24. 24. バイトコード命令の実装を読むinvoke-virtual命令の例 (6) vm-specific-internal-goop・メソッド呼び出しごとにスタックに格納される領域・メソッドの呼び出しが終了すると解放される・呼び出し元のメソッドの情報を保存し、呼び出し先から戻るのに必要・その他にも例外発生時に、この情報を再帰的にたどる// vm-specific-internal-goopの構造体struct StackSaveArea { // 呼び出し元のフレームポインタ // 一階層上のvm-specific-internal-goopの取得にも使われる u4* prevFrame; const u2* savedPc; // 呼び出し元のプログラムカウンタ const Method* method; // 呼び出し先メソッド const u2* currentPc; //呼び出し先のプログラムカウンタ};
  25. 25. バイトコード命令の実装を読むinvoke-virtual命令の例 (6) フレームポインタ現在のメソッドのレジスタ番号0へのポインタ プログラムカウンタ現在実行中のバイトコード命令へのポインタ
  26. 26. バイトコード命令の実装を読む invoke-virtual命令の例 (7) 構造体 InterpSaveState構造体実行中の仮想マシンの状態を保存する構造体struct InterpSaveState { const u2* pc; // 現在実行中のバイトコード命令のポインタ u4* curFrame; // 現在のフレームポインタ const Method *method; // 現在のメソッド DvmDex* methodClassDex; // 現在のメソッドが格納されているDEXファイル JValue retval; // メソッドの戻り値を格納する領域 void* bailPtr; // インタープリタの開始点に戻るためのポインタ struct InterpSaveState* prev; // To follow nested activations} __attribute__ ((__packed__));
  27. 27. まとめ まとめ・バイトコードリファレンスの読み方はパターンがある・バイトコードリファレンスを起点にして実装を読むと楽である以上でバイトコードリファレンスが読めるようになり、かなり実装も追えるようになるはず!
  28. 28. おわり ご清聴ありがとうございました

×