写服务器程序最怕的是百分之一的概率崩溃了,你却不知道为啥,想重现又重现不出来。所以在崩溃时将当时的堆栈保存下来非常重要。网上有很多文章讲解怎么保存,但我使用了发现可以保存,但是没有函数名称和行号,仍然没法定位问题。在stack overflow上有人说只有动态库的代码才能显示出函数名和行号,想完整显示还需要使用某某第三方开源库,不过我幸好发现使用addr2line命令可以将文件名和行号显示出来,轻松定位问题。如下就总结一下整个流程。
void server_backtrace(int sig)
{
//打开文件
time_t tSetTime;
time(&tSetTime);
struct tm* ptm = localtime(&tSetTime);
char fname[256] = {0};
sprintf(fname, "core.%d-%d-%d_%d_%d_%d",
ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,
ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
FILE* f = fopen(fname, "a");
if (f == NULL){
return;
}
int fd = fileno(f);
//锁定文件
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_len = 0;
fl.l_pid = getpid();
fcntl(fd, F_SETLKW, &fl);
//输出程序的绝对路径
char buffer[4096];
memset(buffer, 0, sizeof(buffer));
int count = readlink("/proc/self/exe", buffer, sizeof(buffer));
if(count > 0){
buffer[count] = '\n';
buffer[count + 1] = 0;
fwrite(buffer, 1, count+1, f);
}
//输出信息的时间
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "Dump Time: %d-%d-%d %d:%d:%d\n",
ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,
ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
fwrite(buffer, 1, strlen(buffer), f);
//线程和信号
sprintf(buffer, "Curr thread: %u, Catch signal:%d\n",
(int)pthread_self(), sig);
fwrite(buffer, 1, strlen(buffer), f);
//堆栈
void* DumpArray[256];
int nSize = backtrace(DumpArray, 256);
sprintf(buffer, "backtrace rank = %d\n", nSize);
fwrite(buffer, 1, strlen(buffer), f);
if (nSize > 0){
char** symbols = backtrace_symbols(DumpArray, nSize);
if (symbols != NULL){
for (int i=0; i
CFLAGS :=-g -rdynamic -Wall -Werror -std=gnu99 -D MY_SERVER_DEBUG
/usr/local/bin/my_server
Dump Time: 2017-8-25 23:4:55
Curr thread: 2857228032, Catch signal:6
backtrace rank = 18
my_server() [0x40ce9d]
my_server() [0x401ebf]
/lib64/libc.so.6(+0x32510) [0x7f9da9aeb510]
/lib64/libc.so.6(gsignal+0x35) [0x7f9da9aeb495]
/lib64/libc.so.6(abort+0x175) [0x7f9da9aecc75]
/lib64/libc.so.6(+0x703a7) [0x7f9da9b293a7]
/lib64/libc.so.6(+0x75dee) [0x7f9da9b2edee]
/lib64/libc.so.6(+0x78c80) [0x7f9da9b31c80]
my_server() [0x40cbc3]
my_server() [0x41080f]
my_server() [0x4100fc]
my_server() [0x4039e8]
/usr/lib64/libev.so.4(ev_invoke_pending+0x61) [0x7f9daa0bb071]
/usr/lib64/libev.so.4(ev_run+0x71a) [0x7f9daa0c023a]
my_server() [0x4064cd]
my_server() [0x402d3d]
/lib64/libc.so.6(__libc_start_main+0xfd) [0x7f9da9ad7d1d]
my_server() [0x401de9]
问题是这里的堆栈信息,只有模块的名字,比如my_server,但是里面没有函数名和行号,这样定位问题就难了。但是我们看到libev.so的函数名称都在。而这里显示的信息,都是backtrace_symbols这个函数返回的,并且我们已经加上编译选项-rdynamic了,甚至我的编译选项里面还有-g,所以这个锅我不背。上面说过,stack overflow上有人认为只有动态链接库才有具体的信息,而解决方法是使用某某库,不过对于我已经来不及了。幸好发现了addr2line这个工具可以从地址解析出文件名和行号。(注意是文件名而不是函数名)
addr2line -e <执行文件> <代码地址>
。使用例子:addr2line -e /usr/local/bin/my_server 0x4039e8
/root/build/my_server/src/my_server.c:129