自作エミュレータで学ぶx86アーキテクチャ という本を読んだ。 その続きとして、主に教育向けに使われているx86 OSのxv6を完動させることを目標に、 自作エミュレータを拡張していこうと思う。 比較的長期間にわたって開発することになるので、一旦今の状況をまとめておきたい。 現状では、ブートローダによってカーネル本体をメモリ上に展開し、 カーネルのmain関数から実行を開始できる状態になっている。 より正確には、ブートローダからカーネルのmain関数へと進み、 どこかの地点でカーネルパニックが起きている状態である。 また、シリアルポート(I/O Port 0x3F8)のエミュレートをできるようになっているので、 カーネルから cprintf 関数や panic 関数を呼び出すことで、任意の文字列を出力できる。 このあたりまでくるとCPU自体のエミュレートは落ち着いてきて、 各種IOや割り込みの実装が中心になってきていると感じる。 ある程度までまとまったら、コードを公開したい。

拡張にあたって、まずは本のコードの写経から始めた。 足りない命令については、インテルの仕様書を参考に随時追加していった。 Mod R/MやSIBバイトの動作については、手当たり次第に実装していたが、 インテルの仕様書を見れば一発でわかる話だった。 例外的な動作が多く、例えば、SIBバイトによるアドレス指定で Base=5 としたときには、 レジスタでなく disp8disp32 を使うことになる。 開発初期は各命令の動作を検証するために、QEMUの動作を正として、以下のようにQEMUのレジスタをトレースするGDBスクリプトを用意した。 10万ステップにわたり、自作エミュレータとQEMUのレジスタが一致するかどうかテストしながら、開発を進めた。

target remote localhost:1234
set architecture i8086
set confirm off
break *0x7c00
c
set variable $i = <numstep>
while $i > 0
si
info registers
set variable $i -= 1
end
quit

xv6の動作についても、簡単にまとめておく。 xv6のイメージを指定し自作エミュレータを起動すると、 16bitモードで起動し、EIP=0x7c00 の状態からbootasm.Sのブートローダを開始する。 その後、32bitモードに移行して、Cコード bootmain.cへジャンプする。 bootmain.cでは、ディスクからELF形式のカーネル本体を読み込み、メモリ上に展開する。 main.cのカーネル本体では、 mpinit 関数や lapicinit 関数などから、各種デバイスの初期化を順に実行していく。 上から順番に初期化していれば良いので、わかりやすい。 コンテキストスイッチあたりまで話が進むとデバッグが大変になるんだろうと思う。

参考