段錯誤發生時讓程序停住

最近遇到一個 bug,有一定的偶然性會出現段錯誤。第一步需要確定的是段錯誤出現在哪裏。可由於這個 bug 的偶然性,常規的方法無法確定問題。

我想如果在產生段錯誤的時候程序可以停下來,那麼就可以用 gdb attach 上去調試了。

怎麼讓程序在產生段錯誤時停住呢?

通過研究發現,在程序出現段錯誤時,系統會發送 SIGSEGV 信號給程序。這個信號一般並不會註冊信號處理函數,默認方式就是 kill 進程。

在這裏我們可以利用 SIGSEGV 信號來讓程序在產生段錯誤時停住。具體的實現方法是調用 signal 函數註冊 SIGSEGV 信號的處理函數,將這個處理函數寫成一個死循環。這樣當程序出現段錯誤的時候,內核向程序發送 SIGSEGV 信號,預先註冊的信號處理函數被調用,程序停住。

我使用下面的代碼驗證上述方法是否可行。

#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

void segv_handler(int signo)
{
    printf("in segv_handler\n");
    
    while (signo) {
        sleep(1);
    }
}

int main(int argc, char *argv[])
{
    char *pointer = NULL;

    signal(SIGSEGV, segv_handler);
    
    *pointer = 'c';
    
    return 0;
}

上述程序首先註冊了一個 SIGSEGV 信號的信號處理函數 segv_handler,這個函數裏面實現爲死循環。註冊之後,通過向 0 地址寫值來觸發段錯誤。

使用 gcc -g 編譯帶調試信息的版本,目標文件名爲 segmentation_fault_test。

在一個終端中執行測試程序,輸出的 log 信息如下:

longyu@debian:/tmp$ ./segmentation_fault_test 
in segv_handler

從上面的輸出中可以看到,已經在段錯誤信號 handler 中停住了。這時按 Control+Z 停止此程序。

這樣終端就能夠繼續使用了,我們現在使用 gdb attach segmentation_fault_test 程序,查看棧幀就能看到死在哪裏了。

longyu@debian:/tmp$ ps aux |grep 'seg'
longyu     1218  0.0  0.0   2276   684 pts/0    T    19:07   0:00 ./segmentation_fault_test
longyu     1228  0.0  0.0  15548   884 pts/0    S+   19:10   0:00 grep seg
longyu@debian:/tmp$ gdb -p 1218
.......

For help, type "help".
Type "apropos word" to search for commands related to "word".
Attaching to process 1218
Reading symbols from /tmp/segmentation_fault_test...done.
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug/.build-id/18/b9a9a8c523e5cfe5b5d946d605d09242f09798.debug...done.
done.
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug/.build-id/f2/5dfd7b95be4ba386fd71080accae8c0732b711.debug...done.
done.

Program received signal SIGTSTP, Stopped (user).
0x00007f153e59f6f4 in __GI___nanosleep (requested_time=requested_time@entry=0x7fff71925520, remaining=remaining@entry=0x7fff71925520)
    at ../sysdeps/unix/sysv/linux/nanosleep.c:28
28	../sysdeps/unix/sysv/linux/nanosleep.c: No such file or directory.
(gdb) bt
#0  0x00007f153e59f6f4 in __GI___nanosleep (requested_time=requested_time@entry=0x7fff71925520, remaining=remaining@entry=0x7fff71925520)
    at ../sysdeps/unix/sysv/linux/nanosleep.c:28
#1  0x00007f153e59f62a in __sleep (seconds=0) at ../sysdeps/posix/sleep.c:55
#2  0x000055f0ec640178 in segv_handler (signo=11) at segmentation_fault_test.c:11
#3  <signal handler called>
#4  0x000055f0ec6401ad in main (argc=1, argv=0x7fff71925d28) at segmentation_fault_test.c:21

可以看到 segmentation_fault_test.c 的 21 行觸發了段錯誤。

查看代碼,可以看到第 21 行就是出問題的地方。

21	    *pointer = 'c';

使用這種方法,我們可以讓程序在出現段錯誤的時候停下來,這樣我們就能夠使用 gdb 收集一些發生段錯誤時的信息。這種方法時有用的,但這常常並不能直接解決問題。我們能夠看到段錯誤時的現場,這只是個結果,至於爲何出現段錯誤還需要其它的方法來進一步定位。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章