巧用backtrace系列函数,在不具备gdb环境的Linux系统上大致定位段错误位置

1: 段错误产生的原因

简而言之,产生段错误就是访问了错误的内存段,一般是你没有权限,或者根本就不存在对应的物理内存,
尤其常见的是访问0地址.一旦一个程序发生了越界访问,系统就采取内存保护措施,并给那个程序发送
SIGSEGV信号,程序接到那个信号后就知道segmentation fault出现了。

想对”段错误”有更详细的了解可以去阅读“Linux下的段错误产生的原因及调试方法” 这篇文章,本文的
内容基本是从那文章里提取出来的。

2: SIGSEGV信号处理函数

程序接到SIGSEGV信号后的缺省处理是退出程序,这也是为什么总是看到程序打印一个“segmentation fault”
信息后就消失了。我们可以使用 signal(SIGSEGV, &your_function); 函数来接管SIGSEGV信号的处理,让
程序在发生段错误后,自动调用我们准备好的函数,从而在那个函数里来获取当前函数调用栈。

3: libc的Backtraces函数

在GDB里,可以简单的使用bt命令就可以获取函数调用栈,但如何通过代码获取当前函数调用栈?
这里我们可以通过libc库提供的Backtraces系列函数 。

?  View Code TEXT
[text]  view plain copy
  1. A backtrace is a list of the function calls that are currently active in a  
  2. thread. The usual way to inspect a backtrace of a program is to use an  
  3. external debugger such as gdb. However, sometimes it is useful to  
  4. obtain a backtrace programmatically from within a program, e.g., for the  
  5. purposes of logging or diagnostics.  
  6.    
  7. The header file execinfo.h declares three functions that obtain and  
  8. manipulate backtraces of the current thread.  

4: 实现步骤

4.1 在你的工程中添加如下代码:

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
 
void dump(int signo)
{
    void *array[30];
    size_t size;
    char **strings;
    size_t i;
 
    size = backtrace (array, 30);
    strings = backtrace_symbols (array, size);
 
    fprintf (stderr,"Obtained %zd stack frames.nm", size);
 
    for (i = 0; i < size; i++)
        fprintf (stderr,"%sn", strings[i]);
 
    free (strings);
 
    exit(0);
}
 
Debug_Printf_FrameInfos()
{
    signal(SIGSEGV, &dump);
}

  1. 4.2 在mian函数开始位置处调用 Debug_Printf_FrameInfos() 函数  

4.3 在编译程序时 加上 -g 选项

5 定位出错函数地址实例

这里以 test.c 为例,来查找出错函数地址

  1. <span style="color: #0000ff;">#include <execinfo.h>   
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4. #include <signal.h><br>  
  5. <br>  
  6. void dump(int signo)  
  7. {<br>  
  8.   
  9.     void *array[30];  
  10.     size_t size;   
  11.     char **strings;<br>  
  12.   
  13.     size_t i;<br>  
  14.   
  15.     <br>  
  16.   
  17.     size = backtrace (array, 30);<br>  
  18.   
  19.     strings = backtrace_symbols (array, size);<br>  
  20.   
  21.     fprintf (stderr,"Obtained %zd stack frames.nm", size);<br>  
  22.   
  23.     <br>  
  24.   
  25.     for (i = 0; i <= size; i++)<br>  
  26.   
  27.         fprintf (stderr,"%s/n", strings[i]);<br>  
  28.   
  29.   
  30.     free (strings);<br>  
  31.   
  32.     exit(0);<br>  
  33.   
  34. }<br>  
  35.   
  36. <br>  
  37.   
  38. Debug_Printf_FrameInfos()  
  39. {  
  40.     signal(SIGSEGV, dump);  
  41. }<br>  
  42.   
  43. <br>  
  44.   
  45. void func_c()  
  46. {<br>  
  47.   
  48.     * ((volatile char *) 0x0) = 0x999;<br>  
  49.   
  50. }<br>  
  51.   
  52. <br>  
  53.   
  54. void func_b()  
  55. {<br>  
  56.   
  57.     func_c();<br>  
  58.   
  59. }  
  60. <br>  
  61.   
  62. void func_a()  
  63. {  
  64.     func_b();  
  65. }  
  66. <br>  
  67.   
  68. int main()  
  69. {<br>  
  70.   
  71.     Debug_Printf_FrameInfos();<br>  
  72.   
  73.     func_a();<br>  
  74.   
  75.     return 0;<br>  
  76.   
  77. }</span>  
  78. <br>  
  79. 该例程调用序列为:  

main() -> func_a() -> func_b() -> func_c() -> 出错

5.1编译程序:

# gcc -g test.c -o test

注:选项 -rdynamic 可用来通知链接器将所有符号添加到动态符号表中,如果你的链接器支持-rdynamic的话,
建议将其加上,即
# gcc -rdynamic -g test.c -o test

5.2 运行 test程序:

 

 #./test




Obtained 7



 stack frames.nm./



a.out [



0x80484e3]




[



0xb7f70420]




./



a.out [



0x804859d]




./



a.out [



0x80485a7]




./



a.out [



0x80485c4]




/



lib/



tls/



i686/



cmov/



libc.so.6(



__libc_start_main+0xe0)



 [



0xb7e1e450]




./



a.out [



0x8048461]




Segmentation fault

如果编译似加了-rdynamic选项 的话,将打印如下信息

#./test 




Obtained 7



 stack frames.nm./



test



(



dump+0x1f)



 [



0x80487c3]




[



0xb7fbb420]




./



test



(



func_b+0x8)



 [



0x804887d]




./



test



(



func_a+0x8)



 [



0x8048887]




./



test



(



main+0x1b)



 [



0x80488a4]




/



lib/



tls/



i686/



cmov/



libc.so.6(



__libc_start_main+0xe0)



 [



0xb7e69450]




./



test



 [



0x8048741]



打印信息比没加-rdynamic的程序多出了一个函数名称+偏移地址..(func_b+0×8)

 

5.3 使用objdump获取程序所有符合

objdump -d ./test > tmp.txt

5.4 分析和查找

在tmp.txt 中查找0×80485ad 的地址,你会发现如下信息:
08048595 :
8048595: 55 push %ebp
8048596: 89 e5 mov %esp,%ebp
8048598: e8 eb ff ff ff call 8048588 
804859d : 5d pop %ebp
804859e: c3

其中 804859d 是调用( call 8048588 )c函数后的地址,虽然并没有直接定位到C函数,
通过汇编代码,基本可以推出是在C函数出现问题了。(pop指令不会导致段错误的)

本文最早发布在 我的CSDN blog 上。

本文地址:
http://www.kgdb.info/linuxdev/backtrace_without_gdb/ 
版权所有 © 转载时必须以链接形式注明作者和原始出处!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值