zoukankan      html  css  js  c++  java
  • Linux调用backtrack函数打印程序崩溃时的调用堆栈

     
    #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系统上面要实现同样的功能,可能要调用Debug Help Library 里面的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函数的,呵呵

  • 相关阅读:
    HTML5-Input
    在安装搜狗输入法的时候找到的一个安装手动下载软件的包
    IP结构与操作之__inet_insert_ifa/__inet_del_ifa
    IP结构与操作之inet_addr_onlink
    IP结构与操作之inet_confirm_addr && confirm_addr_indev
    IP结构与操作之inet_select_addr
    IP结构与操作之inetdev_init && inetdev_destroy
    IP结构与操作之in_device结构和in_ifaddr结构
    网络模块初始化
    网络设备之uc_promisc
  • 原文地址:https://www.cnblogs.com/superbi/p/2584893.html
Copyright © 2011-2022 走看看