【Linux笔记】一次 backtrace 问题记录

【Linux笔记】一次 backtrace 问题记录

最近在使用 backtrace 函数和 backtrace_symbols 函数来进行异常时打印栈调用信息时,遇到了程序卡死在 backtrace 函数 或者 backtrace_symbols 函数里面的问题,这里记录下这个问题和我自己的处理方案。

什么是 backtrace

backtrace 函数用于程序异常退出时回溯栈信息,通过回溯上层函数在当前栈中的地址,并将地址指针放入 buffer 缓冲区。然后可以再通过 backtrace_symbols 将从backtrace 函数中获得的函数地址转换为字符串形式的调用栈信息,或者使用 backtrace_symbols_fd 函数将转换的信息写入 fd 中。

函数原型如下:

#include 

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);

详细说明可以看backtrace介绍或者man backtrace

为什么会卡死

由于我是在使用 backtrace 函数和 backtrace_symbols 函数来进行异常时打印栈调用信息,所以是在 signal 处理函数中调用了 backtrace 函数和 backtrace_symbols 函数。而 signal 信号处理函数中只能调用可重入函数,不能调用不可重入函数。如果在信号处理函数中使用不可重入函数,有可能会造成不可预知的错误。 根据介绍:

backtrace() and backtrace_symbols_fd() don't call malloc() 
explicitly, but they are part of libgcc, which gets loaded 
dynamically when first used.  Dynamic loading usually triggers 
a call to malloc(3).  If you need certain calls to these two 
functions to not allocate memory (in signal handlers, for 
example), you need to make sure libgcc is loaded beforehand.

可以看到 backtrace 函数和 backtrace_symbols_fd 函数不会显式调用malloc,但这两个函数是 libgcc 的一部分,在首次使用时会动态加载。动态加载通常会触发对malloc的调用。如果需要对这两个函数进行某些调用以不分配内存(例如,在信号处理程序中),则需要确保预先加载了 libgcc。

因此在没有预先加载 libgcc 的情况下,在信号处理程序中使用 backtrace函数是不安全的,可能会造成死锁,因为调用了malloc函数。malloc函数是一个不可重入函数,为了保证线程安全,内部使用了锁,假如出异常的程序调用malloc函数,函数内获得了锁之后被signal信号中断,再在信号处理程序中又调用了malloc函数,而此时异常程序已经获得了锁,所以信号处理程序中只能等待锁的释放,而异常程序却在等待信号处理程序的返回后才能继续执行,就这样造成了死锁,导致程序卡死。

backtrace_symbols 函数会显式调用malloc,所以也会出现和上诉一样原因的程序卡死问题。

因此在信号处理程序中是不应该使用 backtrace 函数和 backtrace_symbols 函数来打印输出栈调用信息的。如果要使用的话,可以用backtrace_symbols_fd 函数来取代 backtrace_symbols 函数,避免显式调用malloc。然后在确保预先加载了 libgcc 的情况下,在在信号处理程序中使用 backtrace 函数和 backtrace_symbols_fd 函数来打印输出栈调用信息的。

目前我还不知道应该怎么预先加载 libgcc ,所以还没验证过这种方式。但是为了使用 backtrace 函数来进行异常时打印栈调用信息,所以我使用了另外一种方式来处理 backtrace 函数或者 backtrace_symbols 函数卡死的问题。

一种处理方式

我使用了 alarm 来处理 backtrace 函数或者 backtrace_symbols 函数卡死的问题,如下:

    signal(SIGALRM, sighandler);
    alarm(2);
    size = backtrace (array, 32);
    strings = backtrace_symbols (array, size);
    alarm(0);

alarm(2)设置了 2 秒的定时器,如果 backtrace 函数和 backtrace_symbols 函数均正常返回,则alarm(0)取消定时器;如果 backtrace 函数或者 backtrace_symbols 函数卡死在里面,则程序无法正常运行下去,会触发定时器发送 SIGALRM 信号,调用处理函数 sighandler,这个时候我们就可以输出 backtrace 失败的消息,并且退出程序。

void sighandler(int signo)
{
   switch (signo) {
       case SIGALRM:
           printf("backtrace failed\n");
           exit(EXIT_FAILURE);
           break;
   }
   return;
}

alarm是一个可重入函数,所以在异常处理函数中使用,是安全的。

【参考资料】

backtrace

Linux 多线程程序调用malloc,backtrace引发死锁问题的调试

malloc的线程安全与signal使用malloc的陷阱


本文链接:https://blog.csdn.net/u012028275/article/details/131355611

你可能感兴趣的:(Linux笔记,linux,笔记,c语言,c++,学习)