用封装的栈回溯类捕获段错误
Posted 李迟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用封装的栈回溯类捕获段错误相关的知识,希望对你有一定的参考价值。
本文介绍使用自封装的 backtrace 类对段错误进行捕获,以方便分析运行错误的方法。并给出实现和测试代码。
背景
我们写程序难免会运行出错,常在河边,哪能不湿鞋。出错不可怕,怕的是无法定位问题,像段错误,在服务端、嵌入式等领域,很多时候都无迹可寻,我们可以用 coredump 进行事后分析,但还是略显麻烦。
设计思路
本节介绍CBackTracer
类的设计。
- 利用
backtrace
和backtrace_symbols
可以获取函数符号和地址。 - 可以指定获取的函数数量,本文暂定为10,如果回溯的函数数量少于指定的,则按实际数量显示。
- 得到地址后,使用
addr2line
命令解析出对应的文件行号。由于该命令需要程序名称,因此需要调用者提供程序名称,与信号值一并传递。 - 由于
sigaction
的回调函数不能使用类内的函数,因为单独编写之。
实现代码
实现代码如下:
// 头文件 backtraceplus.h
#ifndef BACKTRACEPLUS_H
#define BACKTRACEPLUS_H
class CBackTracer {
public:
CBackTracer() {}
CBackTracer(const char* name, int sig); // argv[0] SIGSEGV
~CBackTracer() {}
void Setup(const char* name, int sig);
private:
};
#endif
// 实现文件 backtraceplus.cpp
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include "backtraceplus.h"
// 程序名,用于输出函数行号
static char g_exeName[256] = {0};
// 不能作为了类成员
void fault_trap(int sig, siginfo_t * siginfo, void *myact)
{
printf("Catch SegmentFault!!\\n");
void *array[10] = { 0 };
int num = backtrace(array, 10);
char **calls = backtrace_symbols(array, num);
for (int i = 0; i < num; i++)
{
char *symbol = calls[i];
char addr[64] = { 0 };
char *p = strstr(symbol, "[0x");
snprintf(addr, sizeof(addr), p + 1);
*(addr + strlen(addr) - 1) = 0;
char cmd[64] = { 0 };
snprintf(cmd, sizeof(cmd), "addr2line %s -s -e %s", addr, g_exeName);
FILE *fp = popen(cmd, "r");
char buf[256] = { 0 };
fread(buf, sizeof(buf), sizeof(char), fp);
printf("%s %s", symbol, buf);
pclose(fp);
fp = NULL;
}
exit(0);
}
CBackTracer::CBackTracer(const char* name, int sig)
{
Setup(name, sig);
}
void CBackTracer::Setup(const char* name, int sig)
{
strncpy(g_exeName, name, sizeof(g_exeName));
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = fault_trap;
sigaction(sig, &act, NULL);
}
测试代码
测试代码如下:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <execinfo.h>
#include "backtraceplus.h"
void badcall(void)
{
int a = 250;
printf("fault: %s\\n", a);
}
void foobar(void)
{
printf("in %s, call bad\\n", __func__);
badcall();
}
void myfunc(void)
{
printf("in %s\\n", __func__);
foobar();
}
int main(int argc, char* argv[])
{
printf("test of backtrace...\\n");
//CBackTracer mybt(argv[0], SIGSEGV);
CBackTracer mybt;
mybt.Setup(argv[0], SIGSEGV);
myfunc();
return 0;
}
输出结果示例如下:
$ ./a.out
test of backtrace...
in myfunc
in foobar, call bad
fault: Catch SegmentFault!!
./a.out(_Z10fault_trapiP9siginfo_tPv+0x57) [0x40159f] backtraceplus.cpp:23
/usr/lib64/libc.so.6(+0x363b0) [0x7f9baac973b0] ??:0
/usr/lib64/libc.so.6(_IO_vfprintf+0x4a79) [0x7f9baacae029] ??:0
/usr/lib64/libc.so.6(_IO_printf+0x99) [0x7f9baacb4459] ??:0
./a.out(_Z7badcallv+0x23) [0x401800] main.cpp:23
./a.out(_Z6foobarv+0x21) [0x401823] main.cpp:31
./a.out(_Z6myfuncv+0x1d) [0x401882] main.cpp:46
./a.out(main+0x50) [0x4018d4] main.cpp:59
/usr/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f9baac83505] ??:0
./a.out() [0x401169] ??:?
注:已经能捕获到段错误,由于系统库没有源码,因此libc.so.6
文件最后显示的是??:0
,但我们的测试程序a.out
可以显示行号。根据行号,可以逐步排查问题。
小结
本文的示例有几个依赖条件:系统需安装有addr2line
命令,程序需使用调试版本,不能strip
,否则无法分析出程序函数位置。
以上是关于用封装的栈回溯类捕获段错误的主要内容,如果未能解决你的问题,请参考以下文章