segfault分析方法

相信写c/c++程序的coder, segmentation fault的问题碰到不少,最近写scylla 测试程序也会经常segv或异常,特意研究和总结segfault的研究方法,主要是要找到引起故障的codepath

示例C程序(来自网络)

#include 
#include 
#include 
#include 
#include 


void handler(int sig) {
  void *array[10];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, 10);

  // print out all the frames to stderr
  fprintf(stderr, "Error: signal %d:\n", sig);
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  exit(1);
}

void baz() {
 int *foo = (int*)-1; // make a bad pointer
  printf("%d\n", *foo);       // causes segfault
}

void bar() { baz(); }
void foo() { bar(); }


int main(int argc, char **argv) {
  signal(SIGSEGV, handler);   // install our handler
  foo(); // this will call foo, bar, and baz.  baz segfaults.
}

示例C++程序(来自网络)

#include 

class foo
{
public:
    foo() { foo1(); }

private:
    void foo1() { foo2(); }
    void foo2() { foo3(); }
    void foo3() { foo4(); }
    void foo4() { crash(); }
    void crash() { char * p = NULL; *p = 0; }
};

int main(int argc, char ** argv)
{
    // Setup signal handler for SIGSEGV

    foo * f = new foo();
    return 0;
}

Item 1: 手工加print/log

此法适用于对代码较为熟悉的,我以前经常用,现在偶尔用,但确实很笨拙啊,提交代码时还得把用于调试的print/log给删掉。下次segfault又得加上,汗。。。

Item 2: 自定义segv handler和添加backtrace()

按照以上C代码编译gcc segfault.c执行

segfault分析方法_第1张图片
image.png

可见有16进制地址,无函数名,所以无法直接观察crash发生在哪里;
由于捕获了segv信号,所以不会产生core文件,也不会有dmesg记录

那只有地址能不能分析呢,事实上也可以,
./a.out 2>&1 | grep -o '0x[0-9a-z].*'将a.out的标准错误定向到grep,挑出那些16进制地址(更准确的做法是过滤到某些行上的第一个0x地址,见后文cut命令)

segfault分析方法_第2张图片
image.png

接着将这些地址喂给addr2line,


segfault分析方法_第3张图片
image.png

还好可以得到函数名及调用栈,大概可以猜测到crash发生在baz调用附近

加-g, 情况就会好更多了,显示源文件名和行号:

segfault分析方法_第4张图片
image.png

对大型程序,也许用-g不太合适,文件太大

加-rdynamic

segfault分析方法_第5张图片
image.png

不错,能出现函数符号名,
也可以将上图产生的地址定向给addr2line(注意要过滤第一次出现的0x),


segfault分析方法_第6张图片
image.png

也只看到函数名,当然是因为没有用-g,它与rdynamic功能是不一样的

Item 3: dmesg + objdump

如果注释掉C代码中的signal(SIGSEGV, handler);,即不install segv handler,则执行时会在dmesg中留下记录,此时可以用objdump -d解析出汇编代码,找到发生crash时的地址(注意不要用-O优化,否则编译器优化了汇编)
图示操作如下:

segfault分析方法_第7张图片
image.png

很明显, 地址发生在baz()调用处
这种方法可能需要熟悉点汇编,不怎么推荐

Item 4: LD_PRELOAD方式(推荐这种方式,catchsegv命令也是基于它)

segfault分析方法_第8张图片
image.png

同理加-rdynamic会生成函数名;
这也和dmesg方法一样,适用于没有install segv handler的情况

对于c++方法要demangle

对于前面的c++代码,注意用LD_PRELOAD方式dump出来的是mangled function name(用了rdynamic时),要把那部分挑出来,用c++filt demangle(这个例子勉强还能猜是什么函数,大型代码中可没法猜了,像seastar的函数,mangled名称太长;10只是大致的行数,根据实际情况调整

segfault分析方法_第9张图片
image.png

当然用addr2line也是可以解析出c++源程序位置的(-g),


segfault分析方法_第10张图片
image.png

Item 5: boost::stacktrace

最近了解到boost 1.65开始增添了此功能,见图示:


segfault分析方法_第11张图片
image.png

确实比自己手写backtrace()要方便些。推荐!

Item 6: gdb + core

这种方式也很常用,找到发生segv或异常的地方,然后bt,就能发现引起crash的codepath。很多人都谈到过,在这里就不说了。

参考文章

  • 阿里同学文章
  • How to automatically generate a stacktrace when my gcc C++ program crashes
  • boost 1.66 stacktrace

你可能感兴趣的:(segfault分析方法)