概述
输出程序的堆栈
backtrace函数
在linux系统中,我们可以通过如下三个库函数来获取程序的调用堆栈,可以通过man 3 backtrace查看。它们由GUN C Library提供。
#include <execinfo.h>
/* Store up to SIZE return address of the current program state in
ARRAY and return the exact number of values stored. */
int backtrace(void **array, int size);
/* Return names of functions from the backtrace list in ARRAY in a newly
malloc()ed memory block. */
char **backtrace_symbols(void *const *array, int size);
/* This function is similar to backtrace_symbols() but it writes the result
immediately to a file. */
void backtrace_symbols_fd(void *const *array, int size, int fd);
在程序中使用backtrace定位异常
通常,在我们的服务程序中都会设置异常退出后自动重启,当程序出现异常退出后,我们需要程序同时输出异常的堆栈来告诉开发人员程序程序出现过异常,以及出现异常的位置在哪里。
在linux中,程序异常退出一般都会收到相关的信号,例如出现了内存错误,进程会收到一个SIGSEGV的信号,然后会执行默认操作退出程序。为此我们可以利用信号的捕捉的方式,在程序退出的前一刻输出其堆栈数据。
示例代码如下,
//main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <execinfo.h>
//下一小节介绍
void start_gperftools(int sig)
{
HeapProfilerStart("heapfile.heap");
}
void stop_gperftools(int sig)
{
HeapProfilerStop();
}
string str_crash_log_path = "xxx.dump";
void dump_crash_statck_no_malloc(const int & i_frame_size)
{
void *array[i_frame_size];
char **strings;
int i_fd = open(str_crash_log_path.c_str(), O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU|S_IRWXG|S_IRWXO);
if (i_fd == -1)
{
exit(1);
}
size_t i_ret_frame_size = backtrace (array, i_frame_size);
backtrace_symbols_fd (array, i_ret_frame_size,i_fd);
close(i_fd);
exit(1);
}
void recv_signal(int sig)
{
dump_crash_statck_no_malloc(100);
/*
这里也可以恢复默认动作,然后重新发送改信号给自己,这样在dump_crash_statck_no_malloc中就无需使用exit函数
signal(sig, SIG_DFL);
raise(sig);
*/
}
void task()
{
char* g;
printf("%cn", g[1]); //模拟段错误
return ;
}
int main()
{
signal(SIGSEGV, recv_signal);
signal(SIGABRT , recv_signal);
//下一小节介绍,本小节可以去掉
signal(SIGUSR1, start_gperftools);
signal(SIGUSR2, stop_gperftools);
task();//业务代码
}
//编译 g++ main.cpp -o a.out -g -rdynamic
如上所示,当我们的业务代码task中出现了内存等异常错误时,就会产生一个xxx.dump文件,里面记录了程序退出时的堆栈信息(如下所示)。如果需要定位具体的位置,可使用addr2line命令,例如,addr2line -e a.out 0x400ed7,即可输出g[1]出现的行;需要注意是,这里的地址需要是在静态链接的情况下(动态链接情况下也是可以获取,但是稍复杂点,感兴趣可以百度了解下)。
1 ./a.out(_Z27dump_crash_statck_no_mallocRKi+0xd0)[0x400e4d]
2 ./a.out(_Z11recv_signali+0x28)[0x400eb0]
3 /lib64/libc.so.6(+0x36400)[0x7f23d9f21400]
4 ./a.out(_Z4taskv+0xc)[0x400ed7] #出问题的点
5 ./a.out(main+0x31)[0x400f0e]
6 /lib64/libc.so.6(__libc_start_main+0xf5)[0x7f23d9f0d555]
7 ./a.out[0x400cb9]
backtrace函数注意点
(1)backtrace的实现依赖于栈指针(fp寄存器),在gcc编译过程中任何非零的优化等级(-On参数)或加入了栈指针优化参数-fomit-frame-pointer后多将不能正确得到程序栈信息;
(2)backtrace_symbols的实现需要符号名称的支持,在gcc编译过程中需要加入-rdynamic参数;
(3)内联函数没有栈帧,它在编译过程中被展开在调用的位置;
(4)尾调用优化(Tail-call Optimization)将复用当前函数栈,而不再生成新的函数栈,这将导致栈信息不能正确被获取。
使用gperftools来获取程序的状态
在进程的运行过程,想要获取进程的各种状态,例如内存分配,cpu使用情况等等。gperftools是一个很好的工具。这里简单的介绍下在程序中加入gperftools,正好我们公司的服务器程序都是这么做的,简要记录下步骤,感兴趣可以自行查找,相关内容在网上有很详细的介绍。
(1)在github/gperftools获取源码,编译,获取工具相关依赖库;
(2)在程序编译时加入-ltcmalloc选项(分析进程不同的状态链接不同的库,使用不同api,这里的状态指内存分配,cpu使用状况等,具体识业务而定);
(3)程序中加入相关api,如上节中示例代码中的一样(上节中为开启和关闭内存检测api);
(4)通过kill -USER1/USER2 PID的方式开启和关闭检测;
(5)通过检测输出文件分析结果。
最后
以上就是超帅洋葱为你收集整理的使用backtrace追踪程序的异常退出的全部内容,希望文章能够帮你解决使用backtrace追踪程序的异常退出所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复