Bochs を使って命令 / メモリトレースを取得する

Bochs の組み込みデバッガはなかなかよくできています。メモリアクセス、実行命令など、必要なほとんどの情報 (ハードウェアからの実際の入力を除く!) を取得することができるのです。私は Bochs for Windows に付属の bochsdbg.exe を用いていますが、必要なら自分でビルドすることができます。(この辺は簡単な上に面白くもないので省略。)

仮想マシンの構成

仮想マシンの構成はほぼ普段通りに行えばそれだけで OK ですが、CPU については

cpu: count=1, ips=1000000

のように、1 秒間に実行する命令数をあらかじめ設定しておくことを強く推奨します。(そうしないと、Bochs の都合で IPS が著しく上下したり、あるいはどんな構成のトレースを行っているのかが分からなくなってしまう。) ここでは 1 MIPS のマシンをエミュレートすることを明示的に指定しています。

スタートアップ

ここで便利なのは、Bochs デバッガには予めデバッグコマンド列をスタート時に仕込んでおくことが可能だという点です。これには -rc コマンドラインオプションを用います。私が用いている debugrc として、次のものがあります。

trace on
trace-mem on

s 2000000000
s 2000000000
s 2000000000
s 2000000000
s 2000000000

q

ここではメモリトレース (trace-mem) とトレース (trace) をともにオンとして、s コマンドを使用して合計 100 億命令のトレースを実行し、最後には終了 (q) します。ここでは 100 億命令固定となっていますが、テキスト形式のトレースは復元が容易であるため、途中で強制終了しても使えます。

実行

実行時の注意として先程の -rc オプションを指定することはもちろん、トレースやすべての詳細出力はファイルへのリダイレクトとすることを推奨します。これは Windows 固有かもしれませんが、Windows においてはコマンドラインへの表示に関する処理の負荷がかなり大きく、コンソールそのものの出力を最小限に抑えないことには実用になりません。(その中で 1 日半かけて Linux の起動シーケンスをトレースしたのですが。)
具体的には次のようになります。

  • コンソールをそのまま表示 : 300〜400 命令
  • コンソールを最小化 : 500〜1000 命令
  • コンソール管理用のプロセスである conhost.exe を kill する : 20000〜30000 命令
  • 最初からリダイレクトする : 25000〜40000命令

解析

トレースのフォーマットは、テキストフォーマットであるため解析が容易です。そのため、落とし穴だけ解説。
これは実際に Bochs を使ってトレースを作成した一例です。

(0).[8760124] [0x000f315a] f000:315a (unk. ctxt): cmp di, 0xf800            ; 81ff00f8
(0).[8760125] [0x000f315e] f000:315e (unk. ctxt): jbe .+7 (0x000f3167)      ; 7607
(0).[8760126] [0x000f3167] f000:3167 (unk. ctxt): mov es, ax                ; 8ec0
(0).[8760127] [0x000f3169] f000:3169 (unk. ctxt): mov dx, word ptr ss:[bp+12] ; 8b560c
[CPU0 RD]: LIN 0x000000000000ff5c PHY 0x0000ff5c (len=2, pl=0): 0x01F0
(0).[8760128] [0x000f316c] f000:316c (unk. ctxt): mov ah, byte ptr ss:[bp+3] ; 8a6603
[CPU0 RD]: LIN 0x000000000000ff53 PHY 0x0000ff53 (len=1, pl=0): 0x01
(0).[8760129] [0x000f316f] f000:316f (unk. ctxt): cmp ah, 0x01              ; 80fc01
(0).[8760130] [0x000f3172] f000:3172 (unk. ctxt): jz .+4 (0x000f3178)       ; 7404
(0).[8760131] [0x000f3178] f000:3178 (unk. ctxt): rep insd dword ptr es:[di], dx ; f3666d
[CPU0 RD]: LIN 0x0000000000007c00 PHY 0x00007c00 (len=4, pl=0): 0x00000000
[CPU0 WR]: PHY 0x00007c00 (len=4): 0x006CEBFA
(0).[8760132] [0x000f3178] f000:3178 (unk. ctxt): rep insd dword ptr es:[di], dx ; f3666d
[CPU0 RD]: LIN 0x0000000000007c04 PHY 0x00007c04 (len=4, pl=0): 0x00000000
[CPU0 WR]: PHY 0x00007c00 (len=4): 0x494C0000
(0).[8760133] [0x000f3178] f000:3178 (unk. ctxt): rep insd dword ptr es:[di], dx ; f3666d
[CPU0 RD]: LIN 0x0000000000007c08 PHY 0x00007c08 (len=4, pl=0): 0x00000000
[CPU0 WR]: PHY 0x00007c00 (len=4): 0x00014F4C
(0).[8760134] [0x000f3178] f000:3178 (unk. ctxt): rep insd dword ptr es:[di], dx ; f3666d
[CPU0 RD]: LIN 0x0000000000007c0c PHY 0x00007c0c (len=4, pl=0): 0x00000000
[CPU0 WR]: PHY 0x00007c00 (len=4): 0x005A0014
(0).[8760135] [0x000f3178] f000:3178 (unk. ctxt): rep insd dword ptr es:[di], dx ; f3666d
[CPU0 RD]: LIN 0x0000000000007c10 PHY 0x00007c10 (len=4, pl=0): 0x00000000
[CPU0 WR]: PHY 0x00007c00 (len=4): 0x00000000
(0).[8760136] [0x000f3178] f000:3178 (unk. ctxt): rep insd dword ptr es:[di], dx ; f3666d
[CPU0 RD]: LIN 0x0000000000007c14 PHY 0x00007c14 (len=4, pl=0): 0x00000000
[CPU0 WR]: PHY 0x00007c00 (len=4): 0x3B05F7A1
(0).[8760137] [0x000f3178] f000:3178 (unk. ctxt): rep insd dword ptr es:[di], dx ; f3666d
[CPU0 RD]: LIN 0x0000000000007c18 PHY 0x00007c18 (len=4, pl=0): 0x00000000
[CPU0 WR]: PHY 0x00007c00 (len=4): 0x00809909
(0).[8760138] [0x000f3178] f000:3178 (unk. ctxt): rep insd dword ptr es:[di], dx ; f3666d
[CPU0 RD]: LIN 0x0000000000007c1c PHY 0x00007c1c (len=4, pl=0): 0x00000000
[CPU0 WR]: PHY 0x00007c00 (len=4): 0x80990A01
(0).[8760139] [0x000f3178] f000:3178 (unk. ctxt): rep insd dword ptr es:[di], dx ; f3666d
[CPU0 RD]: LIN 0x0000000000007c20 PHY 0x00007c20 (len=4, pl=0): 0x00000000
[CPU0 WR]: PHY 0x00007c00 (len=4): 0x99080100

rep insd 命令の実行形式に着目してみてください。Bochs 開発者のひとりによれば、CPU0 RD によるメモリ読み取り (仕様上発生しない) は Bochs の仕様によるダミー操作のようですが、CPU0 WR のメモリアドレスに注目してください。――そう、完全に壊れているのです。
これは見たところ Bochs のバグのようです。この rep insd 操作に限らず、同じメモリアドレスに対して読み / 書きを連続で行うもの (例 : add [esp], edx) 全般に関して書き込み対象のアドレスが壊れているので、値は書き込まれるもの、アドレスは先行する読み取り操作のものを参照するようにしてください。