今天程式出現了如下的一條錯誤:
testrouter[17281]: segfault at 13a4 ip 0000003c0ac0920b sp 00007f1ebdd64bc0 error 4 in libc-2.15.so[3c0ac00000+20000]
查看錯誤類別是段錯誤,並且給出了堆棧指標指向的位置。產生段錯誤的原因一般有以下幾點:
1、記憶體訪問越界
a) 由於使用錯誤的下標,導致數組訪問越界
b) 搜尋字串時,依靠字串結束符來判斷字串是否結束,但是字串沒有正常的使用結束符
c) 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字串操作函數,將目標字串讀/寫爆。應該使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函數防止讀寫越界。
2、多線程程式使用了線程不安全的函數。
應該使用下面這些可重新進入的函數:
asctime_r(3c) gethostbyname_r(3n) getservbyname_r(3n) ctermid_r(3s) gethostent_r(3n) getservbyport_r(3n) ctime_r(3c) getlogin_r(3c) getservent_r(3n) fgetgrent_r(3c) getnetbyaddr_r(3n) getspent_r(3c) fgetpwent_r(3c) getnetbyname_r(3n) getspnam_r(3c) fgetspent_r(3c) getnetent_r(3n) gmtime_r(3c) gamma_r(3m) getnetgrent_r(3n) lgamma_r(3m) getauclassent_r(3) getprotobyname_r(3n) localtime_r(3c) getauclassnam_r(3) etprotobynumber_r(3n) nis_sperror_r(3n) getauevent_r(3) getprotoent_r(3n) rand_r(3c) getauevnam_r(3) getpwent_r(3c) readdir_r(3c) getauevnum_r(3) getpwnam_r(3c) strtok_r(3c) getgrent_r(3c) getpwuid_r(3c) tmpnam_r(3s) getgrgid_r(3c) getrpcbyname_r(3n) ttyname_r(3c) getgrnam_r(3c) getrpcbynumber_r(3n) gethostbyaddr_r(3n) getrpcent_r(3n)
3、多線程讀寫的資料未加鎖保護。
對於會被多個線程同時訪問的全域資料,應該注意加鎖保護,否則很容易造成core dump
4、非法指標
a) 使用null 指標
b) 隨意使用指標轉換。一個指向一段記憶體的指標,除非確定這段記憶體原先就分配為某種結構或類型,或者這種結構或類型的數組,否則不要將它轉換為這種結構或類型的指標,而應該將這段記憶體拷貝到一個這種結構或類型中,再訪問這個結構或類型。這是因為如果這段記憶體的開始地址不是按照這種結構或類型對齊的,那麼訪問它時就很容易因為bus error而core dump.
5、堆疊溢位
不要使用大的局部變數(因為局部變數都分配在棧上),這樣容易造成堆疊溢位,破壞系統的棧和堆結構,導致出現莫名其妙的錯誤。
Linux系統預設的情況下,是不產生段錯誤檔案,可以通過下面的命令來查看系統預設段錯誤檔案的大小:
ulimit -c
一般顯示的結果是0。可以通過以下兩條命令設定段錯誤檔案大小為2048位元組和大小不受限制:
ulimit -c 2048ulimit -c unlimited
注意以上在終端中輸入只是暫時的,若要永久生效需要把以上命令之一加入到/etc/profile或者/檔案中。
core檔案的作用如下:
當我們的程式崩潰時,核心有可能把該程式當前記憶體映射到core檔案裡,方便程式員找到程式出現問題的地方。最常出現的,幾乎所有C程式員都出現過的錯誤就是“段錯誤”。當一個程式崩潰時,在進程當前工作目錄的core檔案中複製了該進程的儲存鏡像。core檔案僅僅是一個記憶體映象(同時加上調試資訊),主要是用來調試的。Linux/Unix收到下面的訊號時候會產生core檔案:
SIGABRT 異常終止(abort) 終止w/core SIGBUS 硬體故障 終止w/core SIGEMT 硬體故障 終止w/core SIGFPE 算術異常 終止w/core SIGILL 非法硬體指令 終止w/core SIGIOT 硬體故障 終止w/core SIGQUIT 終端退出符 終止w/core SIGSEGV 無效儲存訪問 終止w/core SIGSYS 無效系統調用 終止w/core SIGTRAP 硬體故障 終止w/core SIGXCPU 超過CPU限制(setrlimit) 終止w/core SIGXFSZ 超過檔案長度限制(setrlimit) 終止w/core
下面對這幾個訊號做一個詳細的說明:
• SIGABRT調用abort函數時產生此訊號。進程異常終止。
• SIGBUS指示一個實現定義的硬體故障。
• SIGEMT指示一個實現定義的硬體故障。
EMT這一名字來自PDP-11的emulator trap 指令。
• SIGFPE此訊號表示一個算術運算異常,例如除以0,浮點溢出等。
• SIGILL此訊號指示進程已執行一條非法硬體指令。
4.3BSD由abort函數產生此訊號。SIGABRT現在被用於此。
• SIGIOT這指示一個實現定義的硬體故障。
IOT這個名字來自於PDP-11對於輸入/輸出TRAP(input/output TRAP)指令的縮寫。系統V的早期版本,由abort函數產生此訊號。SIGABRT現在被用於此。
• SIGQUIT當使用者在終端上按退出鍵(一般採用Ctrl-/)時,產生此訊號,並送至前台進
程組中的所有進程。此訊號不僅終止前台進程組(如SIGINT所做的那樣),同時產生一個core檔案。
• SIGSEGV指示進程進行了一次無效的儲存訪問。
名字SEGV表示“段違例(segmentation violation)”。
• SIGSYS指示一個無效的系統調用。由於某種未知原因,進程執行了一條系統調用指令,
但其指示系統調用類型的參數卻是無效的。
• SIGTRAP指示一個實現定義的硬體故障。
此訊號名來自於PDP-11的TRAP指令。
• SIGXCPUSVR4和4.3+BSD支援資源限制的概念。如果進程超過了其軟C P U時間限制,則產生此訊號。
• SIGXFSZ如果進程超過了其軟檔案長度限制,則SVR4和4.3+BSD產生此訊號。
下面舉例說明一個產生段錯誤的程式:
#include<stdio.h>int main(){ char *ptr="test"; strcpy(ptr,"TEST"); return 0;}
gcc –g test.c -o core_test
編譯完成後,運行程式./core_test,程式會中斷,產生“段錯誤”這個提示。會看到目前的目錄下會出現一個core.15649的檔案,下面我們利用這個檔案進行錯誤的尋找,利用調試工具gdb實現:
gdb ./core_test
顯示出如下的資訊:
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6)Copyright (C) 2010 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law. Type "show copying"and "show warranty" for details.This GDB was configured as "i686-redhat-linux-gnu".For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>...Reading symbols from /home/cyl/openwrt/switch/switch/test_core...done.(gdb)
調試的時候,我們在gdb下執行run
(gdb) runStarting program: /home/cyl/openwrt/switch/switch/test_core Program received signal SIGSEGV, Segmentation fault.0x00762f16 in __memcpy_ssse3 () from /lib/libc.so.6Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.i686
從此資訊中可以看出, 收到SIGSEGV訊號,觸發段錯誤,並提示地址0x00762f16、調用 _memcpy_ssse3()報的錯,位於/lib/libc.so.6庫中。
完成調試後,鍵入quit退出偵錯模式。
利用objdump反組譯碼尋找段錯誤碼。
首先將反組譯碼的結果重新導向到一個檔案,執行下面的命令:
objdump -d ./test_core > segfault3Dump
然後我們查看segfault3Dump,查看產生段錯誤資訊的位置。
由dmesg可以查看到產生段錯誤的地址和指令指標的地址還有棧的地址:
test_core[15649]: segfault at 80484c4 ip 00762f16 sp bfc9d7b8 error 7 in libc-2.12.so[62e000+191000]
然後正則匹配發生段錯誤的地方:
grep -n -A 10 -B 10 "80484c4" ./segfault3Dump
結果如下:
[direwolf@direwolf switch]$ grep -n -A 10 -B 10 "80484c4" ./segfault3Dump 121- 80483b9:c7 04 24 80 95 04 08 movl $0x8049580,(%esp)122- 80483c0:ff d0 call *%eax123- 80483c2:c9 leave 124- 80483c3:c3 ret 125-126-080483c4 <main>:127- 80483c4:55 push %ebp128- 80483c5:89 e5 mov %esp,%ebp129- 80483c7:83 e4 f0 and $0xfffffff0,%esp130- 80483ca:83 ec 20 sub $0x20,%esp131: 80483cd:c7 44 24 1c c4 84 04 movl $0x80484c4,0x1c(%esp)132- 80483d4:08 133- 80483d5:c7 44 24 08 05 00 00 movl $0x5,0x8(%esp)134- 80483dc:00 135- 80483dd:c7 44 24 04 c9 84 04 movl $0x80484c9,0x4(%esp)136- 80483e4:08 137- 80483e5:8b 44 24 1c mov 0x1c(%esp),%eax138- 80483e9:89 04 24 mov %eax,(%esp)139- 80483ec:e8 03 ff ff ff call 80482f4 <memcpy@plt>140- 80483f1:b8 00 00 00 00 mov $0x0,%eax141- 80483f6:c9
可以看到段錯誤發生在main函數內,查看彙編代碼可以知道, 對應的彙編指令是movl $0x80484c4,0x1c (%esp)。接下來對照c語言的原始碼,我們使用下面的命令進行反組譯碼,先產生可調試檔案,再反組譯碼。 gcc -g test_core.c
objdump -S a.out
可以得到下面的資訊,我們這裡僅僅找到main函數的入口:
080483c4 <main>: ************************************************************************/#include<stdio.h>int main(){ 80483c4:55 push %ebp 80483c5:89 e5 mov %esp,%ebp 80483c7:83 e4 f0 and $0xfffffff0,%esp 80483ca:83 ec 20 sub $0x20,%esp char *ptr="test"; 80483cd:c7 44 24 1c c4 84 04 movl $0x80484c4,0x1c(%esp) 80483d4:08 strcpy(ptr,"TEST"); 80483d5:c7 44 24 08 05 00 00 movl $0x5,0x8(%esp) 80483dc:00 80483dd:c7 44 24 04 c9 84 04 movl $0x80484c9,0x4(%esp) 80483e4:08 80483e5:8b 44 24 1c mov 0x1c(%esp),%eax 80483e9:89 04 24 mov %eax,(%esp) 80483ec:e8 03 ff ff ff call 80482f4 <memcpy@plt> return 0; 80483f1:b8 00 00 00 00 mov $0x0,%eax}找到 movl $0x80484c4,0x1c (%esp)這條彙編指令,可以知道,程式是在調用strcpy的時候發生錯誤。
以上兩種方法就是尋找段錯誤的常用的方法,出現段錯誤的時候,需要考慮一下因素:
1、出現段錯誤時,首先應該想到段錯誤的定義,從它出發考慮引發錯誤的原因。
2、在使用指標時,定義了指標後記得初始化指標,在使用的時候記得判斷是否為NULL。
3、在使用數組時,注意數組是否被初始化,數組下標是否越界,數組元素是否存在等。
4、在訪問變數時,注意變數所佔地址空間是否已經被程式釋放掉。
5、在處理變數時,注意變數的格式控制是否合理等。