如何在进程信号处理程序(armv7-uclibc)中获得适当的回溯?

时间:2015-05-01 06:36:26

标签: linux arm uclibc

我已经多次谷歌搜索信号处理程序中的backtrace()的正确解决方案并尝试了几乎所有但我无法在我的信号处理程序中成功获得回溯 - 这不是SIGUSR1处理程序。

  • 在uclibc config中启用UCLIBC_HAS_BACKTRACE = y并编译它
  • 确认已创建libubacktrace.so
  • 使用以下选项编译了我的应用程序二进制文件 -G -rdynamic -fexception或-funwind-tables
  • 二进制本身似乎被“剥离”

然而,我无法从信号处理程序获得完整的回溯。 只打印了我在信号处理程序中调用的函数地址。

如果我使用target-gdb二进制文件并使用gdb --pid命令附加进程,我就可以正常获得完整的回溯。

另外,我尝试了pstack但是(pstack-1.2 - 尝试了手臂补丁,但它很可怕......没有打印)不是很有帮助。

有什么建议吗?

1)Makefile中的编译器选项

CFLAGS + = -g -fexceptions -funwind-tables -Werror $(WARN)...

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

1 个答案:

答案 0 :(得分:2)

我想在@Basile Starynkevitch的答案中添加一些内容,这过于花哨。 虽然您的信号处理程序确实不是async-signal-safe,但很有可能它通常可以在Linux上运行,因此,如果您看到打印出来的结果,那并不是导致您看不到相关堆栈的问题信息。

更多可能的问题包括:

  1. 平台的错误编译器标志。在没有特殊标志的情况下,回溯通常可以在x86上正常工作,但是ARM可能更挑剔。我尝试过一些我不记得的方法,但是最重要的方法是-fno-omit-frame-pointer-fasynchronous-unwind-tables

  2. 正在崩溃的代码是通过未使用正确标志进行编译以获取堆栈跟踪的代码调用的。例如,源于未使用正确的编译器标志进行编译的.so进行回调的代码中产生的堆栈跟踪通常会导致重复跟踪或截断。

  3. 获得回溯跟踪的信号不是线程控制的信号,而是进程控制的信号。实际上,当线程崩溃时,线程定向信号就像SIGSEGV一样,或者另一个线程发送带有诸如pthread_kill之类的特定线程的信号。有关更多信息,请参见man 7 signal

通过这种方式,我想解决您可以在信号处理程序中执行的操作以获取回溯。的确,您不应调用任何malloc()free()等stdio函数,但不能调用{{1 }}与健全版本的glibc / libgcc。在here中,您可以看到backtrace当前是异步信号安全的。您还可以看到backtrace_symbols_fd不是。看起来非常不安全。但是,man 3 backtrace告诉我们为什么要应用这些限制:

backtrace_symbols_fd()不调用malloc(3),并且 因此可以在后一种功能可能 失败,但请参阅注释。

稍后:

backtrace()和backtrace_symbols_fd()不会调用malloc() 明确地,但是它们是libgcc的一部分,它已加载 首次使用时动态变化。动态加载通常会触发 调用malloc(3)。如果您需要致电给这两个 不分配内存的功能(在信号处理程序中, 例如),您需要确保预先加载了libgcc。

快速查看source for backrace,确认不安全的部分涉及动态加载backtrace。您可以通过静态链接libgccglibc来解决此问题,但最可靠的方法是确保在生成任何信号之前先加载libgcc

我这样做的方法是在程序启动期间调用一次libgcc。请注意,您必须至少索取一个符号,否则函数将不加载libgcc而提前输出。这样的事情会起作用:

backtrace

编辑:OP提到他们使用的是uclibc而不是glibc,但是适用相同的参数,因为它动态加载libgcc来获取回溯。有趣的一点是source for uclibc's bactrace// 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; } 是必需的。

注意:我打算编写一个完整的工作代码示例,但我记得您必须使用信号处理的-fasynchronous-unwind-tables版本并做一些特殊的事情才能在ARM上获得堆栈跟踪。我有可以在工作中执行此操作的代码,一旦获得,我将对其进行编辑。