有哪些方法可以在Unix系统上自动获取堆栈跟踪?我不是指仅获取核心文件或使用GDB进行交互式附加,而是拥有一个SIGSEGV处理程序,将回溯信息转储到文本文件中。
以下是可选的奖励特点:
- 在崩溃时收集额外的信息(例如配置文件)。 - 向开发人员发送崩溃信息包。 - 能够将此添加到"dlopen"共享库中 - 不需要GUI。
以下是可选的奖励特点:
- 在崩溃时收集额外的信息(例如配置文件)。 - 向开发人员发送崩溃信息包。 - 能够将此添加到"dlopen"共享库中 - 不需要GUI。
提醒一下,
建议的解决方案(在信号处理程序中使用backtrace_symbols)存在严重问题。不要使用它 -
是的,backtrace和backtrace_symbols将生成回溯并将其转换为符号名称,但是:
backtrace_symbols使用malloc分配内存,并使用free释放它 - 如果您由于内存损坏而崩溃,则malloc arena很可能已损坏并导致双重故障。
malloc和free在内部使用锁保护malloc arena。您可能在锁定期间发生故障,这将导致这些函数或任何调用它们的函数死锁。
您使用的puts使用标准流,该流也受保护并带有锁。如果在printf中间出现故障,则再次出现死锁。
在32位平台上(例如2年前的普通PC),内核将在堆栈中向内部glibc函数而不是故障函数种植返回地址,因此您最感兴趣的单个最重要的信息 - 程序错误发生在哪个函数,实际上在这些平台上会被破坏。
所以,这个例子中的代码是最糟糕的错误类型 - 它看起来像是运作正常的,但在生产环境中它真的会以意想不到的方式让你失望。
顺便说一句,如果你有兴趣做正确的事情,请查看this。
祝好, Gilad。
backtrace
功能的系统上(如Linux、OSX 1.5和当然也包括BSD),你可以在信号处理程序中以编程方式执行此操作。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),但是它的优点在于非常简单,不需要任何额外的库或程序。
#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);
}
kill -11 <pid>
命令来结束程序,看起来一切都很正常,但如果这是导致你的代码崩溃的原因,那就停止使用它吧... - Andreas Magnusson
fork()
,那么你的子进程将处于刚刚分叉的进程所处的情况。在信号处理程序中使用fork()
可能最有用的是接着使用execv()
/execve()
,否则你只是分叉了一些错误的东西。 - Andreas Magnusson