Linux環(huán)境下段錯(cuò)誤的產(chǎn)生原因及調(diào)試方法小結(jié)
更新時(shí)間:2011年11月06日 23:08:52 作者:
借此機(jī)會(huì)系統(tǒng)學(xué)習(xí)了一下,這里對(duì)Linux環(huán)境下的段錯(cuò)誤做個(gè)小結(jié),方便以后同類問題的排查與解決
最近在Linux環(huán)境下做C語言項(xiàng)目,由于是在一個(gè)原有項(xiàng)目基礎(chǔ)之上進(jìn)行二次開發(fā),而且項(xiàng)目工程龐大復(fù)雜,出現(xiàn)了不少問題,其中遇到最多、花費(fèi)時(shí)間最長(zhǎng)的問題就是著名的“段錯(cuò)誤”(Segmentation Fault)。借此機(jī)會(huì)系統(tǒng)學(xué)習(xí)了一下,這里對(duì)Linux環(huán)境下的段錯(cuò)誤做個(gè)小結(jié),方便以后同類問題的排查與解決。
1. 段錯(cuò)誤是什么
一句話來說,段錯(cuò)誤是指訪問的內(nèi)存超出了系統(tǒng)給這個(gè)程序所設(shè)定的內(nèi)存空間,例如訪問了不存在的內(nèi)存地址、訪問了系統(tǒng)保護(hù)的內(nèi)存地址、訪問了只讀的內(nèi)存地址等等情況。這里貼一個(gè)對(duì)于“段錯(cuò)誤”的準(zhǔn)確定義(參考Answers.com):
A segmentation fault (often shortened to segfault) is a particular error condition that can occur during the operation of computer software. In short, a segmentation fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (e.g., attempts to write to a read-only location, or to overwrite part of the operating system). Systems based on processors like the Motorola 68000 tend to refer to these events as Address or Bus errors.
Segmentation is one approach to memory management and protection in the operating system. It has been superseded by paging for most purposes, but much of the terminology of segmentation is still used, "segmentation fault" being an example. Some operating systems still have segmentation at some logical level although paging is used as the main memory management policy.
On Unix-like operating systems, a process that accesses invalid memory receives the SIGSEGV signal. On Microsoft Windows, a process that accesses invalid memory receives the STATUS_ACCESS_VIOLATION exception.
2. 段錯(cuò)誤產(chǎn)生的原因
2.1 訪問不存在的內(nèi)存地址
#include<stdio.h>
#include<stdlib.h>
void main()
{
int *ptr = NULL;
*ptr = 0;
}
2.2 訪問系統(tǒng)保護(hù)的內(nèi)存地址
#include<stdio.h>
#include<stdlib.h>
void main()
{
int *ptr = (int *)0;
*ptr = 100;
}
2.3 訪問只讀的內(nèi)存地址
#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. 段錯(cuò)誤信息的獲取
程序發(fā)生段錯(cuò)誤時(shí),提示信息很少,下面有幾種查看段錯(cuò)誤的發(fā)生信息的途徑。
3.1 dmesg
dmesg可以在應(yīng)用程序crash掉時(shí),顯示內(nèi)核中保存的相關(guān)信息。如下所示,通過dmesg命令可以查看發(fā)生段錯(cuò)誤的程序名稱、引起段錯(cuò)誤發(fā)生的內(nèi)存地址、指令指針地址、堆棧指針地址、錯(cuò)誤代碼、錯(cuò)誤原因等。以程序2.3為例:
panfeng@ubuntu:~/segfault$ dmesg
[ 2329.479037] segfault3[2700]: segfault at 80484e0 ip 00d2906a sp bfbbec3c error 7 in libc-2.10.1.so[cb4000+13e000]
3.2 -g
使用gcc編譯程序的源碼時(shí),加上-g參數(shù),這樣可以使得生成的二進(jìn)制文件中加入可以用于gdb調(diào)試的有用信息。以程序2.3為例:
panfeng@ubuntu:~/segfault$ gcc -g -o segfault3 segfault3.c
3.3 nm
使用nm命令列出二進(jìn)制文件中的符號(hào)表,包括符號(hào)地址、符號(hào)類型、符號(hào)名等,這樣可以幫助定位在哪里發(fā)生了段錯(cuò)誤。以程序2.3為例:
panfeng@ubuntu:~/segfault$ nm segfault3
08049f20 d _DYNAMIC
08049ff4 d _GLOBAL_OFFSET_TABLE_
080484dc R _IO_stdin_used
w _Jv_RegisterClasses
08049f10 d __CTOR_END__
08049f0c d __CTOR_LIST__
08049f18 D __DTOR_END__
08049f14 d __DTOR_LIST__
080484ec r __FRAME_END__
08049f1c d __JCR_END__
08049f1c d __JCR_LIST__
0804a014 A __bss_start
0804a00c D __data_start
08048490 t __do_global_ctors_aux
08048360 t __do_global_dtors_aux
0804a010 D __dso_handle
w __gmon_start__
0804848a T __i686.get_pc_thunk.bx
08049f0c d __init_array_end
08049f0c d __init_array_start
08048420 T __libc_csu_fini
08048430 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
0804a014 A _edata
0804a01c A _end
080484bc T _fini
080484d8 R _fp_hw
080482bc T _init
08048330 T _start
0804a014 b completed.6990
0804a00c W data_start
0804a018 b dtor_idx.6992
080483c0 t frame_dummy
080483e4 T main
U memcpy@@GLIBC_2.0
3.4 ldd
使用ldd命令查看二進(jìn)制程序的共享鏈接庫依賴,包括庫的名稱、起始地址,這樣可以確定段錯(cuò)誤到底是發(fā)生在了自己的程序中還是依賴的共享庫中。以程序2.3為例:
panfeng@ubuntu:~/segfault$ ldd ./segfault3
linux-gate.so.1 => (0x00e08000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00675000)
/lib/ld-linux.so.2 (0x00482000)
4. 段錯(cuò)誤的調(diào)試方法
4.1 使用printf輸出信息
這個(gè)是看似最簡(jiǎn)單但往往很多情況下十分有效的調(diào)試方式,也許可以說是程序員用的最多的調(diào)試方式。簡(jiǎn)單來說,就是在程序的重要代碼附近加上像printf這類輸出信息,這樣可以跟蹤并打印出段錯(cuò)誤在代碼中可能出現(xiàn)的位置。
為了方便使用這種方法,可以使用條件編譯指令#ifdef DEBUG和#endif把printf函數(shù)包起來。這樣在程序編譯時(shí),如果加上-DDEBUG參數(shù)就能查看調(diào)試信息;否則不加該參數(shù)就不會(huì)顯示調(diào)試信息。
4.2 使用gcc和gdb
4.2.1 調(diào)試步驟
1、為了能夠使用gdb調(diào)試程序,在編譯階段加上-g參數(shù),以程序2.3為例:
panfeng@ubuntu:~/segfault$ gcc -g -o segfault3 segfault3.c
2、使用gdb命令調(diào)試程序:
panfeng@ubuntu:~/segfault$ gdb ./segfault3
GNU gdb (GDB) 7.0-ubuntu
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/panfeng/segfault/segfault3...done.
(gdb)
3、進(jìn)入gdb后,運(yùn)行程序:
(gdb) run
Starting program: /home/panfeng/segfault/segfault3
Program received signal SIGSEGV, Segmentation fault.
0x001a306a in memcpy () from /lib/tls/i686/cmov/libc.so.6
(gdb)
從輸出看出,程序2.3收到SIGSEGV信號(hào),觸發(fā)段錯(cuò)誤,并提示地址0x001a306a、調(diào)用memcpy報(bào)的錯(cuò),位于/lib/tls/i686/cmov/libc.so.6庫中。
4、完成調(diào)試后,輸入quit命令退出gdb:
(gdb) quit
A debugging session is active.
Inferior 1 [process 3207] will be killed.
Quit anyway? (y or n) y
4.2.2 適用場(chǎng)景
1、僅當(dāng)能確定程序一定會(huì)發(fā)生段錯(cuò)誤的情況下使用。
2、當(dāng)程序的源碼可以獲得的情況下,使用-g參數(shù)編譯程序。
3、一般用于測(cè)試階段,生產(chǎn)環(huán)境下gdb會(huì)有副作用:使程序運(yùn)行減慢,運(yùn)行不夠穩(wěn)定,等等。
4、即使在測(cè)試階段,如果程序過于復(fù)雜,gdb也不能處理。
4.3 使用core文件和gdb
在4.2節(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ò)誤。
4.3.1 調(diào)試步驟
1、在一些Linux版本下,默認(rèn)是不產(chǎn)生core文件的,首先可以查看一下系統(tǒng)core文件的大小限制:
panfeng@ubuntu:~/segfault$ ulimit -c
0
2、可以看到默認(rèn)設(shè)置情況下,本機(jī)Linux環(huán)境下發(fā)生段錯(cuò)誤時(shí)不會(huì)自動(dòng)生成core文件,下面設(shè)置下core文件的大小限制(單位為KB):
panfeng@ubuntu:~/segfault$ ulimit -c 1024
panfeng@ubuntu:~/segfault$ ulimit -c
1024
3、運(yùn)行程序2.3,發(fā)生段錯(cuò)誤生成core文件:
panfeng@ubuntu:~/segfault$ ./segfault3
段錯(cuò)誤 (core dumped)
4、加載core文件,使用gdb工具進(jìn)行調(diào)試:
panfeng@ubuntu:~/segfault$ gdb ./segfault3 ./core
GNU gdb (GDB) 7.0-ubuntu
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/panfeng/segfault/segfault3...done.
warning: Can't read pathname for load map: 輸入/輸出錯(cuò)誤.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./segfault3'.
Program terminated with signal 11, Segmentation fault.
#0 0x0018506a in memcpy () from /lib/tls/i686/cmov/libc.6
從輸出看出,同4.2.1中一樣的段錯(cuò)誤信息。
5、完成調(diào)試后,輸入quit命令退出gdb:
(gdb) quit
4.3.2 適用場(chǎng)景
1、適合于在實(shí)際生成環(huán)境下調(diào)試程序的段錯(cuò)誤(即在不用重新發(fā)生段錯(cuò)誤的情況下重現(xiàn)段錯(cuò)誤)。
2、當(dāng)程序很復(fù)雜,core文件相當(dāng)大時(shí),該方法不可用。
4.4 使用objdump
4.4.1 調(diào)試步驟
1、使用dmesg命令,找到最近發(fā)生的段錯(cuò)誤輸出信息:
panfeng@ubuntu:~/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)信息,重定向到文件中:
panfeng@ubuntu:~/segfault$ objdump -d ./segfault3 > segfault3Dump
其中,生成的segfault3Dump文件中包含了二進(jìn)制文件的segfault3的匯編代碼。
3、在segfault3Dump文件中查找發(fā)生段錯(cuò)誤的地址:
panfeng@ubuntu:~/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.4.2 適用場(chǎng)景
1、不需要-g參數(shù)編譯,不需要借助于core文件,但需要有一定的匯編語言基礎(chǔ)。
2、如果使用了gcc編譯優(yōu)化參數(shù)(-O1,-O2,-O3)的話,生成的匯編指令將會(huì)被優(yōu)化,使得調(diào)試過程有些難度。
4.5 使用catchsegv
catchsegv命令專門用來撲獲段錯(cuò)誤,它通過動(dòng)態(tài)加載器(ld-linux.so)的預(yù)加載機(jī)制(PRELOAD)把一個(gè)事先寫好的庫(/lib/libSegFault.so)加載上,用于捕捉斷錯(cuò)誤的出錯(cuò)信息。
panfeng@ubuntu:~/segfault$ catchsegv ./segfault3
Segmentation fault (core dumped)
*** Segmentation fault
Register dump:
EAX: 00000000 EBX: 00fb3ff4 ECX: 00000002 EDX: 00000000
ESI: 080484e5 EDI: 080484e0 EBP: bfb7ad38 ESP: bfb7ad0c
EIP: 00ee806a EFLAGS: 00010203
CS: 0073 DS: 007b ES: 007b FS: 0000 GS: 0033 SS: 007b
Trap: 0000000e Error: 00000007 OldMask: 00000000
ESP/signal: bfb7ad0c CR2: 080484e0
Backtrace:
/lib/libSegFault.so[0x3b606f]
??:0(??)[0xc76400]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xe89b56]
/build/buildd/eglibc-2.10.1/csu/../sysdeps/i386/elf/start.S:122(_start)[0x8048351]
Memory map:
00258000-00273000 r-xp 00000000 08:01 157 /lib/ld-2.10.1.so
00273000-00274000 r--p 0001a000 08:01 157 /lib/ld-2.10.1.so
00274000-00275000 rw-p 0001b000 08:01 157 /lib/ld-2.10.1.so
003b4000-003b7000 r-xp 00000000 08:01 13105 /lib/libSegFault.so
003b7000-003b8000 r--p 00002000 08:01 13105 /lib/libSegFault.so
003b8000-003b9000 rw-p 00003000 08:01 13105 /lib/libSegFault.so
00c76000-00c77000 r-xp 00000000 00:00 0 [vdso]
00e0d000-00e29000 r-xp 00000000 08:01 4817 /lib/libgcc_s.so.1
00e29000-00e2a000 r--p 0001b000 08:01 4817 /lib/libgcc_s.so.1
00e2a000-00e2b000 rw-p 0001c000 08:01 4817 /lib/libgcc_s.so.1
00e73000-00fb1000 r-xp 00000000 08:01 1800 /lib/tls/i686/cmov/libc-2.10.1.so
00fb1000-00fb2000 ---p 0013e000 08:01 1800 /lib/tls/i686/cmov/libc-2.10.1.so
00fb2000-00fb4000 r--p 0013e000 08:01 1800 /lib/tls/i686/cmov/libc-2.10.1.so
00fb4000-00fb5000 rw-p 00140000 08:01 1800 /lib/tls/i686/cmov/libc-2.10.1.so
00fb5000-00fb8000 rw-p 00000000 00:00 0
08048000-08049000 r-xp 00000000 08:01 303895 /home/panfeng/segfault/segfault3
08049000-0804a000 r--p 00000000 08:01 303895 /home/panfeng/segfault/segfault3
0804a000-0804b000 rw-p 00001000 08:01 303895 /home/panfeng/segfault/segfault3
09432000-09457000 rw-p 00000000 00:00 0 [heap]
b78cf000-b78d1000 rw-p 00000000 00:00 0
b78df000-b78e1000 rw-p 00000000 00:00 0
bfb67000-bfb7c000 rw-p 00000000 00:00 0 [stack]
5. 一些注意事項(xiàng)
1、出現(xiàn)段錯(cuò)誤時(shí),首先應(yīng)該想到段錯(cuò)誤的定義,從它出發(fā)考慮引發(fā)錯(cuò)誤的原因。
2、在使用指針時(shí),定義了指針后記得初始化指針,在使用的時(shí)候記得判斷是否為NULL。
3、在使用數(shù)組時(shí),注意數(shù)組是否被初始化,數(shù)組下標(biāo)是否越界,數(shù)組元素是否存在等。
4、在訪問變量時(shí),注意變量所占地址空間是否已經(jīng)被程序釋放掉。
5、在處理變量時(shí),注意變量的格式控制是否合理等。
6. 參考資料列表
1、http://www.docin.com/p-105923877.html
2、http://blog.chinaunix.net/space.php?uid=317451&do=blog&id=92412
=======================================================================
作者:大圓那些事(原攀峰@淘寶)
技術(shù)點(diǎn):架構(gòu)設(shè)計(jì)、數(shù)據(jù)庫、分布式計(jì)算和存儲(chǔ)、Hadoop、C、Erlang、設(shè)計(jì)模式
Email:ypf412@163.com
MSN:ypf412@hotmail.com
博客:http://www.cnblogs.com/panfeng412/
微博:http://weibo.com/moudayuan/
1. 段錯(cuò)誤是什么
一句話來說,段錯(cuò)誤是指訪問的內(nèi)存超出了系統(tǒng)給這個(gè)程序所設(shè)定的內(nèi)存空間,例如訪問了不存在的內(nèi)存地址、訪問了系統(tǒng)保護(hù)的內(nèi)存地址、訪問了只讀的內(nèi)存地址等等情況。這里貼一個(gè)對(duì)于“段錯(cuò)誤”的準(zhǔn)確定義(參考Answers.com):
A segmentation fault (often shortened to segfault) is a particular error condition that can occur during the operation of computer software. In short, a segmentation fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (e.g., attempts to write to a read-only location, or to overwrite part of the operating system). Systems based on processors like the Motorola 68000 tend to refer to these events as Address or Bus errors.
Segmentation is one approach to memory management and protection in the operating system. It has been superseded by paging for most purposes, but much of the terminology of segmentation is still used, "segmentation fault" being an example. Some operating systems still have segmentation at some logical level although paging is used as the main memory management policy.
On Unix-like operating systems, a process that accesses invalid memory receives the SIGSEGV signal. On Microsoft Windows, a process that accesses invalid memory receives the STATUS_ACCESS_VIOLATION exception.
2. 段錯(cuò)誤產(chǎn)生的原因
2.1 訪問不存在的內(nèi)存地址
復(fù)制代碼 代碼如下:
#include<stdio.h>
#include<stdlib.h>
void main()
{
int *ptr = NULL;
*ptr = 0;
}
2.2 訪問系統(tǒng)保護(hù)的內(nèi)存地址
復(fù)制代碼 代碼如下:
#include<stdio.h>
#include<stdlib.h>
void main()
{
int *ptr = (int *)0;
*ptr = 100;
}
2.3 訪問只讀的內(nèi)存地址
復(fù)制代碼 代碼如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void main()
{
char *ptr = "test";
strcpy(ptr, "TEST");
}
2.4 棧溢出
復(fù)制代碼 代碼如下:
#include<stdio.h>
#include<stdlib.h>
void main()
{
main();
}
等等其他原因。
3. 段錯(cuò)誤信息的獲取
程序發(fā)生段錯(cuò)誤時(shí),提示信息很少,下面有幾種查看段錯(cuò)誤的發(fā)生信息的途徑。
3.1 dmesg
dmesg可以在應(yīng)用程序crash掉時(shí),顯示內(nèi)核中保存的相關(guān)信息。如下所示,通過dmesg命令可以查看發(fā)生段錯(cuò)誤的程序名稱、引起段錯(cuò)誤發(fā)生的內(nèi)存地址、指令指針地址、堆棧指針地址、錯(cuò)誤代碼、錯(cuò)誤原因等。以程序2.3為例:
panfeng@ubuntu:~/segfault$ dmesg
[ 2329.479037] segfault3[2700]: segfault at 80484e0 ip 00d2906a sp bfbbec3c error 7 in libc-2.10.1.so[cb4000+13e000]
3.2 -g
使用gcc編譯程序的源碼時(shí),加上-g參數(shù),這樣可以使得生成的二進(jìn)制文件中加入可以用于gdb調(diào)試的有用信息。以程序2.3為例:
panfeng@ubuntu:~/segfault$ gcc -g -o segfault3 segfault3.c
3.3 nm
使用nm命令列出二進(jìn)制文件中的符號(hào)表,包括符號(hào)地址、符號(hào)類型、符號(hào)名等,這樣可以幫助定位在哪里發(fā)生了段錯(cuò)誤。以程序2.3為例:
復(fù)制代碼 代碼如下:
panfeng@ubuntu:~/segfault$ nm segfault3
08049f20 d _DYNAMIC
08049ff4 d _GLOBAL_OFFSET_TABLE_
080484dc R _IO_stdin_used
w _Jv_RegisterClasses
08049f10 d __CTOR_END__
08049f0c d __CTOR_LIST__
08049f18 D __DTOR_END__
08049f14 d __DTOR_LIST__
080484ec r __FRAME_END__
08049f1c d __JCR_END__
08049f1c d __JCR_LIST__
0804a014 A __bss_start
0804a00c D __data_start
08048490 t __do_global_ctors_aux
08048360 t __do_global_dtors_aux
0804a010 D __dso_handle
w __gmon_start__
0804848a T __i686.get_pc_thunk.bx
08049f0c d __init_array_end
08049f0c d __init_array_start
08048420 T __libc_csu_fini
08048430 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
0804a014 A _edata
0804a01c A _end
080484bc T _fini
080484d8 R _fp_hw
080482bc T _init
08048330 T _start
0804a014 b completed.6990
0804a00c W data_start
0804a018 b dtor_idx.6992
080483c0 t frame_dummy
080483e4 T main
U memcpy@@GLIBC_2.0
3.4 ldd
使用ldd命令查看二進(jìn)制程序的共享鏈接庫依賴,包括庫的名稱、起始地址,這樣可以確定段錯(cuò)誤到底是發(fā)生在了自己的程序中還是依賴的共享庫中。以程序2.3為例:
復(fù)制代碼 代碼如下:
panfeng@ubuntu:~/segfault$ ldd ./segfault3
linux-gate.so.1 => (0x00e08000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00675000)
/lib/ld-linux.so.2 (0x00482000)
4. 段錯(cuò)誤的調(diào)試方法
4.1 使用printf輸出信息
這個(gè)是看似最簡(jiǎn)單但往往很多情況下十分有效的調(diào)試方式,也許可以說是程序員用的最多的調(diào)試方式。簡(jiǎn)單來說,就是在程序的重要代碼附近加上像printf這類輸出信息,這樣可以跟蹤并打印出段錯(cuò)誤在代碼中可能出現(xiàn)的位置。
為了方便使用這種方法,可以使用條件編譯指令#ifdef DEBUG和#endif把printf函數(shù)包起來。這樣在程序編譯時(shí),如果加上-DDEBUG參數(shù)就能查看調(diào)試信息;否則不加該參數(shù)就不會(huì)顯示調(diào)試信息。
4.2 使用gcc和gdb
4.2.1 調(diào)試步驟
1、為了能夠使用gdb調(diào)試程序,在編譯階段加上-g參數(shù),以程序2.3為例:
panfeng@ubuntu:~/segfault$ gcc -g -o segfault3 segfault3.c
2、使用gdb命令調(diào)試程序:
復(fù)制代碼 代碼如下:
panfeng@ubuntu:~/segfault$ gdb ./segfault3
GNU gdb (GDB) 7.0-ubuntu
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/panfeng/segfault/segfault3...done.
(gdb)
3、進(jìn)入gdb后,運(yùn)行程序:
復(fù)制代碼 代碼如下:
(gdb) run
Starting program: /home/panfeng/segfault/segfault3
Program received signal SIGSEGV, Segmentation fault.
0x001a306a in memcpy () from /lib/tls/i686/cmov/libc.so.6
(gdb)
從輸出看出,程序2.3收到SIGSEGV信號(hào),觸發(fā)段錯(cuò)誤,并提示地址0x001a306a、調(diào)用memcpy報(bào)的錯(cuò),位于/lib/tls/i686/cmov/libc.so.6庫中。
4、完成調(diào)試后,輸入quit命令退出gdb:
復(fù)制代碼 代碼如下:
(gdb) quit
A debugging session is active.
Inferior 1 [process 3207] will be killed.
Quit anyway? (y or n) y
4.2.2 適用場(chǎng)景
1、僅當(dāng)能確定程序一定會(huì)發(fā)生段錯(cuò)誤的情況下使用。
2、當(dāng)程序的源碼可以獲得的情況下,使用-g參數(shù)編譯程序。
3、一般用于測(cè)試階段,生產(chǎn)環(huán)境下gdb會(huì)有副作用:使程序運(yùn)行減慢,運(yùn)行不夠穩(wěn)定,等等。
4、即使在測(cè)試階段,如果程序過于復(fù)雜,gdb也不能處理。
4.3 使用core文件和gdb
在4.2節(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ò)誤。
4.3.1 調(diào)試步驟
1、在一些Linux版本下,默認(rèn)是不產(chǎn)生core文件的,首先可以查看一下系統(tǒng)core文件的大小限制:
panfeng@ubuntu:~/segfault$ ulimit -c
0
2、可以看到默認(rèn)設(shè)置情況下,本機(jī)Linux環(huán)境下發(fā)生段錯(cuò)誤時(shí)不會(huì)自動(dòng)生成core文件,下面設(shè)置下core文件的大小限制(單位為KB):
panfeng@ubuntu:~/segfault$ ulimit -c 1024
panfeng@ubuntu:~/segfault$ ulimit -c
1024
3、運(yùn)行程序2.3,發(fā)生段錯(cuò)誤生成core文件:
panfeng@ubuntu:~/segfault$ ./segfault3
段錯(cuò)誤 (core dumped)
4、加載core文件,使用gdb工具進(jìn)行調(diào)試:
復(fù)制代碼 代碼如下:
panfeng@ubuntu:~/segfault$ gdb ./segfault3 ./core
GNU gdb (GDB) 7.0-ubuntu
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/panfeng/segfault/segfault3...done.
warning: Can't read pathname for load map: 輸入/輸出錯(cuò)誤.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./segfault3'.
Program terminated with signal 11, Segmentation fault.
#0 0x0018506a in memcpy () from /lib/tls/i686/cmov/libc.6
從輸出看出,同4.2.1中一樣的段錯(cuò)誤信息。
5、完成調(diào)試后,輸入quit命令退出gdb:
(gdb) quit
4.3.2 適用場(chǎng)景
1、適合于在實(shí)際生成環(huán)境下調(diào)試程序的段錯(cuò)誤(即在不用重新發(fā)生段錯(cuò)誤的情況下重現(xiàn)段錯(cuò)誤)。
2、當(dāng)程序很復(fù)雜,core文件相當(dāng)大時(shí),該方法不可用。
4.4 使用objdump
4.4.1 調(diào)試步驟
1、使用dmesg命令,找到最近發(fā)生的段錯(cuò)誤輸出信息:
panfeng@ubuntu:~/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)信息,重定向到文件中:
panfeng@ubuntu:~/segfault$ objdump -d ./segfault3 > segfault3Dump
其中,生成的segfault3Dump文件中包含了二進(jìn)制文件的segfault3的匯編代碼。
3、在segfault3Dump文件中查找發(fā)生段錯(cuò)誤的地址:
復(fù)制代碼 代碼如下:
panfeng@ubuntu:~/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.4.2 適用場(chǎng)景
1、不需要-g參數(shù)編譯,不需要借助于core文件,但需要有一定的匯編語言基礎(chǔ)。
2、如果使用了gcc編譯優(yōu)化參數(shù)(-O1,-O2,-O3)的話,生成的匯編指令將會(huì)被優(yōu)化,使得調(diào)試過程有些難度。
4.5 使用catchsegv
catchsegv命令專門用來撲獲段錯(cuò)誤,它通過動(dòng)態(tài)加載器(ld-linux.so)的預(yù)加載機(jī)制(PRELOAD)把一個(gè)事先寫好的庫(/lib/libSegFault.so)加載上,用于捕捉斷錯(cuò)誤的出錯(cuò)信息。
復(fù)制代碼 代碼如下:
panfeng@ubuntu:~/segfault$ catchsegv ./segfault3
Segmentation fault (core dumped)
*** Segmentation fault
Register dump:
EAX: 00000000 EBX: 00fb3ff4 ECX: 00000002 EDX: 00000000
ESI: 080484e5 EDI: 080484e0 EBP: bfb7ad38 ESP: bfb7ad0c
EIP: 00ee806a EFLAGS: 00010203
CS: 0073 DS: 007b ES: 007b FS: 0000 GS: 0033 SS: 007b
Trap: 0000000e Error: 00000007 OldMask: 00000000
ESP/signal: bfb7ad0c CR2: 080484e0
Backtrace:
/lib/libSegFault.so[0x3b606f]
??:0(??)[0xc76400]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xe89b56]
/build/buildd/eglibc-2.10.1/csu/../sysdeps/i386/elf/start.S:122(_start)[0x8048351]
Memory map:
00258000-00273000 r-xp 00000000 08:01 157 /lib/ld-2.10.1.so
00273000-00274000 r--p 0001a000 08:01 157 /lib/ld-2.10.1.so
00274000-00275000 rw-p 0001b000 08:01 157 /lib/ld-2.10.1.so
003b4000-003b7000 r-xp 00000000 08:01 13105 /lib/libSegFault.so
003b7000-003b8000 r--p 00002000 08:01 13105 /lib/libSegFault.so
003b8000-003b9000 rw-p 00003000 08:01 13105 /lib/libSegFault.so
00c76000-00c77000 r-xp 00000000 00:00 0 [vdso]
00e0d000-00e29000 r-xp 00000000 08:01 4817 /lib/libgcc_s.so.1
00e29000-00e2a000 r--p 0001b000 08:01 4817 /lib/libgcc_s.so.1
00e2a000-00e2b000 rw-p 0001c000 08:01 4817 /lib/libgcc_s.so.1
00e73000-00fb1000 r-xp 00000000 08:01 1800 /lib/tls/i686/cmov/libc-2.10.1.so
00fb1000-00fb2000 ---p 0013e000 08:01 1800 /lib/tls/i686/cmov/libc-2.10.1.so
00fb2000-00fb4000 r--p 0013e000 08:01 1800 /lib/tls/i686/cmov/libc-2.10.1.so
00fb4000-00fb5000 rw-p 00140000 08:01 1800 /lib/tls/i686/cmov/libc-2.10.1.so
00fb5000-00fb8000 rw-p 00000000 00:00 0
08048000-08049000 r-xp 00000000 08:01 303895 /home/panfeng/segfault/segfault3
08049000-0804a000 r--p 00000000 08:01 303895 /home/panfeng/segfault/segfault3
0804a000-0804b000 rw-p 00001000 08:01 303895 /home/panfeng/segfault/segfault3
09432000-09457000 rw-p 00000000 00:00 0 [heap]
b78cf000-b78d1000 rw-p 00000000 00:00 0
b78df000-b78e1000 rw-p 00000000 00:00 0
bfb67000-bfb7c000 rw-p 00000000 00:00 0 [stack]
5. 一些注意事項(xiàng)
1、出現(xiàn)段錯(cuò)誤時(shí),首先應(yīng)該想到段錯(cuò)誤的定義,從它出發(fā)考慮引發(fā)錯(cuò)誤的原因。
2、在使用指針時(shí),定義了指針后記得初始化指針,在使用的時(shí)候記得判斷是否為NULL。
3、在使用數(shù)組時(shí),注意數(shù)組是否被初始化,數(shù)組下標(biāo)是否越界,數(shù)組元素是否存在等。
4、在訪問變量時(shí),注意變量所占地址空間是否已經(jīng)被程序釋放掉。
5、在處理變量時(shí),注意變量的格式控制是否合理等。
6. 參考資料列表
1、http://www.docin.com/p-105923877.html
2、http://blog.chinaunix.net/space.php?uid=317451&do=blog&id=92412
=======================================================================
作者:大圓那些事(原攀峰@淘寶)
技術(shù)點(diǎn):架構(gòu)設(shè)計(jì)、數(shù)據(jù)庫、分布式計(jì)算和存儲(chǔ)、Hadoop、C、Erlang、設(shè)計(jì)模式
Email:ypf412@163.com
MSN:ypf412@hotmail.com
博客:http://www.cnblogs.com/panfeng412/
微博:http://weibo.com/moudayuan/
相關(guān)文章
C++ normal_distribution高斯正態(tài)分布函數(shù)的用法示例
高斯分布也稱為正態(tài)分布(normal distribution),常用的成熟的生成高斯分布隨機(jī)數(shù)序列的方法由Marsaglia和Bray在1964年提出,這篇文章主要給大家介紹了關(guān)于C++ normal_distribution高斯正態(tài)分布函數(shù)用法的相關(guān)資料,需要的朋友可以參考下2021-07-07C語言的動(dòng)態(tài)內(nèi)存管理的深入了解
這篇文章主要為大家詳細(xì)介紹了語言C的動(dòng)態(tài)內(nèi)存管理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02C語言中你不知道的隱式類型轉(zhuǎn)換規(guī)則詳解
在C語言中,類型轉(zhuǎn)換的方式一般可分為隱式類型轉(zhuǎn)換和顯示類型轉(zhuǎn)換(也稱為強(qiáng)制類型轉(zhuǎn)換),其中隱式類型轉(zhuǎn)換由編譯器自動(dòng)進(jìn)行,不需要程序員干預(yù),本文給大家詳細(xì)介紹了C語言中隱式類型轉(zhuǎn)換規(guī)則,需要的朋友可以參考下2024-01-01C++ 中二分查找遞歸非遞歸實(shí)現(xiàn)并分析
這篇文章主要介紹了C++ 中二分查找遞歸非遞歸實(shí)現(xiàn)并分析的相關(guān)資料,需要的朋友可以參考下2017-06-06