【问题标题】:Getting stack traces on Unix systems, automatically在 Unix 系统上自动获取堆栈跟踪
【发布时间】:2010-09-06 07:19:19
【问题描述】:

有哪些方法可以在 Unix 系统上自动获取堆栈跟踪?我的意思不是仅仅获取核心文件或与 GDB 交互地附加,而是具有将回溯转储到文本文件的 SIGSEGV 处理程序。

以下可选功能的奖励积分:

  • 崩溃时收集的额外信息(例如配置文件)。
  • 通过电子邮件将崩溃信息包发送给开发人员。
  • 能够将其添加到 dlopened 共享库中
  • 不需要 GUI

【问题讨论】:

    标签: linux unix stack-trace segmentation-fault


    【解决方案1】:

    仅供参考,

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

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

    1. backtrace_symbols 使用 malloc 分配内存,您使用 free 释放它 - 如果您因内存损坏而崩溃,您的 malloc arena 很可能已损坏并导致双重错误。

    2. malloc 和 free 使用内部锁保护 malloc 竞技场。您可能在 malloc/free 的过程中发生了错误并获得了锁,这将导致这些函数或任何调用它们的函数进入死锁。

    3. 您使用 puts ,它使用标准流,它也受锁保护。如果您在 printf 的中间出现错误,您将再次陷入死锁。

    4. 在 32 位平台上(例如,您的 2 年前的普通 PC),内核会将返回地址植入内部 glibc 函数,而不是堆栈中的错误函数,因此您获得的最重要的一条信息感兴趣 - 程序出错的功能,实际上会在那些平台上被破坏。

    因此,示例中的代码是最严重的错误 - 它看起来可以正常工作,但它在生产中会以意想不到的方式让您失望。

    顺便说一句,有兴趣做吗?检查this

    干杯, 吉拉德。

    【讨论】:

    • 我对这个领域不是很精通。但是如果你在子进程中分叉并执行回溯,你就可以逃脱,不是吗?
    • @LiquidityC 如果您在信号处理程序中fork(),您的子进程将与您刚刚分叉的进程处于相同的情况。信号处理程序中的fork() 可能是最有用的,如果后面跟着execv()/execve(),否则你只是分叉了一些无聊的东西。
    • 奇怪的是,答案是为了包含对所提问题的答案。不是要解释永远不要应用在 99% 的情况下都有效的其他答案或发布断开的链接。因此,请在此处包含您的解决方案。
    【解决方案2】:

    如果您使用的系统具有可用的 BSD backtrace 功能(当然是 Linux、OSX 1.5、BSD),您可以在信号处理程序中以编程方式执行此操作。

    例如(backtrace code derived from IBM example):

    #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 的回答。
    【解决方案3】:

    这里是一个如何使用 demangler 获取更多信息的示例。正如你所看到的,这个也将堆栈跟踪记录到文件中。

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

    【讨论】:

    • 在信号处理程序中使用 C++ - 基因组中的错误。
    • @vitaly.v.ch 这个错误与其说是使用 C++,不如说是使用了大量的信号不安全函数。任何分配内存的东西都几乎可以保证不会在你最想要的时候工作。当然,如果你 kill -11 &lt;pid&gt; 看起来没问题,但如果这就是你的代码崩溃的原因,那就停止这样做......
    【解决方案4】:

    Dereks 解决方案可能是最好的,但无论如何这里有一个替代方案:

    最近的 Linux 内核版本允许您将核心转储通过管道传输到脚本或程序。您可以编写一个脚本来捕获核心转储,收集您需要的任何额外信息并将所有内容邮寄回来。 这是一个全局设置,因此它适用于系统上的任何崩溃程序。它还需要root权限才能设置。 可以通过 /proc/sys/kernel/core_pattern 文件进行配置。将其设置为类似 ' | /home/myuser/bin/my-core-handler-script'。

    Ubuntu 的人也使用此功能。

    【讨论】:

      猜你喜欢
      • 2010-10-11
      • 2015-06-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多