Segmentation fault段錯誤除錯總結
Segmetation fault也叫做段錯誤,引發的原因有好多,這裡我們只說一下段錯誤發生時的除錯方法。
方法1:加列印printf。這是最基本的往往也很有效的方法,在哪裡Core掉就會在哪裡停止列印--一目瞭然。同時這種方法也存在一個致命缺陷:如果恰巧Core掉的地方沒加列印而程式程式碼又非常龐大又可能是多執行緒的,那查詢問題等同於大海撈針。
方法2:gdb除錯。加gdb除錯往往能在Core dump時抓到,甚至能抓到哪一個檔案哪個類哪個函式哪一行,甚是精確。要確保GDB能抓到可用資訊要做一些準備:
(1) 加-g 引數,這樣才會有除錯資訊。 我想是個程式設計師就應該知道吧。
(2) 在Makefile 中加上 -fstack-protector 和 -fstack-protector-all 資訊,確保函式呼叫棧不丟失,當然只能是一定程度的不丟失,要完成保留住是不太可能的,但起碼可以得到棧頂函式。
有了上面兩點對大多數的Segmentation fault都能抓住,但是函式呼叫棧徹底亂掉或者在動態庫so中Core而這個庫編譯時沒有加-g引數,這些情況就gdb就無能為力了。
方法3:手動獲取函式呼叫棧。這種方法其實是借住兩個系統函式backtrace和backtrace_symbol來獲取函式呼叫棧的,把這兩個函式放在訊號處理函式中:當收到 SIGSEG時在訊號處理函式中呼叫這兩個函式列印函式呼叫棧,在沒用GDB除錯的時候這種方法可以代替gdb的一部分功能,這聽起來是不是非常酷啊,來看一看實現吧:
#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <execinfo.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <unistd.h> static void SignalHandle(int sig) { void *array[20]; size_t size; char **strings; int i; size = backtrace(array, 10); strings = backtrace_symbols(array, size); printf("SIGNAL ocurre %d, stack tarce:\n", sig); printf("obtained %d stack frames.\n", size); for (i = 0; i < size; i++) printf("%s\n", strings); free(strings); printf("stack trace over!\n"); exit(0); } int main(int argc, char **argv) { signal(SIGSEGV, SignalHandle); //...程式主體 }
當然這種方法在沒有GDB時候會大顯身手,經過實驗就是有gdb的時候這種方法有時比gdb抓到呼叫棧要多一層;當然這種方法和用gdb除錯一樣要加-g和棧保護引數-fstack-protector 和 -fstack-protector-all。其缺點就是抓到的呼叫棧無效,這是什麼意思呢?有時發生core dump,能定位到甚至哪一行,但是那一行根本沒有明顯的錯誤;或者追到沒有除錯資訊的動態庫裡如glibc。當然這些情況大多數除錯方法都無能為力,只能依靠程式設計師的經驗了。
方法4:經驗之談(一)。如果我們的程式是多執行緒的,發生core dump用以上方法均無效,除了仔細排查程式碼外,還有這麼一方法讓我們縮小範圍。
在每個執行緒中獲取執行緒id(在我的博文《多執行緒除錯的一點思路》中介紹),在出錯時在gdb下檢視當前執行緒info thread。這樣知道是哪一個執行緒引起的core dump,將大大縮小查詢範圍。
(未完待續中....)