CPP 內(nèi)存Core Dump

利用mprotect+backtrace定位故障

利用mprotect保護(hù)椙迮空間:
在操作系統(tǒng)中,進(jìn)程的椬馇模空間(X86_64)默認(rèn)大小:8192KB恩袱。發(fā)生棧溢出時(shí)泣棋,會(huì)產(chǎn)色段錯(cuò)誤。但在協(xié)程中時(shí)畔塔,由于是用戶態(tài)潭辈,保證數(shù)據(jù)安全鸯屿,需要手動(dòng)的對(duì)棧尾進(jìn)行保護(hù)。

  • 利用mprotect保護(hù)棧的Bottom的 page把敢,不允許讀和寫(xiě)

  • 安裝SIGSEGV的信號(hào)處理函數(shù)寄摆,發(fā)生stack overflow時(shí)記錄更多的信息

  • SIGSEGV handler中需要記錄發(fā)生的:addr 及 backtrace

  • Boost中分配具有mprotect的棧

// use mmap/mprotect to allocate 512k coroutine stacks
auto make_stack_allocator() {
  return boost::context::protected_fixedsize_stack{512*1024};
}
  • 安裝信號(hào)處理函數(shù)
    注意此處利用本地棧空間修赞,多線程環(huán)境下婶恼,每個(gè)線程需要獨(dú)立的分配棧空間柏副,重裝信號(hào)處理函數(shù)勾邦。
stack_t segv_stack;
segv_stack.ss_sp = valloc(SEGV_STACK_SIZE);
segv_stack.ss_flags = 0;
segv_stack.ss_size = SEGV_STACK_SIZE;
sigaltstack(&segv_stack, NULL);

struct sigaction action;
bzero(&action, sizeof(action));
action.sa_flags = SA_SIGINFO|SA_STACK;
action.sa_sigaction = &sigsegv_handler;
sigaction(SIGSEGV, &action, NULL);
  • 信號(hào)處理函數(shù)
static void sigsegv_handler(int signum, siginfo_t *info, void *data) {
    std::cout<<" Segment Fault"<<std::endl;
    void *addr = info->si_addr;
    char buff[256];
    int fd = open("./sigsegv.bt",O_CREAT|O_RDWR|O_APPEND);
    int len = snprintf(buff,256,"Addr: %p\n",addr);
    write(fd,buff,len);
    void* array[30];
    size_t size = backtrace(array,30);
    backtrace_symbols_fd(array,size,fd);
    close(fd);
}

測(cè)試?yán)胢protect backtrace定位core dump

原理:

  • 對(duì)可能產(chǎn)生內(nèi)存越界或者內(nèi)存無(wú)效訪問(wèn)的區(qū)域做mprotect保護(hù)
  • 重新定義SIGSEGV信號(hào)的處理函數(shù),對(duì)此保護(hù)區(qū)域的內(nèi)存讀寫(xiě)都會(huì)產(chǎn)生SIGSEGV信號(hào)割择,在此信號(hào)處理函數(shù)中記錄調(diào)用棧
  • mprotect需要頁(yè)對(duì)齊眷篇,valloc分配的是也對(duì)齊的地址,對(duì)于椣峭疲空間可以使用:
    char* buffer = (char*)(((int64_t)p) & ~(ps-1)) //ps是pagesize
    此處buffer是最靠近地址p的頁(yè)對(duì)齊的地址
  • mprotect保護(hù)的區(qū)域铅歼,釋放時(shí)需要恢復(fù)
// SA_RESETHAND: 表示在信號(hào)處理函數(shù)入口處恢復(fù)信號(hào)的默認(rèn)處理句柄,否則SIGSEGV信號(hào)會(huì)無(wú)現(xiàn)產(chǎn)生下去 //或者修復(fù)導(dǎo)致SIGSEGV信號(hào)的問(wèn)題换可,可以繼續(xù)運(yùn)行
struct sigaction action;
bzero(&action, sizeof(action));
action.sa_flags = SA_SIGINFO|SA_RESETHAND;
action.sa_sigaction = &sigsegv_handler;
sigaction(SIGSEGV, &action, NULL);

信號(hào)處理函數(shù),利用backtrace跟蹤程序的調(diào)用棧:

static void sigsegv_handler(int signum, siginfo_t *info, void *data) {
    std::cout<<" Segment Fault"<<std::endl;
    void *addr = info->si_addr;
    char buff[256];
    int fd = open("./sigsegv.bt",O_CREAT|O_RDWR|O_APPEND);
    int len = snprintf(buff,256,"Addr: %p\n",addr);
    write(fd,buff,len);
    void* array[30];
    size_t size = backtrace(array,30);
    backtrace_symbols_fd(array,size,fd);
    close(fd);
}

測(cè)試代碼:

#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <sys/mman.h>
#include <execinfo.h>
#include <cstring>
#include <fcntl.h>
#include <signal.h>
#include <iostream>

static void sigsegv_handler(int signum, siginfo_t *info, void *data) {
    std::cout<<" Segment Fault"<<std::endl;
    void *addr = info->si_addr;
    char buff[256];
    int fd = open("./sigsegv.bt",O_CREAT|O_RDWR|O_APPEND);
    int len = snprintf(buff,256,"Addr: %p\n",addr);
    write(fd,buff,len);
    void* array[30];
    size_t size = backtrace(array,30);
    backtrace_symbols_fd(array,size,fd);
    close(fd);
}

void register_signal(int signum){
    struct sigaction action;
    bzero(&action, sizeof(action));
    sigemptyset(&action.sa_mask);
    action.sa_flags = SA_SIGINFO | SA_RESETHAND;
    action.sa_sigaction = &sigsegv_handler;
    sigaction(signum, &action, NULL);
}

void* core_func(long stack_size){
    void *stack = valloc(stack_size);
    mprotect(stack, getpagesize(), PROT_NONE);
    return stack;
}

void destroy(void* stack){
    mprotect(stack, getpagesize(), PROT_READ|PROT_WRITE);
    free(stack);
}


int main(int argc,char* argv[]){
    register_signal(SIGSEGV);
    void* stack = core_func(1024);
    std::cout<<"Write"<<std::endl;
    *(char *)(stack+10) = 'a';
    std::cout<<"Write Done"<<std::endl;
    destroy(stack);
}

運(yùn)行結(jié)果:

# ./mp 
Write
 Segment Fault
段錯(cuò)誤
# cat sigsegv.bt 

Addr: 0x97e00a
./mp[0x400c4e]
/lib64/libc.so.6(+0x36400)[0x7f4f1f84f400]
./mp[0x400da4]
/lib64/libc.so.6(__libc_start_main+0xf5)[0x7f4f1f83b555]
./mp[0x400ae9]

利用addr2line定位:

# addr2line -afiCe mp 0x400d3a
0x0000000000400d3a
main
/home/working/cpp/test_mp.cc:48

利用valgrind定位:

# valgrind --leak-check=full ./mp 
==9359== Memcheck, a memory error detector
==9359== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==9359== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==9359== Command: ./mp
==9359== 
Write
 Segment Fault
==9359== 
==9359== Process terminating with default action of signal 11 (SIGSEGV)
==9359==  Bad permissions for mapped region at address 0x5AB400A
==9359==    at 0x400DA4: main (test_mp.cc:49)
==9359== 
==9359== HEAP SUMMARY:
==9359==     in use at exit: 1,024 bytes in 1 blocks
==9359==   total heap usage: 3 allocs, 2 frees, 73,784 bytes allocated
==9359== 
==9359== 1,024 bytes in 1 blocks are definitely lost in loss record 1 of 1
==9359==    at 0x4C2C375: memalign (vg_replace_malloc.c:908)
==9359==    by 0x4C2C40A: valloc (vg_replace_malloc.c:956)
==9359==    by 0x400CFB: core_func(long) (test_mp.cc:34)
==9359==    by 0x400D7B: main (test_mp.cc:47)
==9359== 
==9359== LEAK SUMMARY:
==9359==    definitely lost: 1,024 bytes in 1 blocks
==9359==    indirectly lost: 0 bytes in 0 blocks
==9359==      possibly lost: 0 bytes in 0 blocks
==9359==    still reachable: 0 bytes in 0 blocks
==9359==         suppressed: 0 bytes in 0 blocks
==9359== 
==9359== For lists of detected and suppressed errors, rerun with: -s
==9359== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
段錯(cuò)誤

利用gcc asan內(nèi)存檢測(cè):
asan可以檢測(cè):

  • 檢查地址相關(guān)問(wèn)題厦幅,包括釋放后使用沾鳄、重復(fù)釋放、堆溢出确憨、棧溢出等等問(wèn)題
  • 檢查內(nèi)存泄漏問(wèn)題
  • 檢查線程數(shù)據(jù)競(jìng)爭(zhēng)和死鎖問(wèn)題
# g++ --std=c++11 -g -o mp test_mp.cc  -fsanitize=address
# ./mp 
Write
ASAN:DEADLYSIGNAL
=================================================================
==10792==ERROR: AddressSanitizer: SEGV on unknown address 0x62500000100a (pc 0x00000040160b bp 0x7ffc4c685c60 sp 0x7ffc4c685c40 T0)
==10792==The signal is caused by a WRITE memory access.
    #0 0x40160a in main /home/working/cpp/test_mp.cc:49
    #1 0x7f1570d02554 in __libc_start_main (/lib64/libc.so.6+0x22554)
    #2 0x401038  (/home/working/cpp/mp+0x401038)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/working/cpp/test_mp.cc:49 in main
==10792==ABORTING

addr2line

可以定位出現(xiàn)core dump的代碼位置译荞。用例如下:

示例代碼,overflow休弃,段錯(cuò)誤吞歼。

int main(void) {
    char *str;

    /* Stored in read only part of data segment */
    str = "over flow";

    /* Problem:  trying to modify read only memory */
    *(str + 1) = 'n';

    return 0;
}

產(chǎn)生

編譯:
g++ -ggdb -o test test_mprotect
dmesg -C #清空緩存日志./test # 產(chǎn)生core dump`

查看dmesg信息:

# dmesg 
[3381707.691606] test[19966]: segfault at 4005a5 ip 000000000040050b sp 00007ffd68b27aa0 error 7 in test[400000+1000]

可見(jiàn)出現(xiàn)core dump的位置位于: 000000000040050b
利用addr2line定位代碼位置:

addr2line -afiCe test 000000000040050b
0x000000000040050b
main
/home/working/cpp/test_mprotect.cc:8
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市塔猾,隨后出現(xiàn)的幾起案子篙骡,更是在濱河造成了極大的恐慌,老刑警劉巖丈甸,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糯俗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡睦擂,警方通過(guò)查閱死者的電腦和手機(jī)得湘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)顿仇,“玉大人淘正,你說(shuō)我怎么就攤上這事摆马。” “怎么了鸿吆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵囤采,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我伞剑,道長(zhǎng)斑唬,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任黎泣,我火速辦了婚禮恕刘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抒倚。我一直安慰自己褐着,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布托呕。 她就那樣靜靜地躺著含蓉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪项郊。 梳的紋絲不亂的頭發(fā)上馅扣,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音着降,去河邊找鬼差油。 笑死,一個(gè)胖子當(dāng)著我的面吹牛任洞,可吹牛的內(nèi)容都是我干的蓄喇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼交掏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼妆偏!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起盅弛,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤钱骂,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后熊尉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體罐柳,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年狰住,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了张吉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡催植,死狀恐怖肮蛹,靈堂內(nèi)的尸體忽然破棺而出勺择,到底是詐尸還是另有隱情,我是刑警寧澤伦忠,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布省核,位于F島的核電站,受9級(jí)特大地震影響昆码,放射性物質(zhì)發(fā)生泄漏气忠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一赋咽、第九天 我趴在偏房一處隱蔽的房頂上張望旧噪。 院中可真熱鬧,春花似錦脓匿、人聲如沸淘钟。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)米母。三九已至,卻和暖如春毡琉,著一層夾襖步出監(jiān)牢的瞬間铁瞒,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工桅滋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留精拟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓虱歪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親栅表。 傳聞我的和親對(duì)象是個(gè)殘疾皇子笋鄙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355