一. 什么是Segmentation Fault
1.1. 一句話來說,段錯誤是指訪問的內存超出了系統給這個程序所設定的內存空間,例如訪問了不存在的內存地址、訪問了系統保護的內存地址、訪問了只讀的內存地址等等情況
二. Segmentation Fault產生示例
2.1. 訪問不存在的內存地址

#include<stdio.h> #include<stdlib.h> void main() { int *ptr = NULL; *ptr = 0; }
2.2. 訪問系統保護的內存地址

#include<stdio.h> #include<stdlib.h> void main() { int *ptr = (int *)0; *ptr = 100; }
2.3. 訪問只讀的內存地址

#include<stdio.h> #include<stdlib.h> #include<string.h> void main() { char *ptr = "test"; strcpy(ptr, "TEST"); }
2.4. 棧溢出

#include<stdio.h> #include<stdlib.h> void main() { main(); }
三. 獲取段錯誤相信
3.1. dmesg
dmesg可以在應用程序crash掉時,顯示內核中保存的相關信息。通過dmesg命令可以查看發生段錯誤的程序名稱、引起段錯誤發生的內存地址、指令指針地址、堆棧指針地址、錯誤代碼、錯誤原因等
下面以2.3. 代碼為例
3.2. -g
使用gcc編譯程序的源碼時,加上-g參數,這樣可以使得生成的二進制文件中加入可以用於gdb調試的有用信息。以程序2.3為例
gcc -g segment3.c -o segment3
3.3. nm
使用nm命令列出二進制文件中的符號表,包括符號地址、符號類型、符號名等,這樣可以幫助定位在哪里發生了段錯誤。以程序2.3為例:
3.4. ldd
使用ldd命令查看二進制程序的共享鏈接庫依賴,包括庫的名稱、起始地址,這樣可以確定段錯誤到底是發生在了自己的程序中還是依賴的共享庫中。以程序2.3為例
四. 段錯誤的調試方法
4.1. 使用printf打印調試信息
4.1.1. 此方法適合在debug階段使用
4.2. 使用gcc和gdb
4.2.1. 調試過程
a. 為了能夠使用gdb調試程序,在編譯階段加上-g參數,以程序2.3為例
gcc -g segment3.c -o segment3
b. 使用gdb工具,參考之前文章:https://www.cnblogs.com/linux-37ge/p/11826729.html
4.2.2. 使用場景
a. 僅當能確定程序一定會發生段錯誤的情況下使用。
b. 當程序的源碼可以獲得的情況下,使用-g參數編譯程序。
c. 一般用於測試階段,生產環境下gdb會有副作用:使程序運行減慢,運行不夠穩定,等等。
d. 即使在測試階段,如果程序過於復雜,gdb也不能處理
4.3. 使用core文件加gdb
4.3.1. 段錯誤會觸發SIGSEGV信號,通過man 7 signal,可以看到SIGSEGV默認的handler會打印段錯誤出錯信息,並產生core文件,由此我們可以借助於程序異常退出時生成的core文件中的調試信息,使用gdb工具來調試程序中的段錯誤
4.3.2. 調試過程
a. 在一些Linux版本下,默認是不產生core文件的,首先可以查看一下系統core文件的大小限制,可以看到默認設置情況下,本機Linux環境下發生段錯誤時不會自動生成core文件,下面設置下core文件的大小限制(單位為KB)
b. 運行程序2.3,發生段錯誤生成core文件
c. 加載core文件,使用gdb工具進行調試
4.3.3. 適用場景
a. 適合於在實際生成環境下調試程序的段錯誤(即在不用重新發生段錯誤的情況下重現段錯誤)。
4.4. 使用objdump
4.4.1. 調試步驟
a. 使用dmesg命令,找到最近發生的段錯誤輸出信息:
b. 使用objdump生成二進制的相關信息,重定向到文件中:其中,生成的segfault3Dump文件中包含了二進制文件的segfault3的匯編代碼
4.4.2. 適用場景
1. 不需要-g參數編譯,不需要借助於core文件,但需要有一定的匯編語言基礎
2. 如果使用了gcc編譯優化參數(-O1,-O2,-O3)的話,生成的匯編指令將會被優化,使得調試過程有些難度
4.5. 使用catchsegv
4.5.1. catchsegv命令專門用來撲獲段錯誤,它通過動態加載器(ld-linux.so)的預加載機制(PRELOAD)把一個事先寫好的庫(/lib/libSegFault.so)加載上,用於捕捉斷錯誤的出錯信息。
五. 一些注意事項
1、出現段錯誤時,首先應該想到段錯誤的定義,從它出發考慮引發錯誤的原因。
2、在使用指針時,定義了指針后記得初始化指針,在使用的時候記得判斷是否為NULL。
3、在使用數組時,注意數組是否被初始化,數組下標是否越界,數組元素是否存在等。
4、在訪問變量時,注意變量所占地址空間是否已經被程序釋放掉。
5、在處理變量時,注意變量的格式控制是否合理等
參考文獻:https://mp.weixin.qq.com/s/XLpjV0TQsyFQJxH5iNQN8A