linux-coredump

Core,又稱之爲Core Dump文件,是Unix/Linux操做系統的一種機制,對於線上服務而言,Core使人聞之色變,由於出Core的過程意味着服務暫時不能正常響應,須要恢復,而且隨着吐Core進程的內存空間越大,此過程可能持續很長一段時間(例如當進程佔用60G+以上內存時,完整Core文件須要15分鐘才能徹底寫到磁盤上),這期間產生的流量損失,不可估量。linux

凡事皆有兩面性,OS在出Core的同時,雖然會終止掉當前進程,可是也會保留下第一手的現場數據,OS彷彿是一架被按下快門的相機,而照片就是產出的Core文件。裏面含有當進程被終止時內存、CPU寄存器等信息,能夠供後續開發人員進行調試。
關於Core產生的緣由不少,好比過去一些Unix的版本不支持現代Linux上這種GDB直接附着到進程上進行調試的機制,須要先向進程發送終止信號,而後用工具閱讀core文件。在Linux上,咱們就可使用kill向一個指定的進程發送信號或者使用gcore命令來使其主動出Core並退出。若是從淺層次的緣由上來說,出Core意味着當前進程存在BUG,須要程序員修復。從深層次的緣由上講,是當前進程觸犯了某些OS層級的保護機制,逼迫OS向當前進程發送諸如SIGSEGV(即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 –cnide

其中n爲數字,表示容許Core文件體積的最大值,單位爲Kb,若是想設爲無限大,能夠執行:工具

Ulimit -cunlimited優化

產生了Core文件以後,就是如何查看Core文件,並肯定問題所在,進行修復。爲此,咱們不妨先來看看Core文件的格式,多瞭解一些Core文件。操作系統

首先能夠明確一點,Core文件的格式ELF格式,這一點能夠經過使用readelf -h命令來證明,以下圖:
linux-coredump
從讀出來的ELF頭信息能夠看到,此文件類型爲Core文件,那麼readelf是如何得知的呢?能夠從下面的數據結構中窺得一二:debug

linux-coredump
其中當值爲4的時候,表示當前文件爲Core文件。如此,整個過程就很清楚了。3d

瞭解了這些以後,咱們來看看如何閱讀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(){

int stack_of[100000000];

int b=1;

int* a;

*a=b;

}

這段程序使用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可見:
linux-coredump
可知程序出現了段錯誤「Segmentation fault」, 代碼是int b=1這句。咱們來查看一下當前的棧信息:
linux-coredump
其中可見指令指針rip指向地址爲0×400473, 咱們來看下當前的指令是什麼:
linux-coredump
這條movl指令要把當即數1送到0xffffffffe8287bfc(%rbp)這個地址去,其中rbp存儲的是幀指針,而0xffffffffe8287bfc很明顯是一個負數,結果計算爲-400000004。這就能夠解釋了:其中咱們申請的int stack_of[100000000]佔用400000000字節,b是int類型,佔用4個字節,且棧空間是由高地址向低地址延伸,那麼b的棧地址就是0xffffffffe8287bfc(%rbp),也就是$rbp-400000004。當咱們嘗試訪問此地址時:
linux-coredump
能夠看到沒法訪問此內存地址,這是由於它已經超過了OS容許的範圍。

下面咱們把程序進行改進:

#include 「stdio.h」

int main(){

int stack_of = malloc(sizeof(int)100000000);

int b=1;

int* a;

*a=b;

}

使用gcc –O3 –g a.c –o a進行編譯,運行後會再次Core掉,使用gdb查看棧信息,請見下圖:
linux-coredump
可見BUG出在第7行,也就是a=b這句,這時咱們嘗試打印b的值,卻發現符號表中找不到b的信息。爲什麼?緣由在於gcc使用了-O3參數,此參數能夠對程序進行優化,一個負面效應是優化過程當中會捨棄部分局部變量,致使調試時出現困難。在咱們的代碼中,b聲明時即賦值,隨後用於爲a賦值。優化後,此變量再也不須要,直接爲*a賦值爲1便可,若是彙編級代碼上講,此優化能夠減小一條MOV語句,節省一個寄存器。

此時咱們的調試信息已經出現了一些扭曲,爲此咱們從新編譯源程序,去掉-O3參數(這就解釋了爲什麼一些大型軟件都會有debug版本存在,由於debug是未經優化的版本,包含了完整的符號表信息,易於調試),並從新運行,獲得新的core並查看,以下圖:
linux-coredump此次就比較明顯了,b中的值沒有問題,有問題的是a,其指向的地址是非法區域,也就是a沒有分配內存致使的Core。固然,本例中的問題其實很是明顯,幾乎一眼就能看出來,但不妨礙它成爲一個例子,用來解釋在看Core過程當中,須要注意的一些問題。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息