Instruction level tracer作った

VT-xなVMMはエミュレータと違って1命令毎にソフトウェアエミュレーションしている訳では無いので、単純に命令エミュレーションごとにデコード結果をprintfすればいいとかいう簡単な解は無いし、1命令実行ごとにVMExitする為の設定とかいうものも存在していないのでちょっと面倒くさかった、というか単純には作れないのだが、少しだけ工夫したら動くものができた。

何を使ったかというとVT-x側の特別な仕組みじゃなくて、ゲストCPU上のデバッグ機能を利用した。
具体的にはEFLAGSレジスタのTFフラグというもので、これがセットされていると1命令(シングルステップ)毎に#DB例外が発生する(=割り込みベクタ1番)。
この例外をVMMでキャッチして命令をデコード→表示すればよかったわけだが、既に、ゲスト上でソフト割り込み(≒BIOSコール)が発生した時にVMM側で設定した割り込みハンドラ経由でVMExitしてきてVMMでハンドリングする仕組みはBIOSエミュレータとして作り上げていたので、これを利用した。
命令のデコード→表示に関しては自分でやるのはしんどいし既存のコードが何種類かある。
今回はFreeBSDのbaseへ入れる可能性を意識してBSD Licenseのudis86を使った。

で、これを使ってブートローダを実行するとこんなのが表示されるようにした:

[trace] 16bit eip:7d29 eflags:f100 insn:mov %dh, %ch eax:0 ebx:f8 ecx:32bf edx:fe01
[trace] 16bit eip:7d2b eflags:f100 insn:pop %dx eax:0 ebx:f8 ecx:febf edx:fe01
[trace] 16bit eip:7d2c eflags:346 insn:jb 0x7cf9 eax:0 ebx:f8 ecx:febf edx:180
[trace] 16bit eip:7d2e eflags:346 insn:andb $0x3f, %cl eax:0 ebx:f8 ecx:febf edx:180
[trace] 16bit eip:7d31 eflags:346 insn:jz 0x7cf6 eax:0 ebx:f8 ecx:fe3f edx:180
[trace] 16bit eip:7d33 eflags:346 insn:cli  eax:0 ebx:f8 ecx:fe3f edx:180
[trace] 16bit eip:7d34 eflags:346 insn:mov 0x8(%bp), %eax eax:0 ebx:f8 ecx:fe3f edx:180
[trace] 16bit eip:7d38 eflags:346 insn:push %dx eax:0 ebx:f8 ecx:fe3f edx:180
[trace] 16bit eip:7d39 eflags:f100 insn:movzxb %cl, %ebx eax:0 ebx:f8 ecx:fe3f edx:180
[trace] 16bit eip:7d3d eflags:f100 insn:xor %edx, %edx eax:0 ebx:3f ecx:fe3f edx:180
[trace] 16bit eip:7d40 eflags:f100 insn:div %ebx eax:0 ebx:3f ecx:fe3f edx:0
[trace] 16bit eip:7d43 eflags:f100 insn:mov %ch, %bl eax:0 ebx:3f ecx:fe3f edx:0
[trace] 16bit eip:7d45 eflags:f100 insn:mov %dl, %ch eax:0 ebx:fe ecx:fe3f edx:0
[trace] 16bit eip:7d47 eflags:f100 insn:inc %bx eax:0 ebx:fe ecx:3f edx:0
[trace] 16bit eip:7d48 eflags:f100 insn:xor %dl, %dl eax:0 ebx:ff ecx:3f edx:0
[trace] 16bit eip:7d4a eflags:f100 insn:div %ebx eax:0 ebx:ff ecx:3f edx:0
[trace] 16bit eip:7d4d eflags:f100 insn:mov %dl, %bh eax:0 ebx:ff ecx:3f edx:0
[trace] 16bit eip:7d4f eflags:f100 insn:pop %dx eax:0 ebx:ff ecx:3f edx:0
[trace] 16bit eip:7d50 eflags:346 insn:cmp $0x3ff, %eax eax:0 ebx:ff ecx:3f edx:180
[trace] 16bit eip:7d56 eflags:346 insn:sti  eax:0 ebx:ff ecx:3f edx:180
[trace] 16bit eip:7d57 eflags:346 insn:ja 0x7cf6 eax:0 ebx:ff ecx:3f edx:180
[trace] 16bit eip:7d59 eflags:346 insn:xchg %al, %ah eax:0 ebx:ff ecx:3f edx:180
[trace] 16bit eip:7d5b eflags:346 insn:rorb $0x2, %al eax:0 ebx:ff ecx:3f edx:180
[trace] 16bit eip:7d5e eflags:346 insn:or %ch, %al eax:0 ebx:ff ecx:3f edx:180
[trace] 16bit eip:7d60 eflags:346 insn:inc %ax eax:0 ebx:ff ecx:3f edx:180
[trace] 16bit eip:7d61 eflags:346 insn:xchg %ax, %cx eax:1 ebx:ff ecx:3f edx:180
[trace] 16bit eip:7d62 eflags:346 insn:mov %bh, %dh eax:3f ebx:ff ecx:1 edx:180
[trace] 16bit eip:7d64 eflags:346 insn:sub %ah, %al eax:3f ebx:ff ecx:1 edx:80
[trace] 16bit eip:7d66 eflags:346 insn:mov 0x2(%bp), %ah eax:3f ebx:ff ecx:1 edx:80
[trace] 16bit eip:7d69 eflags:346 insn:cmp %ah, %al eax:13f ebx:ff ecx:1 edx:80
[trace] 16bit eip:7d6b eflags:346 insn:jb 0x7d6f eax:13f ebx:ff ecx:1 edx:80
[trace] 16bit eip:7d6d eflags:346 insn:mov $0x1, %al eax:13f ebx:ff ecx:1 edx:80
[trace] 16bit eip:7d6f eflags:346 insn:mov $0x5, %di eax:101 ebx:ff ecx:1 edx:80
[trace] 16bit eip:7d72 eflags:346 insn:les 0x4(%bp), %bx eax:101 ebx:ff ecx:1 edx:80
[trace] 16bit eip:7d75 eflags:346 insn:push %ax eax:101 ebx:8c00 ecx:1 edx:80
[trace] 16bit eip:7d76 eflags:f100 insn:mov $0x2, %ah eax:101 ebx:8c00 ecx:1 edx:80
[trace] 16bit eip:7d78 eflags:f100 insn:int $0x13 eax:201 ebx:8c00 ecx:1 edx:80

ソースコードこの辺のbiosemul.c, cpu.cあたり。
厳密には、

svn diff -r r240565 https://socsvn.freebsd.org/socsvn/soc2012/syuu/bhyve-bios/

という感じ。

…で、すごいだろー完成したぞーみたいに書いておいて、実は重大な欠陥がある。
まぁBIOSエミュレータを使って作ったと書いている時点でお気づきの人も居るだろうけれども、リアルモードの割り込みベクタを使ってハンドリングしているので、プロテクトモードへ切り替えられるとハンドリング出来ない…。
これについてはlidtされた直後辺りにIDT変更してしまえばなんとかなる気がしているので、今後試してみる。