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.

ネットをみると、こんな記事も。

bhyve/Debugging With Gdb

特殊なドライバーをホスト側に組み込めとな。そして、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が、参考になるぞ。