第壹,妳需要源代碼編譯出來的包含調試符號的目標代碼。
第二,backtrace中的內容要盡可能完整。
壹般來說,DDB backtrace中會包含函數的名字,然而,它並不能提供更多的信息,例如變量名稱,對應的源代碼行等等。DDB被設計為盡可能簡單,以避免在調試內核時引發崩潰,從而導致不得不丟開內核,開始調試調試器的尷尬局面。
另壹個不能忽略的影響,便是C/C++編譯器的優化功能可能引發的代碼混淆。許多時候,編譯器給出的代碼可能並不那麽直截了當,有幾種比較常見的優化:
- 循環展開。有時循環會被自動展開,以減少處理器的分支預測帶來的消耗。
- 內聯展開。內聯函數可能會在調用處展開。盡管函數調用很“便宜”,但有時編譯器會通過將函數內聯展開以期避免不必要得快取緩存刷洗效應。
- 寄存器的使用。編譯器會盡可能利用寄存器,以避免(昂貴的)內存操作。
這些都會使匯編代碼的樣式與C元程序產生非常大的差異。
因此,在無法動態調試的時候,特別是當只能拿到DDB Backtrace的時候,需要壹些方法來迅速完成問題的定位。下面我來介紹壹些我本人的實際經驗,希望能夠拋磚引玉:
1. 通過objdump拿到匯編代碼。
方法是objdump -D,這個不必多說。
2. 找到代碼的位置(DDB的backtrace會告訴妳 函數名+偏移 的地址)。
當使用objdump處理.o文件時,我們可以找到函數的入口地址,並計算出加上偏移量之後對應的地址。
3. 尋找匯編代碼中前後的特征值。
如果妳對編譯器和代碼都足夠熟悉,以至於可以徒手把C程序編譯成匯編代碼的話,當然會不需要這樣做。沒有這種功力的話,我們可以用壹些小技巧,例如尋找特征值。
總體而言,內核中所發生的無頭命案多以內存訪問越界(Kernel Trap 12)告終。這類問題最常見的引發條件,則是觸動無效指針(Invalid pointer defrencing)。例如下列代碼:%%%if (vp->vp_state & F_DONE) {
/* do something */}如果vp包含壹無效值,則會引致崩潰。
現在我們有了兩項特征:第壹,我們通常可以在崩潰的附近找到->;第二,這類問題本身多出現於判斷,或周圍存在判斷。上面的例子中,&會最終被翻譯為壹test[bl]指令。
發現崩潰果然是這樣壹個點,於是,查找代碼中包含->和&的if語句(從objdump發現它下面有壹je,因此壹定是判斷性的語句。
找到上述語句段後,應繼續觀察前後的語句。
確定位置之後,就比較容易人為制造或重現問題了。
接下來的問題就是KASSERT和printf。當然,想要更迅速準確地找到問題,還需要非常多的實踐練習才可以。