相信写c/c++程序的coder, segmentation fault的问题碰到不少,最近写scylla 测试程序也会经常segv或异常,特意研究和总结segfault的研究方法,主要是要找到引起故障的codepath
示例C程序(来自网络)
#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
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 <iostream>
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
执行

可见有16进制地址,无函数名,所以无法直接观察crash发生在哪里;
由于捕获了segv信号,所以不会产生core文件,也不会有dmesg记录
那只有地址能不能分析呢,事实上也可以,
用./a.out 2>&1 | grep -o '0x[0-9a-z].*'
将a.out的标准错误定向到grep,挑出那些16进制地址(更准确的做法是过滤到某些行上的第一个0x地址,见后文cut命令)

接着将这些地址喂给addr2line,

还好可以得到函数名及调用栈,大概可以猜测到crash发生在baz调用附近
加-g, 情况就会好更多了,显示源文件名和行号:

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

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

也只看到函数名,当然是因为没有用-g,它与rdynamic功能是不一样的
Item 3: dmesg + objdump
如果注释掉C代码中的signal(SIGSEGV, handler);
,即不install segv handler,则执行时会在dmesg中留下记录,此时可以用objdump -d解析出汇编代码,找到发生crash时的地址(注意不要用-O优化,否则编译器优化了汇编)
图示操作如下:

很明显,地址发生在baz()调用处
这种方法可能需要熟悉点汇编,不怎么推荐
Item 4: LD_PRELOAD方式(推荐这种方式,catchsegv命令也是基于它)

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

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

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

确实比自己手写backtrace()要方便些。推荐!
Item 6: gdb + core
这种方式也很常用,找到发生segv或异常的地方,然后bt,就能发现引起crash的codepath。很多人都谈到过,在这里就不说了。
网友评论