QEMUにはgdbserver1が内蔵されており、ホストのgdbからtcp越しにゲストOSのカーネルをデバッグすることができます。この記事は上のGIF動画のようにNetBSD kernelのデバッグを行うHowtoです。
(VMWareでも同じことができるらしいです。WindowsならVirtualBox+VBoxGDBでもできるらしいです。が、私は日常的にNetBSD仮想マシンを使っている訳では無いので、気軽に環境構築・撤去ができるQEMUが好きです。libvertでもできるらしいのでそのうちやってみようと思ってます。)
注意事項
- ホストOSはLinux (Ubuntu 18.04), macOS (Mojave 10.14.3) で検証済みです。Windowsや他のOSでの動作報告お待ちしております!
- ゲストOSは NetBSD - 8.0-amd64 で検証済みです。
- i386の場合、
qemu-system-x86_64
の代わりにqemu-system-i386
を使用して下さい。
- i386の場合、
- 仮想マシン上のNetBSDで走らないコードはデバッグできません。デバイスドライバなどのマシン依存・アーキテクチャ依存のコードをデバッグする際はkgdbを使って下さい。別記事 NetBSDのkgdbをQEMUでお手軽に試す にHowtoを書きました。
クロスgdbのビルド
-
gdb 最新版を
configure --enable-targets=all
でビルド(--target=x86_64--netbsd
としても良いがあまりサイズは変わらない) -
build.sh -T <TOOLDIR> -VMKCROSSGDB=yes tools
で${TOOLDIR}/tools/bin/x86_64--netbsd-gdb
をビルド
のどちらかでビルドして下さい。2019/04/06現在、gdb最新版のバージョンは8.2.1、NetBSD付属のgdbは8.0.1です。NetBSD付属ののgdbは古くなってくることがあるので、その場合はGNUから最新版をダウンロードしてビルドすることをおすすめします。筆者の経験的に最新版の方がトラブル(ビルドできない、バグってる、mi APIが古い)が少ないです。
仮想イメージの準備
新規仮想イメージにNetBSD 8.0をインストールします。
curl http://ftp.jaist.ac.jp/pub/NetBSD/iso/8.0/NetBSD-8.0-amd64.iso -o NetBSD-8.0-amd64.iso
qemu-img create -f qcow2 netbsd.qcow2 100G
qemu-system-x86_64 -m 256 -hda netbsd.qcow2 -cdrom ./NetBSD-8.0-amd64.iso -display curses -netdev user,id=n1 -device e1000,netdev=n1 -monitor telnet::5555,server,nowait -rtc base=utc
qemu-system-x86_64
のオプションについては カーネルデバッグで使うQEMUオプションチートシート を参照して下さい。強制終了するにはホストから telnet localhost 5555
して q
です。
NetBSDをいつも通りインストールします。いつも通りでない方は @furandon_pig さんの NetBSD-7.0をインストールしてみる - NetBSDのインストール を参考にして下さい。インストール後は一旦 poweroff
でQEMUを終了します。
再インストールする等で、ブートデバイスがhdaになってしまう場合は -boot d
(qemu(1) 2.3.1 Standard options)で明示的にCDROMブートして下さい。
デバッグするkernelのビルドとインストール
build.sh -O <OBJDIR> kernel=GENERIC
で
${OBJDIR}/sys/arch/amd64/compile/GENERIC/netbsd
${OBJDIR}/sys/arch/amd64/compile/GENERIC/netbsd.gdb
をビルドします2。ビルド中、gccに -g
フラグがついていることを確認します。
# compile GENERIC/kern_syscall.o
/home/wsh/src/netbsd/src/../tools/bin/x86_64--netbsd-gcc -mcmodel=kernel ... -fno-delete-null-pointer-checks -g -O2 -fno-omit-frame-pointer ... /home/wsh/src/netbsd/src/sys/kern/kern_syscall.c -o kern_syscall.o
amd64の場合はデフォルトで -g
がついていますが、他のアーキテクチャでは -VMKKDEBUG=yes
で明示的に -g
をつける必要があるかもしれません。
カーネルをビルドしたら仮想マシンを起動してインストールします。
qemu-system-x86_64 -m 512 -hda netbsd.qcow2 -display curses -netdev user,id=n1,hostfwd=tcp::10022-:22 -device e1000,netdev=n1 -monitor telnet::5555,server,nowait -rtc base=utc
仮想マシンが起動したら、rootでログインして以下を実行します。
telnet -4 qiita.com 80 # ネットワーク疎通性のテスト
passwd # root のパスワードが設定されていない場合。sshログインのために必要
cp /netbsd /netbsd.orig
vi /etc/rc.conf # dhcpcd=YES, dhcpcd_flags="-qM wm0", sshd=YES を確認
vi /etc/ssh/sshd_config # PermitRootLogin yes
service sshd reload
Linuxホストのuser mode networkではTCPとUDPしか使えないため、pingではなくtelnetでテストしています。
ホストから scp -P10022 ${OBJDIR}/sys/arch/amd64/compile/GENERIC/netbsd root@localhost:/netbsd
を実行して、ゲストにデバッグ対象のカーネルをインストールします。
接続できなければまず ssh -p10022 root@localhost
でログインできるか確認して下さい。
仮想マシンを reboot
して正常に起動できることを確認後、poweroff
で一旦シャットダウンします。
正常に起動しない場合は、起動直後のブートオプションで 5. Drop to boot prompt
を選択後 boot netbsd.orig
でオリジナルのカーネルを起動して…頑張って下さい。
gdbでステップ実行する
以下のコマンドでゲストを起動:
qemu-system-x86_64 -m 512 -hda netbsd.qcow2 -display curses -netdev user,id=n1,hostfwd=tcp::10022-:22 -device e1000,netdev=n1 -monitor telnet::5555,server,nowait -S -s -rtc base=utc
-s
は -gdb tcp::1234
等価で、ポート1234でgdbプロトコルをlistenします。このポートが使われていたら他のポートを使用して下さい。
ホストからgdbserverにつないでみます。
$ ${TOOLDIR}/bin/x86_64--netbsd-gdb -q ${OBJDIR}/sys/arch/amd64/compile/GENERIC/netbsd.gdb
Reading symbols from ../obj.amd64/sys/arch/amd64/compile/GENERIC/netbsd.gdb...done.
Reading in symbols for /home/wsh/src/netbsd/src/sys/kern/init_main.c...done.
(gdb) b main
Breakpoint 1 at 0xffffffff80d262cf: file /home/wsh/src/netbsd/src/sys/kern/init_main.c, line 275.
(gdb) target remote :1234
Remote debugging using :1234
0x000000000000fff0 in ?? ()
(gdb) l
245 static void start_init(void *);
246 static void configure(void);
247 static void configure2(void);
248 static void configure3(void);
249 void main(void);
250
251 /*
252 * System startup; initialize the world, create process 0, mount root
253 * filesystem, and fork to create init and pagedaemon. Most of the
254 * hard work is done in the lower-level initialization routines including
(gdb) c
Continuing.
^CReading in symbols for /home/wsh/src/netbsd/src/sys/external/bsd/acpica/dist/dispatcher/dsmthdat.c...done.
Program received signal SIGINT, Interrupt.
AcpiDsMethodDataInit (
Reading in symbols for /home/wsh/src/netbsd/src/sys/external/bsd/acpica/dist/dispatcher/dswstate.c...done.
WalkState=0xffffe4000fc65810)
at /home/wsh/src/netbsd/src/sys/external/bsd/acpica/dist/dispatcher/dsmthdat.c:111
111 for (i = 0; i < ACPI_METHOD_NUM_ARGS; i++)
(gdb) l
106 ACPI_FUNCTION_TRACE (DsMethodDataInit);
107
108
109 /* Init the method arguments */
110
111 for (i = 0; i < ACPI_METHOD_NUM_ARGS; i++)
112 {
113 ACPI_MOVE_32_TO_32 (&WalkState->Arguments[i].Name,
114 NAMEOF_ARG_NTE);
115
(gdb) bt
Reading in symbols for /home/wsh/src/netbsd/src/sys/external/bsd/acpica/dist/parser/psxface.c...done.
...
Reading in symbols for /home/wsh/src/netbsd/src/sys/arch/amd64/amd64/autoconf.c...done.
#0 AcpiDsMethodDataInit (WalkState=0xffffe4000fc65810)
at /home/wsh/src/netbsd/src/sys/external/bsd/acpica/dist/dispatcher/dsmthdat.c:111
#1 0xffffffff8125a131 in AcpiDsCreateWalkState (OwnerId=11 '\v', Origin=0x0, MethodDesc=0x0,
Thread=0x0) at /home/wsh/src/netbsd/src/sys/external/bsd/acpica/dist/dispatcher/dswstate.c:629
#2 0xffffffff81278335 in AcpiPsExecuteMethod (Info=0xffffe40002686638)
at /home/wsh/src/netbsd/src/sys/external/bsd/acpica/dist/parser/psxface.c:185
#3 0xffffffff8126e5c8 in AcpiNsEvaluate (Info=0xffffe40002686638)
at /home/wsh/src/netbsd/src/sys/external/bsd/acpica/dist/namespace/nseval.c:251
...
#21 0xffffffff80d26ae7 in configure () at /home/wsh/src/netbsd/src/sys/kern/init_main.c:754
#22 0xffffffff80d265dc in main () at /home/wsh/src/netbsd/src/sys/kern/init_main.c:517
(gdb)
ctrl-Cで任意の場所で停止できます。適当に止めたらACPIのアタッチで止まりました。
s, n, fin
で進めていくとちょっとずつconfigureされていきます。これは楽しいですw
生の gdb
でデバッグしてもあまりうれしくないのでテキストエディタやIDEから使いましょう。冒頭のGIF動画はCLionから使っています。別記事で解説予定です。
最適化を止める
カーネルはデフォルトで -O2
でビルドされるのでデバッグしづらいです。
カーネルコンフィグの makeoptions COPTS=-O2
を -O0
にしましょう。ビルド中、-O0
でコンパイルされていることを確認します。
# compile GENERIC/kern_syscall.o
/home/wsh/src/netbsd/src/../tools/bin/x86_64--netbsd-gcc ... -g -O0 ...
トラブルシューティング
gdbでソースコードが表示できない
netbsd.gdb
にはソースコードのパスがハードコードされています。そのため、ビルド後にソースの場所を変えたりすると、以下のようにgdbがソースを発見できません。
^C
Program received signal SIGINT, Interrupt.
0xffffffff8021ebfe in ?? ()
(gdb) l
245 /home/wsh/src/netbsd/src/sys/kern/init_main.c: No such file or directory.
(gdb)
この場合は set substitute-path
でパスのマッピングをしてやると良いです。
(gdb) set substitute-path /home/wsh/src/netbsd/src/ /usr/opt/src/netbsd/
(gdb) l
245 static void start_init(void *);
246 static void configure(void);
247 static void configure2(void);
248 static void configure3(void);
249 void main(void);
250
251 /*
252 * System startup; initialize the world, create process 0, mount root
253 * filesystem, and fork to create init and pagedaemon. Most of the
254 * hard work is done in the lower-level initialization routines including
(gdb) info source
Current source file is /home/wsh/src/netbsd/src/sys/kern/init_main.c
...
特定のファイルだけソースコードが表示されない
そのソースコードのみ -g
でコンパイルされてないと考えられます。gdbの info sources
に当該ファイルが含まれているか確認して下さい。
'g' packet reply is too long
gdbserverとgdbの間の通信が上手くできていません。例えば、i386のカーネルを qemu-system-i386
ではなく qemu-system-x86_64
で起動してデバッグしようとすると、以下のようなエラーが出力されます:
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
warning: Selected architecture i386 is not compatible with reported target architecture i386:x86-64
Remote 'g' packet reply is too long: 2ad034740000000080faffef000000001501000000000000010000000000000000000000000000002300000000000000f41f44f000000000cc1f44f00 ...
他には
- クロスgdbでなく普通のgdbを使っている
- クロスgdbを使っているがアーキテクチャを誤って解釈している
など考えられます。後者の場合は show architecture
set architecture
で何とかなるかもしれません。set debug remote 1
が助けになるかもしれません。
ライセンス
この 作品 は クリエイティブ・コモンズ 表示 4.0 国際 ライセンスの下に提供されています。
関連するソフトウェアの公式ドキュメントのメンテナの方へ:この記事の内容を公式ドキュメントに記載して頂ける場合、この記事にコメントして頂くか、twitter @wata_ash にご連絡下さい。
-
"gdbserver" というとGNU gdbについてくるgdbserverのことを指すので "gdb stub" という一般名称にしようかと思いましたが、QEMUがgdbserverという名詞を使っているので "gdbserver" にしておきました。 ↩
-
-O <OBJDIR>
を指定しない場合${OBJDIR}/sys/arch/amd64/compile/GENERIC/netbsd
、${OBJDIR}/sys/arch/amd64/compile/GENERIC/netbsd.gdb
↩