如何在程序内部获得call back trace

 #include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>

/* Obtain a backtrace and print it to stdout. */
void
print_trace (void)
{
void *array[10];
size_t size;
char **strings;
size_t i;

size = backtrace (array, 10);
strings = backtrace_symbols (array, size);

printf ("Obtained %zd stack frames.\n", size);

for (i = 0; i < size; i++)
printf ("%s\n", strings[i]);

free (strings);
}

/* A dummy function to make the backtrace more interesting. */
void dummy_function(void);
void test(void){
dummy_function();
}
void test1(void){
test();
printf("ok");
}
void
dummy_function (void)
{
//test();
print_trace ();
}

int
main (void)
{
//dummy_function ();
test1();
//print_trace();
return 0;
}

输出:
Obtained 7 stack frames.
./test(print_trace+0x14) [0x804878c]
./test(dummy_function+0xb) [0x8048837]
./test(test+0xb) [0x804880d]
./test(test1+0xb) [0x804881a]
./test(main+0x15) [0x804884e]
/lib/i686/libc.so.6(__libc_start_main+0xc7) [0x4003eb77]
./test(backtrace_symbols+0x31) [0x80486ed]



我理解这个函数只能取得直接调用的轨迹。而无法得到下面这样代码的调用轨迹

#include <execinfo.h>

#include <stdio.h>

#include <stdlib.h>



/* Obtain a backtrace and print it to stdout. */

void

print_trace (void)

{

  void *array[10];

  size_t size;

  char **strings;

  size_t i;



  size = backtrace (array, 10);

  strings = backtrace_symbols (array, size);



  printf ("Obtained %zd stack frames.\n", size);



  for (i = 0; i < size; i++)

     printf ("%s\n", strings[i]);



  free (strings);

}



/* A dummy function to make the backtrace more interesting. */

void dummy_function(void);

void test(void){

  //dummy_function();

}

void test1(void){

  test();

  printf("ok");

}

void

dummy_function (void)

{

  test();

  print_trace ();

}



int

main (void)

{

  dummy_function ();

  test1();

  //print_trace();

  return 0;

}

输出:

Obtained 5 stack frames.

./test(print_trace+0x14) [0x804878c]

./test(dummy_function+0x10) [0x8048834]

./test(main+0x15) [0x804884b]

/lib/i686/libc.so.6(__libc_start_main+0xc7) [0x4003eb77]

./test(backtrace_symbols+0x31) [0x80486ed]


没有得到test调用信息


2  #include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>

/* Obtain a backtrace and print it to stdout. */
void
print_trace (void)
{
void *array[10];
size_t size;
char **strings;
size_t i;

size = backtrace (array, 10);
strings = backtrace_symbols (array, size);

printf ("Obtained %zd stack frames.\n", size);

for (i = 0; i < size; i++)
printf ("%s\n", strings[i]);

free (strings);
}

/* A dummy function to make the backtrace more interesting. */
void dummy_function(void);
void test(void){
dummy_function();
}
void test1(void){
test();
printf("ok");
}
void
dummy_function (void)
{
//test();
print_trace ();
}

int
main (void)
{
//dummy_function ();
test1();
//print_trace();
return 0;
}

输出:
Obtained 7 stack frames.
./test(print_trace+0x14) [0x804878c]
./test(dummy_function+0xb) [0x8048837]
./test(test+0xb) [0x804880d]
./test(test1+0xb) [0x804881a]
./test(main+0x15) [0x804884e]
/lib/i686/libc.so.6(__libc_start_main+0xc7) [0x4003eb77]
./test(backtrace_symbols+0x31) [0x80486ed]



我理解这个函数只能取得直接调用的轨迹。而无法得到下面这样代码的调用轨迹

#include <execinfo.h>

#include <stdio.h>

#include <stdlib.h>



/* Obtain a backtrace and print it to stdout. */

void

print_trace (void)

{

  void *array[10];

  size_t size;

  char **strings;

  size_t i;



  size = backtrace (array, 10);

  strings = backtrace_symbols (array, size);



  printf ("Obtained %zd stack frames.\n", size);



  for (i = 0; i < size; i++)

     printf ("%s\n", strings[i]);



  free (strings);

}



/* A dummy function to make the backtrace more interesting. */

void dummy_function(void);

void test(void){

  //dummy_function();

}

void test1(void){

  test();

  printf("ok");

}

void

dummy_function (void)

{

  test();

  print_trace ();

}



int

main (void)

{

  dummy_function ();

  test1();

  //print_trace();

  return 0;

}

输出:

Obtained 5 stack frames.

./test(print_trace+0x14) [0x804878c]

./test(dummy_function+0x10) [0x8048834]

./test(main+0x15) [0x804884b]

/lib/i686/libc.so.6(__libc_start_main+0xc7) [0x4003eb77]

./test(backtrace_symbols+0x31) [0x80486ed]


没有得到test调用信息


2   直接BUG

3 调用dump_stack()就会打印当前cpu的堆栈的调用函数了。参考内核源代码
arch/x86_64/kernel/traps.c


/*
* The architecture-independent dump_stack generator
*/
void dump_stack(void)
{
        unsigned long dummy;
        show_trace(&dummy);
}



        /*
         * When in-kernel, we also print out the stack and code at the
         * time of the fault..
         */
        if (in_kernel) {

                printk("Stack: ");
                show_stack(NULL, (unsigned long*)rsp);

 

 

 

“我的2011”博客频道跨年征文活动火爆上线!                                             正在写年终总结?速度参加CSDN“我的2011”征文大赛  
下载频道领任务赚下载分活动开始啦!!!                                                             点击了解英特尔云计算
 

Linux调用backtrack函数打印程序崩溃时的调用堆栈

分类: 调试   78人阅读   评论(0)   收藏   举报
  #include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <signal.h>


//signal 函数用法参考http://www.kernel.org/doc/man-pages/online/pages/man2/signal.2.html
//backtrace ,backtrace_symbols函数用法参考 http://www.kernel.org/doc/man-pages/online/pages/man3/backtrace.3.html

static void WidebrightSegvHandler(int signum) {
    void *array[10];
    size_t size;
    char **strings;
    size_t i, j;

    signal(signum, SIG_DFL); /* 还原默认的信号处理赏罚 handler */

    size = backtrace (array, 10);
    strings = (char **)backtrace_symbols (array, size);

    fprintf(stderr, "widebright received SIGSEGV! Stack trace:\n");
    for (i = 0; i < size; i++) {
        fprintf(stderr, "%d %s \n",i,strings[i]);
    }

    free (strings);
    exit(1);
}

int invalide_pointer_error(char * p)
{
    *p = 'd'; //让这里出现一个拜访非法指针的过错 
    return 0;
}


void error_2(char * p)
{
    invalide_pointer_error(p);
}

void error_1(char * p)
{
     error_2(p);
}

void error_0(char * p)
{
     error_1(p);
}





int main() 
{

    //设置 信好的处理赏罚 函数,种种 信号的界说见http://www.kernel.org/doc/man-pages/online/pages/man7/signal.7.html
    signal(SIGSEGV, WidebrightSegvHandler); // SIGSEGV      11       Core    Invalid memory reference
    signal(SIGABRT, WidebrightSegvHandler); // SIGABRT       6       Core    Abort signal from


    char *a = NULL;
    error_0(a);
    exit(0);

}

widebright@widebright:~/桌面$ gcc main.c  
widebright@widebright:~/桌面$ ./a.out  
widebright received SIGSEGV! Stack trace:
0 ./a.out [0x8048580]  
1 [0xb807a400]  
2 ./a.out [0x8048636]  
3 ./a.out [0x8048649]  
4 ./a.out [0x804865c]  
5 ./a.out [0x80486a9]  
6 /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5) [0xb7f19775]  

然后为了定位过错,我们必要 加上-g参数重新编译一个带调试信息的版本
widebright@widebright:~/桌面$ gcc -g main.c  
widebright@widebright:~/桌面$ ./a.out  
widebright received SIGSEGV! Stack trace:
0 ./a.out [0x8048580]  
1 [0xb7fb3400]  
2 ./a.out [0x8048636]  
3 ./a.out [0x8048649]  
4 ./a.out [0x804865c]  
5 ./a.out [0x80486a9]  
6 /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5) [0xb7e52775]  
7 ./a.out [0x80484c1]  

加上-rdynamic 参数的话,输出的标记更明白一些,不外好像 所在不一样了。
widebright@widebright:~/桌面$ gcc -g -rdynamic main.c  
widebright@widebright:~/桌面$ ./a.out  
widebright received SIGSEGV! Stack trace:
0 ./a.out [0x8048840]  
1 [0xb7f3d400]  
2 ./a.out(error_2+0x11) [0x80488f6]  
3 ./a.out(error_1+0x11) [0x8048909]  
4 ./a.out(error_0+0x11) [0x804891c]  
5 ./a.out(main+0x4b) [0x8048969]  
6 /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5) [0xb7ddc775]  
7 ./a.out [0x8048781]  


可以看到有调试信息的时间,过错是一样的。然后就可以用gdb定位和调试过错了:
-----------------------
(gdb) info line *0x8048580
Line 19 of "main.c" starts at address 0x804856d <WidebrightSegvHandler+25>
   and ends at 0x8048583 <WidebrightSegvHandler+47>.
(gdb) list *0x8048580
0x8048580 is in WidebrightSegvHandler (main.c:19).
14        char **strings;
15        size_t i, j;
16     
17        signal(signum, SIG_DFL); /* 还原默认的信号处理赏罚 handler */
18     
19        size = backtrace (array, 10);
20        strings = (char **)backtrace_symbols (array, size);
21     
22        fprintf(stderr, "widebright received SIGSEGV! Stack trace:\n");
23        for (i = 0; i < size; i++) {
-----------------
(gdb) list *0x8048636
0x8048636 is in error_2 (main.c:41).
36     
37     
38    void error_2(char * p)
39    {
40        invalide_pointer_error(p);
41    }
42     
43    void error_1(char * p)
44    {
45         error_2(p);
--------------
(gdb) list *0x8048649
0x8048649 is in error_1 (main.c:46).
41    }
42     
43    void error_1(char * p)
44    {
45         error_2(p);
46    }
47     
48    void error_0(char * p)
49    {
50         error_1(p);

=============
(gdb) br main.c:40
Breakpoint 1 at 0x804862b: file main.c, line 40.
(gdb) run
Starting program: /home/widebright/桌面/a.out  

Breakpoint 1, error_2 (p=0x0) at main.c:40
40        invalide_pointer_error(p);
(gdb) stepi
0x0804862e    40        invalide_pointer_error(p);
(gdb) stepi
0x08048631    40        invalide_pointer_error(p);
(gdb) stepi
invalide_pointer_error (p=0x0) at main.c:32
32    {
(gdb) stepi
0x08048616    32    {
(gdb) stepi
33        *p = 'd'; //让这里出现一个拜访非法指针的过错  
(gdb) stepi
0x0804861b    33        *p = 'd'; //让这里出现一个拜访非法指针的过错  
(gdb) stepi

Program received signal SIGSEGV, Segmentation fault.
0x0804861b in invalide_pointer_error (p=0x0) at main.c:33
33        *p = 'd'; //让这里出现一个拜访非法指针的过错  

(gdb) print p
$1 = 0x0
(gdb) print *p
Cannot access memory at address 0x0




===============================================
好像 应用      
    int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
http://www.kernel.org/doc/man-pages/online/pages/man2/sigaction.2.html
这个函数注册信号的处理赏罚 函数的话,可以得到更多的信息,比如 堕落 时间的寄放器的值等等。
由于他函数 末了一个参数传过来一个ucontext_t *ucontext 的指针
可以看到 “善用backtrace办理大题目 ” http://blog.chinaunix.net/u/3425/showart_263408.html 这个网页上有给出一个例子。


最初看到这个用法的的在redhat的安装措施的anaconda内里的。


------------------------
关于backtrack的原理 的表明 ,参考这个:
从别人blog上拷来的,所在:http://blog.csdn.net/absurd/archive/2005/12/13/551585.aspx  

开拓嵌入式软件通常是比拟 贫穷 的事,一些常用的器材每每无法应用,在开拓PC软件时大略 的义务 ,此时变得很繁杂。本日就碰到了如许一件事,折腾了几个小时,仅仅是为知道call stack。  

我编译了一个措施放到PDA(ARM9+LINUX+UCLIBC)上面运行,出现了一个ASSERT,并表现了文件名和行号,本来是调用了一个没有实现 的函数,我很想知道是谁调用了它,这看似大略 的题目却让我很头疼,假如有gdb,那好办-用bt下令就可以搞定,假如用的libc,那也好办-用 backtrace函数就可以搞定,题目是两者都没有。  

想来想去只有本身写一个backtrace,要实现这个功能并不难,假如我们知道调用堆栈的技俩,就可以很轻易取出上层调用者的指令所在,有了这些上层调用者的指令所在,我们可以通过MAP文件找到指令所在对应的源文件名和行号。  

下面扼要 先容一下实现原理:  

要得到调用者的所在,有须要先容一下堆栈的技俩:  

+---------------------------+ (高所在)  
+_参数1__________+  
+---------------------------+  
+_参数2__________+  
+---------------------------+ 参数的序次 凭借于调用行动  
+_参数.__________+  
+---------------------------+  
+_参数N__________+  
+---------------------------+  
+_eip____________+ 返回本次调用后,下一条指令的所在  
+----------------------------+  
+_ebp____________+ 这里生涯的调用者的ebp  
+----------------------------+  
(ebp 指向这里:相等于调用者和被调用者的分边界)  
+----------------------------+  
+_临时 变量1_______+  
+----------------------------+  
+_临时 变量2_______+  
+----------------------------+  
+_临时 变量.________+  
+----------------------------+  
+----------------------------+  
+_临时 变量N_______+  
+----------------------------+(低所在)  
由于优化、调用行动、编译器的差别 ,上述构造部也许有所差别 ,但一样平常来说,第一个局部变量前是调用者的ebp,ebp前是返回后下一条指令的所在。  

知道了这个结构,要得到上层调用的者指令所在就轻易了,我们可以用如下代码仿照glibc供给 的backtrace的功能:  
int backtrace (void **BUFFER, int SIZE)  
{  
int n = 0;  
int *p = &n;  
int i = 0;  

int ebp = p[1];  
int eip = p[2];  

for(i = 0; i < SIZE; i++)  
{  
BUFFER[i] = (void*)eip;  
p = (int*)ebp;  
ebp = p[0];  
eip = p[1];  
}  

return SIZE;  
}  

附:  
通过addr2line可以找到所在对应的文件名和行号,不消手动去查MAP文件了。  


=======================
windows体系 上面要实现同样的功能,也许要调用 内里的StackWalk64 等函数。
http://msdn.microsoft.com/en-us/library/ms680650(VS.85).aspx


找到一个应用 StackWalk64 的例子http://www.cppblog.com/kevinlynx/archive/2008/03/28/45628.html
这里又是一个仿照backtrace(stackwalk)函数的例子
http://www.cnblogs.com/lbq1221119/archive/2008/04/18/1159956.html


着实 你可以在措施的任何地方 调用backtrace和 stackwalk函数的,呵呵

查看评论

  暂无评论

发表评论
  • 用 户 名:
  • xushiyan
  • 评论内容:
  • 插入代码
  •   
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
  • 个人资料
  •  
    liyongming1982
    • 访问:3494次
    • 积分:929分
    • 排名:第6392名
    • 原创:76篇
    • 转载:75篇
    • 译文:0篇
    • 评论:1条
  • 文章存档
  • 推荐文章
    • 最新评论
     
    公司简介| 招贤纳士| 广告服务| 银行汇款帐号| 联系方式| 版权声明| 法律顾问| 问题报告
    北京创新乐知信息技术有限公司 版权所有, 京 ICP 证 070598 号
    世纪乐知(北京)网络技术有限公司 提供技术支持
    江苏乐知网络技术有限公司 提供商务支持
     Email:webmaster@csdn.net
    Copyright © 1999-2011, CSDN.NET, All Rights Reserved
    GongshangLogo


     

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

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

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

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值