Chapter 23. Kernel 除錯

Table of Contents
23.1. 使用 gdb 來除錯 Kernel 的 Crash Dump
23.2. 使用 DDD 來除錯 Crash Dump
23.3. 對 dump 的事後分析
23.4. 用 DDB 對 kernel 做線上除錯
23.5. 使用遠端 GDB 來線上除錯 Kernel
23.6. 使用 GDB 來除錯可載入模組
23.7. 除錯一個 console driver

Contributed by Paul Richards and Jörg Wunsch

23.1. 使用 gdb 來除錯 Kernel 的 Crash Dump

假設您有足夠的 swap space 來存 crash dump, 這裡有如何利用 crash dump 來進行 kernel 除錯的一些步驟。 如果您有很多 swap partition,但是第一個 swap partition 太小, 不夠存 crash dump 的話,您可以設定您的 kernel 使用其它的 dump device (在 config kernel 那行), 或者可以用 dumpon(8) 命令來指定。目前並不支援 dump 在非 swap 的 device,如 tape 上。 首先您要用 config -g 來設定您的 kernel。 參考 Kernel Configuration 有關設定 kernel 的細節。

使用 dumpon(8) 命令讓 kernel 知道 dump 到那個地方 (這必需要在該 partition 用 swapon(8) 設定為 swap space 之後才能做)。通常這是在 /etc/rc.conf/etc/rc 裡完成。 或者,您可以在 kernel config 檔案的 config 這行中寫死 dump 這個設定。 這是比較爛的方法,除非您希望留下 kernel 在 booting 時 crash 的 crash dump。

Note: 底下提到 gdb 這個字時, 是指在 ``kernel debug mode'' 的 gdb。 您可以執行 gdb 再加上 -k 的選項,或者把 gdb 聯結到 gdb,並且執行 gdb, 無論如何,這不是原來的設定,所以您要這樣做。 這個特性在未來的 release 可能終止。

Tip: 如果你使用 FreeBSD 3 或是更早的版本,你必須複製一份除錯版本 的核心並精簡之,而不是安裝原本肥大的除錯版本核心:

    # cp kernel kernel.debug
    # strip -g kernel

這個步驟不是必須的,但是我們建議你這麼作。(在 FreeBSD 4 跟之 後的版本,make 指令會自動在核心編譯完成時執行這個步驟)當核心被精 簡之後─不論是自動是手動使用上面的指令─你可以像平常一樣,打 make install 來把它安裝好。

注意!舊版的 FreeBSD(3.1 之前但不含 3.1)預設使用 a.out 核心, a.out 核心需要在實體記憶體中固定保留一塊空間給其符號表使用。未精 簡的核心會有一個很大的符號表,這會浪費許多記憶體空間。目前的 FreeBSD 版本使用 ELF 核心,所以這個問題不復存在。

如果您在測試這樣的一個新 kerenl,如在 boot 提示時打這個新 kernel 的名字,不過卻跑不起來,這時您必須用另一個 kernel 來讓系統跑起來。 您用 -s 將系統 boot 到單人模式,然後依下列步驟操作:

    # fsck -p
    # mount -a -t ufs       # so your file system for /var/crash is writable
    # savecore -N /kernel.panicked /var/crash
    # exit                  # ...to multi-user

這讓 savecore(8) 用另外一個 kernel 的 symbol name 來做 core dump。否則,原來是用正在跑的 kernel,這很有可能會讓 dump 沒用, 因為 crash dump 跟 kernel 的 symbol 是不一樣的。

這時,在 crash dump 完成後,到 /sys/compile/WHATEVER 並執行 gdb。在 gdb 做:

    symbol-file kernel.debug
    exec-file /var/crash/kernel.0
    core-file /var/crash/vmcore.0
您就可以像除錯其它程式般的用 kernel 原始碼來除錯 crash dump。

底下是用 script 記錄的 gdb 運作過程。 為了方便閱讀,比較長的幾行都有換行,且加上上行號以利索引。 除此之外,這是在 pcvt console driver 發展時為了 trace 錯誤的真實例子。

     1:Script started on Fri Dec 30 23:15:22 1994
     2:# cd /sys/compile/URIAH
     3:# gdb -k kernel /var/crash/vmcore.1 
     4:Reading symbol data from /usr/src/sys/compile/URIAH/kernel
    ...done.
     5:IdlePTD 1f3000
     6:panic: because you said to!
     7:current pcb at 1e3f70
     8:Reading in symbols for ../../i386/i386/machdep.c...done.
     9:(kgdb) where
    10:#0  boot (arghowto=256) (../../i386/i386/machdep.c line 767)
    11:#1  0xf0115159 in panic ()
    12:#2  0xf01955bd in diediedie () (../../i386/i386/machdep.c line 698)
    13:#3  0xf010185e in db_fncall ()
    14:#4  0xf0101586 in db_command (-266509132, -266509516, -267381073)
    15:#5  0xf0101711 in db_command_loop ()
    16:#6  0xf01040a0 in db_trap ()
    17:#7  0xf0192976 in kdb_trap (12, 0, -272630436, -266743723)
    18:#8  0xf019d2eb in trap_fatal (...)
    19:#9  0xf019ce60 in trap_pfault (...)
    20:#10 0xf019cb2f in trap (...)
    21:#11 0xf01932a1 in exception:calltrap ()
    22:#12 0xf0191503 in cnopen (...)
    23:#13 0xf0132c34 in spec_open ()
    24:#14 0xf012d014 in vn_open ()
    25:#15 0xf012a183 in open ()
    26:#16 0xf019d4eb in syscall (...)
    27:(kgdb) up 10
    28:Reading in symbols for ../../i386/i386/trap.c...done.
    29:#10 0xf019cb2f in trap (frame={tf_es = -260440048, tf_ds = 16, tf_\
    30:edi = 3072, tf_esi = -266445372, tf_ebp = -272630356, tf_isp = -27\
    31:2630396, tf_ebx = -266427884, tf_edx = 12, tf_ecx = -266427884, tf\
    32:_eax = 64772224, tf_trapno = 12, tf_err = -272695296, tf_eip = -26\
    33:6672343, tf_cs = -266469368, tf_eflags = 66066, tf_esp = 3072, tf_\
    34:ss = -266427884}) (../../i386/i386/trap.c line 283)
    35:283                             (void) trap_pfault(&frame, FALSE);
    36:(kgdb) frame frame->tf_ebp frame->tf_eip
    37:Reading in symbols for ../../i386/isa/pcvt/pcvt_drv.c...done.
    38:#0  0xf01ae729 in pcopen (dev=3072, flag=3, mode=8192, p=(struct p\
    39:roc *) 0xf07c0c00) (../../i386/isa/pcvt/pcvt_drv.c line 403)
    40:403             return ((*linesw[tp->t_line].l_open)(dev, tp));
    41:(kgdb) list
    42:398        
    43:399             tp->t_state |= TS_CARR_ON;
    44:400             tp->t_cflag |= CLOCAL;  /* cannot be a modem (:-) */
    45:401     
    46:402     #if PCVT_NETBSD || (PCVT_FREEBSD >= 200)
    47:403             return ((*linesw[tp->t_line].l_open)(dev, tp));
    48:404     #else
    49:405             return ((*linesw[tp->t_line].l_open)(dev, tp, flag));
    50:406     #endif /* PCVT_NETBSD || (PCVT_FREEBSD >= 200) */
    51:407     }
    52:(kgdb) print tp
    53:Reading in symbols for ../../i386/i386/cons.c...done.
    54:$1 = (struct tty *) 0x1bae
    55:(kgdb) print tp->t_line
    56:$2 = 1767990816
    57:(kgdb) up
    58:#1  0xf0191503 in cnopen (dev=0x00000000, flag=3, mode=8192, p=(st\
    59:ruct proc *) 0xf07c0c00) (../../i386/i386/cons.c line 126)
    60:       return ((*cdevsw[major(dev)].d_open)(dev, flag, mode, p));
    61:(kgdb) up
    62:#2  0xf0132c34 in spec_open ()
    63:(kgdb) up
    64:#3  0xf012d014 in vn_open ()
    65:(kgdb) up
    66:#4  0xf012a183 in open ()
    67:(kgdb) up
    68:#5  0xf019d4eb in syscall (frame={tf_es = 39, tf_ds = 39, tf_edi =\
    69: 2158592, tf_esi = 0, tf_ebp = -272638436, tf_isp = -272629788, tf\
    70:_ebx = 7086, tf_edx = 1, tf_ecx = 0, tf_eax = 5, tf_trapno = 582, \
    71:tf_err = 582, tf_eip = 75749, tf_cs = 31, tf_eflags = 582, tf_esp \
    72:= -272638456, tf_ss = 39}) (../../i386/i386/trap.c line 673)
    73:673             error = (*callp->sy_call)(p, args, rval);
    74:(kgdb) up
    75:Initial frame selected; you cannot go up.
    76:(kgdb) quit
    77:# exit
    78:exit
    79:
    80:Script done on Fri Dec 30 23:18:04 1994

底下是註解:

line 6:

這個 dump 是進入 DDB 時產生的 (請看下面), 所以 panic 的註解是 "because you said to!", 還有一些稍長的 stack trace;最初進入 DDB 的原因是個 page fault 的 trap。

line 20:

這是函數 trap() 在 stack trace 裡的位置 。

line 36:

強迫使用另一個新的 trap frame;現在應該不需要這樣做了, Stack frame 縱使在 trap 的狀況下也會指向正確的位置。 (我手邊現在沒有新的 core dump <g>,我的 kernel 已經很久沒有 panic 了。) 從第 403 行的原始碼看來, 有很大的可能是因為 ``tp'' 的 pointer access 亂掉了, 或者是陣列使用超過陣列的上下限。

line 52:

這個指標看來怪怪的,但剛好是個有效的 address。

line 56:

不管怎樣,它是指向一堆沒用的東西, 所以我們已經找到我們的問題了! (對那些不熟悉這些原始碼的人,tp->t_line 是指 console device 裡的 line discipline, 它應該是個小很多的整數。