linux c开发: 程序崩溃时保存堆栈信息并解析具体代码行
2017-09-13 16:36
555 查看
写服务器程序最怕的是百分之一的概率崩溃了,你却不知道为啥,想重现又重现不出来。所以在崩溃时将当时的堆栈保存下来非常重要。网上有很多文章讲解怎么保存,但我使用了发现可以保存,但是没有函数名称和行号,仍然没法定位问题。在stack overflow上有人说只有动态库的代码才能显示出函数名和行号,想完整显示还需要使用某某第三方开源库,不过我幸好发现使用addr2line命令可以将文件名和行号显示出来,轻松定位问题。如下就总结一下整个流程。
* 首先,我们需要在进程崩溃时调用某个函数。请参考上一篇文章:linux c开发: 在程序退出时进行处理
* 然后,我们获取堆栈信息并保存的一个文件,代码如下所示,使用了网上的一些代码:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
注意编译的时候,需要在makefile里面加入编译选项rdynamic。例如:
然后我们运行编译好的程序,在崩溃的时候就可以获取一个core dump文件了,例如:core.2017-8-28_23_4_55。内容大概是这样:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
问题是这里的堆栈信息,只有模块的名字,比如my_server,但是里面没有函数名和行号,这样定位问题就难了。但是我们看到libev.so的函数名称都在。而这里显示的信息,都是backtrace_symbols这个函数返回的,并且我们已经加上编译选项-rdynamic了,甚至我的编译选项里面还有-g,所以这个锅我不背。上面说过,stack overflow上有人认为只有动态链接库才有具体的信息,而解决方法是使用某某库,不过对于我已经来不及了。幸好发现了addr2line这个工具可以从地址解析出文件名和行号。(注意是文件名而不是函数名)
* 使用addr2line解析出文件名和行号
addr2line的使用方法很简单
这里的两个参数就是我上面core dump文件里面能看到的内容。得到结果如下:
代码轻松定位了,然后继续向上看堆栈上各个地址,找到崩溃的地方。需要注意的是,定位出来的代码行可能是括号,并不是函数调用语句或表达式,不过不要紧,结合堆栈里面上下文关系还是很容易确定具体代码位置的。
转载自:http://blog.csdn.net/n5/article/details/77651120
* 首先,我们需要在进程崩溃时调用某个函数。请参考上一篇文章:linux c开发: 在程序退出时进行处理
* 然后,我们获取堆栈信息并保存的一个文件,代码如下所示,使用了网上的一些代码:
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<nSize; i++){ fwrite(symbols[i], 1, strlen(symbols[i]), f); fwrite("\n", 1, 1, f); } free(symbols); } } //文件解锁后关闭 fl.l_type = F_UNLCK; fcntl(fd, F_SETLK, &fl); fclose(f); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
注意编译的时候,需要在makefile里面加入编译选项rdynamic。例如:
CFLAGS :=-g -rdynamic -Wall -Werror -std=gnu99 -D MY_SERVER_DEBUG
然后我们运行编译好的程序,在崩溃的时候就可以获取一个core dump文件了,例如:core.2017-8-28_23_4_55。内容大概是这样:
/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_ cc1d 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]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
问题是这里的堆栈信息,只有模块的名字,比如my_server,但是里面没有函数名和行号,这样定位问题就难了。但是我们看到libev.so的函数名称都在。而这里显示的信息,都是backtrace_symbols这个函数返回的,并且我们已经加上编译选项-rdynamic了,甚至我的编译选项里面还有-g,所以这个锅我不背。上面说过,stack overflow上有人认为只有动态链接库才有具体的信息,而解决方法是使用某某库,不过对于我已经来不及了。幸好发现了addr2line这个工具可以从地址解析出文件名和行号。(注意是文件名而不是函数名)
* 使用addr2line解析出文件名和行号
addr2line的使用方法很简单
addr2line -e <执行文件> <代码地址>。使用例子:
addr2line -e /usr/local/bin/my_server 0x4039e8
这里的两个参数就是我上面core dump文件里面能看到的内容。得到结果如下:
/root/build/my_server/src/my_server.c:129
代码轻松定位了,然后继续向上看堆栈上各个地址,找到崩溃的地方。需要注意的是,定位出来的代码行可能是括号,并不是函数调用语句或表达式,不过不要紧,结合堆栈里面上下文关系还是很容易确定具体代码位置的。
转载自:http://blog.csdn.net/n5/article/details/77651120
相关文章推荐
- linux c开发: 程序崩溃时保存堆栈信息并解析具体代码行
- 【iOS开发】如何在程序出错崩溃时快速定位到具体出错代码行
- iOS开发 - iOS崩溃堆栈信息的符号化解析
- Android将程序崩溃信息保存本地文件发送至服务器
- Android将程序崩溃信息保存本地文件
- Android将程序崩溃信息保存本地文件
- Android将程序崩溃信息保存本地文件
- 第一天:Java源码级实战速成(通过动手实战类、对象等,通过Spark和Hadoop案例代码和源码解析具体指知识的应用、深度详解匿名接口在Spark开发中的运用)
- Android将程序崩溃信息保存本地文件
- 一个WinForm记事本程序(包含主/下拉/弹出菜单/打开文件/保存文件/打印/页面设置/字体/颜色对话框/剪切版操作等等控件用法以及记事本菜单事件/按键事件的具体代码)
- iOS崩溃堆栈信息的符号化解析
- 微信公众平台java开发具体解释(project代码+解析)
- 进程间通信具体实现代码(两个程序间互发信息)
- Android开发如何使用UncaughtExceptionHandler捕获程序崩溃错误信息?
- Android将程序崩溃信息保存本地文件
- Android将程序崩溃信息保存本地文件
- iOS开发中程序崩溃邮件通知代码
- Android将程序崩溃信息保存本地文件
- Android将程序崩溃信息保存到本地文件
- iOS开发中程序崩溃邮件通知代码