我使用GCC编译器在Linux上工作。当我的c++程序崩溃时,我希望它自动生成一个堆栈跟踪。
我的程序由许多不同的用户运行,它也可以在Linux、Windows和Macintosh上运行(所有版本都使用gcc编译)。
我希望我的程序能够在崩溃时生成堆栈跟踪,下次用户运行它时,它会问他们是否可以将堆栈跟踪发送给我,这样我就可以跟踪到问题。我可以处理发送信息给我,但我不知道如何生成跟踪字符串。什么好主意吗?
我使用GCC编译器在Linux上工作。当我的c++程序崩溃时,我希望它自动生成一个堆栈跟踪。
我的程序由许多不同的用户运行,它也可以在Linux、Windows和Macintosh上运行(所有版本都使用gcc编译)。
我希望我的程序能够在崩溃时生成堆栈跟踪,下次用户运行它时,它会问他们是否可以将堆栈跟踪发送给我,这样我就可以跟踪到问题。我可以处理发送信息给我,但我不知道如何生成跟踪字符串。什么好主意吗?
当前回答
Ulimit -c <value>设置unix上的核心文件大小限制。缺省情况下,内核文件大小限制为0。可以使用ulimit -a查看ulimit值。
此外,如果您从gdb内部运行程序,它将在“分段违规”(SIGSEGV,通常当您访问未分配的内存块时)时停止程序,或者您可以设置断点。
DDD和nemiver是GDB的前端,这使得新手更容易使用GDB。
其他回答
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
除了上面的答案,这里还介绍了如何让Debian Linux操作系统生成核心转储
Create a “coredumps” folder in the user's home folder Go to /etc/security/limits.conf. Below the ' ' line, type “ soft core unlimited”, and “root soft core unlimited” if enabling core dumps for root, to allow unlimited space for core dumps. NOTE: “* soft core unlimited” does not cover root, which is why root has to be specified in its own line. To check these values, log out, log back in, and type “ulimit -a”. “Core file size” should be set to unlimited. Check the .bashrc files (user, and root if applicable) to make sure that ulimit is not set there. Otherwise, the value above will be overwritten on startup. Open /etc/sysctl.conf. Enter the following at the bottom: “kernel.core_pattern = /home//coredumps/%e_%t.dump”. (%e will be the process name, and %t will be the system time) Exit and type “sysctl -p” to load the new configuration Check /proc/sys/kernel/core_pattern and verify that this matches what you just typed in. Core dumping can be tested by running a process on the command line (“ &”), and then killing it with “kill -11 ”. If core dumping is successful, you will see “(core dumped)” after the segmentation fault indication.
需要注意的是,一旦生成了核心文件,就需要使用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/unix/MacOSX上使用核心文件(您可以通过ulimit或兼容的系统调用启用它们)。在Windows上使用Microsoft错误报告(您可以成为合作伙伴并访问您的应用程序崩溃数据)。
忘记改变你的源代码,用backtrace()函数或宏来做一些hack -这些只是糟糕的解决方案。
作为一个有效的解决方案,我的建议是:
用"-g"标志编译程序,用于将调试符号嵌入二进制(不用担心,这不会影响您的性能)。 在linux上运行下一个命令:"ulimit -c unlimited" -允许系统进行大崩溃转储。 当你的程序崩溃时,在工作目录中你会看到文件“core”。 gdb -batch -ex "backtrace" ./your_program_exe ./core . exe .执行下一个命令,将backtrace输出到标准输出
这将以人类可读的方式(包含源文件名和行号)打印程序的正确可读回溯。 此外,这种方法将给你自由自动化你的系统: 编写一个简短的脚本,检查进程是否创建了核心转储,然后通过电子邮件向开发人员发送回溯信息,或将其记录到一些日志系统中。