詳解coredump

工程師WWW發表於2015-01-22

一,什麼是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

      更多ulimit的命令使用,可以參考:http://baike.baidu.com/view/4832100.htm

      這些需要有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 程式檔名