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

  僻地からの出稼ぎプログラマ
       kmt-t
自己紹介
   上での活動
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部作
Dalvik仮想マシン 部作
      仮想マシン
Dalvik仮想マシンの発表を以下の3回にわけて行います
1. Dalvik仮想マシンのアーキテクチャ
2. Dalvikバイトコードのリファレンスの読み方 ←今回はここの発表
         バイトコードのリファレンスの読み方
3. DEXファイルフォーマット

  発表の目的


         仮想マシンのソースコードが誰でも読めるようにする
1. Dalvik仮想マシンのソースコードが誰でも読めるようにする
         仮想マシンに対するみんなのリテラシを上げる
2. Dalvik仮想マシンに対するみんなのリテラシを上げる
3. より深い部分の発表をするための下地をつくる
本日の発表の概要
        バイトコードリファレンスの読み方
  Dalvikバイトコードリファレンスの読み方
・Dalvikバイトコードの命令バイナリフォーマット
・Dalvikバイトコード命令に対応する実装
→リファレンスから実装をトレースできるように
  リファレンスから実装をトレースできるように

     リファレンス
・dalvik/docs/dalvik-bytecode.html (概略リファレンス ←注目
                                        概略リファレンス)
                                        概略リファレンス
・dalvik/docs/instruction-formats.html (バイナリフォーマット)
・dalvik/docs/opcodeディレクトリ以下 (セマンティクス)

        実装
今回はC言語バージョンの実装を参照
・dalvik/vm/mterp/out/InterpC-portstd.c
dalvik/docs/dalvik-bytecode.html
を開いてみる
      注目点
「Summary of Instruction Set」の章に注目
記述されている内容
       表の列
注目する章の表には以下の列がある
・Op & Format
→命令のオペコードとバイナリフォーマット
・Mnemonic / Syntax
→命令のオペランドとそのサイズと並び
・Arguments
→オペランド一覧
・Description
→命令の概要
Op & Format
    列の意味

・「01 12x」とは何か?
→「01」は命令のオペコード (16進数)
→「12x」は命令のバイナリフォーマットID
・命令のバイナリフォーマットの詳細
→この命令の場合は命令のバイナリフォーマットIDは「12x」
→dalvik/docs/instruction-formats.htmlの対応する行を参照
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にアクセスできない命令がほとんど
Arguments
   列の意味


・オペランドとしてレジスタAとレジスタBを持つ
・レジスタAはデストネーションレジスタ (レジスタ番号は4bit幅)
・レジスタAはソースレジスタ(レジスタ番号は4bit幅)
Description
   列の意味

命令の概要
→「オブジェクトではないレジスタの内容を他のレジスタにコピーする」
→命令ごとの覚書が書かれている
→命令の挙動はあまり書かれていない場合も多い
→詳細はdalvik/docs/opcodeディレクトリ以下参照
dalvik/docs/instruction-
formats.htmlを開いてみる
   表の読み方


ID「12x」に対応するFormat「B | A | op」とは何か?
→「op」はオペコード (すべての命令で8bit長)
→「A」はオペランドA (Aの数が1個なので4bit長)
→「B」はオペランドB (Bの数が1個なので4bit長)
命令のバイナリフォーマットの
制限から導き出す
命令のバイナリフォーマットの制限


・命令は16bitアライメントされている
・最初の16bitの下位8bitは必ずオペコード
・16bit境界の下位ビットからオペランドはアサインされる
以上からmove命令のバイナリフォーマットは以下のようになる



レジスタ番号       レジスタ番号
                           オペコード = 0x01 (8bit)
 vB (4bit)    vA (4bit)

                  命令長 = 16bit
リファレンスを読む上で
その他ハマリどころ
 ハマリどころ



・レジスタに格納される値は32bit幅
・Javaのlong/double型(64bit値)はレジスタ番号NとN+1が使われる
・発生した例外を保持する専用領域がスレッド毎にある
・メソッドの戻り値を保存する専用領域がある
→一部命令でこの領域をテンポラリで使用する
・定数文字列は定数文字列プール上の32bitインデックスでロードされる
・クラスやメソッドのようなメタ情報はID(16bit整数)で管理されている
バイトコード命令の実装を読む
sparse-switch命令の例 (1)
 命令の概略



Javaのswitch文に対応するバイトコード命令は以下の二種類
・packed-switch命令
→switch文の比較値(case文の値)が連続している場合の命令
・sparse-switch命令
→switch文の比較値(case文の値)が飛び飛びの場合の命令

※パフォーマンスとしてはpacked-switchの方が高速な分岐が可能
バイトコード命令の実装を読む
sparse-switch命令の例 (2)
 リファレンスの内容

以下のオペランドを持つ
・分岐に使う値を格納したレジスタvAA (8bit幅)
・不明のデータへのオフセット+BBBBBBBB (32bit幅)
バイトコード命令の実装を読む
sparse-switch命令の例 (3)
不明のデータの中身

+BBBBBBBBは以下のデータへのオフセット
→オフセットの原点は現在実行中のバイトコードの位置
バイトコード命令の実装を読む
sparse-switch命令の例 (4)
sparse-switch-payload


不明の構造体(sparse-switch-payload)の要素
・ident
→ここから先はバイトコード命令ではないことを示すマーク
・size
→case文の数
・keys
→case文の値の配列
・targets
→case文のジャンプ先バイトコード命令へのオフセットの配列
バイトコード命令の実装を読む
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
バイトコード命令の実装を読む
invoke-virtual命令の例 (1)
 リファレンスの内容
仮想関数の呼び出し
詳細は今までの説明から推測してください (説明略)
バイトコード命令の実装を読む
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インデックスを取得
バイトコード命令の実装を読む
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. 現在のメソッド情報構造体のポインタを保存
バイトコード命令の実装を読む
invoke-virtual命令の例 (4)
       続き)
 処理内容 (続き
       続き




14. (以下ネイティブメソッドでない場合)
15. 「InterpSaveState構造体
                    構造体※4」の書き換え
                    構造体
 15.1. 実行中のメソッドを呼び出し側に変更
 15.2. プログラムカウンタを呼び出し先のものに変更
 15.3. フレームポインタを呼び出し先のものに変更
バイトコード命令の実装を読む
 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; // バイトコードのポインタ
  // 以下省略
};
バイトコード命令の実装を読む
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; //呼び出し先のプログラムカウンタ
};
バイトコード命令の実装を読む
invoke-virtual命令の例 (6)
 フレームポインタ


現在のメソッドのレジスタ番号0へのポインタ



 プログラムカウンタ


現在実行中のバイトコード命令へのポインタ
バイトコード命令の実装を読む
 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__));
まとめ
   まとめ




・バイトコードリファレンスの読み方はパターンがある
・バイトコードリファレンスを起点にして実装を読むと楽である

以上でバイトコードリファレンスが読めるようになり、かなり実装も追える
ようになるはず!
おわり

      ご清聴ありがとうございました

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