FreeBSD kernel remote debug
図書館へ行ったら、新刊コーナーに面白そうな本が多数並べてあった。限度いっぱいに 借りてきたぞ。新年度予算が執行出来たからでしょうかね?
501,2500,昭和解体 273,2500,国際テロリズム(その戦術と実態から抑止まで) 425,2500,デジタルアポロ 273,1600,POST TRUTH (ポスト真実時代のネットニュースの読み方) 341,1700,茜の茶碗 364,1600,天下、なんぼや
CSV風にリスト。最左から、ページ数、価格、書名だ。 総ページ数、2177頁。金額、12400円。図書館さん、有難う。
これを15日間で読破せいとな。 まずは、一番の大著で昭和解体が、ランキューから取り出されて、ランニングとなりました。
赤く染まった国鉄を分割民営化する過程を描いたもの。読んでも読んでも終わらない。 タイムスロットを使い果たしたと言う理由で、この本は、ランキューの末尾へと追いやられ ました。
そして、次にランニングになったのは、デジタルアポロ。月へ人類をどうやって送り届けよう かというNASAの人達の物語。これも面白いけど、時間を使い果たしたので、次へスイッチ。
今度は、テロの実態とそれをどう抑止するかという話。安倍ちゃんが、オリンピックに備え 法案を立てるとやっきになってるけど、鍵はITだと思うぞ。連中同士の連絡から始まって 資金の調達やらにITは必須ですからねぇ。
と、今回は、パラレルに本を読み進めているけど、意外に中断してた所にさっと復帰できる ものだな。まだまだ、ヒューマンコンピュータは正常なり。
FreeBSD in FreeBSD
前回はFreeBSDの中でbhyveを動かしOpneBSDを動かした。そしてkgdbでOpenBSDのの動きを 観察しようとして、あえなく撃沈。今回はそのリベンジ。観察対象をFreeBSDにしてみる。
まずは、仮想マシンにFreeBSDを入れて起動してみる。
truncate -s 1000M disk sudo sh ./vmrun.sh -t tap0 -d disk -i -I amd64-disc1.iso FB11 sudo sh ./vmrun.sh -m 256M -t tap0 -d disk FB11
正式なISOファイル名は、もう少し長いんだけど、ちょっと短縮してる。使ったスクリプトは、 サンプルの所から引っ張ってきたものだ。
root@vmfb:~ # df Filesystem 1K-blocks Used Avail Capacity Mounted on /dev/vtbd0a 991640 576968 335344 63% / devfs 1 1 0 100% /dev
DISKの使用状況を確認すると、結構喰ってるな。これじゃ、sysのソースを入れて、コンパイル するには空きが無いだろう。涙をのんでdisk容量を1500Mにした。
$ cat /etc/rc.conf hostname="vmfb" ifconfig_vtnet0="inet XXX.XXX.XXX.11 netmask 255.255.255.0" defaultrouter="XXX.XXX.XXX.2" sshd_enable="YES" dumpdev="NO" sendmail_enable="NONE" clear_tmp_enable="YES"
sendmailがうざいので、殺しておいたぞ。それから、インストール時にtimezoneをJSTに しておいたにも関わらず、9時間ずれてた。親の時計情報をゲストが貰ってきて、UTCと 誤認してるんだな。しょうがないので、/etc/wall_cmos_clock ファイルを作って親から の時刻はローカルだよと教えておいた。
コンパイルに先立ち、いらないと思うドライバーを外し(USBとかNICとかRAID等)、更に モジュールは止めて、モノシリックカーネルなやつを作るように指示。
makeoptions NO_MODULES=yes
コンパイルはCPUが総出で頑張るように指示。
--- kernel.debug --- make -j2 : objcopy --only-keep-debug kernel.full kernel.debug --- kernel --- objcopy --strip-debug --add-gnu-debuglink=kernel.debug kernel.full kernel 436.571u 92.978s 5:40.96 155.3% 47811+488k 1673+3118io 1976pf+1w
OpenBSDに比べて、随分と時間がかかるな。
root@vm11:/sys/amd64/compile/DEBUG # make install thiskernel=`sysctl -n kern.bootfile` ; if [ ! "`dirname "$thiskernel"`" -ef /boot/kernel ] ; then chflags -R noschg /boot/kernel ; rm -rf /boot/kernel ; rm -rf /usr/lib/debug/boot/kernel ; else if [ -d /boot/kernel.old ] ; then chflags -R noschg /boot/kernel.old ; rm -rf /boot/kernel.old ; fi ; mv /boot/kernel /boot/kernel.old ; if [ -n "/usr/lib/debug" -a -d /usr/lib/debug/boot/kernel ]; then rm -rf /usr/lib/debug/boot/kernel.old ; mv /usr/lib/debug/boot/kernel /usr/lib/debug/boot/kernel.old ; fi ; sysctl kern.bootfile=/boot/kernel.old/"`basename "$thiskernel"`" ; fi kern.bootfile: /boot/kernel/kernel -> /boot/kernel.old/kernel mkdir -p /boot/kernel install -p -m 555 -o root -g wheel kernel /boot/kernel/ mkdir -p /usr/lib/debug/boot/kernel install -p -m 555 -o root -g wheel kernel.debug /usr/lib/debug/boot/kernel/
そしてインストール。早速nmdmケーブルを使って、ホスト側と接続するも、やっぱり無しの つぶて。これはもう、諦めろと言う事?
bhyve -g
諦めかけて、例のvmrun.shを見てたら、-g に気が付いて。manから該当部分を拾って みると、先行きは怪しいようだけど、光明が見えた。
-g gdbport For FreeBSD kernels compiled with device bvmdebug, allow a remote kernel kgdb to be relayed to the guest kernel gdb stub via a local IPv4 address and this port. This option will be deprecated in a future version.
ネットをみると、こんな記事も。
特殊なドライバーをホスト側に組み込めとな。そして、DDBをイネーブルにしたターゲット側の OSも必要との事。ホスト側にコンパイル、インストールした後、カーネルをターゲット側に 転送しよう。
ホスト側と兼用になるんで、大胆にドライバーを削るのは控え、GENERICに下記の設定を追加。
device bvmdebug options DDB
そしてコンパイル。
:> export_syms awk -f /usr/src/sys/conf/kmod_syms.awk zfs.ko.full export_syms | xargs -J% objcopy % zfs.ko.full --- zfs.ko.debug --- objcopy --only-keep-debug zfs.ko.full zfs.ko.debug --- zfs.ko --- objcopy --strip-debug --add-gnu-debuglink=zfs.ko.debug zfs.ko.full zfs.ko 1333.391u 160.175s 12:39.81 196.5% 47492+487k 5174+17300io 5020pf+0w root@fb11:/sys/amd64/compile/DEBUG # ls -l kernel* -rwxr-xr-x 1 root wheel 27360888 May 14 05:41 kernel -rwxr-xr-x 1 root wheel 75375728 May 14 05:40 kernel.debug -rwxr-xr-x 1 root wheel 98770088 May 14 05:40 kernel.full root@fb11:/sys/amd64/compile/DEBUG # cd .. root@fb11:/sys/amd64/compile # du -sh DEBUG/ 1.5G DEBUG/
make -j2 でコンパイルしたにも関わらず、22分もコンパイルに時間がかかった。その残骸も 凄まじいものになった。
早速トライ。boot中の10秒間の間に、ESCキーを押し下げる。
[fb11: FB11]$ sudo sh ./vmrun.sh -g 6466 -t tap0 -d disk FB11 +============Welcome to FreeBSD===========+ +o .--` /y:` +. | | yo`:. :o `+- | 1. Boot Multi User [Enter] | y/ -/` -o/ | 2. Boot [S]ingle User | .- ::/sy+:. | 3. [Esc]ape to loader prompt | / `-- / | 4. Reboot | `: :` | | `: :` | Options: | / / | 5. [K]ernel: kernel (1 of 2) | .- -. | 6. Configure Boot [O]ptions... | -- -. | | `:` `:` | | .-- `--. | | .---.....----. +=========================================+ To get back to the menu, type `menu' and press ENTER or type `boot' and press ENTER to start FreeBSD. Type '?' for a list of commands, 'help' for more detailed help. OK boot -dg /boot/kernel/kernel text=0x151a158 data=0x1366e0+0x4bdee8 syms=[0x8+0x161fe8+0x8+0x17a7a9] /boot/entropy size=0x1000 Booting... KDB: debugger backends: ddb KDB: current backend: ddb KDB: enter: Boot flags requested debugger [ thread pid 0 tid 0 ] Stopped at kdb_enter+0x3b: movq $0,kdb_why db> gdb The remote GDB backend could not be selected.
boot -dgでkdbを指定したけど、そんなの無いって! いやな予感。めげずに、ホスト側でも 行動を起こす。
[fb11: DEBUG]$ sudo kgdb kernel.debug Password: GNU gdb 6.1.1 [FreeBSD] Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "amd64-marcel-freebsd"... (kgdb) target remote localhost:6466 Remote debugging using localhost:6466 Ignoring packet error, continuing... Ignoring packet error, continuing... Ignoring packet error, continuing... Couldn't establish connection to remote target Malformed response to offset query, timeout
やっぱり繋がらない。困った事だ。
取り合えず、ゲスト側を立ち上げ、強制的にkdbに入れないか試す。
root@vmfb:~ # sysctl debug.kdb.enter=1 debug.kdb.enter: 0KDB: enter: sysctl debug.kdb.enter [ thread pid 688 tid 100055 ] Stopped at kdb_sysctl_enter+0x8b: movq $0,kdb_why db> trace Tracing pid 688 tid 100055 td 0xfffff80003523a00 kdb_sysctl_enter() at kdb_sysctl_enter+0x8b/frame 0xfffffe0019fd0780 sysctl_root_handler_locked() at sysctl_root_handler_locked+0xbf/frame 0xfffffe0019fd07c0 sysctl_root() at sysctl_root+0x1f6/frame 0xfffffe0019fd0830 userland_sysctl() at userland_sysctl+0x1c4/frame 0xfffffe0019fd08e0 sys___sysctl() at sys___sysctl+0x74/frame 0xfffffe0019fd0990 amd64_syscall() at amd64_syscall+0x4ce/frame 0xfffffe0019fd0ab0 Xfast_syscall() at Xfast_syscall+0xfb/frame 0xfffffe0019fd0ab0 --- syscall (202, FreeBSD ELF64, sys___sysctl), rip = 0x800978f5a, rsp = 0x7fffffffd9d8, rbp = 0x7fffffffda10 --- db>
これはもう、ソース嫁の世界に突入かな。
subr_kdb.c
カーネル探検をするにあたって、emacs用のTAGSを作っておいた。(前回参照)
上記のエラーメッセージを頼りにgrepしてみると、amd64/machdep.c から呼ばれる kdb_init が機能してない疑惑。定義は、kern/subr_kdb.c に有るっぽい。
/* * Initialize before referring to a given linker set. */ #define SET_DECLARE(set, ptype) \ extern ptype __weak_symbol *__CONCAT(__start_set_,set); \ extern ptype __weak_symbol *__CONCAT(__stop_set_,set) SET_DECLARE(kdb_dbbe_set, struct kdb_dbbe);
sys/kdb.hにある参考資料
struct kdb_dbbe { const char *dbbe_name; dbbe_init_f *dbbe_init; dbbe_trace_f *dbbe_trace; dbbe_trace_thread_f *dbbe_trace_thread; dbbe_trap_f *dbbe_trap; int dbbe_active; }; #define KDB_BACKEND(name, init, trace, trace_thread, trap) \ static struct kdb_dbbe name##_dbbe = { \ .dbbe_name = #name, \ .dbbe_init = init, \ .dbbe_trace = trace, \ .dbbe_trace_thread = trace_thread, \ .dbbe_trap = trap \ }; \ DATA_SET(kdb_dbbe_set, name##_dbbe) extern int kdb_active; /* Non-zero while in debugger. */
kern_kdb.cの中。
kdb_init(void) { struct kdb_dbbe *be, **iter; int cur_pri, pri; kdb_active = 0; kdb_dbbe = NULL; cur_pri = -1; SET_FOREACH(iter, kdb_dbbe_set) { be = *iter; pri = (be->dbbe_init != NULL) ? be->dbbe_init() : -1; be->dbbe_active = (pri >= 0) ? 0 : -1; if (pri > cur_pri) { cur_pri = pri; kdb_dbbe = be; } } :
kernelを逆アセンブルして、ddbからgdbに切り替えるコマンド、 db_gdbのアドレスを割り出し、BPを置くと、下記のように怒られた。
db> break *ffffffff8039c310 Symbol not found KDB: reentering KDB: stack backtrace: db_trace_self_wrapper() at db_trace_self_wrapper+0x2b/frame 0xfffffe0019fbbfe0 kdb_reenter() at kdb_reenter+0x8e/frame 0xfffffe0019fbc090 db_term() at db_term+0x97/frame 0xfffffe0019fbc0b0 db_unary() at db_unary+0xa1/frame 0xfffffe0019fbc0d0 db_unary() at db_unary+0x29/frame 0xfffffe0019fbc0f0 db_mult_expr() at db_mult_expr+0x17/frame 0xfffffe0019fbc120 db_add_expr() at db_add_expr+0x19/frame 0xfffffe0019fbc160 db_shift_expr() at db_shift_expr+0x1d/frame 0xfffffe0019fbc1b0 db_logical_relation_expr() at db_logical_relation_expr+0x1d/frame 0xfffffe0019fbc200 db_logical_and_expr() at db_logical_and_expr+0x19/frame 0xfffffe0019fbc240 db_expression() at db_expression+0x19/frame 0xfffffe0019fbc280 db_command() at db_command+0x32a/frame 0xfffffe0019fbc350 db_command_loop() at db_command_loop+0x64/frame 0xfffffe0019fbc360 db_trap() at db_trap+0xdb/frame 0xfffffe0019fbc3f0 kdb_trap() at kdb_trap+0x193/frame 0xfffffe0019fbc480 trap() at trap+0x255/frame 0xfffffe0019fbc690 calltrap() at calltrap+0x8/frame 0xfffffe0019fbc690 --- trap 0x3, rip = 0xffffffff80b327cb, rsp = 0xfffffe0019fbc760, rbp = 0xfffffe0019fbc780 ---
クリチカルな部分はdebuggerにはかけられないのね。残念。
そこで、いよいよ有名なprintf文の挿入ですよ。kdb_initのFOREACHを回してる所で、 構造体のアドレスを出すようにしてみた。 やったのは、仮想PCの方のFreeBSD。メモリーを十分(768M)に取っておかないと、最後の リンクの所で、領域不足になる。段々大掛かりなOSになってるね。
SET_FOREACH(iter, kdb_dbbe_set) { be = *iter; printf("BE = %p\n", be);
OK boot -d /boot/kernel/kernel text=0x90551d data=0xb0338+0x390ac8 syms=[0x8+0xe3a18+0x8+0x103bfe] /boot/entropy size=0x1000 Booting... BE = 0xffffffff80d18d30 ;; ddb_dbbe BE = 0xffffffff80d65268 ;; null_dbbe KDB: debugger backends: ddb KDB: current backend: ddb KDB: enter: Boot flags requested debugger [ thread pid 0 tid 0 ] Stopped at kdb_enter+0x3b: movq $0,kdb_why
上記 BE のアドレス解決情報は、下記のように採取した。
root@vm12:/sys/amd64/compile/SA # nm kernel.debug | grep _dbbe ffffffff80d18d30 b ddb_dbbe ffffffff80fdf6e0 B kdb_dbbe ffffffff80d65268 b null_dbbe
メモリー内容を調べようとして、man ddbしてたら、ddb(4)も見ておけとか言われた。 そしたら、衝撃的な事が書いてあったぞ。
To enable the gdb(1) backend, so that remote debugging with kgdb(1) is possible, include: options GDB
kernelを作り直した後、bhyve の -g を使って接続しようとしたが、やはりつながらず。 でも、状況が少しは進歩してる気がする。
OK boot -d /boot/kernel/kernel text=0x907989 data=0xb06b8+0x3912c8 syms=[0x8+0xe3fd0+0x8+0x103fbd] /boot/entropy size=0x1000 Booting... BE = 0xffffffff80d1ad30 ;; ddb_dbbe BE = 0xffffffff80d48b98 ;; gdb_dbbe GDB: no debug ports present BE = 0xffffffff80d675a8 ;; null_dbbe KDB: debugger backends: ddb KDB: current backend: ddb KDB: enter: Boot flags requested debugger [ thread pid 0 tid 0 ] Stopped at kdb_enter+0x3b: movq $0,kdb_why
今度は、gdb用の構造体が追加されてる。コンパイルして出来た残骸は、245M、sysエリアも 含めれば、558Mで、1500Mのdiskのほぼ100%にもなってしまってるぞ。
上で言ってるGDB用のポートが無いって言うの追及を諦めよう。昔からの流儀、シリアル ケーブル接続に再挑戦。vmrun.shをちょっと改造して、ヌルモデムケーブルをcom2に繋ぐ。 (前回やったような)
-l com1,${console} \ -l com2,/dev/nmdm0A \
comポートってのは、IBMパソコンでの名称。FreeBSDでは昔は、cuauとかsioとか言ってる けど、今はuartとか言うのかな。どのポートをdebugger用に割り当てるか設定が必要だったはず。物の本によると、/boot/device.hintsが担当してる。
root@vm12:/boot # cat device.hints : hint.uart.1.flags="0x80"
以下、uart(4)から抜粋
In /boot/device.hints: hint.uart.0.disabled="1" hint.uart.0.baud="38400" hint.uart.0.port="0x3f8" hint.uart.0.flags="0x10" With flags encoded as: 0x00010 device is potential system console 0x00080 use this port for remote kernel debugging
OK boot -d /boot/kernel/kernel text=0x907979 data=0xb06b8+0x3912c8 syms=[0x8+0xe3fd0+0x8+0x103fbd] /boot/entropy size=0x1000 Booting... GDB: debug ports: uart GDB: current port: uart KDB: debugger backends: ddb gdb KDB: current backend: ddb KDB: enter: Boot flags requested debugger [ thread pid 0 tid 0 ] Stopped at kdb_enter+0x3b: movq $0,kdb_why
ここで、別の端末からkgdbを起動して、モデムケーブルを接続して用意。すかさずに、ゲスト側 で、gdbに切り替える。
やっと繋がったぞーーー。
(kgdb) target remote /dev/nmdm0B Remote debugging using /dev/nmdm0B kdb_enter (why=0xffffffff80ad9c7b "bootflags", msg=0xa <Address 0xa out of bounds>) at ../../../kern/subr_kdb.c:444 444 kdb_why = KDB_WHY_UNSET; Current language: auto; currently minimal
後は適当に
Breakpoint 1, sys_execve (td=0xfffff8000235a500, uap=0xfffffe000ebcea40) at ../../../kern/kern_exec.c:212 212 error = pre_execve(td, &oldvmspace); (kgdb) c Continuing. Breakpoint 1, sys_execve (td=0xfffff8000235a500, uap=0xfffffe000ebcea40) at ../../../kern/kern_exec.c:212 212 error = pre_execve(td, &oldvmspace); (kgdb) c 100 Will ignore next 99 crossings of breakpoint 1. Continuing. [New Thread 100051] :
from dmesg
uart0: <16550 or compatible> port 0x3f8-0x3ff irq 4 flags 0x10 on acpi0 uart0: console (9600,n,8,1) uart1: <16550 or compatible> port 0x2f8-0x2ff irq 3 flags 0x80 on acpi0 uart1: debug port (9600,n,8,1)
まとめ
肝は、ゲスト側のOSをGDB対応にする事。そして、nmdm デバイスでシリアル接続する事
makeoptions NO_MODULES=yes options DDB options GDB
ゲストOSでは、コンソールがシリアル接続になっている。端末の行サイズが25と小さく、 一度viを起動しちゃうと、画面が有効に使えなくなるので、sttyで最適な行数を教えて あげる。それから、何時でもkgbを起動出来るように(tcsh用の)エイリアスを設定。
root@vm12:~ # cat .cshrc : stty rows 38 alias kdb 'sysctl debug.kdb.enter=1' alias lv less
これで、FreeBSDの中に居ながら、閉じた環境で観光が出来るようになったぞ。
root@vm12:~ # kdb debug.kdb.enter: 0KDB: enter: sysctl debug.kdb.enter [ thread pid 551 tid 100051 ] Stopped at kdb_sysctl_enter+0x8b: movq $0,kdb_why db> gdb (ctrl-c will return control to ddb) Switching to gdb back-end
gdbに切り替える前に、Host側の準備をしておく事
[fb11: SA]$ kgdb kernel.debug GNU gdb 6.1.1 [FreeBSD] Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "amd64-marcel-freebsd"... (kgdb) target remote /dev/nmdm0B Remote debugging using /dev/nmdm0B kdb_sysctl_enter (oidp=0xffffffff80d676e0, arg1=<value optimized out>, arg2=<value optimized out>, req=0xfffffe001e932858) at ../../../kern/subr_kdb.c:444 444 kdb_why = KDB_WHY_UNSET; Current language: auto; currently minimal (kgdb)
一度gdbに切り替えてしまうと、ddbにはgdbコマンドから戻る術がない。 こんな時には、下記のようにする。
root@vm12:~ # sysctl debug.kdb.current=ddb debug.kdb.current: gdb -> ddb
後、 FreeBSD Developers' Handbookが、参考になるぞ。