內核態有三種出錯狀況,分別是bug, oops和panic。 函數
bug屬於輕微錯誤,好比在spin_lock期間調用了sleep,致使潛在的死鎖問題,等等。 oop
oops表明某一用戶進程出現錯誤,須要殺死用戶進程。這時若是用戶進程佔用了某些信號鎖,因此這些信號鎖將永遠不會獲得釋放,這會致使系統潛在的不穩定性。 ui
panic是嚴重錯誤,表明整個系統崩潰。 spa
OOPS 指針
先介紹下oops狀況的處理。Linux oops時,會進入traps.c中的die函數。 調試
int die(const char *str, struct pt_regs *regs, long err) orm
。。。 進程
show_regs(regs); rpc
void show_regs(struct pt_regs * regs)函數中,會調用show_stack函數,這個函數會打印系統的內核態堆棧。 回調函數
具體原理爲:
從寄存器裏找到當前棧,在棧指針裏會有上一級調用函數的棧指針,根據這個指針回溯到上一級的棧,依次類推。
在powerpc的EABI標準中,當前棧的棧底(注意是棧底,不是棧頂,即Frame Header的地址)指針保存在寄存器GPR1中。在GPR1指向的棧空間,第一個DWORD爲上一級調用函數的Frame Header指針(Back Chain Word),第二個DWORD是當前函數在上一級函數中的返回地址(LR Save Word)。經過此種方式一級級向上回溯,完成整個call dump。除了這種方法,內建函數__builtin_frame_address函數理論上也應該能用,雖然在內核中沒有見到。(2.6.29的ftrace模塊用到了__builtin_return_address函數)。
show_regs函數在call trace的時候,只是用printk打印了一下棧中的信息。若是當前系統沒有終端,那就須要修改內核,把這些棧信息根據需求保存到其它地方。
例如,能夠在系統的flash中開出一塊空間專門用於打印信息的保存。而後,寫一個內核模塊,再在die函數中加一個回調函數。這樣,每當回調函數被調用,就通知自定義的內核模塊,在模塊中能夠把調用棧還有其它感興趣的信息保存到那塊專用flash空間中去。這裏有一點須要注意的是,oops時內核可能不穩定,因此爲了確保信息能被正確寫入flash,在寫flash的函數中儘可能不要用中斷,而用輪循的方式。另外信號量、sleep等可能致使阻塞的函數也不要使用。
此外,因爲oops時系統還在運行,因此能夠發一個消息(信號,netlink等)到用戶空間,通知用戶空間作一些信息收集工做。
Panic
Panic時,Linux處於更最嚴重的錯誤狀態,標誌着整個系統不可用,即中斷、進程調度等都已經中止,但棧還沒被破壞。因此,oops中的棧回溯理論上仍是能用。printk函數中由於沒有阻塞,也仍是可以使用。
用戶程序能夠在如下情形call trace,以方便調試:
l 程序崩潰時,都會收到一個信號。Linux系統接收到某些信號時會自動打印call trace。
l 在用戶程序中添加檢查點,相似於assert機制,若是檢查點的條件不知足,就執行call trace。
用戶態的call trace與內核態相同,一樣知足EABI標準,原理以下:
在GNU標準中,有一個內建函數__builtin_frame_address。這個函數能夠返回當前執行上下文的棧底(Frame Header)指針(同時也是指向Back Chain Word的指針),經過這個指針獲得當前調用棧。而這個調用棧中,會有上一級調用函數的棧底指針,經過這個指針再回溯到上一級的調用棧。以此類推完成整個call dump過程。
獲得函數的地址後,能夠經過符號表獲得函數名字。若是是動態庫中定義的函數,還能夠經過擴展函數dladdr獲得這個函數的動態庫信息。