FreeBSDのオンラインカーネルデバッグ with QEMU

この記事は自作OS Advent Calendar 2018の12日目の記事です。

はじめに

最近xv6の教科書を読んでみている。 xv6の教科書ではQEMUの-sオプションを用いたデバッグが推奨されている。
-sオプションは-gdb tcp::1234の省略版でこのオプションを付けてQEMUを使うと1234番ポートでgdbserverを立ち上げてくれる。
あとはgdbでtarget remote :1234とかをするとQEMUにアタッチされていい感じにデバッグできる。 FreeBSDでこれができたらコードリーディングとか捗りそう…
いろいろ試行錯誤してみたらできました。
ということでQEMUの-sオプションでFreeBSDのカーネルのデバッグをしてみました。

デバッグ構成

今回デバッグするのに2つのFreeBSDが必要になる。

内容 OS 仮想化 ここでの名称
デバッグする側 FreeBSD 11.2 KVM Source
デバッグされる側 FreeBSD 11.2 QEMU Target
ホスト Ubuntu 18.04.1 Desktop     × Host

Sourceの方はKVMを使っていますがなんでもよいと思います。
ただし、Sourceの方でカーネル再構築を行います。

Host20022番ポートがTarget22番ポートにリダイレクトされるように設定した。 HostSource192.168.122.0/24で通信できるようにした。
この辺はHostからFreeBSDに相互で通信できれば特に問題ないと思われる。

やり方

1. FreeBSDのインストール

まずはSourceTargetを用意します。 詳しいインストールの方法1 は割愛します。 同じバージョン、アーキテクチャでインストールしていきます。
私の環境ではFreeBSD-11.2-RELEASE-amd64を選択しました。

Targetのインストール準備としてHostで以下のコマンド実行します。
-redir tcp:20022::22のオプションでHost20022番ポートがTarget22番ポートにリダイレクトされるようにしています。

$ qemu-img create -f qcow freebsd_debug.img 30G
$ qemu-system-x86_64 -m 4096 -hda freebsd_debug.img -boot c -cdrom FreeBSD-11.2-RELEASE-amd64-dvd1.iso -redir tcp:20022::22

Sourcesrcを含むようにしましょう※

SourceTargetともに起動させます。

2. カーネルの再構築

Sourceのカーネルを再構築してデバッグ情報を含むようにします。 カーネルの再構築はFreeBSDのバージョンによって多少異なる点があるので自分が選択したバージョンに合わせましょう。 まずはもとの設定ファイルをコピーします。

$ cd /usr/src/sys/amd64/conf
$ cp GENERIC MYKERNEL

MYKERNELの設定に以下を足します。

makeoptions DEBUG=-g
options KDB
options DDB

実際のMYKERNELをgistsにあげておきます。 該当箇所は24/ 83/ 85行目です。

再構築をします。

$ cd /usr/src
$ make buildkernel KERNCONF=MYKERNEL
$ make installkernel KERNCONF=MYKERNEL

make buildkernelは時間がかかるのでここでコーヒーブレイクです。
終わったらSourceを再起動させましょう。

3. 転送

Source/boot/kernel/以下のファイルを全てTarget/boot/kernel/に送りつけます。 Hostで以下のコマンドを実行しました。

$ scp -r root@192.168.122.2:/boot/kernel .
$ scp -P 20022 kernel/* root@127.0.0.1:/boot/kernel

2行目のコマンドの前にHoststrip -xとかするとシンボル情報がなくなってスマートになります。

$ du kernel -h
123M    kernel
$ sudo strip -x kernel/*.ko
$ sudo strip -x kernel/kernel
$ du kernel -h
117M    kernel

4. 再起動

Targetをシャットダウンさせます。 Target-sオプションを付けて起動させます。 これでgdbserver立ち上げてくれます。

$ qemu-system-x86_64 -m 4096 -hda freebsd_debug.img -boot c -cdrom FreeBSD-11.2-RELEASE-amd64-dvd1.iso -redir tcp:20022::22  -s

これに加えて-Sオプションを付けると起動からデバッグできます。

5. kgdb

kgdb2はカーネルデバッグのためのデバッガーです。 Sourceでkgdbを起動させてHostで起動しているQEMUにアタッチします。 この時点でTargetは停止します。
ここからは好きにデバッグしてみてください。 ここでは動作の確認のためにsys_mkdirにブレイクポイントを設定して、Sourceの動作を再開させます。

$ kgdb /boot/kernel/kernel
GNU gdb 6.1.1 [FreeBSD]
...
This GDB was configured as "amd64-marcel-freebsd"...
(kgdb) target remote 192.168.122.1:1234
...
(kgdb) b sys_mkdir
Breakpoint 1 at 0xffffffff80bdac14: file /usr/src/sys/kern/vfs_syscalls.c, line 3337.
(kgdb) c
Continuing.

そしておもむろにTargetmkdir testを実行すると
きちんと止まった!でけた!

クリックすると画像は大きくなります。

これだけでもシステムコールがどういう感じで呼ばれてるか少しわかるかと思います。
やったー!

終わりに

これでいい感じにFreeBSDのカーネルコードリーディングができそうです。
やっぱりコードとにらめっこするよりも実行しながらできると楽しいですね。
たぶん、本来は2つをシリアル通信でつなげてやるんだと思うんですがQEMUの機能でやってみました。
なぜFreeBSDなのかという疑問については聞かないでください。
13日目の自作OS Advent Calendar 2018garasuboさんの 「RustでArm Cortex-Mプログラミングをする 2018」です。
お!Rust好きとしては楽しみです。

参考文献

freebsd  os  gdb