Linux下gcc編譯程序出現「段錯誤」

這兩天在Github上找到了一個開源的project能夠來拿爲個人相關研究工做所使用,不過須要進行二次開發,因而狠下心來啃這個project的代碼。php

等到終於啃完代碼了,瞭解了程序的意圖,因而想着就先讓其跑起來試試,但是一運行就各類錯誤,不過大部分的錯誤在調試過程當中慢慢就被消滅了,html

只剩下一個叫作Fragementation Fault的問題,徹底沒有頭緒啊,提示信息也不夠,另外本人對彙編不熟,已經多年不碰它了,實在沒法靠一己之力找出問題,linux

因而便在萬能的百度上尋找答案,翻閱了衆多博客以後發現一篇寫fragmentation fault挺不錯的文章,因而引用過來,以備之後不時之需。程序員

 

感謝原做者的分享,如下的內容轉載於:http://www.cnblogs.com/panfeng412/archive/2011/11/06/2237857.htmlredis

1. 段錯誤是什麼

一句話來講,段錯誤是指訪問的內存超出了系統給這個程序所設定的內存空間,例如訪問了不存在的內存地址、訪問了系統保護的內存地址、訪問了只讀的內存地址等等狀況。這裏貼一個對於「段錯誤」的準肯定義(參考Answers.com):ubuntu

複製代碼
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. 段錯誤產生的緣由

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. 段錯誤信息的獲取

程序發生段錯誤時,提示信息不多,下面有幾種查看段錯誤的發生信息的途徑。session

3.1 dmesg

dmesg能夠在應用程序crash掉時,顯示內核中保存的相關信息。以下所示,經過dmesg命令能夠查看發生段錯誤的程序名稱、引發段錯誤發生的內存地址、指令指針地址、堆棧指針地址、錯誤代碼、錯誤緣由等。以程序2.3爲例:app

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編譯程序的源碼時,加上-g參數,這樣可使得生成的二進制文件中加入能夠用於gdb調試的有用信息。以程序2.3爲例:函數

panfeng@ubuntu:~/segfault$ gcc -g -o segfault3 segfault3.c

3.3 nm

使用nm命令列出二進制文件中的符號表,包括符號地址、符號類型、符號名等,這樣能夠幫助定位在哪裏發生了段錯誤。以程序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命令查看二進制程序的共享連接庫依賴,包括庫的名稱、起始地址,這樣能夠肯定段錯誤究竟是發生在了本身的程序中仍是依賴的共享庫中。以程序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. 段錯誤的調試方法

4.1 使用printf輸出信息

這個是看似最簡單但每每不少狀況下十分有效的調試方式,也許能夠說是程序員用的最多的調試方式。簡單來講,就是在程序的重要代碼附近加上像printf這類輸出信息,這樣能夠跟蹤並打印出段錯誤在代碼中可能出現的位置。

爲了方便使用這種方法,可使用條件編譯指令#ifdef DEBUG和#endif把printf函數包起來。這樣在程序編譯時,若是加上-DDEBUG參數就能查看調試信息;不然不加該參數就不會顯示調試信息。

4.2 使用gcc和gdb

4.2.1 調試步驟

 一、爲了可以使用gdb調試程序,在編譯階段加上-g參數,以程序2.3爲例:

panfeng@ubuntu:~/segfault$ gcc -g -o segfault3 segfault3.c

二、使用gdb命令調試程序:

複製代碼
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)
複製代碼

三、進入gdb後,運行程序:

複製代碼
(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信號,觸發段錯誤,並提示地址0x001a306a、調用memcpy報的錯,位於/lib/tls/i686/cmov/libc.so.6庫中。

四、完成調試後,輸入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 適用場景

一、僅當能肯定程序必定會發生段錯誤的狀況下使用。

二、當程序的源碼能夠得到的狀況下,使用-g參數編譯程序。

三、通常用於測試階段,生產環境下gdb會有反作用:使程序運行減慢,運行不夠穩定,等等。

四、即便在測試階段,若是程序過於複雜,gdb也不能處理。

4.3 使用core文件和gdb

在4.2節中提到段錯誤會觸發SIGSEGV信號,經過man 7 signal,能夠看到SIGSEGV默認的handler會打印段錯誤出錯信息,併產生core文件,由此咱們能夠藉助於程序異常退出時生成的core文件中的調試信息,使用gdb工具來調試程序中的段錯誤。

4.3.1 調試步驟

一、在一些Linux版本下,默認是不產生core文件的,首先能夠查看一下系統core文件的大小限制:

panfeng@ubuntu:~/segfault$ ulimit -c
0

二、能夠看到默認設置狀況下,本機Linux環境下發生段錯誤時不會自動生成core文件,下面設置下core文件的大小限制(單位爲KB):

panfeng@ubuntu:~/segfault$ ulimit -c 1024
panfeng@ubuntu:~/segfault$ ulimit -c
1024

三、運行程序2.3,發生段錯誤生成core文件:

panfeng@ubuntu:~/segfault$ ./segfault3
段錯誤 (core dumped)

四、加載core文件,使用gdb工具進行調試:

複製代碼
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: 輸入/輸出錯誤.
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中同樣的段錯誤信息。

五、完成調試後,輸入quit命令退出gdb:

(gdb) quit

4.3.2 適用場景

一、適合於在實際生成環境下調試程序的段錯誤(即在不用從新發生段錯誤的狀況下重現段錯誤)。

二、當程序很複雜,core文件至關大時,該方法不可用。

4.4 使用objdump

4.4.1 調試步驟

一、使用dmesg命令,找到最近發生的段錯誤輸出信息:

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]

其中,對咱們接下來的調試過程有用的是發生段錯誤的地址:80484e0和指令指針地址:0018506a。

二、使用objdump生成二進制的相關信息,重定向到文件中:

panfeng@ubuntu:~/segfault$ objdump -d ./segfault3 > segfault3Dump

其中,生成的segfault3Dump文件中包含了二進制文件的segfault3的彙編代碼。

三、在segfault3Dump文件中查找發生段錯誤的地址:

複製代碼
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
複製代碼

經過對以上彙編代碼分析,得知段錯誤發生main函數,對應的彙編指令是movl $0x80484e0,0x1c(%esp),接下來打開程序的源碼,找到彙編指令對應的源碼,也就定位到段錯誤了。

4.4.2 適用場景

一、不須要-g參數編譯,不須要藉助於core文件,但須要有必定的彙編語言基礎。

二、若是使用了gcc編譯優化參數(-O1,-O2,-O3)的話,生成的彙編指令將會被優化,使得調試過程有些難度。

4.5 使用catchsegv

catchsegv命令專門用來撲獲段錯誤,它經過動態加載器(ld-linux.so)的預加載機制(PRELOAD)把一個事先寫好的庫(/lib/libSegFault.so)加載上,用於捕捉斷錯誤的出錯信息。

複製代碼
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. 一些注意事項

一、出現段錯誤時,首先應該想到段錯誤的定義,從它出發考慮引起錯誤的緣由。

二、在使用指針時,定義了指針後記得初始化指針,在使用的時候記得判斷是否爲NULL。

三、在使用數組時,注意數組是否被初始化,數組下標是否越界,數組元素是否存在等。

四、在訪問變量時,注意變量所佔地址空間是否已經被程序釋放掉。

五、在處理變量時,注意變量的格式控制是否合理等。

6. 參考資料列表

一、http://www.docin.com/p-105923877.html

二、http://blog.chinaunix.net/space.php?uid=317451&do=blog&id=92412

相關文章
相關標籤/搜索