【问题标题】:How to get proper backtrace in process signal handler (armv7-uclibc)?如何在进程信号处理程序(armv7-uclibc)中获得正确的回溯?
【发布时间】:2015-05-01 06:36:26
【问题描述】:

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

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

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

如果我使用 target-gdb 二进制文件并使用 gdb --pid 命令附加进程,我能够正确获取完整的回溯。

另外,我尝试了 pstack,但是(pstack-1.2 - 尝试了 arm-patch,但太可怕了……没有打印出来)不是很有帮助。

有什么建议吗?


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

【问题讨论】:

  • 您需要展示一些代码,尤其是您的信号处理程序代码。请编辑您的问题以改进它。
  • 你在编写什么样的应用程序?你有一些事件循环吗?
  • 那种......这个进程正在等待来自其他进程的“消息”。 SysV IPC 消息在多个进程之间建立,它们相互发布消息。除此之外,没有太多特殊操作……没有套接字 I/O。它通过调用 open() 和 write() 来更新字符设备,但只写入 1 个字节。
  • 为什么不使用AF_UNIX 套接字?还是管道,还是 FIFO?
  • 没有防故障方法,因为您违反了异步信号安全要求。您可以尝试 Ian Taylor libbacktrace,但您确实应该重新设计和重构您的应用程序以遵守 signal(7) 的要求。出于调试目的,使用gdb 要简单得多!

标签: linux arm uclibc


【解决方案1】:

仔细阅读signal(7)signal-safety(7)

信号处理程序仅限于调用(直接或间接)async-signal-safe-functions(实际上,大多数仅syscalls(2))和backtrace(3) 甚至printf(3)malloc(3)free 不是异步信号安全的。所以你的代码不正确:信号处理程序 sigHandler 正在调用 printf 并间接(通过 print_stackfree 并且它们不是异步信号安全的。

所以您唯一的选择是使用gdb 调试器。

阅读更多关于 POSIX signal.hsignal concepts 的信息。实际上,信号处理程序可以做的几乎唯一明智的事情是设置一些全局的、线程本地的或静态的volatile sig_atomic_tflag,必须在其他地方进行测试。它还可以直接将write(2) 几个字节转换为pipe(7),您的应用程序可以在其他地方读取(例如,在其事件循环中,如果它是一个 GUI 应用程序)。

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

请注意,内核在处理信号时正在为sigreturn(2) 设置一个调用帧(在call stack 中)。

您也可以使用(特别是如果您的应用程序是单线程的)sigaltstack(2) 来拥有备用信号堆栈。我不确定它是否会有所帮助。

如果你有一个事件循环,你可以考虑使用 Linux 特定的signalfd(2) 并将你的事件循环请求poll 它。对于SIGTERMSIGQUITSIGALRM 这是一个非常有用的技巧。

【讨论】:

  • 使用 signalfd() 将完全违背尝试在信号处理程序中获取回溯的目的,因为此时您的回溯将始终 100% 指向您从 poll() 循环调用的函数。跨度>
【解决方案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 了解更多信息。

除此之外,我想说明您可以在信号处理程序中执行哪些操作以获取回溯。确实,您不应该调用任何 stdio 函数,malloc()free() 等,但不是正确的是,您不能使用健全的版本调用 backtrace glibc/libgcc 的。从here,您可以看到backtrace_symbols_fd 当前是异步信号安全的。您还可以看到backtrace 不是。它看起来非常不安全。但是,man 3 backtrace 告诉我们这些限制为何适用:

backtrace_symbols_fd() 不调用 malloc(3),并且 所以可以在后一种功能可能的情况下使用 失败,但请参阅注释。

稍后:

backtrace() 和 backtrace_symbols_fd() 不调用 malloc() 明确地,但它们是被加载的 libgcc 的一部分 首次使用时动态显示。动态加载通常会触发 调用 malloc(3)。如果您需要对这两个进行某些调用 不分配内存的函数(在信号处理程序中,对于 例如),您需要确保事先加载了 libgcc。

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

我这样做的方法是在程序启动期间调用一次backtrace。请注意,您必须要求至少一个符号,或者在不加载 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;
}

编辑:OP 提到他们使用的是 uclibc 而不是 glibc,但同样的参数适用,因为它动态加载 libgcc 以获取回溯。有趣的一点是source for uclibc's bactrace 提到-fasynchronous-unwind-tables 是必要的。

注意:我正计划编写一个完整的工作代码示例,但我记得您必须使用sigaction 版本的信号处理并做一些特殊的事情来获取 ARM 上的堆栈跟踪。我有代码可以在工作中完成它,一旦我有它,我会编辑这篇文章。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-25
    • 1970-01-01
    • 1970-01-01
    • 2019-01-30
    相关资源
    最近更新 更多