NetBSDのkgdbをQEMUでお手軽に試す

[NetBSD ドキュメンテーション: GDB を使い NetBSD カーネルをデバッグする HOWTO](http://www.jp.netbsd.org/ja/docs/kernel/kgdb.html) をQEMUでサクッと試すためのHowtoです。

<!-- http://www.netbsd.org/docs/kernel/kgdb.html -->

<!-------------------------------------------------->
## 前提
- 先に [QEMUのgdbserver機能でNetBSD kernelをデバッグする](https://qiita.com/wataash/items/2f68717a5109ea8e1b75) を終えて下さい。こちらの方が難易度（ハマり度）が低いですし、こちらに載っているビルド方法などの手順は本記事でも必要になります。
- Linux (Ubuntu 17.10), macOS (High Sierra) で検証済みです。Windowsや他OSでの動作報告お待ちしております！

<!-------------------------------------------------->
## 注意事項
NetBSD-currentはkgdbが壊れていて動作しません。リリース版のソースか、[2017/08/15 08:35:56](https://github.com/IIJ-NetBSD/netbsd-src/commit/0c3ee77af5382a873742) より前のソースを使って下さい。（currentではこのコミット以外にも複数の問題があるようです）

実用面では、QEMUでkgdbを使う意味はありません[1]。QEMU gdbserverに勝るメリットが無いためです[2]。このHowtoの目的は、kgdbの正常な挙動を理解することです。その知識は物理マシンでkgdbを使う際に役立つと思います（実際自分がそうだった）。逆に既に物理マシンで問題なくkgdbを使えている方には不要なHowtoでしょう。

（注釈 [1]: ただしkgdb自体のデバッグをするためにQEMU上でkgdbを使うのは有意です。QEMU gdbserver+kgdbでKGDB自体（[kgdb_stub.c](https://github.com/IIJ-NetBSD/netbsd-src/blob/master/sys/kern/kgdb_stub.c) など）のデバッグが可能になります。[2]: qemuの方がデバッグできる範囲が広い（kgdb_stub.c、[kgdb_connect()](https://github.com/IIJ-NetBSD/netbsd-src/blob/354fb97770c69aa22e7506ce6c8e6460da78ad79/sys/arch/amd64/amd64/machdep.c#L1863) の前、etc）、qemuの方が通信が速い、後述するような「中断できない」などの制限が無い）

<!--
実際自分も、QEMUで得た知識のおかげで、物理マシンのkgdbは使えませんでした。
 で失敗・何が悪いのか分からない（シリアルケーブルのピンアサインが悪い、gdbが悪い、kgdbがバグってる、kgdbモードに入れていない）→ QEMUで試して、期待していた挙動がずれていた事に気づく → 

kgdbは物理マシンで走るNetBSDをデバッグする際に有益（得にデバイスドライバのデバッグ）

kgdbの動作
どんな動作が正常か
物理マシンで問題が起きた際に原因の切り分け

ヌルモデムケーブル
-->

<!-------------------------------------------------->
## kgdbを有効にしたカーネルのビルド

[GENERICカーネルコンフィグ](https://github.com/IIJ-NetBSD/netbsd-src/blob/6a402eeb4f85febc59e1804c0c4041adfdb9ac48/sys/arch/amd64/conf/GENERIC#L107) を編集して、

- 最適化無効
- DDB無効
- KGDB有効、`KGDB_DEVRATE=115200`

にしてビルドします。

```diff
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index 4170eaa92a3..e751a603b39 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -104,13 +104,13 @@ options   DIAGNOSTIC      # inexpensive kernel consistency checks
 # Because gcc omits the frame pointer for any -O level, the line below
 # is needed to make backtraces in DDB work.
 #
-makeoptions    COPTS="-O2 -fno-omit-frame-pointer"
-options        DDB             # in-kernel debugger
+makeoptions    COPTS="-O0 -fno-omit-frame-pointer"
+#options       DDB             # in-kernel debugger
 #options       DDB_COMMANDONENTER="bt" # execute command when ddb is entered
 #options       DDB_ONPANIC=1   # see also sysctl(7): `ddb.onpanic'
 options        DDB_HISTORY_SIZE=512    # enable history editing in DDB
-#options       KGDB            # remote debugger
-#options       KGDB_DEVNAME="\"com\"",KGDB_DEVADDR=0x3f8,KGDB_DEVRATE=9600
+options        KGDB            # remote debugger
+options        KGDB_DEVNAME="\"com\"",KGDB_DEVADDR=0x3f8,KGDB_DEVRATE=115200
 makeoptions    DEBUG="-g"      # compile full symbol table for CTF
 #options       SYSCALL_STATS   # per syscall counts
 #options       SYSCALL_TIMES   # per syscall times
```

`0x3f8` はx86での [I/Oアドレス](https://ja.wikipedia.org/wiki/%E5%85%A5%E5%87%BA%E5%8A%9B%E3%83%9D%E3%83%BC%E3%83%88#I/O%E7%A9%BA%E9%96%93) で [com0を表します](https://en.wikipedia.org/wiki/COM_(hardware_interface)#I/O_addresses)。

<!--
2018/04/18現在、DDB無効・KGDB有効でビルドすると以下のようにエラーとなってしまいます。とりえあずDDB有効・KGDB有効でビルドして下さい。

```
#      link  GENERIC/netbsd
/home/wsh/src/netbsd/src/../tools/bin/x86_64--netbsd-ld -Map netbsd.map --cref -T netbsd.ldscr
db_memrw.o: In function `db_read_bytes':
/home/wsh/src/netbsd/src/sys/arch/x86/x86/db_memrw.c:94: undefined reference to `db_printf'
/home/wsh/src/netbsd/src/sys/arch/x86/x86/db_memrw.c:115: undefined reference to `db_printf'
db_memrw.o: In function `db_write_text':
/home/wsh/src/netbsd/src/sys/arch/x86/x86/db_memrw.c:148: undefined reference to `db_printf'
*** [netbsd] Error code 1

nbmake: stopped in /home/wsh/src/netbsd/obj.amd64/sys/arch/amd64/compile/GENERIC
1 error
```
-->

<!--
e0cb4eae8cb1
Fix build of x86 kernel with KGDB (TODO check build success)

If option KGDB is enabled and DDB is enabled, subr_prf.c:db_printf()
will not be undefined:

(build log)

Thus db_memrw.c should be removed on this kernel config.
-->


<!--
sys/arch/x86/conf/files.x86
-file   arch/x86/x86/db_memrw.c         ddb | kgdb
+file   arch/x86/x86/db_memrw.c         ddb

disable DDB
enable  KGDB

#      link  GENERIC/netbsd
/home/wsh/src/netbsd/src/../tools/bin/x86_64--netbsd-ld -Map netbsd.map --cref -T netbsd.ldscript -Ttext 0xffffffff80200000 -e start -z max-page-size=0x200000 -X -o netbsd ${SYSTEM_OBJ:[@]:Nswapnetbsd.o} ${EXTRA_OBJ} vers.o swapnetbsd.o
kgdb_stub.o: In function `kgdb_trap':
/home/wsh/src/netbsd/src/sys/kern/kgdb_stub.c:478: undefined reference to `db_read_bytes'
/home/wsh/src/netbsd/src/sys/kern/kgdb_stub.c:508: undefined reference to `db_write_bytes'
*** [netbsd] Error code 1
-->

ビルド後、カーネルを仮想マシンにインストール（`scp -P10022 ${OBJDIR}/sys/arch/amd64/compile/GENERIC/netbsd root@localhost:/netbsd`）後 `reboot` して、正常に起動できるか確認後 `poweroff` します。

<!-------------------------------------------------->

<!-------------------------------------------------->

<!-------------------------------------------------->
## com0を使う修行

一旦読み飛ばして頂いて大丈夫です。ハマったら読んでみて下さい。

`qemu-system-x86_64 -rtc base=utc -m 256 -hda netbsd.qcow2 -net nic -net user,hostfwd=tcp::10022-:22 -serial telnet::4444,server` で仮想マシンを起動します。QEMUのオプションについては[カーネルデバッグで使うQEMUオプションチートシート](https://qiita.com/wataash/items/174b454d4478898a556b)を参考にして下さい。別ターミナルで `telnet localhost 4444` でQEMUに接続すると、QEMUウィンドウが表示されてNetBSDがブートします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/167409/f0bedc82-4a2a-a77e-7543-c314c661c2ca.png)

ブート中に流れるdmesgに `com0: kgdb` が表示されていることを確認します。

`/var/run/dmesg.boot`:

```
...
[   1.0536313] iic0 at piixpm0: I2C bus
[   1.0536313] vga0 at pci0 dev 2 function 0: vendor 1234 product 1111 (rev. 0x02)
[   1.0536313] wsdisplay0 at vga0 kbdmux 1: console (80x25, vt100 emulation), using wskbd0
[   1.0536313] wsmux1: connecting to wsdisplay0
[   1.0536313] drm at vga0 not configured
...
[   1.0536313] lpt0 at isa0 port 0x378-0x37b irq 7
[   1.0536313] com0 at isa0 port 0x3f8-0x3ff irq 4: ns16550a, working fifo
[   1.0536313] com0: kgdb
[   1.0536313] attimer0 at isa0 port 0x40-0x43
...
```

`reboot` 後、QEMUウィンドウのboot promptで `consdev com0` と入力します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/167409/3db8feb6-315e-ab23-5641-dd24f06bbabe.png)

左のターミナル (com0) がコンソールになりました。コンソールをQEMUウィンドウに戻すには `consdev pc` と入力すれば良いです。このままcom0で `boot` と打つとcom0にdmesgが流れて。`login: ` が表示されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/167409/0e41fa26-3a1d-6fca-12ce-8db3b4e608e2.png)

dmesgには `wsdisplay0 at vga0 kbdmux 1: console` が無くなり、`com0: console` が現れます。

```
...
[   1.0301096] iic0 at piixpm0: I2C bus
[   1.0301096] vga0 at pci0 dev 2 function 0: vendor 1234 product 1111 (rev. 0x02)
[   1.0301096] wsdisplay0 at vga0 kbdmux 1
[   1.0301096] wsmux1: connecting to wsdisplay0
[   1.0301096] wskbd0: connecting to wsdisplay0
[   1.0301096] drm at vga0 not configured
[   1.0301096] wm0 at pci0 dev 3 function 0: Intel i82540EM 1000BASE-T Ethernet (rev. 0x03)
...
[   1.0301096] lpt0 at isa0 port 0x378-0x37b irq 7
[   1.0301096] com0 at isa0 port 0x3f8-0x3ff irq 4: ns16550a, working fifo
[   1.0301096] com0: console
[   1.0301096] com0: kgdb
[   1.0301096] attimer0 at isa0 port 0x40-0x43
...
```

<!-------------------------------------------------->

<!-------------------------------------------------->

<!-------------------------------------------------->
## kgdbに接続する
![Peek 2018-04-19 23-08.gif](https://qiita-image-store.s3.amazonaws.com/0/167409/3ff079b5-6168-abcf-d2a3-84913dff623b.gif)


仮想マシンを `qemu-system-x86_64 -rtc base=utc -m 256 -hda netbsd.qcow2 -net nic -net user,hostfwd=tcp::10022-:22 -serial telnet::4444,server,nowait` で起動します。QEMUのオプションについては[カーネルデバッグで使うQEMUオプションチートシート](https://qiita.com/wataash/items/174b454d4478898a556b)を参考にして下さい。
QEMUウィンドウでスペースを押して5を選択した後、`boot -d` で起動すると `kgdb waiting...` が表示されたます。
この状態でホストのクロスgdbから `target remote :4444` を実行すると、kgdbに接続されてデバッグが開始されます。

NetBSDのkgdbはクセが強く、色々気をつけるべきことがあります。

1. gdbを一度切断すると、二度と接続できなくなります。もう再接続するには仮想マシンを再起動する必要があります。
1. `continue` した後、Ctrl-Cによる中断ができません。うっかり押してしまうと再接続できなくなり、仮想マシンの再起動が必要になります。[公式HOWTO](http://www.jp.netbsd.org/ja/docs/kernel/kgdb.html) を見ると、昔はCtr-Cで中断できたようです。Regressionしてるっぽいです。（調査中です）
1. 上の性質から、`continue` する前に `break` を入れることが必須です。

gdbを直接使うは非常に使いづらいので、テキストエディタやIDEから使いましょう。CLionから使うHowtoを近日中に公開予定です。

## ライセンス

<a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="クリエイティブ・コモンズ・ライセンス" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br />この 作品 は <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">クリエイティブ・コモンズ 表示 4.0 国際 ライセンス</a>の下に提供されています。

関連するソフトウェアの公式ドキュメントのメンテナの方へ：この記事の内容を公式ドキュメントに記載して頂ける場合、この記事にコメントして頂くか、twitter [@wata_ash](https://twitter.com/wata_ash) にご連絡下さい。