天天看點

詳解coredump

一,什麼是coredump

        我們經常聽到大家說到程式core掉了,需要定位解決,這裡說的大部分是指對應程式由于各種異常或者bug導緻在運作過程中異常退出或者中止,并且在滿足一定條件下(這裡為什麼說需要滿足一定的條件呢?下面會分析)會産生一個叫做core的檔案。

        通常情況下,core檔案會包含了程式運作時的記憶體,寄存器狀态,堆棧指針,記憶體管理資訊還有各種函數調用堆棧資訊等,我們可以了解為是程式工作目前狀态存儲生成第一個檔案,許多的程式出錯的時候都會産生一個core檔案,通過工具分析這個檔案,我們可以定位到程式異常退出的時候對應的堆棧調用等資訊,找出問題所在并進行及時解決。

二,coredump檔案的存儲位置

   core檔案預設的存儲位置與對應的可執行程式在同一目錄下,檔案名是core,大家可以通過下面的指令看到core檔案的存在位置:

   cat  /proc/sys/kernel/core_pattern

   預設值是core

注意:這裡是指在程序目前工作目錄的下建立。通常與程式在相同的路徑下。但如果程式中調用了chdir函數,則有可能改變了目前工作目錄。這時core檔案建立在chdir指定的路徑下。有好多程式崩潰了,我們卻找不到core檔案放在什麼位置。和chdir函數就有關系。當然程式崩潰了不一定都産生 core檔案。

如下程式代碼:則會把生成的core檔案存儲在/data/coredump/wd,而不是大家認為的跟可執行檔案在同一目錄。

通過下面的指令可以更改coredump檔案的存儲位置,若你希望把core檔案生成到/data/coredump/core目錄下:

   echo “/data/coredump/core”> /proc/sys/kernel/core_pattern

注意,這裡目前使用者必須具有對/proc/sys/kernel/core_pattern的寫權限。

預設情況下,核心在coredump時所産生的core檔案放在與該程式相同的目錄中,并且檔案名固定為core。很顯然,如果有多個程式産生core檔案,或者同一個程式多次崩潰,就會重複覆寫同一個core檔案,是以我們有必要對不同程式生成的core檔案進行分别命名。

我們通過修改kernel的參數,可以指定核心所生成的coredump檔案的檔案名。例如,使用下面的指令使kernel生成名字為core.filename.pid格式的core dump檔案:

echo “/data/coredump/core.%e.%p” >/proc/sys/kernel/core_pattern

這樣配置後,産生的core檔案中将帶有崩潰的程式名、以及它的程序ID。上面的%e和%p會被替換成程式檔案名以及程序ID。

如果在上述檔案名中包含目錄分隔符“/”,那麼所生成的core檔案将會被放到指定的目錄中。 需要說明的是,在核心中還有一個與coredump相關的設定,就是/proc/sys/kernel/core_uses_pid。如果這個檔案的内容被配置成1,那麼即使core_pattern中沒有設定%p,最後生成的core dump檔案名仍會加上程序ID。

三,如何判斷一個檔案是coredump檔案?

在類unix系統下,coredump檔案本身主要的格式也是ELF格式,是以,我們可以通過readelf指令進行判斷。

     可以看到ELF檔案頭的Type字段的類型是:CORE (Core file)

     可以通過簡單的file指令進行快速判斷:     

四,産生coredum的一些條件總結

1,  産生coredump的條件,首先需要确認目前會話的ulimit –c,若為0,則不會産生對應的coredump,需要進行修改和設定。

ulimit  -c unlimited  (可以産生coredump且不受大小限制)

若想甚至對應的字元大小,則可以指定:

ulimit –c [size]

       可以看出,這裡的size的機關是blocks,一般1block=512bytes

        如:

        ulimit –c 4  (注意,這裡的size如果太小,則可能不會産生對應的core檔案,筆者設定過ulimit –c 1的時候,系統并不生成core檔案,并嘗試了1,2,3均無法産生core,至少需要4才生成core檔案)

但目前設定的ulimit隻對目前會話有效,若想系統均有效,則需要進行如下設定:

  在/etc/profile中加入以下一行,這将允許生成coredump檔案

ulimit-c unlimited

  在rc.local中加入以下一行,這将使程式崩潰時生成的coredump檔案位于/data/coredump/目錄下:

echo /data/coredump/core.%e.%p> /proc/sys/kernel/core_pattern 

注意rc.local在不同的環境,存儲的目錄可能不同,susu下可能在/etc/rc.d/rc.local

      這些需要有root權限, 在ubuntu下每次重新打開中斷都需要重新輸入上面的ulimit指令, 來設定core大小為無限.

2, 目前使用者,即執行對應程式的使用者具有對寫入core目錄的寫權限以及有足夠的空間。

3, 幾種不會産生core檔案的情況說明:

The core file will not be generated if

(a)    the process was set-user-ID and the current user is not the owner of the program file, or

(b)     the process was set-group-ID and the current user is not the group owner of the file,

(c)     the user does not have permission to write in the current working directory, 

(d)     the file already exists and the user does not have permission to write to it, or 

(e)     the file is too big (recall the RLIMIT_CORE limit in Section 7.11). The permissions of the core file (assuming that the file doesn't already exist) are usually user-read and user-write, although Mac OS X sets only user-read.

五,coredump産生的幾種可能情況

造成程式coredump的原因有很多,這裡總結一些比較常用的經驗吧:

 1,記憶體通路越界

  a) 由于使用錯誤的下标,導緻數組通路越界。

  b) 搜尋字元串時,依靠字元串結束符來判斷字元串是否結束,但是字元串沒有正常的使用結束符。

  c) 使用strcpy, strcat, sprintf, strcmp,strcasecmp等字元串操作函數,将目标字元串讀/寫爆。應該使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函數防止讀寫越界。

 2,多線程程式使用了線程不安全的函數。

應該使用下面這些可重入的函數,它們很容易被用錯:

asctime_r(3c) gethostbyname_r(3n) getservbyname_r(3n)ctermid_r(3s) gethostent_r(3n) getservbyport_r(3n) ctime_r(3c) getlogin_r(3c)getservent_r(3n) fgetgrent_r(3c) getnetbyaddr_r(3n) getspent_r(3c)fgetpwent_r(3c) getnetbyname_r(3n) getspnam_r(3c) fgetspent_r(3c)getnetent_r(3n) gmtime_r(3c) gamma_r(3m) getnetgrent_r(3n) lgamma_r(3m) getauclassent_r(3)getprotobyname_r(3n) localtime_r(3c) getauclassnam_r(3) etprotobynumber_r(3n)nis_sperror_r(3n) getauevent_r(3) getprotoent_r(3n) rand_r(3c) getauevnam_r(3)getpwent_r(3c) readdir_r(3c) getauevnum_r(3) getpwnam_r(3c) strtok_r(3c) getgrent_r(3c)getpwuid_r(3c) tmpnam_r(3s) getgrgid_r(3c) getrpcbyname_r(3n) ttyname_r(3c)getgrnam_r(3c) getrpcbynumber_r(3n) gethostbyaddr_r(3n) getrpcent_r(3n)

 3,多線程讀寫的資料未加鎖保護。

對于會被多個線程同時通路的全局資料,應該注意加鎖保護,否則很容易造成coredump

 4,非法指針

  a) 使用空指針

  b) 随意使用指針轉換。一個指向一段記憶體的指針,除非确定這段記憶體原先就配置設定為某種結構或類型,或者這種結構或類型的數組,否則不要将它轉換為這種結構或類型的指針,而應該将這段記憶體拷貝到一個這種結構或類型中,再通路這個結構或類型。這是因為如果這段記憶體的開始位址不是按照這種結構或類型對齊的,那麼通路它時就很容易因為bus error而core dump。

 5,堆棧溢出

不要使用大的局部變量(因為局部變量都配置設定在棧上),這樣容易造成堆棧溢出,破壞系統的棧和堆結構,導緻出現莫名其妙的錯誤。  

六,利用gdb進行coredump的定位

  其實分析coredump的工具有很多,現在大部分類unix系統都提供了分析coredump檔案的工具,不過,我們經常用到的工具是gdb。

  這裡我們以程式為例子來說明如何進行定位。

1,  段錯誤 – segmentfault

  我們寫一段代碼往受到系統保護的位址寫内容。

  按如下方式進行編譯和執行,注意這裡需要-g選項編譯。

可以看到,當輸入12的時候,系統提示段錯誤并且core dumped

  我們進入對應的core檔案生成目錄,優先确認是否core檔案格式并啟用gdb進行調試。

從紅色方框截圖可以看到,程式中止是因為信号11,且從bt(backtrace)指令(或者where)可以看到函數的調用棧,即程式執行到coremain.cpp的第5行,且裡面調用scanf 函數,而該函數其實内部會調用_IO_vfscanf_internal()函數。

接下來我們繼續用gdb,進行調試對應的程式。

記住幾個常用的gdb指令:

l(list) ,顯示源代碼,并且可以看到對應的行号;

b(break)x, x是行号,表示在對應的行号位置設定斷點;

p(print)x, x是變量名,表示列印變量x的值

r(run), 表示繼續執行到斷點的位置

n(next),表示執行下一步

c(continue),表示繼續執行

q(quit),表示退出gdb

啟動gdb,注意該程式編譯需要-g選項進行。

注:  SIGSEGV     11       Core    Invalid memoryreference

七,附注:

1,  gdb的檢視源碼

顯示源代碼

GDB 可以列印出所調試程式的源代碼,當然,在程式編譯時一定要加上-g的參數,把源程式資訊編譯到執行檔案中。不然就看不到源程式了。當程式停下來以後,GDB會報告程式停在了那個檔案的第幾行上。你可以用list指令來列印程式的源代碼。還是來看一看檢視源代碼的GDB指令吧。

list<linenum>

顯示程式第linenum行的周圍的源程式。

list<function>

顯示函數名為function的函數的源程式。

list

顯示目前行後面的源程式。

list -

顯示目前行前面的源程式。

一般是列印目前行的上5行和下5行,如果顯示函數是是上2行下8行,預設是10行,當然,你也可以定制顯示的範圍,使用下面指令可以設定一次顯示源程式的行數。

setlistsize <count>

設定一次顯示源代碼的行數。

showlistsize

檢視目前listsize的設定。

list指令還有下面的用法:

list<first>, <last>

顯示從first行到last行之間的源代碼。

list ,<last>

顯示從目前行到last行之間的源代碼。

list +

往後顯示源代碼。

一般來說在list後面可以跟以下這些參數:

<linenum>   行号。

<+offset>   目前行号的正偏移量。

<-offset>   目前行号的負偏移量。

<filename:linenum>  哪個檔案的哪一行。

<function>  函數名。

<filename:function>哪個檔案中的哪個函數。

<*address>  程式運作時的語句在記憶體中的位址。

2,  一些常用signal的含義

SIGABRT:調用abort函數時産生此信号。程序異常終止。

SIGBUS:訓示一個實作定義的硬體故障。

SIGEMT:訓示一個實作定義的硬體故障。EMT這一名字來自PDP-11的emulator trap 指令。

SIGFPE:此信号表示一個算術運算異常,例如除以0,浮點溢出等。

SIGILL:此信号訓示程序已執行一條非法硬體指令。4.3BSD由abort函數産生此信号。SIGABRT現在被用于此。

SIGIOT:這訓示一個實作定義的硬體故障。IOT這個名字來自于PDP-11對于輸入/輸出TRAP(input/outputTRAP)指令的縮寫。系統V的早期版本,由abort函數産生此信号。SIGABRT現在被用于此。

SIGQUIT:當使用者在終端上按退出鍵(一般采用Ctrl-/)時,産生此信号,并送至前台進

程組中的所有程序。此信号不僅終止前台程序組(如SIGINT所做的那樣),同時産生一個core檔案。

SIGSEGV:訓示程序進行了一次無效的存儲通路。名字SEGV表示“段違例(segmentationviolation)”。

SIGSYS:訓示一個無效的系統調用。由于某種未知原因,程序執行了一條系統調用指令,但其訓示系統調用類型的參數卻是無效的。

SIGTRAP:訓示一個實作定義的硬體故障。此信号名來自于PDP-11的TRAP指令。

SIGXCPUSVR4和4.3+BSD支援資源限制的概念。如果程序超過了其軟C P U時間限制,則産生此信号。

SIGXFSZ:如果程序超過了其軟檔案長度限制,則SVR4和4.3+BSD産生此信号。

3,  Core_pattern的格式

可以在core_pattern模闆中使用變量還很多,見下面的清單:

%% 單個%字元

%p 所dump程序的程序ID

%u 所dump程序的實際使用者ID

%g 所dump程序的實際組ID

%s 導緻本次core dump的信号

%t core dump的時間 (由1970年1月1日計起的秒數)

%h 主機名

%e 程式檔案名

      本文轉自Tenderrain 51CTO部落格,原文連結http://blog.51cto.com/tenderrain/1978424:,如需轉載請自行聯系原作者