自动获取 Unix 系统上的堆栈跟踪
-
09-06-2019 - |
题
有哪些方法可以在 Unix 系统上自动获取堆栈跟踪?我的意思并不是仅仅获取核心文件或与 GDB 交互附加,而是拥有一个将回溯转储到文本文件的 SIGSEGV 处理程序。
以下可选功能的奖励积分:
- 崩溃时收集额外信息(例如配置文件)。
- 通过电子邮件将崩溃信息包发送给开发人员。
- 能够将其添加到
dlopen
ed共享库 - 不需要 GUI
解决方案
如果您使用的是 BSD 系统 backtrace
如果功能可用(Linux、OSX 1.5、BSD 当然),您可以在信号处理程序中以编程方式执行此操作。
例如 (backtrace
代码源自 IBM 示例):
#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),但是,它确实具有非常简单的优点,并且不需要任何额外的库或程序。
其他提示
供参考,
建议的解决方案(在信号处理程序中使用 backtrace_symbols)被危险地破坏了。不要使用它 -
是的,backtrace 和 backtrace_symbols 将产生一个 backtrace 并将其转换为符号名称,但是:
backtrace_symbols 使用 malloc 分配内存,然后使用 free 释放它 - 如果由于内存损坏而崩溃,则 malloc 区域很可能已损坏并导致双重错误。
malloc 和 free 通过内部锁保护 malloc 区域。您可能在 malloc/free 中间发生错误并获取了锁,这将导致这些函数或任何调用它们的东西死锁。
您使用的 put 使用标准流,它也受到锁的保护。如果你在 printf 中间出错,你就会再次陷入僵局。
在 32 位平台上(例如2年前的普通PC),内核将在堆栈中植入内部glibc函数的返回地址,而不是错误函数,因此您感兴趣的最重要的一条信息 - 程序在哪个函数中发生错误,实际上会在这些平台上被损坏。
因此,示例中的代码是最严重的错误 - 它看起来好像可以工作,但在生产中它确实会以意想不到的方式让您失败。
顺便说一句,有兴趣这样做吗?查看 这 出去。
欢呼,吉拉德。
以下是如何使用 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);
}
德里克斯的解决方案可能是最好的,但无论如何,这里有一个替代方案:
最近的 Linux 内核版本允许您将核心转储传输到脚本或程序。您可以编写一个脚本来捕获核心转储,收集您需要的任何额外信息并将所有内容邮寄回来。不过,这是一个全局设置,因此它适用于系统上的任何崩溃程序。它还需要 root 权限才能设置。可以通过/proc/sys/kernel/core_pattern 文件进行配置。将其设置为'| /home/myuser/bin/my-core handler-script'。
Ubuntu 的人也使用这个功能。