Linux平臺(tái)Segmentation fault(段錯(cuò)誤)調(diào)試過程
1. 段錯(cuò)誤是什么
一句話來說,段錯(cuò)誤是指訪問的內(nèi)存超出了系統(tǒng)給這個(gè)程序所設(shè)定的內(nèi)存空間,
例如訪問了不存在的內(nèi)存地址、訪問了系統(tǒng)保護(hù)的內(nèi)存地址、訪問了只讀的內(nèi)存地址等等情況。
2. 段錯(cuò)誤的原因
段錯(cuò)誤,英文segmentation fault
段錯(cuò)誤的定義
segementation fault (often shortened to segfault) or access violation
often raised by hardware with memory protection, notifying an operation system(OS) the software has attempted to access a restricted area of memory.
段錯(cuò)誤常見于提供低級(jí)別的內(nèi)存訪問機(jī)制(如C語言)的語言程序中,通常由非法訪問導(dǎo)致,即錯(cuò)誤地使用指針訪問虛擬內(nèi)存。
原因列舉
- 對(duì)空指針賦值——由內(nèi)存管理硬件所導(dǎo)致
- 嘗試去訪問一個(gè)不存在的內(nèi)存地址(超出進(jìn)程分配的地址)
- 嘗試去訪問程序無權(quán)限訪問的地址(如進(jìn)程上下文中的內(nèi)核結(jié)構(gòu)(kernel structures))
- 嘗試去訪問只讀的內(nèi)存(如代碼段)
對(duì)應(yīng)著程序中的情況分別為:
- 引用或者賦值一個(gè)為初始化的指針(野指針(wild pointer),隨機(jī)指向一個(gè)內(nèi)存地址)
- 引用或者賦值一個(gè)已經(jīng)被free的指針(dangling pointer,指向一個(gè)已經(jīng)被freed/deallocated/deleted的內(nèi)存地址)
- A buffer overflow
- A stack overflow
3. 段錯(cuò)誤信息的獲取
3.1 dmesg
dmesg可以在應(yīng)用程序crash掉時(shí),顯示內(nèi)核中保存的相關(guān)信息。
如下所示,通過dmesg命令可以查看發(fā)生段錯(cuò)誤的程序名稱、引起段錯(cuò)誤發(fā)生的內(nèi)存地址、指令指針地址、堆棧指針地址、錯(cuò)誤代碼、錯(cuò)誤原因等。
3.2 -g
使用gcc編譯程序的源碼時(shí),加上-g參數(shù),這樣可以使得生成的二進(jìn)制文件中加入可以用于gdb調(diào)試的有用信息。
3.3 nm
使用nm命令列出二進(jìn)制文件中的符號(hào)表,包括符號(hào)地址、符號(hào)類型、符號(hào)名等,這樣可以幫助定位在哪里發(fā)生了段錯(cuò)誤。
3.4 ldd
使用ldd命令查看二進(jìn)制程序的共享鏈接庫依賴,包括庫的名稱、起始地址,這樣可以確定段錯(cuò)誤到底是發(fā)生在了自己的程序中還是依賴的共享庫中。
4. 段錯(cuò)誤的調(diào)試方法
4.1 使用gcc和gdb
4.1.1 調(diào)試步驟
為了能夠使用gdb調(diào)試程序,在編譯階段加上-g參數(shù),以程序segfault3.c為例。
root@twq2018:~/segfault$ gcc -g -o segfault3 segfault3.c root@twq2018:~/segfault$ gdb ./segfault3 (gdb) run Starting program: /home/twq/segfault/segfault3 Program received signal SIGSEGV, Segmentation fault. 0x001a306a in memcpy () from /lib/tls/i686/cmov/libc.so.6 (gdb)
從輸出看出,程序segfault3.c收到SIGSEGV信號(hào),觸發(fā)段錯(cuò)誤,并提示地址0x001a306a、調(diào)用memcpy報(bào)的錯(cuò),位于/lib/tls/i686/cmov/libc.so.6庫中。
4.1.2 適用場景
1、僅當(dāng)能確定程序一定會(huì)發(fā)生段錯(cuò)誤的情況下使用。
2、當(dāng)程序的源碼可以獲得的情況下,使用-g參數(shù)編譯程序。
3、一般用于測試階段,生產(chǎn)環(huán)境下gdb會(huì)有副作用:使程序運(yùn)行減慢,運(yùn)行不夠穩(wěn)定,等等。
4、即使在測試階段,如果程序過于復(fù)雜,gdb也不能處理。
4.2 使用gdb和core文件
在4.1節(jié)中提到段錯(cuò)誤會(huì)觸發(fā)SIGSEGV信號(hào),通過man 7 signal,可以看到SIGSEGV默認(rèn)的handler會(huì)打印段錯(cuò)誤出錯(cuò)信息,并產(chǎn)生core文件,由此我們可以借助于程序異常退出時(shí)生成的core文件中的調(diào)試信息。
使用gdb工具來調(diào)試程序中的段錯(cuò)誤,用bt命令查看backtrace以檢查發(fā)生程序運(yùn)行到哪里, 來定位core dump的文件->行.。
4.2.1 調(diào)試步驟
1、在一些Linux版本下,默認(rèn)是不產(chǎn)生core文件的,首先可以查看一下系統(tǒng)core文件的大小限制:
root@twq2018:~/segfault$ ulimit -c 0
2、可以看到默認(rèn)設(shè)置情況下,本機(jī)Linux環(huán)境下發(fā)生段錯(cuò)誤時(shí)不會(huì)自動(dòng)生成core文件,下面設(shè)置下core文件的大小限制(單位為KB),其中使用ulimit -c unlimited來設(shè)置無限大,則任意情況下都會(huì)產(chǎn)生core文件。
root@twq2018:~/segfault$ ulimit -c 1024 root@twq2018:~/segfault$ ulimit -c 1024
3、運(yùn)行程序segfault3.c,發(fā)生段錯(cuò)誤生成core文件:
root@twq2018:~/segfault$ ./segfault3 段錯(cuò)誤 (core dumped)
4、加載core文件,使用gdb工具進(jìn)行調(diào)試:
root@twq2018:~/segfault$ gdb ./segfault3 ./core
4.2.2 適用場景
1、適合于在實(shí)際生成環(huán)境下調(diào)試程序的段錯(cuò)誤(即在不用重新發(fā)生段錯(cuò)誤的情況下重現(xiàn)段錯(cuò)誤)。
2、當(dāng)程序很復(fù)雜,core文件相當(dāng)大時(shí),該方法不可用。
4.3 使用objdump
4.3.1 調(diào)試步驟
1、使用dmesg命令,找到最近發(fā)生的段錯(cuò)誤輸出信息:
root@twq2018:~/segfault$ dmesg ... ... [17257.502808] segfault3[3320]: segfault at 80484e0 ip 0018506a sp bfc1cd6c error 7 in libc-2.10.1.so[110000+13e000]
其中,對(duì)我們接下來的調(diào)試過程有用的是發(fā)生段錯(cuò)誤的地址:80484e0和指令指針地址:0018506a。
2、使用objdump生成二進(jìn)制的相關(guān)信息,重定向到文件中:
root@twq2018:~/segfault$ objdump -d ./segfault3 > segfault3Dump
其中,生成的segfault3Dump文件中包含了二進(jìn)制文件的segfault3的匯編代碼。
3、在segfault3Dump文件中查找發(fā)生段錯(cuò)誤的地址:
root@twq2018:~/segfault$ grep -n -A 10 -B 10 "80484e0" ./segfault3Dump 121- 80483df: ff d0 call *%eax 122- 80483e1: c9 leave 123- 80483e2: c3 ret 124- 80483e3: 90 nop 125- 126-080483e4 <main>: 127- 80483e4: 55 push %ebp 128- 80483e5: 89 e5 mov %esp,%ebp 129- 80483e7: 83 e4 f0 and $0xfffffff0,%esp 130- 80483ea: 83 ec 20 sub $0x20,%esp 131: 80483ed: c7 44 24 1c e0 84 04 movl $0x80484e0,0x1c(%esp) 132- 80483f4: 08 133- 80483f5: b8 e5 84 04 08 mov $0x80484e5,%eax 134- 80483fa: c7 44 24 08 05 00 00 movl $0x5,0x8(%esp) 135- 8048401: 00 136- 8048402: 89 44 24 04 mov %eax,0x4(%esp) 137- 8048406: 8b 44 24 1c mov 0x1c(%esp),%eax 138- 804840a: 89 04 24 mov %eax,(%esp) 139- 804840d: e8 0a ff ff ff call 804831c <memcpy@plt> 140- 8048412: c9 leave 141- 8048413: c3 ret
通過對(duì)以上匯編代碼分析,得知段錯(cuò)誤發(fā)生main函數(shù),對(duì)應(yīng)的匯編指令是movl $0x80484e0,0x1c(%esp),接下來打開程序的源碼,找到匯編指令對(duì)應(yīng)的源碼,也就定位到段錯(cuò)誤了。
4.3.2 適用場景
1、不需要-g參數(shù)編譯,不需要借助于core文件,但需要有一定的匯編語言基礎(chǔ)。
2、如果使用了gcc編譯優(yōu)化參數(shù)(-O1,-O2,-O3)的話,生成的匯編指令將會(huì)被優(yōu)化,使得調(diào)試過程有些難度。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解linux中nginx啟動(dòng) 重啟 關(guān)閉命令
本篇文章主要介紹了詳解linux中nginx啟動(dòng) 重啟 關(guān)閉命令,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02Linux、ubuntu系統(tǒng)下查看顯卡型號(hào)、顯卡信息詳解
這篇文章主要介紹了如何在Linux、ubuntu系統(tǒng)下查看顯卡型號(hào)、顯卡信息的方法,需要的朋友可以參考下2022-04-04