uchan note

プログラミングや電子工作の話題を書きます

QEMUのトレース機能でOSのデバッグを支援する

この記事では QEMU が持つ「トレース」および DPRINTF マクロの機能を有効化して OS 開発のデバッグに役立てる方法を説明します. 自作 OS アドベントカレンダー 2018 の 1 日目の記事です.

OS,特にハードウェアを制御するドライバの開発はなかなか苦労することが多いと思います. 正しく書いているはずなのになぜかうまく動かない,という経験は誰しもあるのではないでしょうか? QEMU に備わる機能を使って,ドライバ開発を効率的に行いましょう.

トレース機能とは

QEMU が持つ「トレース(tracing)」は,QEMU の動作を記録するための機能です. QEMU 本体の各所にトレースするための関数呼び出しが仕込まれており,その行が実行されるたびにトレース情報がファイルや標準エラー出力などに出ます.

トレース機能は,自作 OS からハードウェアを制御するドライバを作っているときに大変便利に使えます. ドライバはハードウェアを叩いて処理を進めていきます.特殊な器具などを使わない限り,実機ではハードウェア側の処理まで追うことができません. しかし,QEMU 上で実行している場合には,ハードウェアを叩くというのはすなわち「QEMU のハードウェアエミュレーションプログラムを実行する」ということですから, ハードウェアエミュレーションプログラムのトレースを行うことで,ハードウェアの動きを追えるのです.

DPRINTF とは

デバッグ用の出力をするために,トレースが登場する以前から使われてきたマクロです. トレースより細かくソースコード各所に埋め込まれています. トレースと一緒に有効化することで,より細かく QEMU の動作を出力することができます.

トレースと DPRINTF を有効化する

APT などでインストールできる標準的な QEMU ではトレースと DPRINTF は無効化されています. そのため自分で QEMU をビルドする必要があります.

QEMU をビルドするのに必要な依存パッケージを導入します.

sudo apt install libpixman-1-dev libfdt-dev libsdl2-dev

次に,ソースコードを取得します.

$ git clone git://git.qemu-project.org/qemu.git
$ cd qemu
$ QEMU_BASE=$(pwd)

ビルド用ディレクトリを作ってビルドします.

$ mkdir build
$ cd build
$ ../configure --prefix=$HOME/usr --target-list=x86_64-softmmu --enable-trace-backends=log --enable-sdl --extra-cflags="-DDEBUG_XHCI"
$ make -j 4

configure のオプションが重要なので少し説明します.

オプション 意味
--prefix=$HOME/usr make install した場合にインストールするパスを $HOME/usr にする.
--target-list=x86_64-softmmu x86-64 用の QEMU だけをビルドする(ビルド時間の短縮のため).
--enable-trace-backends=log トレースをどこに出すのかを指定.log は標準エラー出力へトレースを出力するモード.
--enable-sdl QEMU の画面出力を SDL にする.SDL が最も依存が少ない気がする.
--extra-cflags="-DDEBUG_XHCI" hw/usb/hcd-xhci.c の DPRINTF を有効化する.

トレースする

ビルドできた QEMU を使ってトレースを取ってみましょう.

$ cat trace.event
usb_xhci_reset
usb_xhci_queue_event
$ $QEMU_BASE/build/x86_64-softmmu/qemu-system-x86_64 \
    --display sdl --trace events=trace.event \
    -drive if=pflash,format=raw,readonly,file=OVMF_CODE.fd \
    -drive if=pflash,format=raw,file=OVMF_VARS.fd \
    -drive if=ide,index=0,media=disk,format=raw,file=diskimg \
    -device nec-usb-xhci,id=xhci \
    -device usb-mouse -device usb-kbd \
    -monitor stdio

QEMU のオプションに --trace events=trace.event と指定していることに注目してください. trace.event ファイル(名前は任意)にあらかじめトレースしたいトレースポイントの名前を列挙しておき,それを --trace オプションの events プロパティに指定します. こうすることで,指定したトレースポイントが実行されたときにトレースが出力されます. DPRINTF は動的に出力を制御する方法はありませんが,トレースなら再コンパイル無しで出力のオン・オフができ,便利です.

トレースポイントの一覧は QEMUソースコードの各ディレクトリにある trace-events ファイルに列挙されています. USB 関係のトレースポイント一覧は hw/usb/trace-events です.

QEMU-drive if から始まる 3 行のオプションの意味は EDK II で UEFI アプリケーションを作る の「テスト実行」に詳しく書いてあります. その他のオプションの意味は次の通りです.

オプション 意味
--display sdl SDL を用いた画面出力を行う.
-device nec-usb-xhci,id=xhci NEC 製の USB ホストコントローラをエミュレートする.hw/usb/hcd-xhci.h で定義された値.
-device usb-mouse USB マウスを接続する.
-device usb-kbd USB キーボードを接続する.
-monitor stdio QEMU Monitor(レジスタ値を確認したり,エミュレートを停止・再開したりする機能)を stdio に接続する.