我使用GCC编译器在Linux上工作。当我的c++程序崩溃时,我希望它自动生成一个堆栈跟踪。

我的程序由许多不同的用户运行,它也可以在Linux、Windows和Macintosh上运行(所有版本都使用gcc编译)。

我希望我的程序能够在崩溃时生成堆栈跟踪,下次用户运行它时,它会问他们是否可以将堆栈跟踪发送给我,这样我就可以跟踪到问题。我可以处理发送信息给我,但我不知道如何生成跟踪字符串。什么好主意吗?


当前回答

需要注意的是,一旦生成了核心文件,就需要使用gdb工具来查看它。为了让gdb理解你的核心文件,你必须告诉gcc用调试符号来检测二进制文件:要做到这一点,你需要使用-g标志进行编译:

$ g++ -g prog.cpp -o prog

然后,您可以设置“ulimit -c unlimited”来转储一个核心,或者只是在gdb中运行您的程序。我更喜欢第二种方法:

$ gdb ./prog
... gdb startup output ...
(gdb) run
... program runs and crashes ...
(gdb) where
... gdb outputs your stack trace ...

我希望这能有所帮助。

其他回答

Linux

虽然已经有人建议使用execinfo.h中的backtrace()函数来打印堆栈跟踪并在出现分段错误时优雅地退出,但我没有看到提到确保所产生的回溯指向错误的实际位置所必需的复杂性(至少对于某些架构- x86和ARM)。

进入信号处理程序时,堆栈帧链中的前两个条目在信号处理程序中包含一个返回地址,在libc中的sigaction()中包含一个返回地址。在信号(即故障位置)之前调用的最后一个函数的堆栈帧丢失。

Code

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif

#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#include <unistd.h>

/* This structure mirrors the one found in /usr/include/asm/ucontext.h */
typedef struct _sig_ucontext {
 unsigned long     uc_flags;
 ucontext_t        *uc_link;
 stack_t           uc_stack;
 sigcontext_t      uc_mcontext;
 sigset_t          uc_sigmask;
} sig_ucontext_t;

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
 void *             array[50];
 void *             caller_address;
 char **            messages;
 int                size, i;
 sig_ucontext_t *   uc;

 uc = (sig_ucontext_t *)ucontext;

 /* Get the address at the time the signal was raised */
#if defined(__i386__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.eip; // EIP: x86 specific
#elif defined(__x86_64__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.rip; // RIP: x86_64 specific
#else
#error Unsupported architecture. // TODO: Add support for other arch.
#endif

 fprintf(stderr, "signal %d (%s), address is %p from %p\n", 
  sig_num, strsignal(sig_num), info->si_addr, 
  (void *)caller_address);

 size = backtrace(array, 50);

 /* overwrite sigaction with caller's address */
 array[1] = caller_address;

 messages = backtrace_symbols(array, size);

 /* skip first stack frame (points here) */
 for (i = 1; i < size && messages != NULL; ++i)
 {
  fprintf(stderr, "[bt]: (%d) %s\n", i, messages[i]);
 }

 free(messages);

 exit(EXIT_FAILURE);
}

int crash()
{
 char * p = NULL;
 *p = 0;
 return 0;
}

int foo4()
{
 crash();
 return 0;
}

int foo3()
{
 foo4();
 return 0;
}

int foo2()
{
 foo3();
 return 0;
}

int foo1()
{
 foo2();
 return 0;
}

int main(int argc, char ** argv)
{
 struct sigaction sigact;

 sigact.sa_sigaction = crit_err_hdlr;
 sigact.sa_flags = SA_RESTART | SA_SIGINFO;

 if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
 {
  fprintf(stderr, "error setting signal handler for %d (%s)\n",
    SIGSEGV, strsignal(SIGSEGV));

  exit(EXIT_FAILURE);
 }

 foo1();

 exit(EXIT_SUCCESS);
}

输出

signal 11 (Segmentation fault), address is (nil) from 0x8c50
[bt]: (1) ./test(crash+0x24) [0x8c50]
[bt]: (2) ./test(foo4+0x10) [0x8c70]
[bt]: (3) ./test(foo3+0x10) [0x8c8c]
[bt]: (4) ./test(foo2+0x10) [0x8ca8]
[bt]: (5) ./test(foo1+0x10) [0x8cc4]
[bt]: (6) ./test(main+0x74) [0x8d44]
[bt]: (7) /lib/libc.so.6(__libc_start_main+0xa8) [0x40032e44]

在信号处理程序中调用backtrace()函数的所有危险仍然存在,不应忽视,但我发现这里描述的功能对调试崩溃非常有帮助。

需要注意的是,我提供的示例是在x86的Linux上开发/测试的。我也成功地实现了这在ARM上使用uc_mcontext。Arm_pc代替uc_mcontext.eip。

下面是我了解这个实现细节的文章的链接: http://www.linuxjournal.com/article/6391

它看起来就像在最后一个c++增强版本中出现的库提供了你想要的东西,可能代码会是多平台的。 它是boost::stacktrace,你可以像在boost sample中那样使用:

#include <filesystem>
#include <sstream>
#include <fstream>
#include <signal.h>     // ::signal, ::raise
#include <boost/stacktrace.hpp>

const char* backtraceFileName = "./backtraceFile.dump";

void signalHandler(int)
{
    ::signal(SIGSEGV, SIG_DFL);
    ::signal(SIGABRT, SIG_DFL);
    boost::stacktrace::safe_dump_to(backtraceFileName);
    ::raise(SIGABRT);
}

void sendReport()
{
    if (std::filesystem::exists(backtraceFileName))
    {
        std::ifstream file(backtraceFileName);

        auto st = boost::stacktrace::stacktrace::from_dump(file);
        std::ostringstream backtraceStream;
        backtraceStream << st << std::endl;

        // sending the code from st

        file.close();
        std::filesystem::remove(backtraceFileName);
    }
}

int main()
{
    ::signal(SIGSEGV, signalHandler);
    ::signal(SIGABRT, signalHandler);

    sendReport();
    // ... rest of code
}

在Linux中编译上面的代码:

g++ --std=c++17 file.cpp -lstdc++fs -lboost_stacktrace_backtrace -ldl -lbacktrace

从boost文档复制的反向跟踪示例:

0# bar(int) at /path/to/source/file.cpp:70
1# bar(int) at /path/to/source/file.cpp:70
2# bar(int) at /path/to/source/file.cpp:70
3# bar(int) at /path/to/source/file.cpp:70
4# main at /path/to/main.cpp:93
5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
6# _start

我可以帮助Linux版本:函数backtrace, backtrace_symbols和backtrace_symbols_fd可以使用。请参见相应的手册。

您没有指定您的操作系统,因此这个问题很难回答。如果你使用的是基于gnu libc的系统,你可以使用libc函数backtrace()。

GCC还有两个内置组件可以帮助你,但它们可能也可能不能在你的架构上完全实现,它们是__builtin_frame_address和__builtin_return_address。两者都需要一个即时整数级别(这里的即时是指它不能是一个变量)。如果给定级别的__builtin_frame_address非零,那么获取同一级别的返回地址应该是安全的。

一些版本的libc包含处理堆栈跟踪的函数;你可能会用到它们:

http://www.gnu.org/software/libc/manual/html_node/Backtraces.html

我记得很久以前使用过libunwind来获取堆栈跟踪,但您的平台可能不支持它。