Linux上Core Dump檔案的形成和分析

2021-09-21 22:26:49 字數 3383 閱讀 7154

core,又稱之為core dump檔案,是unix/linux作業系統的一種機制,對於線上服務而言,core令人聞之色變,因為出core的過程意味著服務暫時不能正常響應,需要恢復,並且隨著吐core程序的記憶體空間越大,此過程可能持續很長一段時間(例如當程序占用60g+以上記憶體時,完整core檔案需要15分鐘才能完全寫到磁碟上),這期間產生的流量損失,不可估量。

凡事皆有兩面性,os在出core的同時,雖然會終止掉當前程序,但是也會保留下第一手的現場資料,os彷彿是一架被按下快門的相機,而**就是產出的core檔案。裡面含有當程序被終止時記憶體、cpu暫存器等資訊,可以供後續開發人員進行除錯。

關於core產生的原因很多,比如過去一些unix的版本不支援現代linux上這種gdb直接附著到程序上進行除錯的機制,需要先向程序傳送終止訊號,然後用工具閱讀core檔案。在linux上,我們就可以使用kill向乙個指定的程序傳送訊號或者使用gcore命令來使其主動出core並退出。如果從淺層次的原因上來講,出core意味著當前程序存在bug,需要程式設計師修復。從深層次的原因上講,是當前程序觸犯了某些os層級的保護機制,逼迫os向當前程序傳送諸如sigse**(即signal 11)之類的訊號, 例如訪問空指標或陣列越界出core,實際上是觸犯了os的記憶體管理,訪問了非當前程序的記憶體空間,os需要通過出core來進行警示,這就好像乙個人身體內存在病毒,免疫系統就會通過發熱來警示,並導致人體發燒是乙個道理(有意思的是,並不是每次陣列越界都會出core,這和os的記憶體管理中虛擬頁面分配大小和邊界有關,即使不出core,也很有可能讀到髒資料,引起後續程式行為紊亂,這是一種很難追查的bug)。

說了這些,似乎感覺core很強勢,讓人感覺缺乏控制力,其實不然。控制core產生的行為和方式,有兩個途徑:

1.修改/proc/sys/kernel/core_pattern檔案,此檔案用於控制core檔案產生的檔名,預設情況下,此檔案內容只有一行內容:「core」,此檔案支援定製,一般使用%配合不同的字元,這裡羅列幾種:

%p  出core程序的pid
%u  出core程序的uid
%s  造成core的signal號
%t  出core的時間,從1970-01-0100:00:00開始的秒數
%e  出core程序對應的可執行檔名
2.ulimit –c命令,此命令可以顯示當前os對於core檔案大小的限制,如果為0,則表示不允許產生core檔案。如果想進行修改,可以使用:

ulimit –cn

其中n為數字,表示允許core檔案體積的最大值,單位為kb,如果想設為無限大,可以執行:

ulimit -cunlimited

產生了core檔案之後,就是如何檢視core檔案,並確定問題所在,進行修復。為此,我們不妨先來看看core檔案的格式,多了解一些core檔案。

首先可以明確一點,core檔案的格式elf格式,這一點可以通過使用readelf -h命令來證實,如下圖:

從讀出來的elf頭資訊可以看到,此檔案型別為core檔案,那麼readelf是如何得知的呢?可以從下面的資料結構中窺得一二:

其中當值為4的時候,表示當前檔案為core檔案。如此,整個過程就很清楚了。

了解了這些之後,我們來看看如何閱讀core檔案,並從中追查bug。在linux下,一般讀取core的命令為:

gdb exec_file core_file

使用gdb,先從可執行檔案中讀取符號表資訊,然後讀取core檔案。如果不與可執行檔案攪合在一起可以嗎?答案是不行,因為core檔案中沒有符號表資訊,無法進行除錯,可以使用如下命令來驗證:

objdump –x core_file | tail

我們看到如下兩行資訊:

symbol table:

no symbols

表明當前的elf格式檔案中沒有符號表資訊。

為了解釋如何看core中資訊,我們來舉乙個簡單的例子:

#include 「stdio.h」

int main()

這段程式使用gcc –g a.c –o a進行編譯,執行後直接會core掉,使用gdb a core_file檢視棧資訊,可見其core在了這行**:

int stack_of[100000000];

原因很明顯,直接在棧上申請如此大的陣列,導致棧空間溢位,觸犯了os對於棧空間大小的限制,所以出core(這裡是否出core還和os對棧空間的大小配置有關,一般為8m)。但是這裡要明確一點,真正出core的**不是分配棧空間的int stack_of[100000000], 而是後面這句int b=1, 為何?出core的一種原因是因為對記憶體的非法訪問,在上面的**中分配陣列stack_of時並未訪問它,但是在其後宣告變數並賦值,就相當於進行了越界訪問,繼而出core。為了解釋得更詳細些,讓我們使用gdb來看一下出core的地方,使用命令gdb a core_file可見:

可知程式出現了段錯誤「segmentation fault」, **是int b=1這句。我們來檢視一下當前的棧資訊:

其中可見指令指標rip指向位址為0×400473, 我們來看下當前的指令是什麼:

這條movl指令要把立即數1送到0xffffffffe8287bfc(%rbp)這個位址去,其中rbp儲存的是幀指標,而0xffffffffe8287bfc很明顯是乙個負數,結果計算為-400000004。這就可以解釋了:其中我們申請的int stack_of[100000000]占用400000000位元組,b是int型別,占用4個位元組,且棧空間是由高位址向低位址延伸,那麼b的棧位址就是0xffffffffe8287bfc(%rbp),也就是$rbp-400000004。當我們嘗試訪問此位址時:

可以看到無法訪問此記憶體位址,這是因為它已經超過了os允許的範圍。

下面我們把程式進行改進:

#include 「stdio.h」

int main()

使用gcc –o3 –g a.c –o a進行編譯,執行後會再次core掉,使用gdb檢視棧資訊,請見下圖:

可見bug出在第7行,也就是*a=b這句,這時我們嘗試列印b的值,卻發現符號表中找不到b的資訊。為何?原因在於gcc使用了-o3引數,此引數可以對程式進行優化,乙個負面效應是優化過程中會捨棄部分區域性變數,導致除錯時出現困難。在我們的**中,b宣告時即賦值,隨後用於為*a賦值。優化後,此變數不再需要,直接為*a賦值為1即可,如果彙編級**上講,此優化可以減少一條mov語句,節省乙個暫存器。

此時我們的除錯資訊已經出現了一些扭曲,為此我們重新編譯源程式,去掉-o3引數(這就解釋了為何一些大型軟體都會有debug版本存在,因為debug是未經優化的版本,包含了完整的符號表資訊,易於除錯),並重新執行,得到新的core並檢視,如下圖:

這次就比較明顯了,b中的值沒有問題,有問題的是a,其指向的位址是非法區域,也就是a沒有分配記憶體導致的core。當然,本例中的問題其實非常明顯,幾乎一眼就能看出來,但不妨礙它成為乙個例子,用來解釋在看core過程中,需要注意的一些問題。

【本文首發於:

搜尋研發部官方部落格】

Linux上Core Dump檔案的形成和分析

core,又稱之為 core dump檔案 是unix linux 作業系統的一種機制,對於線上服務而言,core令人聞之色變,因為出core的過程意味著服務暫時不能正常響應,需要恢復,並且隨著吐core程序的記憶體空間越大,此過程可能持續很長一段時間 例如當程序占用60g 以上記憶體時,完整cor...

Linux上Core Dump檔案的形成和分析

core,又稱之為core dump檔案,是unix linux作業系統的一種機制,對於線上服務而言,core令人聞之色變,因為出core的過程意味著服務暫時不能正常響應,需要恢復,並且隨著吐core程序的記憶體空間越大,此過程可能持續很長一段時間 例如當程序占用60g 以上記憶體時,完整core檔...

linux開啟core dump自動生成

使用c c 語言開發程式時,當程式crash的時候產生core dump檔案對於除錯程式是很有幫助的。在redhat linux系統中預設是不生成core dump檔案的,這是因為在 etc profile檔案中有這樣一行 ulimit s c 0 dev null 2 1 如何開啟core dum...