应用程序调试-signal和backtrace

优秀博客:
https://blog.csdn.net/skdkjzz/article/details/46046271
https://blog.csdn.net/littlefang/article/details/42295803
http://blog.chinaunix.net/uid-24774106-id-3457205.html

一.利用backtrace,栈信息找bug

  一般察看函数运行时堆栈的方法是使用GDB(bt命令)调试工具。但是,有些时候为了分析程序的BUG(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的。
  作用:用于程序异常退出时寻找错误原因。
  功能:回溯堆栈,简单的说就是可以列出当前函数调用关系。
原理:

  • 1. 通过对当前堆栈的分析,找到其上层函数在栈中的帧地址,再分析上层函数的堆栈,再找再上层的帧地址……一直找到最顶层为止,帧地址指的是一块:在栈上存放局部变量,上层返回地址,及寄存器值的空间。
  • 2. 由于不同处理器堆栈方式不同,此功能的具体实现是编译器的内建函数__buildin_frame_address
    __buildin_return_address中,它涉及工具glibc和gcc, 如果编译器不支持此函数,也可自己实现此函数,举例中有arm上的实现。
    ··

二. 相关函数接口简析

  在glibc头文件”execinfo.h”中声明了三个函数用于获取当前线程的函数调用堆栈。

2.1 backtrace()

/*
说明:
     1. 该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针列表。
        参数 size 用来指定buffer中可以保存多少个void* 元素。函数返回值是实际获取的指针个数,
        最大不超过size大小。

     2. 在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址。

     3. 某些编译器的优化选项会对获取正确的调用堆栈有干扰;

     4. 内联函数没有堆栈框架;

     5. 删除框架指针也会导致无法正确解析堆栈内容。
*/
    int backtrace(void **buffer, int size)  

2.2 backtrace_symbols ()

/*
说明:
    1. backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组。

    2. 参数buffer应该是从backtrace函数获取的指针数组,size是该数组中的元素个数(backtrace的返回值)。

    3. 函数返回值是一个指向字符串数组的指针,它的大小同buffer相同。每个字符串包含了一个相对于
       buffer中对应元素的可打印信息。它包括函数名、函数的偏移地址、实际的返回地址。

    4. 目前,只有使用ELF二进制格式的程序才能获取函数名称和偏移地址。在其他系统,只有16进制的返回地址能被
       获取。另外,你可能需要传递相应的符号给链接器,以能支持函数名功能。(比如,在使用GNU ld链接器的系统
       中,器支持-rdynamic的话,建议将其加上!)

    5. 该函数的返回值是通过malloc函数申请的空间,因此调用者必须使用free函数来释放指针。

    6. 如果不能为字符串获取足够的空间函数的返回值将会为NULL。
*/
char ** backtrace_symbols (void *const *buffer, int size) 

2.3 backtrace_symbols_fd()

/*
说明:
    1. backtrace_symbols_fd与backtrace_symbols 函数具有相同的功能,不同的是它不会给调用者
       返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行.它不需要调用malloc函数,
       因此适用于有可能调用该函数会失败的情况。
*/
void backtrace_symbols_fd(void *const *buffer, int size, int fd)

三. 实例

3.1 编译

  1. 如果是x86平台gcc编译:
    gcc -g -rdynamic backtrace.c -o backtrace
  2. 如果是ARM平台arm-linux-gcc交叉编译,需要带如下编译参数:
    -rdynamic
    -funwind-tables
    -ffunction-sections

    否则可能backtrace返回值为0,得不到需要的信息。

3.2 实例代码


/* backtrace.c */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <stdarg.h>
#include <time.h>
#include <stdbool.h>
#include <inttypes.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <memory.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <poll.h>
#include <sys/epoll.h>
#include <sys/select.h>
#include <dirent.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <malloc.h>
#include <pthread.h>
#include <sys/types.h>
#include <execinfo.h>


#define BACKTRACE_BUF_SIZE          50

void sig_children(int signal)
{
    pid_t pid;
    int stat;

    while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) {
        printf("children %d terminated\n", pid);
    }

}

void SigSegv_handler(int signal)
{
    int j, nptrs;
    void *buffer[BACKTRACE_BUF_SIZE];
    char **strings;

#if 1
    nptrs = backtrace(buffer, BACKTRACE_BUF_SIZE);
#else
    nptrs = backtrace_arm(buffer, BACKTRACE_BUF_SIZE);
#endif

    printf("signal_test exit: backtrace() returned %d addresses, sig:%d\n", nptrs, signal);

    switch (signal) {
    case SIGFPE: // 8:
        printf("The signal type: Floating point exception!\n");
        break;
    case SIGILL: // 4:
        printf("The signal type: Illegal instruction!\n");
        break;
    case SIGSEGV: // 11:
        printf("The signal type: Segmentation fault!\n");
        break;
    case SIGBUS: // 7:
        printf("The signal type: Bus error!\n");
        break;
    case SIGABRT: // 6:
        printf("The signal type: Aborted!\n");
        break;
    case SIGTRAP: // 5:
        printf("The signal type: Trace/breakpoint trap!\n");
        break;
    case SIGSYS: // 31:
        printf("The signal type: Bad system call!\n");
        break;
    case SIGTERM: // 15:
        printf("The signal type: Terminated!\n");
        break;
    case SIGINT: // 2:
        printf("The signal type: Interrupt!\n");
        break;
    case SIGQUIT: // 3:
        printf("The signal type: Quit!\n");
        break;
    case SIGKILL: // 9:
        printf("The signal type: Killed!\n");
        break;
    case SIGHUP: // 1:
        printf("The signal type: Hangup!\n");
        break;
    case SIGALRM: // 14:
        printf("The signal type: Alarm clock!\n");
        break;
    case SIGVTALRM: // 26:
        printf("The signal type: Virtual timer expired!\n");
        break;
    case SIGPROF: // 27:
        printf("The signal type: Profiling timer expired!\n");
        break;
    case SIGIO: // 29:
        printf("The signal type: I/O possible!\n");
        break;
    case SIGPIPE: // 13:
        printf("The signal type: Broken pipe!\n");
        break;
    case SIGXCPU: // 24:
        printf("The signal type: CPU time limit exceeded!\n");
        break;
    case SIGXFSZ: // 25:
        printf("The signal type: File size limit exceeded!\n");
        break;
    case SIGUSR1: // 10:
        printf("The signal type: User defined signal 1!\n");
        break;
    case SIGUSR2: // 12:
        printf("The signal type: User defined signal 2!\n");
        break;
    default:
        printf("The signal type: Unkown signal:%d!\n", signal);
        break;
    }

    /* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
    would produce similar output to the following: */

    strings = backtrace_symbols(buffer, nptrs);
    if (strings == NULL) {
        printf("backtrace_symbols");
        exit(EXIT_FAILURE);
    }

    for (j = 0; j < nptrs; j++) {
        printf("%s\n", strings[j]);
    }

    free(strings);
    exit(-1);
}



void main(int argc, char *argv[])
{
    signal(SIGCHLD, sig_children);
    signal(SIGSEGV, SigSegv_handler);
    signal(SIGILL, SigSegv_handler);
    signal(SIGBUS, SigSegv_handler);
    signal(SIGFPE, SigSegv_handler);
    signal(SIGABRT, SigSegv_handler);
    signal(SIGTRAP, SigSegv_handler);
    signal(SIGSYS, SigSegv_handler);
    signal(SIGTERM, SigSegv_handler);
    signal(SIGINT, SigSegv_handler);
    signal(SIGQUIT, SigSegv_handler);
    signal(SIGKILL, SigSegv_handler);
    signal(SIGHUP, SigSegv_handler);
    signal(SIGALRM, SigSegv_handler);
    signal(SIGVTALRM, SigSegv_handler);
    signal(SIGPROF, SigSegv_handler);
    signal(SIGIO, SigSegv_handler);
    signal(SIGPIPE, SigSegv_handler);
    signal(SIGXCPU, SigSegv_handler);
    signal(SIGXFSZ, SigSegv_handler);
    signal(SIGUSR1, SigSegv_handler);
    signal(SIGUSR2, SigSegv_handler);

    while(1)
        sleep(10);

}

3.3 定位问题(c文件:backtrace.c ;编译结果:backtrace;报错行:0x4007e9)

1.通过objdump工具(注:只有加了编译选项的,汇编结果才能有对应的c语言。)
x86:
objdump

arm:
arm-poky-linux-gnueabi-objdump

例如:
objdump -d backtrace > backtrace.s //没有对应的c语句
objdump -S backtrace | less //可能有对应的c语句,取决于编译时添加的编译选项。

这里写图片描述
2.通过addr2line工具
addr2line 0x4007e9 -e backtrace -f
这里写图片描述

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值