如何調(diào)試沒有core文件的coredump

摘要:在我們往期對(duì)coredump的分析中,是依賴于core文件的,而core文件中也幾乎包含了程序當(dāng)前的所有狀態(tài)(堆棧、內(nèi)存、寄存器等)。然而在實(shí)際的線上環(huán)境中,由于core文件太大、保存core文件耗時(shí)太久,出于線上系統(tǒng)的穩(wěn)定性與快速恢復(fù)考慮,我們往往不會(huì)保留core文件。同時(shí),程序堆棧被破壞的情況下,即使我們保留了core文件,也無(wú)法準(zhǔn)確獲取程序崩潰時(shí)準(zhǔn)確的上下文信息。本文主要介紹在不保留core文件的情況下,如何獲取程序崩潰時(shí)候的上下文信息(主要是函數(shù)調(diào)用棧)。

## 1.coredump原理

當(dāng)程序發(fā)生內(nèi)存越界訪問(wèn)等行為時(shí),會(huì)觸發(fā)OS的保護(hù)機(jī)制,此時(shí)OS會(huì)產(chǎn)生一個(gè)信號(hào)(signal)發(fā)送給對(duì)應(yīng)的進(jìn)程。當(dāng)進(jìn)程從內(nèi)核態(tài)到用戶態(tài)切換時(shí),該進(jìn)程會(huì)處理這個(gè)信號(hào)。此類信號(hào)(比如SEGV)的默認(rèn)處理行為生成一個(gè)coredump文件。

這里會(huì)涉及以下幾個(gè)問(wèn)題:

1. 保存的core文件在什么地方?

2. core文件,具體會(huì)把進(jìn)程地址空間的哪些內(nèi)容保存下來(lái)?

3. 如何控制core文件的大???

4. 如果在處理信號(hào)的時(shí)候,又產(chǎn)生了新的同類信號(hào),該如何處理?

5. 處理信號(hào)的代碼,是運(yùn)行在用戶態(tài)還是內(nèi)核態(tài)?

6. 在一個(gè)多線程的程序中,是由哪個(gè)線程在處理這個(gè)信號(hào)?

問(wèn)題4~問(wèn)題6是信號(hào)處理的相關(guān)內(nèi)容,我們不在這里解釋,會(huì)在信號(hào)處理的章節(jié)詳細(xì)分析。問(wèn)題1~問(wèn)題3解釋如下

* `/proc/sys/kernel/core_pattern` 指定core文件存儲(chǔ)的位置,缺省值是`core`,表示將core文件存儲(chǔ)到當(dāng)前目錄。這個(gè)pattern是可以定制的,模式如下:

```

%p? 出Core進(jìn)程的PID

%u? 出Core進(jìn)程的UID

%s? 造成Core的signal號(hào)

%t? 出Core的時(shí)間,從1970-01-0100:00:00開始的秒數(shù)

%e? 出Core進(jìn)程對(duì)應(yīng)的可執(zhí)行文件名

```

* `/proc/sys/kernel/core_uses_pid` 取值是0或者1,表示是否在core文件名字后面加上進(jìn)程號(hào)

* `/proc/$pid/coredump_filter` 設(shè)置那些內(nèi)存會(huì)被dump出來(lái)

```

? ? ? ? ? bit 0? Dump anonymous private mappings.

? ? ? ? ? bit 1? Dump anonymous shared mappings.

? ? ? ? ? bit 2? Dump file-backed private mappings.

? ? ? ? ? bit 3? Dump file-backed shared mappings.

? ? ? ? ? bit 4 (since Linux 2.6.24)

? ? ? ? ? ? ? ? ? Dump ELF headers.

? ? ? ? ? bit 5 (since Linux 2.6.28)

? ? ? ? ? ? ? ? ? Dump private huge pages.

? ? ? ? ? bit 6 (since Linux 2.6.28)

? ? ? ? ? ? ? ? ? Dump shared huge pages.

```

* `ulimit? -c ` 決定save的core文件大小限制

## 2.自定義信號(hào)處理函數(shù)

我們需要在自定義的信號(hào)處理函數(shù)中打印出程序崩潰時(shí)候的活躍函數(shù)堆棧信息。這里我們有兩種方式:1.使用backtrace等方法,讀取進(jìn)程堆棧上的信息;2.在函數(shù)調(diào)用的同時(shí),用戶自己維護(hù)一套數(shù)據(jù)結(jié)構(gòu),用于保存函數(shù)調(diào)用鏈,在信號(hào)處理函數(shù)中,將這個(gè)函數(shù)調(diào)用鏈打印出來(lái)。

### 2.1使用backtrace獲取函數(shù)調(diào)用鏈

在[從匯編語(yǔ)言看函數(shù)調(diào)用](http://www.uufool.com/?p=54)和[棧破壞下的coredump分析方法](http://www.uufool.com/?p=78)兩篇文章中,我們知道進(jìn)程堆棧上保存了rbp寄存器對(duì)應(yīng)的list。backtrace本質(zhì)上就是利用進(jìn)程堆棧上的數(shù)據(jù),推斷出來(lái)的當(dāng)前函數(shù)調(diào)用鏈。這里我們不分析backtrace的源碼,直接給出關(guān)鍵性質(zhì)的代碼。

```cpp

void dump_trace(int Signal)

{

? ? const int len = 200;

? ? void* buffer[len];

? ? printf("dump_trace\n");

? ? int nptrs = ::backtrace(buffer, len);

? ? printf("backtrace\n");

? ? char** buffer_array = ::backtrace_symbols(buffer, nptrs);

? ? printf("sig:%d nptrs:%d\n", Signal, nptrs);

? ? if (buffer_array) {

? ? ? ? for (int i = 0; i < nptrs; ++i) {

? ? ? ? ? ? printf("frame=%d||trace_back=%s||\n", i, buffer_array[i]);

? ? ? ? }

? ? ? ? free(buffer_array);

? ? }

? ? exit(0);

}

signal(SIGSEGV, dump_trace);//注冊(cè)信號(hào)處理函數(shù)

```

完整的代碼可以參考[這里](https://github.com/yukun89/draft/tree/master/dump)。利用signal函數(shù),我們將dump_trace注冊(cè)為SIGSEGV的信號(hào)處理函數(shù),來(lái)取代默認(rèn)的保存core文件的行為。

### 2.2 用戶自己維護(hù)一個(gè)函數(shù)調(diào)用鏈

為什么我們需要費(fèi)力自己去維護(hù)一個(gè)函數(shù)調(diào)用鏈而不是直接調(diào)用backtrace呢? 因?yàn)橛龅竭M(jìn)程堆棧被寫花的時(shí)候,我們是無(wú)法找到完整的函數(shù)調(diào)用棧信息的。自己去維護(hù)函數(shù)調(diào)用鏈的原理如下:維護(hù)一個(gè)堆棧,在函數(shù)調(diào)用的時(shí)候,將調(diào)用的函數(shù)入棧;函數(shù)調(diào)用結(jié)束時(shí),將這個(gè)函數(shù)出棧。這樣當(dāng)coredump發(fā)生時(shí),即使進(jìn)程堆棧被破壞的情況下,這個(gè)用戶自定義的函數(shù)堆棧中依然保存了函數(shù)調(diào)用鏈的信息。

那么如何在函數(shù)調(diào)用的開始和結(jié)束執(zhí)行對(duì)應(yīng)的操作呢?g++/gcc正好提供了這種功能,能夠讓我們?cè)诤瘮?shù)的開始和結(jié)束嵌入對(duì)應(yīng)的代碼。我們需要做的僅僅是實(shí)現(xiàn)兩個(gè)預(yù)先聲明的函數(shù),核心代碼如下

```cpp

#ifdef __cplusplus

extern "C" {

#endif

void __attribute__((no_instrument_function))

__cyg_profile_func_enter(void *this_func, void *call_site);

void __attribute__((no_instrument_function))

__cyg_profile_func_exit(void *this_func, void *call_site);

#ifdef __cplusplus

};

#endif

void __cyg_profile_func_enter(void *this_func, void *call_site)

{

? ? char buffer[64] = {0};

? ? int len = snprintf(buffer, 60, "%p call %p", this_func, call_site);

? ? std::string content = std::string(buffer);

? ? call_list.push(content);

? ? return ;

}

void __cyg_profile_func_exit(void *this_func, void *call_site)

{

? ? call_list.pop();

? ? return ;

}

```

代碼解釋:`void __cyg_profile_func_enter(void *this_func, void *call_site)` 函數(shù)有兩個(gè)參數(shù),第一參數(shù)表示調(diào)用方的地址,第二個(gè)參數(shù)表示被調(diào)用方的地址。需要注意的有以下幾點(diǎn):

* `__cyg_profile_func_enter`和`__cyg_profile_func_exit`這兩個(gè)函數(shù)本身是需要設(shè)置屬性`no_instrument_function`的;否則會(huì)陷入對(duì)這兩個(gè)函數(shù)本身的無(wú)限遞歸調(diào)用。

* 為了將上述代碼嵌入到每個(gè)函數(shù)的開始和結(jié)束,需要在編譯代碼的時(shí)候使用特定的編譯參數(shù)`-finstrument-functions`

* 上述代碼所在的編譯單元是必須不能使用`-finstrument-functions`的:否則會(huì)陷入循環(huán)調(diào)用(想想為什么)

相關(guān)demo代碼的說(shuō)明在[這里](https://github.com/yukun89/draft/tree/master/dump),大家可以自行測(cè)試。我們只給出函數(shù)在crash時(shí)候的輸出結(jié)果。對(duì)于文中的地址信息,我們可以使用addr2line獲得這些地址對(duì)應(yīng)的源文件地址。

```shell

sig:11

frame_0: 0x401172 call 0x4011f0

frame_1: 0x401172 call 0x4011e1

frame_2: 0x401172 call 0x4011f0

frame_3: 0x401172 call 0x4011e1

frame_4: 0x401172 call 0x4011f0

frame_5: 0x401172 call 0x40129f

frame_6: 0x40123e call 0x7f6e0bf52b15

```

## 3.coredump的各種可能性

綜合前面所講解的所有coredump的種類,我們總結(jié)coredump的各種可能性如下:

- 內(nèi)存訪問(wèn)越界

? + 下標(biāo)導(dǎo)致的數(shù)組訪問(wèn)越界

+ 字符串不包含對(duì)應(yīng)結(jié)束符導(dǎo)致的越界訪問(wèn)

+ 使用strcpy, strcat, sprintf, strcmp,strcasecmp等字符串操作函數(shù),將目標(biāo)字符串讀/寫爆

2. 多線程數(shù)據(jù)未進(jìn)行加鎖保護(hù):STL容器vector、map等都是非線程安全的

3. 指針相關(guān)

? + 空指針解引用

? + 非法的指針轉(zhuǎn)化

? + use after free

? + double free

4. 堆棧相關(guān)

? + 棧變量過(guò)大導(dǎo)致的堆棧溢出

? + 棧變量的非法寫入,導(dǎo)致程序調(diào)用棧被破壞無(wú)法回溯

原文發(fā)表于:[如何調(diào)試沒有core文件的coredump](http://www.uufool.com/?p=151) 更多內(nèi)容請(qǐng)?jiān)L問(wèn)[優(yōu)孚網(wǎng)](www.uufool.com)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Lua 5.1 參考手冊(cè) by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 14,243評(píng)論 0 38
  • 什么是coredump Coredump叫做核心轉(zhuǎn)儲(chǔ),它是進(jìn)程運(yùn)行時(shí)在突然崩潰的那一刻的一個(gè)內(nèi)存快照。操作系統(tǒng)在程...
    java菜閱讀 4,325評(píng)論 0 4
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,629評(píng)論 1 32
  • 無(wú)意中翻看歷史新聞,得知在2016年十月房?jī)r(jià)新聞曝過(guò)蘇州房?jī)r(jià)猛跌的情況,這個(gè)事自然事出有因,但是什么因我等布衣是...
    饅頭仙子閱讀 492評(píng)論 15 22
  • 問(wèn)題來(lái)源 舉個(gè)例子 如上,為啥0.1+0.2加出來(lái)不是0.3呢? 原因分析 解決辦法 淺談JavaScript浮點(diǎn)...
    鴨梨山大哎閱讀 392評(píng)論 0 2

友情鏈接更多精彩內(nèi)容