
相信大家在嵌入式C開發中,或多或少都會遇到段錯誤(segmentation fault )。昨天分享了一個總線錯誤的例子:嵌入式軟件中,關于總線錯誤,我幫你們踩了這些坑!相比總線錯誤,段錯誤是一種更為常見的錯誤。
那么,段錯誤是怎么產生的呢?簡單來說,段錯誤是因為訪問不可訪問的內存產生的。
下面是一些典型的段錯誤產生的原因:
(資料圖片僅供參考)
訪問不存在的內存地址
訪問只讀的內存地址
棧溢出
內存越界
……
段錯誤實例
1、實例1:訪問不存在的內存地址
#includeintmain(intargc,char**argv){printf("==================segmentation fault test==================");int*p=NULL;*p=1234;return0;}
2、實例2:訪問只讀的內存地址
#includeintmain(intargc,char**argv){printf("==================segmentation fault test1==================");char*str="hello";str[0]="H";return0;}
3、實例3:棧溢出
#includestaticvoidtest(void){charbuf[1024*1024]={0};staticinti=0;i++;printf("i=%d",i);test();}intmain(intargc,char**argv){printf("==================segmentation fault test2==================");test();return0;}
4、實例4:內存越界
#includeintmain(intargc,char**argv){printf("==================segmentation fault test3==================");staticchararr[5]={0,1,2,3,4};printf("arr[10000]=%d",arr[10000]);return0;}
段錯誤調試方法
從上面的幾個例子中,我們應該對段錯誤有了一定的認識,但實際項目中,實際中,段錯誤可能沒有上面的例子那么明顯看出。如果之前沒有這方面的經驗,可能一時半會也定位不到問題。
接下來,分享一下段錯誤的3種調試方法,供大家參考。
我們依舊使用例子來說明,例子:
#includestaticvoidfunc0(void){printf("This is func0");int*p=NULL;*p=1234;}staticvoidfunc1(void){printf("This is func1");func0();}intmain(intargc,char**argv){printf("==================segmentation fault test4==================");func1();return0;}
1、gdb一步步運行
使用gdb調試,打一些斷點、按流程運行下去,運行到段錯誤的地方會直接提示報錯。
或者使用命令行直接gdb調試:
這里我們是在x86上運行,如果是定位arm嵌入式Linux程序,我們怎么做的?
同樣也是可以使用gdb的,可以參考我們之前分享的文章:VSCode+gdb+gdbserver遠程調試ARM程序
2、通過core文件
Linux下,一個程序崩潰時,它一般會在指定目錄下生成一個core文件。core文件僅僅是一個內存映象(同時加上調試信息),主要是用來調試的。
core文件可打開與關閉。相關命令:
ulimit-c#查看core文件是否打開ulimit-c0#禁止產生core文件ulimit-c unlimited#設置core文件大小為不限制大小ulimit-c1024#限制產生的core文件的大小不能超過1024KB
0代表關閉。下面我們打開它:
運行程序時,程序崩潰時,在程序目錄下會生成core文件,比如:
調試core文件:
gdbtestcore
3、利用backtrace進行分析
#include#include #include #include voidfunc0(void){printf("This is func0");int*p=NULL;*p=1234;}voidfunc1(void){printf("This is func1");func0();}voidfunc2(void){printf("This is func2");func1();}voiddump(intsigno){void*array[100];size_tsize;char**strings;size=backtrace(array,100);strings=backtrace_symbols(array,size);printf("Obtained%zd stacks.",size);for(inti=0;i
當程序發生段錯誤時,內核會向程序發送SIGSEGV信號。dump為SIGSEGV信號處理函數,其實現用到了execinfo.h里的兩個函數:
intbacktrace(void**buffer,intsize);char**backtrace_symbols(void*const*buffer,intsize);
backtrace函數用于獲取當前線程的調用堆棧,獲取的信息將會被存放在buffer中,它是一個指針列表。參數 size 用來指定buffer中可以保存多少個void* 元素。函數返回值是實際獲取的指針個數,最大不超過size大小 在buffer中的指針實際是從堆棧中獲取的返回地址,每一個堆棧框架有一個返回地址。
backtrace_symbols將從backtrace函數獲取的信息轉化為一個字符串數組。參數buffer應該是從backtrace函數獲取的指針數組,size是該數組中的元素個數(backtrace的返回值)。函數返回值是一個指向字符串數組的指針,它的大小同buffer相同。
每個字符串包含了一個相對于buffer中對應元素的可打印信息。它包括函數名,函數的偏移地址,和實際的返回地址。
注意:該函數的返回值是通過malloc函數申請的空間,因此調用者必須使用free函數來釋放指針。如果不能為字符串獲取足夠的空間函數的返回值將會為NULL。
以上就是本次介紹的3種定位段錯誤問題的方法,可以定位不同程度的問題。
審核編輯:湯梓紅
標簽: