Dalvikバイトコードリファレンスの読み方
僻地からの出稼ぎプログラマ
kmt-t
自己紹介
・ハンドルネーム : kmt-t・はてなダイアリ ID : kmt-t2・Twitter ID : kmt_t
Web上での活動上での活動上での活動上での活動
・組み込みプログラマらしい・ミドルウェアが得意です→画像処理(2D/3D)、ファイルシステム、仮想マシンが専門です・使用言語はC++(not C)/C#/Python→C++11とかC#の最新の仕様がキャッチアップできていません�
属性属性属性属性
・鳥取県から大阪に出稼ぎ中です・組み込みプログラマらしい・ミドルウェアが得意です→画像処理(2D/3D)、ファイルシステム、仮想マシンが専門です・使用言語はC++(not C)/C#/Python→C++11とかC#の最新の仕様がキャッチアップできていません�
属性属性属性属性
発表の構成
1. Dalvik仮想マシンのソースコードが誰でも読めるようにする仮想マシンのソースコードが誰でも読めるようにする仮想マシンのソースコードが誰でも読めるようにする仮想マシンのソースコードが誰でも読めるようにする2. Dalvik仮想マシンに対するみんなのリテラシを上げる仮想マシンに対するみんなのリテラシを上げる仮想マシンに対するみんなのリテラシを上げる仮想マシンに対するみんなのリテラシを上げる3. より深い部分の発表をするための下地をつくるより深い部分の発表をするための下地をつくるより深い部分の発表をするための下地をつくるより深い部分の発表をするための下地をつくる
発表の目的発表の目的発表の目的発表の目的
Dalvik仮想マシンの発表を以下の3回にわけて行います1. Dalvik仮想マシンのアーキテクチャ2. Dalvikバイトコードのリファレンスの読み方バイトコードのリファレンスの読み方バイトコードのリファレンスの読み方バイトコードのリファレンスの読み方 ←今回はここの発表←今回はここの発表←今回はここの発表←今回はここの発表3. DEXファイルフォーマット
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命令のバイナリフォーマットは以下のようになる
命令のバイナリフォーマットの制限命令のバイナリフォーマットの制限命令のバイナリフォーマットの制限命令のバイナリフォーマットの制限
レジスタ番号vB (4bit)
レジスタ番号vA (4bit)
オペコード = 0x01 (8bit)
命令長 = 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)の要素・ident
→ここから先はバイトコード命令ではないことを示すマーク・size→case文の数・keys→case文の値の配列・targets→case文のジャンプ先バイトコード命令へのオフセットの配列
sparse-switch-payload
バイトコード命令の実装を読む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にメソッド呼び出し元情報を保存13.1. 呼び出し元の「フレームポインタフレームポインタフレームポインタフレームポインタ※3」を保存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__));
まとめ
・バイトコードリファレンスの読み方はパターンがある・バイトコードリファレンスを起点にして実装を読むと楽である
以上でバイトコードリファレンスが読めるようになり、かなり実装も追えるようになるはず!
まとめまとめまとめまとめ
おわり
ご清聴ありがとうございました
Top Related