在Unix系统上自动获取堆栈跟踪

7
有哪些方法可以在Unix系统上自动获取堆栈跟踪?我不是指仅获取核心文件或使用GDB进行交互式附加,而是拥有一个SIGSEGV处理程序,将回溯信息转储到文本文件中。
以下是可选的奖励特点:
- 在崩溃时收集额外的信息(例如配置文件)。 - 向开发人员发送崩溃信息包。 - 能够将此添加到"dlopen"共享库中 - 不需要GUI。
4个回答

15

提醒一下,

建议的解决方案(在信号处理程序中使用backtrace_symbols)存在严重问题。不要使用它 -

是的,backtrace和backtrace_symbols将生成回溯并将其转换为符号名称,但是:

  1. backtrace_symbols使用malloc分配内存,并使用free释放它 - 如果您由于内存损坏而崩溃,则malloc arena很可能已损坏并导致双重故障。

  2. malloc和free在内部使用锁保护malloc arena。您可能在锁定期间发生故障,这将导致这些函数或任何调用它们的函数死锁。

  3. 您使用的puts使用标准流,该流也受保护并带有锁。如果在printf中间出现故障,则再次出现死锁。

  4. 在32位平台上(例如2年前的普通PC),内核将在堆栈中向内部glibc函数而不是故障函数种植返回地址,因此您最感兴趣的单个最重要的信息 - 程序错误发生在哪个函数,实际上在这些平台上会被破坏。

所以,这个例子中的代码是最糟糕的错误类型 - 它看起来像是运作正常的,但在生产环境中它真的会以意想不到的方式让你失望。

顺便说一句,如果你有兴趣做正确的事情,请查看this

祝好, Gilad。


我对这个领域并不是非常了解。但如果你在子进程中派生和执行回溯,你就可以逃脱,对吧? - LiquidityC
如果你在信号处理程序中使用fork(),那么你的子进程将处于刚刚分叉的进程所处的情况。在信号处理程序中使用fork()可能最有用的是接着使用execv()/execve(),否则你只是分叉了一些错误的东西。 - Andreas Magnusson
奇怪的是,答案应该包含对所提出问题的回答。而不是解释永远不要应用其他在99%情况下有效的答案或发布损坏的链接。所以请在这里包含您的解决方案。 - calandoa

7
如果你在使用具有BSD backtrace功能的系统上(如Linux、OSX 1.5和当然也包括BSD),你可以在信号处理程序中以编程方式执行此操作。
例如(从IBM示例派生的backtrace代码):
#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void sig_handler(int sig)
{
    void * array[25];
    int nSize = backtrace(array, 25);
    char ** symbols = backtrace_symbols(array, nSize);

    for (int i = 0; i < nSize; i++)
    {
        puts(symbols[i]);;
    }

    free(symbols);

    signal(sig, &sig_handler);
}

void h()
{
    kill(0, SIGSEGV);
}

void g()
{
    h();
}

void f()
{
    g();
}

int main(int argc, char ** argv)
{
    signal(SIGSEGV, &sig_handler);
    f();
}

输出:

0   a.out                               0x00001f2d sig_handler + 35
1   libSystem.B.dylib                   0x95f8f09b _sigtramp + 43
2   ???                                 0xffffffff 0x0 + 4294967295
3   a.out                               0x00001fb1 h + 26
4   a.out                               0x00001fbe g + 11
5   a.out                               0x00001fcb f + 11
6   a.out                               0x00001ff5 main + 40
7   a.out                               0x00001ede start + 54

这并不因为可选的特性而获得额外的加分(除了不需要GUI),但是它的优点在于非常简单,不需要任何额外的库或程序。


这只是一个玩具示例(例如,当您手动引发SIGSEGV时),但在您最需要它的时候可能 不会 起作用。请参见@Gilad Ben-Yossef的答案。 - Andreas Magnusson

3
以下是如何使用解码器获取更多信息的示例。您可以看到此示例还将堆栈跟踪记录到文件中。
#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
#include <cxxabi.h>

void sig_handler(int sig)
{
    std::stringstream stream;
    void * array[25];
    int nSize = backtrace(array, 25);
    char ** symbols = backtrace_symbols(array, nSize);
    for (unsigned int i = 0; i < size; i++) {
        int status;
        char *realname;
        std::string current = symbols[i];
        size_t start = current.find("(");
        size_t end = current.find("+");
        realname = NULL;
        if (start != std::string::npos && end != std::string::npos) {
            std::string symbol = current.substr(start+1, end-start-1);
            realname = abi::__cxa_demangle(symbol.c_str(), 0, 0, &status);
        }
        if (realname != NULL)
            stream << realname << std::endl;
        else
            stream << symbols[i] << std::endl;
        free(realname);
    }
    free(symbols);
    std::cerr << stream.str();
    std::ofstream file("/tmp/error.log");
    if (file.is_open()) {
        if (file.good())
            file << stream.str();
        file.close();
    }
    signal(sig, &sig_handler);
}

1
在信号处理程序中使用C++ - 基因中的错误。 - vitaly.v.ch
@vitaly.v.ch 这个 bug 并不是使用 C++ 的问题,而是使用了大量的信号不安全函数。任何分配内存的操作几乎都不能在你最需要它时正常工作。当然,如果你使用 kill -11 <pid> 命令来结束程序,看起来一切都很正常,但如果这是导致你的代码崩溃的原因,那就停止使用它吧... - Andreas Magnusson

2
Dereks的解决方案可能是最好的,但无论如何这里有一个替代方案: 最近的Linux内核版本允许您将核心转储管道输出到脚本或程序。您可以编写一个脚本来捕获核心转储,收集所需的任何其他信息并将所有内容发送邮件。这是一个全局设置,因此它适用于系统上的任何崩溃程序。它还需要root权限进行设置。 可以通过/proc/sys/kernel/core_pattern文件进行配置。将其设置为类似于' | /home/myuser/bin/my-core-handler-script'的东西。 Ubuntu人也使用这个功能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接