1. 程式人生 > >Segmentation fault段錯誤除錯總結

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,將大大縮小查詢範圍。

(未完待續中....)