转自:https://blog.csdn.net/gqtcgq/article/details/53883546

C程序运行时,经常会碰到”segmentfault”错误。这是由于程序中非法访问内存导致的。当操作系统的内存保护机制发现进程访问了非法内存的时候会向此进程发送一个SIGSEGV信号,导致进程直接退出,并在shell中提示segment fault。

因此,可以通过设置SIGSEGV信号处理函数,在处理函数中调用backtrace系列函数得到异常时的函数调用栈信息。

一:backtrace

backtrace系列函数的原型如下:

  1. #include <execinfo.h>
  2.  
  3. int backtrace(void **buffer, int size);
  4. char **backtrace_symbols(void *const *buffer, int size);
  5. void backtrace_symbols_fd(void *const *buffer, int size, int fd);

backtrace函数通过指针数组buffer返回调用程序的回溯信息,也就是所谓的函数调用栈。buffer数组中的元素是void*类型,也就是栈中保存的返回地址。

size参数指定buffer中可以保存的地址的最大个数。如果实际的回溯信息大于size,则只返回最近的size个地址。

backtrace函数返回buffer中保存的地址个数,返回值不会大于size。如果返回值小于size,则说明所有的回溯信息都已经返回了,如果等于size,则有可能被截断了。

backtrace函数在buffer数组中返回的都是一些虚拟地址,不适于分析。backtrace_symbols函数可以将backtrace返回的buffer中的地址,根据符号表中的信息,转换为字符串(函数名+偏移地址)。size参数指明了buffer中的地址个数。

backtrace_symbols返回字符串数组的首地址,该字符串是在backtrace_symbols中通过malloc分配的,因此,调用者必须使用free释放内存。如果发生了错误,则backtrace_symbols返回NULL。

backtrace_symbols_fd类似于backtrace_symbols,只不过它是把字符串信息写到文件描述符fd所表示的文件中。backtrace_symbols_fd不会调用malloc函数。

注意,编译器的优化策略,可能导致得到的回溯信息不准确。而且,对于GUN编译器而言,必须使用-rdynamic链接选项,才能正确解析出符号名。

二:示例

  1. #include <signal.h>
  2. #include <execinfo.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <ucontext.h>
  6.  
  7. #define BTSIZE 100
  8.  
  9. static void sig_handler(int sig, siginfo_t *info, void *secret)
  10. {
  11. ucontext_t *uc = (ucontext_t*) secret;
  12.  
  13. void *buffer[BTSIZE];
  14. char **strings;
  15. int nptrs = 0;
  16.  
  17. printf("in sig_handler\n");
  18. printf("sig is %d, SIGSEGV is %d\n", sig, SIGSEGV);
  19. printf("info.si_signo is %d, info.si_addr is %p\n",
  20. info->si_signo, info->si_addr);
  21.  
  22. if (sig == SIGSEGV)
  23. {
  24. nptrs = backtrace(buffer, BTSIZE);
  25. printf("backtrace() returned %d addresses\n", nptrs);
  26.  
  27. strings = backtrace_symbols(buffer, nptrs);
  28. if (strings == NULL)
  29. {
  30. perror("backtrace_symbols");
  31. exit(EXIT_FAILURE);
  32. }
  33.  
  34. printf("backtrace: \n");
  35. int j = 0;
  36. for (j = 0; j < nptrs; j++)
  37. {
  38. printf("[%d]%s\n", j, strings[j]);
  39. }
  40. free(strings);
  41.  
  42. exit(0);
  43. }
  44. }
  45.  
  46. void fun3()
  47. {
  48. int *ptr = (int *)0x123;
  49. printf("this is fun3\n");
  50.  
  51. *ptr = 0;
  52. }
  53.  
  54. void fun2()
  55. {
  56. printf("this is fun2\n");
  57. fun3();
  58. }
  59.  
  60. void fun1()
  61. {
  62. printf("this is fun1\n");
  63. fun2();
  64. }
  65.  
  66. int main()
  67. {
  68. struct sigaction act;
  69. sigemptyset(&act.sa_mask);
  70. act.sa_flags = SA_SIGINFO;
  71. act.sa_sigaction = sig_handler;
  72. sigaction(SIGSEGV, &act, NULL);
  73.  
  74. fun1();
  75. }

main函数中,使用sigaction设置SIGSEGV信号的处理函数,通过SA_SIGINFO标志,可以得到信号发生时的额外信息,比如引起信号的内存地址等。

在fun3函数中,尝试将内存地址为0x123的内存赋值为0,这是一个明显的非法内存访问,将导致SIGSEGV信号的产生。

在SIGSEGV信号处理函数sig_handler中,首先打印出引起异常的内存地址info->si_addr,然后调用backtrace和backtrace_symbols打印出栈帧。

结果如下:

[root@localhost test]# gcc -o testbacktrace testbacktrace.c
[root@localhost test]# ./testbacktrace
this is fun1
this is fun2
this is fun3
in sig_handler
sig is 11, SIGSEGV is 11
info.si_signo is 11, info.si_addr is 0x123
backtrace() returned 7 addresses
backtrace:
[0]./testbacktrace [0x80485d0]
[1][0xec8440]
[2]./testbacktrace [0x80486ba]
[3]./testbacktrace [0x80486d3]
[4]./testbacktrace [0x804872e]
[5]/lib/libc.so.6(__libc_start_main+0xdc) [0xa9cedc]
[6]./testbacktrace [0x80484a1]

打印出了info.si_addr的值为0x123。并且打印出了7个地址信息。通过objdump,对testbacktrace进行反汇编,可以得到如下信息:

080483e8 <__libc_start_main@plt>:
80483e8: ff 25 40 9a 04 08 jmp *0x8049a40
80483ee: 68 10 00 00 00 push $0x10
80483f3: e9 c0 ff ff ff jmp 80483b8 <_init+0x18> 08048480 <_start>:
...
8048497: 68 d5 86 04 08 push $0x80486d5
804849c: e8 47 ff ff ff call 80483e8 <__libc_start_main@plt>
80484a1: f4 hlt
... 08048554 <sig_handler>:
...
80485cb: e8 78 fe ff ff call 8048448 <backtrace@plt>
80485d0: 89 45 f8 mov %eax,0xfffffff8(%ebp) 0804867f <fun3>:
...
8048685: c7 45 fc 23 01 00 00 movl $0x123,0xfffffffc(%ebp)
804868c: c7 04 24 b1 88 04 08 movl $0x80488b1,(%esp)
8048693: e8 c0 fd ff ff call 8048458 <puts@plt>
8048698: 8b 45 fc mov 0xfffffffc(%ebp),%eax
804869b: c7 00 00 00 00 00 movl $0x0,(%eax)
80486a1: c9 leave
... 080486a3 <fun2>:
...
80486b0: e8 a3 fd ff ff call 8048458 <puts@plt>
80486b5: e8 c5 ff ff ff call 804867f <fun3>
80486ba: c9 leave
... 080486bc <fun1>:
...
80486c9: e8 8a fd ff ff call 8048458 <puts@plt>
80486ce: e8 d0 ff ff ff call 80486a3 <fun2>
80486d3: c9 leave
... 080486d5 <main>:
...
8048724: e8 ff fc ff ff call 8048428 <sigaction@plt>
8048729: e8 8e ff ff ff call 80486bc <fun1>
804872e: 81 c4 a4 00 00 00 add $0xa4,%esp
...

根据上面的反汇编信息,可知backtrace返回的7个地址信息,都是call指令后面紧跟着的指令地址。这是因为call指令在将子程序的起始地址送入指令寄存器(于是CPU的下一条指令就会转去执行子程序)之前,首先会将call指令的下一条指令的所在地址入栈。所以,函数调用时的栈内容如下:

backtrace返回的buffer中保存的地址,就是所有call指令后续紧跟的返回地址。

上面的结果,因为没有加”-rdynamic”链接选项,所以打印出来的都是虚拟地址。增加”-rdynamic”后的结果如下:

[root@localhost test]# gcc -o testbacktrace testbacktrace.c -rdynamic
[root@localhost test]# ./testbacktrace
this is fun1
this is fun2
this is fun3
in sig_handler
sig is 11, SIGSEGV is 11
info.si_signo is 11, info.si_addr is 0x123
backtrace() returned 7 addresses
backtrace:
[0]./testbacktrace [0x80487b0]
[1][0xda2440]
[2]./testbacktrace(fun2+0x17) [0x804889a]
[3]./testbacktrace(fun1+0x17) [0x80488b3]
[4]./testbacktrace(main+0x59) [0x804890e]
[5]/lib/libc.so.6(__libc_start_main+0xdc) [0x3daedc]
[6]./testbacktrace [0x8048681]

这样可以在不使用objdump的情况下,大体了解函数调用的关系了。

三:指令地址

上面通过backtrace可以大体得到”segmentfault”错误时的函数调用栈,然而仅凭backtrace还是不能得到引起异常的指令地址(甚至连引起异常的函数也无法得到)。

在Redis的源码中,看到了打印指令地址的方法。使用ucontext_t结构,打印出指令寄存器的内容。

代码如下:

  1. #include <signal.h>
  2. #include <execinfo.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <ucontext.h>
  6.  
  7. #define BTSIZE 100
  8.  
  9. static void *getMcontextEip(ucontext_t *uc) {
  10. #if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
  11. /* OSX < 10.6 */
  12. #if defined(__x86_64__)
  13. return (void*) uc->uc_mcontext->__ss.__rip;
  14. #elif defined(__i386__)
  15. return (void*) uc->uc_mcontext->__ss.__eip;
  16. #else
  17. return (void*) uc->uc_mcontext->__ss.__srr0;
  18. #endif
  19. #elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
  20. /* OSX >= 10.6 */
  21. #if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
  22. return (void*) uc->uc_mcontext->__ss.__rip;
  23. #else
  24. return (void*) uc->uc_mcontext->__ss.__eip;
  25. #endif
  26. #elif defined(__linux__)
  27. /* Linux */
  28. #if defined(__i386__)
  29. return (void*) uc->uc_mcontext.gregs[14]; /* Linux 32 */
  30. #elif defined(__X86_64__) || defined(__x86_64__)
  31. return (void*) uc->uc_mcontext.gregs[16]; /* Linux 64 */
  32. #elif defined(__ia64__) /* Linux IA64 */
  33. return (void*) uc->uc_mcontext.sc_ip;
  34. #endif
  35. #else
  36. return NULL;
  37. #endif
  38. }
  39.  
  40. static void sig_handler(int sig, siginfo_t *info, void *secret)
  41. {
  42. ucontext_t *uc = (ucontext_t*) secret;
  43.  
  44. void *buffer[BTSIZE];
  45. char **strings;
  46. int nptrs = 0;
  47.  
  48. printf("in sig_handler\n");
  49. printf("sig is %d, SIGSEGV is %d\n", sig, SIGSEGV);
  50. printf("info.si_signo is %d, info.si_addr is %p\n",
  51. info->si_signo, info->si_addr);
  52.  
  53. if (sig == SIGSEGV)
  54. {
  55. nptrs = backtrace(buffer, BTSIZE);
  56. printf("backtrace() returned %d addresses\n", nptrs);
  57.  
  58. if (getMcontextEip(uc) != NULL)
  59. buffer[1] = getMcontextEip(uc);
  60.  
  61. strings = backtrace_symbols(buffer, nptrs);
  62. if (strings == NULL) {
  63. perror("backtrace_symbols");
  64. exit(EXIT_FAILURE);
  65. }
  66.  
  67. printf("backtrace: \n");
  68. int j;
  69. for (j = 0; j < nptrs; j++)
  70. {
  71. printf("[%d]%s\n", j, strings[j]);
  72. }
  73. free(strings);
  74.  
  75. exit(0);
  76. }
  77. }
  78.  
  79. void fun3()
  80. {
  81. int *ptr = (int *)0x123;
  82. printf("this is fun3\n");
  83.  
  84. *ptr = 0;
  85. }
  86.  
  87. void fun2()
  88. {
  89. printf("this is fun2\n");
  90. fun3();
  91. }
  92.  
  93. void fun1()
  94. {
  95. printf("this is fun1\n");
  96. fun2();
  97. }
  98.  
  99. int main()
  100. {
  101. struct sigaction act;
  102. sigemptyset(&act.sa_mask);
  103. act.sa_flags = SA_SIGINFO;
  104. act.sa_sigaction = sig_handler;
  105. sigaction(SIGSEGV, &act, NULL);
  106.  
  107. fun1();
  108. }

在使用sigaction函数设置SIGSEGV信号的处理函数时,使用SA_SIGINFO标志,可以得到信号发生时的更多信息。

当信号发生调用处理函数sig_handler时,传递给该函数的第三个参数,是一个ucontext_t类型的结构,该结构在头文件ucontext.h中定义,其中包含了信号发生时的CPU状态,也就是所有寄存器的内容。

函数getMcontextEip用于返回指令寄存器的内容。使用该内容,替换buffer[1]的内容。代码运行结果如下:

  1. [root@localhost test]# gcc -o testbacktrace testbacktrace.c -rdynamic
  2. [root@localhost test]# ./testbacktrace
  3. this is fun1
  4. this is fun2
  5. this is fun3
  6. in sig_handler
  7. sig is 11, SIGSEGV is 11
  8. info.si_signo is 11, info.si_addr is 0x123
  9. backtrace() returned 7 addresses
  10. backtrace:
  11. [0]./testbacktrace [0x80487bb]
  12. [1]./testbacktrace(fun3+0x1c) [0x804889f]
  13. [2]./testbacktrace(fun2+0x17) [0x80488be]
  14. [3]./testbacktrace(fun1+0x17) [0x80488d7]
  15. [4]./testbacktrace(main+0x59) [0x8048932]
  16. [5]/lib/libc.so.6(__libc_start_main+0xdc) [0xd6dedc]
  17. [6]./testbacktrace [0x8048681]

可以看见buffer[1]的内容已经被替换成了信号发生时的指令寄存器内容。通过objdump,得到fun3的汇编指令如下:

  1. 08048883 <fun3>:
  2. 8048883: 55 push %ebp
  3. 8048884: 89 e5 mov %esp,%ebp
  4. 8048886: 83 ec 18 sub $0x18,%esp
  5. 8048889: c7 45 fc 23 01 00 00 movl $0x123,0xfffffffc(%ebp)
  6. 8048890: c7 04 24 b1 8a 04 08 movl $0x8048ab1,(%esp)
  7. 8048897: e8 98 fd ff ff call 8048634 <puts@plt>
  8. 804889c: 8b 45 fc mov 0xfffffffc(%ebp),%eax
  9. 804889f: c7 00 00 00 00 00 movl $0x0,(%eax)
  10. 80488a5: c9 leave
  11. 80488a6: c3 ret

地址0x804889f就是引起异常的指令地址。

利用backtrace和ucontex定位segment错误【转】的更多相关文章

  1. 利用backtrace和ucontex定位segment错误

    C程序运行时,经常会碰到"segmentfault"错误.这是由于程序中非法访问内存导致的.当操作系统的内存保护机制发现进程访问了非法内存的时候会向此进程发送一个SIGSEGV信号 ...

  2. 嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误

    嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误 2015-05-27 14:19 184人阅读 评论(0) 收藏 举报  分类: 嵌入式(928)  一般察看函数运行时堆栈的 ...

  3. linux下利用backtrace追踪函数调用堆栈以及定位段错误

    一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的. 在glibc ...

  4. Linux下利用backtrace追踪函数调用堆栈以及定位段错误[转]

    来源:Linux社区  作者:astrotycoon 一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序 ...

  5. 用户态使用 glibc/backtrace 追踪函数调用堆栈定位段错误【转】

    转自:https://blog.csdn.net/gatieme/article/details/84189280 版权声明:本文为博主原创文章 && 转载请著名出处 @ http:/ ...

  6. 在Linux中如何利用backtrace信息解决问题

    在Linux中如何利用backtrace信息解决问题 一.导读 在程序调试过程中如果遇到程序崩溃死机的情况下我们通常多是通过出问题时的栈信息来找到出错的地方,这一点我们在调试一些高级编程语言程序的时候 ...

  7. 利用backtrace和objdump进行分析挂掉的程序

    转自:http://blog.csdn.net/hanchaoman/article/details/5583457 汇编不懂,先把方法记下来. glibc为我们提供了此类能够dump栈内容的函数簇, ...

  8. html利用锚点实现定位代码实例

    本章节介绍介绍一下如何利用锚点实现定位,使用锚点实现定位是html固有的功能,当然比较简单,也实现了基本的功能,但是功能相对简单一些,如果想要实现平滑的定位可以参阅jquery实现的点击页面动画方式平 ...

  9. [置顶] 利用Global.asax的Application_Error实现错误记录,错误日志

    利用Global.asax的Application_Error实现错误记录 错误日志 void Application_Error(object sender, EventArgs e) { // 在 ...

随机推荐

  1. Locality Sensitive Hashing,LSH

    1. 基本思想 局部敏感(Locality Senstitive):即空间中距离较近的点映射后发生冲突的概率高,空间中距离较远的点映射后发生冲突的概率低. 局部敏感哈希的基本思想类似于一种空间域转换思 ...

  2. 关于vs2013进行单元测试

    安装vs的过程就不多说了,做为一个学计算机的学生十基本技能. 第一步建立新工程.使用c#语言, 第二步,建立一个类.输入要测试的代码 第三步 建立一个类 第四步  运行测试

  3. Confluence的简单安装以及与jira链接(Confluence不知道有没有破解)

    1. 前提是安装好了jira以及下载好了confluence的安装包 这里 jira的版本是 7.2.4 confluence的版本是6.8 2. 服务器上面有sqlserver数据库. 3. 为了便 ...

  4. leetcode Database4

    一.Department Top Three Salaries The Employee table holds all employees. Every employee has an Id, an ...

  5. word 里面没输入法

    文件,选项,高级,输入法控制处于活动状态   ,有勾选就去掉,无勾选就勾上,确定后重开word即可

  6. 简单prufer应用

    [bzoj1005] Description 自从明明学了树的结构,就对奇怪的树产生了兴趣......给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树? ...

  7. SpringBoot中的定时任务与Quartz的整合

    SpringBoot集成Quartz 定时任务Quartz : 就是在指定的时间执行一次或者循环执行,在项目的开发中有时候会需要的, 还是很有用的. SpringBoot内置的定时 添加依赖 < ...

  8. MyBatis 源码分析——配置信息

    MyBatis框架的启动前期需要加载相关的XML配置信息.从官网上我们可以了解到他具有十几个节点.其中笔者认为比较重要的节点是settings节点.properties节点.environments节 ...

  9. ASP.NET调用cmd命令提示符拒绝访问解决方案

    using System.Diagnostics; public class CmdHelper { private static string CmdPath = @"C:\Windows ...

  10. js判断是否为空

    http://dushanggaolou.iteye.com/blog/1293803 1.<input type="hidden" id="key" n ...