ARM平台上使用backtrace定位程序崩溃问题

本文详细介绍了如何在嵌入式arm平台上使用backtrace工具追踪SIGSEGV段错误,包括backtrace系列函数的使用方法、注意事项及在实际项目中的应用实例。重点讲述了配置coredump生成和如何通过backtrace_symbols解析异常堆栈信息。
摘要由CSDN通过智能技术生成

        最近项目中遇到一个偶现的SIGSEGV段错误问题,使用普通的printf方法排查了一段时间依旧没有定位到问题点,于是尝试使用backtrace来跟进问题。本文主要记录一下我在arm平台上使用backtrace来回溯函数调用栈的步骤,作为对问题的整理和回顾。

        嵌入式开发过程中,难免会遇到各种死机问题,用以下方法来帮助定位

        1、添加打印/日志信息梳理业务逻辑,跟踪代码运行轨迹,找到死机位置;

        2、根据设备死机时输出函数调用栈(backtrace),结合符号文件表定位问题;

        3、保留死机时的内存镜像(coredump),利用gdb工具来还原“案发现场”;

        以上三种定位手段中,第1种是最基本最常用的方法,但信息量较少,需要多次添加日志来跟踪定位; 第2种能够给出函数调用关系,但是一般无法给出各个参数的值;第3种不仅能够给出函数调用关系,还能查看各个参数的值。后两种方法对编译工具链、系统都有一定的要求。

        不过大部分嵌入式操作系统因存储空间有限,程序出现异常时不会自动生成coredump,想要生成core文件需要在程序运行前手动进行以下配置(因设备端空间有限,此处以nfs挂载/mnt为例):

        sysctl -w kernel.core_pattern=/mnt/core.%e.%p

        chmod 666 /mnt/ -R

        ulimit -c unlimited

        执行以上操作后,程序出现异常崩溃时会自动在/mnt目录下生成core文件,再使用gdb进行调试即可。

        下面主要介绍backtrace。

        当程序出现异常时通常伴随着会收到一个由内核发过来的异常信号,如当对内存出现非法访问时将收到段错误信号SIGSEGV,然后才退出。利用这一点,当我们在收到异常信号后将程序的调用栈进行输出。

        backtrace()系列函数有3个:backtrace,backtrace_symbols,backtrace_symbols_fd。主要用于应用程序反调试(self-debugging)。

        参见man 3 BACKTRACE,3个函数原型:

        #include <execinfo.h>

        int backtrace(void **buffer, int size);

        char **backtrace_symbols(void *const *buffer, int size);

        void backtrace_symbols_fd(void *const *buffer, int size, int fd);

        1、int backtrace(void **buffer, int size);

        backtrace() 返回调用程序的回溯(跟踪)信息,存储在由buffer指向的数组中。对于特定程序,backtrace就是一系列当前激活的函数调用(active function call)。

        参数:
        buffer 由buffer指向的数组,每一项都是void*类型,存储的是相应(调用函数的)栈帧的返回地址。
        size 指定存储在buffer中的地址最大数量。

        返回值:
        返回buffer中实际地址的数量,应当<=size。如果返回值 < size,那么完整的回溯信息被存储;如果返回值 = size,那么它可能被截断,最旧的栈帧可能没有返回

        2、char **backtrace_symbols(void *const *buffer, int size);

        backtrace() 返回一组地址,backtrace_symbols()象征性地翻译这些地址为一个描述地址的字符串数组。

        参数:
        buffer一个字符串数组,由backtrace()返回的buffer,每项代表一个函数地址。backtrace_symbols()会用字符串描述每个函数地址,字符串包括:函数名称,一个16进制偏移(offset),实际的返回地址(16进制)。
        size 表明buffer中的地址个数。

        返回值:
        成功时,返回一个指向由malloc(3)分配的array;失败时,返回NULl。
        arrary是一个二维数组,该数组的每个元素 指向一个代表backtrace()返回的函数地址的符号信息的字符串,数组由函数内部调用malloc分配空间,必须由调用者free
        注意:指向字符串的指针的数组,不必释放,而且不应该释放。应该释放的是返回的二维数组指针。

        3、void backtrace_symbols_fd(void *const *buffer, int size, int fd);

   backtrace_symbols_fd()的参数buffer、size同backtrace_symbos(),不同之处在于,backtrace_symbols_fd()并不会返回一个字符串数组给调用者,而是将字符串写入fd对应文件。backtrace_symbols_fd()也不会调用malloc分配二维数组空间,因此可应用于malloc可能会失败的情形。

        backtrace,backtrace_symbols,backtrace_symbols_fd在glibc 2.1以后就提供了。

        3个函数是GNU 扩展(GNU extensions),因此只能用于GNU gcc/g++系列编译器。

        使用它们时需要注意以下几点

    1、backtrace的实现依赖于栈指针(fp寄存器),在gcc编译过程中任何非零的优化等级(-On参数)或加入了栈指针优化参数-fomit-frame-pointer后多将不能正确得到程序栈信息;

    2、backtrace_symbols的实现需要符号名称的支持,在gcc编译过程中需要加入-rdynamic参数;

    3、内联函数没有栈帧,它在编译过程中被展开在调用的位置;

    4、尾调用优化(Tail-call Optimization)将复用当前函数栈,而不再生成新的函数栈,这将导致栈信息不能正确被获取。

    我的应用平台是armv7,编译选项中增加了以下几个参数:

          -O0 -rdynamic -g -funwind-tables -ffunction-sections

    刚开始只使用了-rdynamic -g参数,在我所使用的平台板子上并不能抓取程序运行时的栈信息,加了-funwind-tables -ffunction-sections之后才成功抓取到栈回溯信息。

下面是我在使用backtrace调试实际应用程序前,编写的一个简单的测试用例:

用户空间的程序无法直接访问物理地址,此处显示的地址是经过MMU(内存管理单元)映射过的。通过maps信息显示test程序运行时的栈起始地址为0x55f6e2212000,则程序运行过程出现异常时的偏移地址应该为:

        0x55f6e2212cf8 - 0x55f6e2212000 = 0xcf8

找到了正确的地址,此时可以使用addr2line工具来找到程序中的具体位置:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值