“段錯誤”定位及除錯的一點經驗
今天除錯程式碼的時候,遇到一個問題就是出現了“段錯誤”。出現“段錯誤”的原因就是:訪問的記憶體超出了系統給這個程式所設定的記憶體空間。知道原因是一個很好的開始,但是並不代表就很容易解決,特別是在程式碼量較大的情況下,如何才能定位到出錯的地方?接下來,我就大概講一下自己的一點經驗,如何在Linux C中用幾個命令搞定“段錯誤”。
1、dmesg
通過dmesg命令可以檢視發生段錯誤的程式名稱、引起段錯誤發生的記憶體地址、指令指標地址、堆疊指標地址、錯誤程式碼、錯誤原因等。
如,執行dmesg命令後,
[91046.776582] Test[26966]: segfault at 4 ip 0804f57f sp bfa0a224 error 6 in Test[8048000+1e000]
可以看出,發生段錯誤的地址:4, 和指令指標地址:0804f57f
2、ldd
使用ldd命令檢視二進位制程式的共享連結庫依賴,包括庫的名稱、起始地址,這樣可以確定段錯誤到底是發生在了自己的程式中還是依賴的共享庫中。
如,執行 ldd bin/Test
linux-gate.so.1 => (0xb77d7000) libevent-2.0.so.5 => /usr/lib/libevent-2.0.so.5 (0xb777d000) libpthread.so.0 => /lib/tls/i686/cmov/libpthread.so.0 (0xb7764000) librt.so.1 => /lib/tls/i686/cmov/librt.so.1 (0xb775a000) libxml2.so.2 => /usr/lib/libxml2.so.2 (0xb762f000) libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb74d5000) /lib/ld-linux.so.2 (0xb77d8000) libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0xb74d1000) libz.so.1 => /lib/libz.so.1 (0xb74bc000) libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0xb7495000)
通過以上資訊可以排除“段錯誤”發生在共享連結庫的可能。
3、nm
使用nm命令列出二進位制檔案中的符號表,包括符號地址、符號型別、符號名等,這樣可以幫助定位在哪裡發生了段錯誤。
如,執行 nm bin/Test | grep 0804f5
0804f560 T nwGtpv1uMsgAddIeTV1
0804f5a0 T nwGtpv1uMsgAddIeTV2
0804f5e0 T nwGtpv1uMsgAddIeTV4
0804f500 T nwGtpv1uMsgGetTpduHandle
0804f530 T nwGtpv1uMsgGetTpduLength
在步驟1的時候,發生段錯誤的指令指標地址:0804f57f。結合以上資訊,可以定位出錯誤的發生應該是在執行 nwGtpv1uMsgAddIeTV1 函式的時候。
4、objdump
objdump命令是Linux下的反彙編目標檔案或者可執行檔案的命令,使用objdump生成二進位制的相關資訊,並重定向到檔案中
如,執行 objdump -d bin/Test > testDump
接下來,需要在 testDump 檔案中查詢發生段錯誤的地址
如,執行 grep -n -A 10 -B 10 "0804f57f" ./testDump
3327:0804f560 <nwGtpv1uMsgAddIeTV1>:
3328- 804f560: 83 ec 08 sub $0x8,%esp
3329- 804f563: 8b 44 24 0c mov 0xc(%esp),%eax
3330- 804f567: 89 34 24 mov %esi,(%esp)
3331- 804f56a: 8b 74 24 10 mov 0x10(%esp),%esi
3332- 804f56e: 89 7c 24 04 mov %edi,0x4(%esp)
3333- 804f572: 8b 7c 24 14 mov 0x14(%esp),%edi
3334- 804f576: 0f b7 50 0c movzwl 0xc(%eax),%edx //裝入的32位暫存器 edx 前16位
3335- 804f57a: 03 50 18 add 0x18(%eax),%edx
3336- 804f57d: 89 f1 mov %esi,%ecx
3337- 804f57f: 88 0a mov %cl,(%edx)
可以發現,段錯誤就發生的位置:在 nwGtpv1uMsgAddIeTV1 函式中,執行彙編命令
3337- 804f57f: 88 0a mov %cl,(%edx)
找到對應的 nwGtpv1uMsgAddIeTV1 函式原始碼:
411 NwGtpv1uRcT
412 nwGtpv1uMsgAddIeTV1(NW_IN NwGtpv1uMsgHandleT hMsg,
413 NW_IN uint8_t type,
414 NW_IN uint8_t value)
415 {
416 NwGtpv1uMsgT *pMsg = (NwGtpv1uMsgT *) hMsg;
417 NwGtpv1uIeTv1T *pIe;
418
419
420 pIe = (NwGtpv1uIeTv1T *) (pMsg->msgBuf + pMsg->msgLen);
421 pIe->t = type;
422 pIe->v = value;
423
424 pMsg->msgLen += sizeof(NwGtpv1uIeTv1T);
425
426 return NW_GTPV1U_OK;
427 }
很明顯,其實就是指標pIe出現的問題。
5、fprintf
加上列印語句,看看變數值究竟是個什麼鬼。
411 NwGtpv1uRcT
412 nwGtpv1uMsgAddIeTV1(NW_IN NwGtpv1uMsgHandleT hMsg,
413 NW_IN uint8_t type,
414 NW_IN uint8_t value)
415 {
416 NwGtpv1uMsgT *pMsg = (NwGtpv1uMsgT *) hMsg;
417 NwGtpv1uIeTv1T *pIe;
418 fprintf(stderr,"pMsg->msgBuf:%d \n",pMsg->msgBuf);//0
419 fprintf(stderr,"pMsg->msgLen:%d \n",pMsg->msgLen);//4
420 pIe = (NwGtpv1uIeTv1T *) (pMsg->msgBuf + pMsg->msgLen);
421 pIe->t = type;
422 pIe->v = value;
423
424 pMsg->msgLen += sizeof(NwGtpv1uIeTv1T);
425
426 return NW_GTPV1U_OK;
427 }
6、gdb
使用gdb工具來對程式進行除錯
如,執行 gdb --args ./bin/Test 2 127.0.0.1 127.0.0.1
加斷點
(gdb) b NwGtpv1uMsg.c:418
(gdb) r
(gdb) s
除錯資訊發現,pMsg->msgBuf的值為0,pMsg->msgLen的值為4,很明顯:指標pIe訪問的地址就出問題了!!
再往下除錯就會發現
Program received signal SIGSEGV, Segmentation fault.
nwGtpv1uMsgAddIeTV1 (hMsg=539354136, type=14 '\016', value=0 '\000') at /home/zlj/openair4G/openair-cn/GTPV1-U/nw-gtpv1u/src/NwGtpv1uMsg.c:424
424 pIe->t = type;
(gdb)
Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
段錯誤產生了!!
總結
在Linux環境下使用C做專案,“段錯誤”的出現比較常見,首先就是要知道段錯誤發生的原因,然後快速準確地找到錯誤位置,結合程式碼分析問題,最後幹掉bug。