調試segfault 經驗總結

最近遇到一個棘手的問題,用在現場的程序崩潰了,但是卻無法復現,隔個一個月左右出一次,已經出現了三次,各種測試並沒有能復現問題。現在只好把目光聚焦在能夠獲得的僅有的信息,系統日誌上了,查看/var/log/message,找到了關鍵的一套信息:

 kernel: myapp[1427]: segfault at 494d1f84 ip 00d73e54 sp b17fac20 error 6 in libThdDllAlg.so[d63000+1e000]

這條信息太寶貴了,這是唯一能夠獲得的信息。但是怎麼用呢?一頭霧水。

經過查找資料終於有了一點眉目。

1.myapp是崩潰的程序名,後面中括號中的是PID號,對我沒什麼用。

2.後面的at 494d1f84 ip 00d73e54 sp b17fac20 ,at後面是發生段錯誤時訪問的地址,是錯誤當時的地址,好像對我的調試用處不大,ip後面是指令地址,這個就非常重要了,配合後面庫的基地址就可以找到出錯的代碼了。sp後面的stack pointer棧頂指針,貌似我也沒用上。

3.error後面的6,這個還是比較重要的,但是得到的信息不多,只能得到信息:由於用戶態程序寫操作訪問越界造成的。

error number是由三個字位組成的,從高到底分別爲bit2 bit1和bit0,所以它的取值範圍是0~7.
bit2: 值爲1表示是用戶態程序內存訪問越界,值爲0表示是內核態程序內存訪問越界
bit1: 值爲1表示是寫操作導致內存訪問越界,值爲0表示是讀操作導致內存訪問越界
bit0: 值爲1表示沒有足夠的權限訪問非法地址的內容,值爲0表示訪問的非法地址根本沒有對應的頁面,也就是無效地址

4. libThdDllAlg.so[d63000+1e000]這個是崩在了哪個庫上,這個非常重要,方框號裏第一個是運行時庫的基地址,跟ip後面的一塊用,用ip後面的值減去基地址就可以得到出錯在這個庫裏的哪個位置了。在這裏是00d73e54-d63000=10e54.至於+號後面是什麼意思,我還沒搞懂。希望以後能補充

下面知道了出錯的庫和出錯的指令地址,就可以看看是出在了哪條指令上了

還好這個庫是我自己 寫的庫,不是系統庫。用objdump -d libThdDllAlg.so>dumpcode 把文件反彙編寫進dumpcode文件。這個libThdDllAlg.so是從現場拷貝回來了,而不是自己重新編譯的。

打開dumpcode文件,找到10e54地址,往上翻能夠找到是在哪個函數裏,再結合源文件,找到上下文,大概就能知道出錯在哪裏了。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------

引用以下文章

1.一次segfault錯誤的排查過程,引用地址https://blog.csdn.net/zhaohaijie600/article/details/45246569

                                                                                                 一次segfault錯誤的排查過程

正常運行了幾年的程序忽然崩潰了,由於機器沒有設置CORE文件,無法從CORE中取得錯誤信息,程序運行在centOS 7上, 本來對centOS用的也是不熟,只能邊查資料邊查問題。

首先、我需要確認程序是否真的崩潰了,還是別人誤操作關閉了。如果程序真的崩潰了,會在系統中留下痕跡,我查了一下,在messages文件中發現了一條信息:

xxxxx.o[2374]: segfault at7f0ed0bfbf70 ip 00007f0edd646fe7 sp 00007f0ed3603978 error 4 inlibc-2.17.so[7f0edd514000+1b6000]

由上面信息看出,系統確實是崩潰了,發生了段錯誤。

查看messages需要root權限,用命令:cat /var/log/messages 就可以了,還有一個命令dmesg也可以查到上面的信息。

從上面的信息,我們可以得到以下信息:

1、從libc-2.17.so[7f0edd514000+1b6000]可以看出錯誤發生在libc上,libc在此程序中映射的內存基址爲7f0edd514000,這個信息是個壞消息,這個so上的東西太多了;

2、segfault at和error 4這兩條信息可以得出是內存讀出錯,4的意義如下,可以對照參考:

bit2:值爲1表示是用戶態程序內存訪問越界,值爲0表示是內核態程序內存訪問越界
bit1: 值爲1表示是寫操作導致內存訪問越界,值爲0表示是讀操作導致內存訪問越界
bit0: 值爲1表示沒有足夠的權限訪問非法地址的內容,值爲0表示訪問的非法地址根本沒有對應的頁面,也就是無效地址

4正好爲用戶態內存讀操作訪問出界。

3、7f0ed0bfbf70,00007f0edd646fe7,00007f0ed3603978這三個值:第一個值爲出錯的地址,用處不大;第二個值爲發生錯誤時指令的地址,這個值在有些錯誤時是錯誤的,下面會講一下,第三個值爲堆棧指針。

除了以上信息,就是六七萬行的代碼。感覺沒有太大的指望。

C++段錯誤就幾類,讀寫錯誤,這個主要是參數沒有控制好,這種錯誤比較常見,我們經常把NULL指針、未初始化或非法值的指針傳遞給函數,從而引出此錯誤;指令地址出錯,這類錯誤主要是由虛函數,回調函數引起,最常出現的是虛函數,由於虛函數保存在類變量中,如果不小心用了非安全函數,就可能把虛數指針覆蓋掉,從而影響出現錯誤。但指令地址出錯的情況相對參數出錯來講還是要少很多的,因爲用到此功能的還是需要一定的水平的,不容易犯一些低級錯誤。

從上面分析的第二點來看,基本上屬於讀寫錯誤,但從六七萬行代碼找出問題,可能性不大,只能縮小範圍,我決定從上面提到的三點,找到出錯的函數,然後再從代碼中找出所有出錯函數調用的地方來定位問題。由於錯誤指出出錯的組件爲libc,而且基本上是參數出現,所以發現錯誤的指令地址應該是可信的,我們可以根據指令地址查出是哪個函數。指令地址爲:00007f0edd646fe7 ,libc指令的基地址爲:7f0edd514000,可以根據這兩個值計算一下該指令的相對地址爲132FE7,下面我們需要找到相對代碼段地址爲132FE7的地方爲什麼函數。

開始我想得到反彙編代碼,但這個組件代碼太多,看不到頭,於是我找了個取巧的辦法,查看導出函數和基地址,結果所以還是很多,我就用132和133進行了一下過濾,得出以下信息

[root@localhostlib64]# objdump -tT libc-2.17.so | grep 132

000000000008284fl     F .text  000000000000001b              _L_unlock_1325

0000000000082ebfl     F .text  000000000000001c              _L_lock_11322

000000000010b952l     F .text  000000000000001b              _L_unlock_132

000000000010ce62l     F .text  000000000000001b              _L_unlock_132

00000000001132e0l     F .text  00000000000001cf              ruserok2_sa

00000000000f1320l     F .text  00000000000001ce              __ecvt_r

00000000000bf370l     F .text  0000000000000132              __statfs_link_max

0000000000132080l     F .text  0000000000000068              __nss_gshadow_lookup

0000000000132f50l     F .text  0000000000000fd9              __strncmp_sse42

00000000001320f0l     F .text  00000000000000a5              __strchr_sse42

0000000000132020l     F .text  000000000000005e              __nss_aliases_lookup

00000000001321a0l     F .text  0000000000000da9              __strcmp_sse42

00000000001153b0g     F .text  0000000000000132              setnetgrent

00000000000f1320g     F .text  00000000000001ce              ecvt_r

0000000000112b50g     F .text  0000000000000132              ether_ntohost

00000000000f1320g    DF .text  00000000000001ce  GLIBC_2.2.5 ecvt_r

0000000000112b50g    DF .text  0000000000000132  GLIBC_2.2.5 ether_ntohost

00000000001153b0g    DF .text  0000000000000132  GLIBC_2.2.5 setnetgrent

[root@localhostlib64]# objdump -tT libc-2.17.so | grep 133

000000000006e3cal     F .text  000000000000001b              _L_unlock_133

0000000000075055l     F .text  0000000000000018              _L_unlock_133

000000000008286al     F .text  000000000000001f              _L_unlock_1335

000000000008305al     F .text  000000000000001b              _L_lock_13385

0000000000133f30l     F .text  000000000000019a              __strrchr_sse42

注意,我標紅的部分,132f50和132fe7很接近,很大可能是這個函數出現,而且又是讀地址非法,這個函數有可能會出錯這個問題,__strncmp_sse42這個函數是被strncmp調用的,看到這個函數基本上可以確定應該是這個函數惹得禍。

我又寫了兩行代碼確認了一下

strncmp(0,“1234”, 5);

strncmp(“1234”,0, 5);

分別編繹成兩個應用運行,出錯,messages中的錯誤信息如下:

Apr23 01:38:03 localhost kernel: a.out[3254]: segfault at 0 ip 00007f58a5386f7c sp00007fffb3de5d18 error 4 in libc-2.17.so[7f58a5254000+1b6000]

Apr23 01:39:34 localhost kernel: a.out[3267]: segfault at 0 ip 00007f8bb1908f80 sp00007fff61695408 error 4 in libc-2.17.so[7f8bb17d6000+1b6000]

計算了一下,出錯的相對地址爲:132F80和132F7C,和我們遇到的錯誤地址很接近,於是,把__strncmp_sse42的彙編代碼打印了部分如下:

Dump of assemblercode for function __strncmp_sse42:

   0x00007ffff732ef50 <+0>: test   %rdx,%rdx

   0x00007ffff732ef53 <+3>: je     0x7ffff732ff14<__strncmp_sse42+4036>

   0x00007ffff732ef59 <+9>: cmp    $0x1,%rdx

   0x00007ffff732ef5d <+13>: je     0x7ffff732ff20<__strncmp_sse42+4048>

   0x00007ffff732ef63 <+19>: mov    %rdx,%r11

   0x00007ffff732ef66 <+22>: mov    %esi,%ecx

   0x00007ffff732ef68 <+24>: mov    %edi,%eax

   0x00007ffff732ef6a <+26>: and    $0x3f,%rcx

   0x00007ffff732ef6e <+30>: and    $0x3f,%rax

   0x00007ffff732ef72 <+34>: cmp    $0x30,%ecx

   0x00007ffff732ef75 <+37>: ja     0x7ffff732efc0 <__strncmp_sse42+112>

   0x00007ffff732ef77 <+39>: cmp    $0x30,%eax

   0x00007ffff732ef7a <+42>: ja     0x7ffff732efc0 <__strncmp_sse42+112>

           0x00007ffff732ef7c <+44>: movdqu(%rdi),%xmm1

           0x00007ffff732ef80 <+48>:movdqu (%rsi),%xmm2

   0x00007ffff732ef84 <+52>: pxor   %xmm0,%xmm0

   0x00007ffff732ef88 <+56>: pcmpeqb%xmm1,%xmm0

   0x00007ffff732ef8c <+60>: pcmpeqb%xmm2,%xmm1

   0x00007ffff732ef90 <+64>: psubb  %xmm0,%xmm1

   0x00007ffff732ef94 <+68>: pmovmskb%xmm1,%edx

   0x00007ffff732ef98 <+72>: sub    $0xffff,%edx

   0x00007ffff732ef9e <+78>: jne    0x7ffff732ff00 <__strncmp_sse42+4016>

   0x00007ffff732efa4 <+84>: sub    $0x10,%r11

   0x00007ffff732efa8 <+88>: jbe    0x7ffff732ff14 <__strncmp_sse42+4036>

   0x00007ffff732efae <+94>: add    $0x10,%rsi

   0x00007ffff732efb2 <+98>: add    $0x10,%rdi

   0x00007ffff732efb6 <+102>: nopw   %cs:0x0(%rax,%rax,1)

   0x00007ffff732efc0 <+112>: and    $0xfffffffffffffff0,%rsi

   0x00007ffff732efc4 <+116>: and    $0xfffffffffffffff0,%rdi

   0x00007ffff732efc8 <+120>: mov    $0xffff,%edx

   0x00007ffff732efcd <+125>: xor    %r8d,%r8d

   0x00007ffff732efd0 <+128>: and    $0xf,%ecx

   0x00007ffff732efd3 <+131>: and    $0xf,%eax

   0x00007ffff732efd6 <+134>: pxor   %xmm0,%xmm0

   0x00007ffff732efda <+138>: cmp    %eax,%ecx

   0x00007ffff732efdc <+140>: je     0x7ffff732f010 <__strncmp_sse42+192>

   0x00007ffff732efde <+142>: ja     0x7ffff732efe7 <__strncmp_sse42+151>

   0x00007ffff732efe0 <+144>: mov    %edx,%r8d

   0x00007ffff732efe3 <+147>: xchg   %eax,%ecx

   0x00007ffff732efe4 <+148>: xchg   %rsi,%rdi

       0x00007ffff732efe7<+151>: movdqa (%rdi),%xmm2

       0x00007ffff732efeb <+155>: movdqa(%rsi),%xmm1

   0x00007ffff732efef <+159>: lea    0xf(%rax),%r9

   0x00007ffff732eff3 <+163>: sub    %rcx,%r9

   0x00007ffff732eff6 <+166>: lea    0x4d4c3(%rip),%r10

紅色部分爲我自己寫的測試程序出錯的地址,

綠色部分爲所要查的程序出錯的地址,從這部分可以看到,出錯的參數爲strncmp的第二個參數。

四、多線程

由於用UE對源代碼進行查找,還好只有335處調用,對第二個參數爲常量和不被調用的代碼再排除,只有20多處可疑。

現在需要再分析一下,第二個參數讀非法,那麼變量如果在棧中話,需要訪問到棧外的空間,這樣,如果是第二個參數爲棧變量的話,第三個參數值應該會很大。如果在堆變量,情況就比較複雜了,由於第三個參數值都很少,最大的爲128,所以又排除了棧變量出錯的可能性,從上面的數據來看,出錯的地址比棧小了很多,也證明了不是棧變量出錯。最後只剩下8處,我對這8處仔細查了一下,感覺都沒有問題,參數控制,變量長度,控制得都沒有問題。

如果代碼上沒有問題,那麼問題就比較難查了,我再進行分析一下,函數用戶和變量上表面上沒有問題,那麼就可能出現幾種可能:變量指針覆蓋,內存移動。變量指針覆蓋在實際中出現的概率比較大,往往使用了strcpy和memcpy之類的函數,沒有進行邊界值檢查,把一些指針的值覆蓋了,對於這種情況,我對代碼進行了檢查,可能性很小,而且上面給出的出錯的參數值不像隨意覆蓋的值。如果這種情況可能性很小,對於一個變量只在單個線程被訪問的情況出錯的可能性也很小,於是,我又排除了6處。

剩下的兩處代碼在一起的,參數爲一個類的內部變量,這個類可能會被多個線程訪問,所以此此出現的可能性很大,我仔細看了一下,邊界檢查也做了,線程互斥訪問控制也進行了處理,沒有明顯的問題,還好用到這個變量的只有一個文件,我對整個文件對這個變量進行搜索,發現了一個函數realloc。感覺可能是這個函數導致的問題,此變量存放的是一個數組,數組會不斷增長,當空間不夠時,就用realloc再重申請,看到這個函數後,心中一陣激動,感覺問題就在這兒,我把這幾段代碼仔細看了幾遍,訪問控制,鎖住修改,動態申請,最終發現了一個問題,在使用realloc時,臨界區沒有保護好。

事情似乎比較明朗了,在一個線程對這個變量指針進行訪問時(即用strnmcp處),線程切換到另一個線程對這個變量指針進行了realloc操作,realloc重新申請了新的空間,把老的數據移動的新的空間,然後把舊空間處的內存徹底釋放掉了,然後又切換到原先的線程,執行strncmp函數,這個函數用的指針參數還是舊的空間地址,而舊的空間已經被釋放了,所以出現了訪問非法的錯誤。

我查了一下程序日誌,發現出現錯誤時,正好需要重新申請空間,應該是這個問題導致的。

這種情況發生的概率很低,很難重現,因爲realloc和strncmp函數執行時間都很短,而且realloc的概率不高,所以運行了4,5年纔出現錯誤,但是總體來講,還是比較幸運的,如果舊的空間沒有被釋放,那麼接下來的各種操作全在舊的空間上,可能會導致更大的損失。

 

———趙海傑

20150424
---------------------
版權聲明:本文爲CSDN博主「zhaohaijie600」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zhaohaijie600/article/details/45246569

 

2.Linux環境下段錯誤的產生原因及調試方法小結

引用地址https://www.cnblogs.com/panfeng412/archive/2011/11/06/segmentation-fault-in-linux.html

最近在Linux環境下做C語言項目,由於是在一個原有項目基礎之上進行二次開發,而且項目工程龐大複雜,出現了不少問題,其中遇到最多、花費時間最長的問題就是著名的“段錯誤”(Segmentation Fault)。藉此機會系統學習了一下,這裏對Linux環境下的段錯誤做個小結,方便以後同類問題的排查與解決。

1. 段錯誤是什麼

一句話來說,段錯誤是指訪問的內存超出了系統給這個程序所設定的內存空間,例如訪問了不存在的內存地址、訪問了系統保護的內存地址、訪問了只讀的內存地址等等情況。這裏貼一個對於“段錯誤”的準確定義(參考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. 段錯誤產生的原因

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

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

3.1 dmesg

dmesg可以在應用程序crash掉時,顯示內核中保存的相關信息。如下所示,通過dmesg命令可以查看發生段錯誤的程序名稱、引起段錯誤發生的內存地址、指令指針地址、堆棧指針地址、錯誤代碼、錯誤原因等。以程序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編譯程序的源碼時,加上-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 調試步驟

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

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

2、使用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) 

複製代碼

3、進入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庫中。

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

1、僅當能確定程序一定會發生段錯誤的情況下使用。

2、當程序的源碼可以獲得的情況下,使用-g參數編譯程序。

3、一般用於測試階段,生產環境下gdb會有副作用:使程序運行減慢,運行不夠穩定,等等。

4、即使在測試階段,如果程序過於複雜,gdb也不能處理。

4.3 使用core文件和gdb

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

4.3.1 調試步驟

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

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

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

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

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

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

4、加載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中一樣的段錯誤信息。

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

(gdb) quit

4.3.2 適用場景

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

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

4.4 使用objdump

4.4.1 調試步驟

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。

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

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

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

3、在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 適用場景

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

2、如果使用了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. 一些注意事項

1、出現段錯誤時,首先應該想到段錯誤的定義,從它出發考慮引發錯誤的原因。

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

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

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

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

6. 參考資料列表

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章