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: 這跟 gdb 的 next 不一樣, 它比較像 gdb 的 finish。
要看現在記憶體裡的資料,用 (例如):
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。