23.4. 用 DDB 對 kernel 做線上除錯

gdb 這樣一個能提供高層次使用介面的離線除錯器, 還是有些事情它不能做到。最重要的幾點就是設 breakpoint 跟單步執行您的 kernel 碼。

如果您需要做到這樣低層的 kernel 除錯,有一個叫 DDB 的線上除錯器可以用。它允許您設 breakpoint,單步執行您的 kernel 函數, 檢驗跟改變 kernel 裡的變數值等等。不過,它不能讓您參考 kernel 的原始碼 ,且只能處理 global 跟 static 的 symbols,不像 gdb 有全部的除錯資料。

要設定您的 kernel 內含 DDB,在您的設定檔內加上這行

    options DDB
,然後重建您的 kernel。(參考 Kernel Configuration 裡有關設定 FreeBSD kernel 的細節。)

Note: 如果您只有舊版的 boot block,可能只有部份的 debug symbol 會被載入。更新您的 boot block,最近的幾版都會自動載入 DDB 的 symbol。)

一但您那份含有 DDB 的 kernel 開始執行了, 您有數種進入 DDB 的方法。第一個,也是最簡單的一個方法就是在 boot prompt 時加上 -d 這個 boot flag。 這時 kernel 在進行任何 device probing 前就會變成 debug mode 並進入 DDB 。所以您甚至可以對 device probe/attach 函數進行除錯。

第二種場合就是用鍵盤上的 hot-key,通常是 Ctrl-Alt-ESC。 如果是用 syscons 的話,可以重設 hot-key,且有一些 map 已經四處流傳, 所以自己注意一下。有一個使用 serial console 時能用的選項可以讓您透過 conole line 上的 serial line BREAK 來進入 DDB (kernel 設定檔裡的 options BREAK_TO_DEBUGGER)。這不是原本的設定, 因為現在有很多 serial adapters 不必要的在像您拉扯線材時產生 BREAK。

第三種方法就是當您有做設定時,任何的 panic 狀況會切進 DDB 裡。 就因為這個原因,設定一台沒人看管的機器用 DDB 是不智的。

粗略的來說,DDB 的命令跟 gdb 部份命令有點類似。 首先您最需要的就是設定 breakpoint:

    b function-name
    b address

沒有特別設定的話,數字都是用 16 進位的。為了要跟 symbol name 有所區分,用字元 a-f 當開頭的 16 進位數字要在前面加上 0x (對其它的數字, 您可以決定要用或不用的)。簡單的運算式是看得懂的,例如: function-name + 0x103

要繼續執行已被中斷的 kernel,只要打:

    c

想要看 stack trace,用:

    trace

Note: 要注意的是當 DDB 是因為 hot-key 而進入的話, kernel 正在處理 interrupt,所以 stack trace 對您可能沒什麼用處。

如果您要移除 breakpoint,用

    del
    del address-expression

第一種型式是當您在 breakpoint 遇到後馬上用, 會移除現在這個 breakpoint。第二種型式可以移除任何 breakpoint, 但您必須給定確切的 address,就像是您用下列指令所看到的:

    show b

要單步執行您的 kernel,試著用:

    s

這會一步步執行函數,但您可以讓 DDB 自己 trace 直到遇上機器的 return 碼,這時用:

    n

Note: 這跟 gdbnext 不一樣, 它比較像 gdbfinish

要看現在記憶體裡的資料,用 (例如):

    x/wx 0xf0133fe0,40
    x/hd db_symtab_space
    x/bc termbuf,10
    x/s stringbuf
來做 word/halfword/byte 的存取,跟 hexadecimal/decimal/character/ 格式的顯示,在逗號後的數字是您要顯示幾個 object。要顯示下面 0x10 個, 只要用:

    x ,10

同樣地,用

    x/ia foofunc,10
可以反組譯 foofunc 的前 0x10 個指令, 然後按照它們相對於函數 foofunc 開頭的依序顯示。

要更改記憶體的內容,用 write 命令:

    w/b termbuf 0xa 0xb 0
    w/w 0xf0010030 0 0

這個命令的 modifier (b/h/w) 是指定您要寫入的資料大小,第一個緊接的表示式是所要寫入的 address, 剩餘的就被當作是要依序寫入後續記憶體的資料。

如果您需要知道暫存器的內容,用:

    show reg

或者是,您可以顯示單一個暫存器的內容用類似

    p $eax
跟更改它用:

    set $eax new-value

如果您該從 DDB 中去呼叫某些 kernel 函數,只要用:

    call func(arg1, arg2, ...)

傳回值會被印出來。

要像 ps(1) 般的得到所有正在跑的 process 訊息,用:

    ps

假設現在您知道為什麼您的 kernel 會有問題,然後想要 reboot 機器, 記得,您原來的問題可能嚴重到會讓您 kernel 的其它部份不如預期般運作, 採取下述的行動:

    panic

再關機及重開您的系統會讓您的 kernel dump core, 所以您可以再來用 gdb 從高層次分析。 通常在這個命令後必須要跟著用 continue。 現在有 panic 命令會幫您做這些事。

    call boot(0)

也算是個完整關機的好方法,它會 sync() 所有的磁碟機,最後再 reboot。只要跟 kernel 有關係的磁碟機跟 file system 都沒有受損壞,這是一個算完整的關機。

    call cpu_reset()

是最後的方法,就像您按 reset 鈕一樣。

如果您要一段簡短的指令說明,直接打:

    help

無論如何,強列建議您在做 debug 時印一份 ddb(4) 的 manual page 。記得當您在做 kernel 單步運作時,您很難能看到 on-line manual。