http://www.cnblogs.com/panfeng412/archive/2011/11/06/2237857.html
最近在Linux環境下做C語言項目,由于是在一個原有項目基礎之上進行二次開發,而且項目工程龐大複雜,出現了不少問題,其中遇到最多、花費時間最長的問題就是著名的“段錯誤”(Segmentation Fault)。借此機會系統學習了一下,這裡對Linux環境下的段錯誤做個小結,友善以後同類問題的排查與解決。
一句話來說,段錯誤是指通路的記憶體超出了系統給這個程式所設定的記憶體空間,例如通路了不存在的記憶體位址、通路了系統保護的記憶體位址、通路了隻讀的記憶體位址等等情況。這裡貼一個對于“段錯誤”的準确定義(參考Answers.com):
<a></a>
等等其他原因。
程式發生段錯誤時,提示資訊很少,下面有幾種檢視段錯誤的發生資訊的途徑。
dmesg可以在應用程式crash掉時,顯示核心中儲存的相關資訊。如下所示,通過dmesg指令可以檢視發生段錯誤的程式名稱、引起段錯誤發生的記憶體位址、指令指針位址、堆棧指針位址、錯誤代碼、錯誤原因等。以程式2.3為例:
使用gcc編譯程式的源碼時,加上-g參數,這樣可以使得生成的二進制檔案中加入可以用于gdb調試的有用資訊。以程式2.3為例:
使用nm指令列出二進制檔案中的符号表,包括符号位址、符号類型、符号名等,這樣可以幫助定位在哪裡發生了段錯誤。以程式2.3為例:
使用ldd指令檢視二進制程式的共享連結庫依賴,包括庫的名稱、起始位址,這樣可以确定段錯誤到底是發生在了自己的程式中還是依賴的共享庫中。以程式2.3為例:
這個是看似最簡單但往往很多情況下十分有效的調試方式,也許可以說是程式員用的最多的調試方式。簡單來說,就是在程式的重要代碼附近加上像printf這類輸出資訊,這樣可以跟蹤并列印出段錯誤在代碼中可能出現的位置。
為了友善使用這種方法,可以使用條件編譯指令#ifdef DEBUG和#endif把printf函數包起來。這樣在程式編譯時,如果加上-DDEBUG參數就能檢視調試資訊;否則不加該參數就不會顯示調試資訊。
1、為了能夠使用gdb調試程式,在編譯階段加上-g參數,以程式2.3為例:
2、使用gdb指令調試程式:
3、進入gdb後,運作程式:
從輸出看出,程式2.3收到SIGSEGV信号,觸發段錯誤,并提示位址0x001a306a、調用memcpy報的錯,位于/lib/tls/i686/cmov/libc.so.6庫中。
4、完成調試後,輸入quit指令退出gdb:
1、僅當能确定程式一定會發生段錯誤的情況下使用。
2、當程式的源碼可以獲得的情況下,使用-g參數編譯程式。
3、一般用于測試階段,生産環境下gdb會有副作用:使程式運作減慢,運作不夠穩定,等等。
4、即使在測試階段,如果程式過于複雜,gdb也不能處理。
在4.2節中提到段錯誤會觸發SIGSEGV信号,通過man 7 signal,可以看到SIGSEGV預設的handler會列印段錯誤出錯資訊,并産生core檔案,由此我們可以借助于程式異常退出時生成的core檔案中的調試資訊,使用gdb工具來調試程式中的段錯誤。
1、在一些Linux版本下,預設是不産生core檔案的,首先可以檢視一下系統core檔案的大小限制:
2、可以看到預設設定情況下,本機Linux環境下發生段錯誤時不會自動生成core檔案,下面設定下core檔案的大小限制(機關為KB):
3、運作程式2.3,發生段錯誤生成core檔案:
4、加載core檔案,使用gdb工具進行調試:
從輸出看出,同4.2.1中一樣的段錯誤資訊。
5、完成調試後,輸入quit指令退出gdb:
1、适合于在實際生成環境下調試程式的段錯誤(即在不用重新發生段錯誤的情況下重制段錯誤)。
2、當程式很複雜,core檔案相當大時,該方法不可用。
1、使用dmesg指令,找到最近發生的段錯誤輸出資訊:
其中,對我們接下來的調試過程有用的是發生段錯誤的位址:80484e0和指令指針位址:0018506a。
2、使用objdump生成二進制的相關資訊,重定向到檔案中:
其中,生成的segfault3Dump檔案中包含了二進制檔案的segfault3的彙編代碼。
3、在segfault3Dump檔案中查找發生段錯誤的位址:
通過對以上彙編代碼分析,得知段錯誤發生main函數,對應的彙編指令是movl $0x80484e0,0x1c(%esp),接下來打開程式的源碼,找到彙編指令對應的源碼,也就定位到段錯誤了。
1、不需要-g參數編譯,不需要借助于core檔案,但需要有一定的彙編語言基礎。
2、如果使用了gcc編譯優化參數(-O1,-O2,-O3)的話,生成的彙編指令将會被優化,使得調試過程有些難度。
catchsegv指令專門用來撲獲段錯誤,它通過動态加載器(ld-linux.so)的預加載機制(PRELOAD)把一個事先寫好的庫(/lib/libSegFault.so)加載上,用于捕捉斷錯誤的出錯資訊。
1、出現段錯誤時,首先應該想到段錯誤的定義,從它出發考慮引發錯誤的原因。
2、在使用指針時,定義了指針後記得初始化指針,在使用的時候記得判斷是否為NULL。
3、在使用數組時,注意數組是否被初始化,數組下标是否越界,數組元素是否存在等。
4、在通路變量時,注意變量所占位址空間是否已經被程式釋放掉。
5、在處理變量時,注意變量的格式控制是否合理等。
1、http://www.docin.com/p-105923877.html
2、http://blog.chinaunix.net/space.php?uid=317451&do=blog&id=92412