いろんな事情でQEMUについて勉強したくなったので、QEMUとはいったいどういう機構で動いているのか、簡単に勉強できる文章を探していたのだが、以下のような論文を見つけた。
- QEMU, a Fast and Portable Dynamic Translator, Fabrice Bellard
http://archives.cse.iitd.ernet.in/~sbansal/csl862-virt/2010/readings/bellard.pdf
このFabrice Bellardさんと言えば、QEMUを開発した張本人でもあるし、RISC-V界隈の人にとって見れば、128ビットまで対応したRISC-Vのエミュレータを開発された方でもある。とにかく超絶な方だ。
開発者本人の論文なら、QEMUの仕組みの概要をつかめるだろう。読んでみよう。
1. イントロダクション
QEMUはターゲットの機械全体をエミュレーションするためのプラットフォーム。
対象としては、x86, PowerPC, ARM, Sparcなどをエミュレーションすることが出来る。x86のエミュレーションにおいては、x86のバイナリを実行することによりほぼネイティブと同等の性能を出すことが出来るが、他のアーキテクチャのエミュレーションについても一度動的変換をはさむ事により一般のISSよりも高速にエミュレーションすることができる。 これをDynamic Translation(動的変換)と呼んでいる。
つまり、QEMUはターゲットの実行バイナリを解析して位置命令ずつデコードして実行しているのではなく、動的変換をすることで最小限のx86コードに変換して実行している。これにより高速なエミュレーションが可能になっているというわけだ。
このためにQEMUのCPU部の機能として、
- 変換コードキャッシュの管理
- レジスタ割り当て
- 条件分岐コードの最適化
- 直接ブロックチェイニング
- メモリ管理
- 自己書き換えコードのサポート
などが実装されている。
動的コード変換
QEMUの根幹を成す機能であるが、ターゲットのアーキテクチャで記述されている機械語を、ホストのアーキテクチャの機械語に変換する機能のことだ。
ざっくりと概要を述べてしまうと、ターゲットの機械語を、ホストの機械語の数命令で表現される「マイクロオペレーション」に変換してしまう。 これを実行するための、いわゆる変換機、コンパイラを"dyngen"と呼んでいる。
dyngenはエミュレーション中に逐次呼ばれていき、変換されたコードはキャッシュされ同じコードを実行する際は再利用される。
例
論文では下記のような簡単なPowerPCの命令をx86に変換する例が挙げられていた。
addi r1,r1,-16
これをまずは以下のマイクロオペレーションに分割する。
movl_T0_r1 addl_T0_im -16 movl_r1_T0
このマイクロオペレーションが、それぞれC言語で機能が記述されており、上記のPowerPCの1命令はそれぞれのマイクロオペレーションに変換される。
例えばmovl_T0_r1
は以下のような感じだ。
void op_movl_T0_r1(void) { T0 = env->regs[1]; } extern int __op_param1; void op_addl_T0_im(void) { T0 = T0 + ((long)(&__op_param1)); }
ここからが大変なのだが、dyngenはターゲットのバイナリシーケンスを上から順に解析して行き、
を繰り返す。この結果マイクロオペレーションで構成されたバイナリに変換されるというわけだ。
これを実現するための擬似コードとして、dyngenは
case INDEX_op_addl_T0_im: // op_addl_T0_imマイクロオペレーションを変換先バイナリに追加する long param1; extern void op_addl_T0_im(); memcpy(gen_code_ptr, (char *)&op_addl_T0_im+0, 6); param1 = *opparam_ptr++; *(uint32_t *)(gen_code_ptr + 2) = param1; gen_code_ptr += 6; break; }
変換先のバイナリの最後尾(gen_code_ptr)にマイクロオペレーションのオブジェクトを追加する(memcpy)。パラメータを設定し、変換先バイナリの最後尾を更新するといった具合になる。
この結果、いろいろと省略するがaddi r1,r1,-16
のPowerPCのバイナリは以下のように変換される。
mov 0x4(%ebp), %ebx add $0xfffffff0, %ebx mov %ebx,0x4(%ebp)