Linux 如何在进程信号处理程序(armv7 uclibc)中获得正确的回溯跟踪?

Linux 如何在进程信号处理程序(armv7 uclibc)中获得正确的回溯跟踪?,linux,arm,uclibc,Linux,Arm,Uclibc,我已经在google上搜索了很多次,为信号处理程序中的backtrace()找到了正确的解决方案,并尝试了几乎所有的方法,但我无法在信号处理程序中成功地获得backtrace-这不是SIGUSR1处理程序 enable UCLIBC_在UCLIBC config中有_BACKTRACE=y并编译了它 已验证是否已创建libubacktrace.so 使用以下选项编译我的应用程序二进制文件 -g -动力学 -fexception或-funwind表 二进制文件本身似乎被“剥离” 然而,我无法从

我已经在google上搜索了很多次,为信号处理程序中的backtrace()找到了正确的解决方案,并尝试了几乎所有的方法,但我无法在信号处理程序中成功地获得backtrace-这不是SIGUSR1处理程序

  • enable UCLIBC_在UCLIBC config中有_BACKTRACE=y并编译了它
  • 已验证是否已创建libubacktrace.so
  • 使用以下选项编译我的应用程序二进制文件 -g -动力学 -fexception或-funwind表
  • 二进制文件本身似乎被“剥离”
然而,我无法从信号处理程序获得完整的回溯。 只打印了我在信号处理程序中调用的函数地址

如果我使用目标gdb二进制文件并使用gdb--pid命令连接进程,我就能够正确地获得完整的回溯

另外,我试过pstack,但(pstack-1.2-试过手臂贴片,但很糟糕…没有打印)没有太大帮助

有什么建议吗


1) Makefile中的编译器选项

CFLAGS+=-g-feexceptions-funwind表-Werror$(警告)

2) 代码

代码非常简单

#define CALLSTACK_SIZE 10

static void print_stack(void) {
    int i, nptrs;
    void *buf[CALLSTACK_SIZE + 1];
    char **strings;

    nptrs = backtrace(buf, CALLSTACK_SIZE);
    printf("%s: backtrace() returned %d addresses\n", __func__, nptrs);

    strings = backtrace_symbols(buf, nptrs);

    if(strings == NULL) {
        printf("%s: no backtrace captured\n", __func__);
        return;
    }

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

    free(strings);
}

...
static void sigHandler(int signum)
{
    printf("%s: signal %d\n", __FUNCTION__, signum);
    switch(signum ) {
    case SIGUSR2:
        // told to quit
        print_stack();
        break;
    default:
        break;
    }
}
#定义调用堆栈大小10
静态void打印堆栈(void){
int i,NPTR;
void*buf[CALLSTACK_SIZE+1];
字符**字符串;
nptrs=回溯(buf、调用堆栈大小);
printf(“%s:backtrace()返回了%d个地址\n”,\uuuu func\uuuu,nptrs);
字符串=回溯符号(buf、NPTR);
if(strings==NULL){
printf(“%s:未捕获回溯\n”,\uuuu func\uuuu);
返回;
}
对于(i=0;i
请仔细阅读并确认

信号处理程序仅限于调用(直接或间接)异步信号安全函数(实际上,仅限于大多数函数),或者甚至
free
都不是异步信号安全函数。因此,您的代码是不正确的:信号处理程序
signandler
正在调用
printf
,并且间接地(通过
print\u stack
free
,它们不是异步信号安全的

因此,您唯一的选择是使用
gdb
调试器

阅读有关POSIX&的更多信息。实际上,信号处理程序可以做的几乎唯一明智的事情是设置一些全局、线程本地或静态的
易失性sig_原子\u t
,这必须在其他地方进行测试。它还可以直接将几个字节放入一个文件中,应用程序将在其他地方读取该文件(例如,如果它是GUI应用程序,则在其事件循环中)

您也可以使用Ian Taylor(假设您的程序是使用调试信息编译的,例如使用
-g
)。它不能保证在信号处理程序中工作(因为它不仅使用异步信号安全函数),但实际上非常有用

请注意,内核正在为处理信号时设置调用帧(在中)

您还可以使用(特别是在应用程序是单线程的情况下)来拥有备用信号堆栈。我不确定这是否有帮助


如果你有一个事件循环,你可能会考虑使用Linux特定的,并要求你的事件循环到<代码>投票/代码>。对于

SIGTERM
SIGQUIT
SIGALRM
来说,这是一个非常有用的技巧。

我想在@Basile Starynkevitch的答案中添加一些东西,这太迂腐了。 虽然您的信号处理程序确实不是,但它很有可能在Linux上正常工作,因此,如果您看到结果被打印出来,这并不是导致您无法看到相关堆栈信息的原因

一些更可能出现的问题包括:

  • 您的平台的编译器标志不正确。回溯通常在x86上工作良好,没有特殊标志,但ARM可能更挑剔。有一些我已经尝试过,但我记不起来了,但最重要的尝试是
    -fno省略帧指针
    -fasynchronous unwind tables

  • 崩溃的代码是通过未使用正确的标志编译以获取堆栈跟踪的代码调用的。例如,源于从
    调用的代码的堆栈跟踪。因此,未使用正确的编译器标志编译的
    通常会导致重复或截断的回溯

  • 获取回溯的信号不是线程定向的信号,而是进程定向的信号。实际上,当线程崩溃时,线程定向信号类似于
    SIGSEGV
    ,或者另一个线程使用类似于
    pthread\u kill
    的东西发送特定线程。有关更多信息,请参阅

  • 有了这一点,我想谈谈你可以在你的信号处理器中做些什么来获得回溯。确实,您不应该调用任何stdio函数,
    malloc()
    free()
    ,等等,但不能使用健全版本的glibc/libgcc调用
    backtrace
    。从中可以看出,
    backtrace\u symbols\u fd
    当前是异步信号安全的。您还可以看到,
    backtrace
    不是。看起来很不安全。但是,请告诉我们为什么适用这些限制:

    回溯\u符号\u fd()不调用malloc(3),并且 因此,可以在后一种功能可能发生故障的情况下使用 失败,但请参见注释

    后来:

    backtrace()和backtrace_符号_fd()不调用malloc() 显式地,但它们是libgcc的一部分,libgcc将被加载 第一次使用时动态地。动态加载通常会触发 打电话给malloc(3)。如果你需要打电话给这两个 不分配内存的函数(例如,在信号处理程序中) 例如),您需要确保事先加载libgcc

    快速浏览一下下面的内容
    // On linux, especially on ARM, you want to use the sigaction version of this call.
    // See my comments below.
    static void
    handle_signal(int sig)
    {
        // Check signal type or whatever you want to do.
        // ...
        
        void* symbols[100];
        int n = backtrace(symbols, 100);
        
        // You could also either call a string formatting routine that you know
        // is async-signal-safe or save your backtrace and let another thread know
        // that this thread has crashed and the backtrace needs to be printed.
        //
        write(STDERR_FILENO, "Crash:\n", 7);
        backtrace_symbols_fd(symbols, n, STDERR_FILENO);
    
        // In the case of notifying another thread, which is what I do, you would
        // do something like this:
        //
        // threadLocalSymbolCount = backtrace(threadLocalSymbols, 100);
        // sem_post() or write() to an eventfd or whatever.
    }
    
    int main(int argc, char** argv)
    {
        void* dummy = NULL;
        backtrace(&dummy, 1);
        
        // Setup custom signal handling
        // ...
    
        function_that_crashes();
    
        return 0;
    }