如何调试没有core文件的coredump

摘要:在我们往期对coredump的分析中,是依赖于core文件的,而core文件中也几乎包含了程序当前的所有状态(堆栈、内存、寄存器等)。然而在实际的线上环境中,由于core文件太大、保存core文件耗时太久,出于线上系统的稳定性与快速恢复考虑,我们往往不会保留core文件。同时,程序堆栈被破坏的情况下,即使我们保留了core文件,也无法准确获取程序崩溃时准确的上下文信息。本文主要介绍在不保留core文件的情况下,如何获取程序崩溃时候的上下文信息(主要是函数调用栈)。

## 1.coredump原理

当程序发生内存越界访问等行为时,会触发OS的保护机制,此时OS会产生一个信号(signal)发送给对应的进程。当进程从内核态到用户态切换时,该进程会处理这个信号。此类信号(比如SEGV)的默认处理行为生成一个coredump文件。

这里会涉及以下几个问题:

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

2. core文件,具体会把进程地址空间的哪些内容保存下来?

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

4. 如果在处理信号的时候,又产生了新的同类信号,该如何处理?

5. 处理信号的代码,是运行在用户态还是内核态?

6. 在一个多线程的程序中,是由哪个线程在处理这个信号?

问题4~问题6是信号处理的相关内容,我们不在这里解释,会在信号处理的章节详细分析。问题1~问题3解释如下

* `/proc/sys/kernel/core_pattern` 指定core文件存储的位置,缺省值是`core`,表示将core文件存储到当前目录。这个pattern是可以定制的,模式如下:

```

%p  出Core进程的PID

%u  出Core进程的UID

%s  造成Core的signal号

%t  出Core的时间,从1970-01-0100:00:00开始的秒数

%e  出Core进程对应的可执行文件名

```

* `/proc/sys/kernel/core_uses_pid` 取值是0或者1,表示是否在core文件名字后面加上进程号

* `/proc/$pid/coredump_filter` 设置那些内存会被dump出来

```

          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.自定义信号处理函数

我们需要在自定义的信号处理函数中打印出程序崩溃时候的活跃函数堆栈信息。这里我们有两种方式:1.使用backtrace等方法,读取进程堆栈上的信息;2.在函数调用的同时,用户自己维护一套数据结构,用于保存函数调用链,在信号处理函数中,将这个函数调用链打印出来。

### 2.1使用backtrace获取函数调用链

在[从汇编语言看函数调用](http://www.uufool.com/?p=54)和[栈破坏下的coredump分析方法](http://www.uufool.com/?p=78)两篇文章中,我们知道进程堆栈上保存了rbp寄存器对应的list。backtrace本质上就是利用进程堆栈上的数据,推断出来的当前函数调用链。这里我们不分析backtrace的源码,直接给出关键性质的代码。

```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);//注册信号处理函数

```

完整的代码可以参考[这里](https://github.com/yukun89/draft/tree/master/dump)。利用signal函数,我们将dump_trace注册为SIGSEGV的信号处理函数,来取代默认的保存core文件的行为。

### 2.2 用户自己维护一个函数调用链

为什么我们需要费力自己去维护一个函数调用链而不是直接调用backtrace呢? 因为遇到进程堆栈被写花的时候,我们是无法找到完整的函数调用栈信息的。自己去维护函数调用链的原理如下:维护一个堆栈,在函数调用的时候,将调用的函数入栈;函数调用结束时,将这个函数出栈。这样当coredump发生时,即使进程堆栈被破坏的情况下,这个用户自定义的函数堆栈中依然保存了函数调用链的信息。

那么如何在函数调用的开始和结束执行对应的操作呢?g++/gcc正好提供了这种功能,能够让我们在函数的开始和结束嵌入对应的代码。我们需要做的仅仅是实现两个预先声明的函数,核心代码如下

```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)` 函数有两个参数,第一参数表示调用方的地址,第二个参数表示被调用方的地址。需要注意的有以下几点:

* `__cyg_profile_func_enter`和`__cyg_profile_func_exit`这两个函数本身是需要设置属性`no_instrument_function`的;否则会陷入对这两个函数本身的无限递归调用。

* 为了将上述代码嵌入到每个函数的开始和结束,需要在编译代码的时候使用特定的编译参数`-finstrument-functions`

* 上述代码所在的编译单元是必须不能使用`-finstrument-functions`的:否则会陷入循环调用(想想为什么)

相关demo代码的说明在[这里](https://github.com/yukun89/draft/tree/master/dump),大家可以自行测试。我们只给出函数在crash时候的输出结果。对于文中的地址信息,我们可以使用addr2line获得这些地址对应的源文件地址。

```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的种类,我们总结coredump的各种可能性如下:

- 内存访问越界

  + 下标导致的数组访问越界

+ 字符串不包含对应结束符导致的越界访问

+ 使用strcpy, strcat, sprintf, strcmp,strcasecmp等字符串操作函数,将目标字符串读/写爆

2. 多线程数据未进行加锁保护:STL容器vector、map等都是非线程安全的

3. 指针相关

  + 空指针解引用

  + 非法的指针转化

  + use after free

  + double free

4. 堆栈相关

  + 栈变量过大导致的堆栈溢出

  + 栈变量的非法写入,导致程序调用栈被破坏无法回溯

原文发表于:[如何调试没有core文件的coredump](http://www.uufool.com/?p=151) 更多内容请访问[优孚网](www.uufool.com)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容