10
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

QEMUのgdbserver機能でNetBSD kernelをデバッグする

Last updated at Posted at 2018-04-15

Peek 2018-04-15 17-18.gif

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 を使用して下さい。
  • 仮想マシン上の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 dqemu(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のアタッチで止まりました。

image.png

s, n, fin で進めていくとちょっとずつconfigureされていきます。これは楽しいですw

image.png

生の 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 にご連絡下さい。

  1. "gdbserver" というとGNU gdbについてくるgdbserverのことを指すので "gdb stub" という一般名称にしようかと思いましたが、QEMUがgdbserverという名詞を使っているので "gdbserver" にしておきました。

  2. -O <OBJDIR> を指定しない場合 ${OBJDIR}/sys/arch/amd64/compile/GENERIC/netbsd${OBJDIR}/sys/arch/amd64/compile/GENERIC/netbsd.gdb

10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?