最近遇到一個 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 收集一些發生段錯誤時的信息。這種方法時有用的,但這常常並不能直接解決問題。我們能夠看到段錯誤時的現場,這只是個結果,至於爲何出現段錯誤還需要其它的方法來進一步定位。