如何使用gcc和行号信息来获得C ++的堆栈跟踪?

我们在像macros这样的专有assert使用堆栈跟踪来捕捉开发人员的错误 – 当错误被捕获时,堆栈跟踪被打印。

我发现gcc的对backtrace() / backtrace_symbols()方法不够:

  1. 名字被打乱
  2. 没有线路信息

第一个问题可以通过abi :: __ cxa_demangle解决。

然而第二个问题更加困难。 我find了backtrace_symbols()的替代品 。 这比gcc的backtrace_symbols()更好,因为它可以检索行号(如果使用-g编译),并且不需要使用-rdynamic进行编译。

Hoverer的代码是GNU许可的,所以恕我直言,我不能在商业代码中使用它。

任何build议?

PS

gdb能够打印出传递给函数的参数。 可能它已经太多要求:)

PS 2

类似的问题 (谢谢nobar)

不久之前, 我回答了一个类似的问题 。 您应该查看方法#4上的源代码,该代码还会打印行号和文件名。

所以你想要一个独立的函数,打印一个堆栈跟踪与所有的function,GDB堆栈跟踪具有,并不会终止您的应用程序。 答案是以非交互模式自动启动gdb来执行你想要的任务。

这是通过在subprocess中执行gdb来完成的,使用fork(),并在您的应用程序等待完成时脚本化它来显示堆栈跟踪。 这可以在不使用核心转储的情况下执行,也不需要中止应用程序。 我从这个问题中学到了如何做到这一点: 如何从程序中调用gdb来打印堆栈跟踪?

这个问题发布的例子并不完全符合我的工作,所以这里是我的“固定”版本(我在Ubuntu 9.04上运行这个)。

 #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> void print_trace() { char pid_buf[30]; sprintf(pid_buf, "%d", getpid()); char name_buf[512]; name_buf[readlink("/proc/self/exe", name_buf, 511)]=0; int child_pid = fork(); if (!child_pid) { dup2(2,1); // redirect output to stderr fprintf(stdout,"stack trace for %s pid=%s\n",name_buf,pid_buf); execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL); abort(); /* If gdb failed to start */ } else { waitpid(child_pid,NULL,0); } } 

如引用的问题所示,gdb提供了可以使用的其他选项。 例如,使用“bt full”而不是“bt”会产生更详细的报告(局部variables包含在输出中)。 gdb的manpages很轻,但是完整的文档可以在这里find 。

由于这是基于gdb,输出包括demangled名称行号函数参数 ,甚至可选的本地variables 。 此外,gdb是线程感知的,所以你应该能够提取一些线程特定的元数据。

下面是我用这种方法看到的那种堆栈跟踪的例子。

 0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6 [Current thread is 0 (process 15573)] #0 0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6 #1 0x0000000000400bd5 in print_trace () at ./demo3b.cpp:496 2 0x0000000000400c09 in recursive (i=2) at ./demo3b.cpp:636 3 0x0000000000400c1a in recursive (i=1) at ./demo3b.cpp:646 4 0x0000000000400c1a in recursive (i=0) at ./demo3b.cpp:646 5 0x0000000000400c46 in main (argc=1, argv=0x7fffe3b2b5b8) at ./demo3b.cpp:70 

注意:我发现这与使用valgrind不兼容(可能是由于Valgrind使用虚拟机)。 当你在gdb会话中运行程序时,它也不起作用(不能向进程应用第二个“ptrace”实例)。

有一个基本相同的问题,在强大的讨论: 如何生成一个堆栈跟踪,当我的gcc C ++应用程序崩溃 。 提供了许多build议,包括关于如何在运行时生成堆栈跟踪的大量讨论。

我的个人最喜欢的答案是启用核心转储 ,它允许您在崩溃时查看完整的应用程序状态 (包括函数参数,行号和未encryption的名称)。 这种方法的另一个好处是它不仅适用于断言 ,而且适用于分段错误未处理的exception

不同的Linux shell使用不同的命令来启用核心转储,但是您可以在应用程序代码中使用类似这样的方法来完成这项工作。

 #include <sys/resource.h> ... struct rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY }; assert( setrlimit( RLIMIT_CORE, &core_limit ) == 0 ); // enable core dumps for debug builds 

在崩溃之后,运行您最喜欢的debugging器来检查程序状态。

 $ kdbg executable core 

这是一些示例输出…

替代文字

也可以从命令行的核心转储中提取堆栈跟踪。

 $ ( CMDFILE=$(mktemp); echo "bt" >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} temp.exe core ) Core was generated by `./temp.exe'. Program terminated with signal 6, Aborted. [New process 22857] #0 0x00007f4189be5fb5 in raise () from /lib/libc.so.6 #0 0x00007f4189be5fb5 in raise () from /lib/libc.so.6 #1 0x00007f4189be7bc3 in abort () from /lib/libc.so.6 #2 0x00007f4189bdef09 in __assert_fail () from /lib/libc.so.6 #3 0x00000000004007e8 in recursive (i=5) at ./demo1.cpp:18 #4 0x00000000004007f3 in recursive (i=4) at ./demo1.cpp:19 #5 0x00000000004007f3 in recursive (i=3) at ./demo1.cpp:19 #6 0x00000000004007f3 in recursive (i=2) at ./demo1.cpp:19 #7 0x00000000004007f3 in recursive (i=1) at ./demo1.cpp:19 #8 0x00000000004007f3 in recursive (i=0) at ./demo1.cpp:19 #9 0x0000000000400849 in main (argc=1, argv=0x7fff2483bd98) at ./demo1.cpp:26 

由于GPL许可代码旨在帮助您在开发过程中为您提供帮助,因此您可能不会将其包含在最终产品中。 GPL限制您分发与非GPL兼容代码链接的GPL许可证代码。 只要你只使用GPL代码,你应该没问题。

使用谷歌glog库。 它有新的BSD许可证。

它在stacktrace.h文件中包含一个GetStackTrace函数。

编辑

我在这里findhttp://blog.bigpixel.ro/2010/09/09/stack-unwinding-stack-trace-with-gcc/有一个实用工具叫做addr2line,它将程序地址转换成文件名和行号。

http://linuxcommand.org/man_pages/addr2line1.html

这是另一种方法。 一个debug_assert ()macros以编程方式设置条件断点 。 如果你正在运行一个debugging器,当assertexpression式为false时你将会遇到一个断点 – 你可以分析活动堆栈 (程序不会终止)。 如果你没有在debugging器中运行,debug_assert()失败会导致程序中止, 你会得到一个核心转储,你可以从中分析堆栈 (请参阅我之前的回答)。

与普通断言相比,这种方法的优点是可以在触发debug_assert(在debugging器中运行)后继续运行程序。 换句话说,debug_assert()比assert()稍微灵活一些。

  #include <iostream> #include <cassert> #include <sys/resource.h> // note: The assert expression should show up in // stack trace as parameter to this function void debug_breakpoint( char const * expression ) { asm("int3"); // x86 specific } #ifdef NDEBUG #define debug_assert( expression ) #else // creates a conditional breakpoint #define debug_assert( expression ) \ do { if ( !(expression) ) debug_breakpoint( #expression ); } while (0) #endif void recursive( int i=0 ) { debug_assert( i < 5 ); if ( i < 10 ) recursive(i+1); } int main( int argc, char * argv[] ) { rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY }; setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps recursive(); } 

注意:有时在debugging器中设置“条件断点”可能会很慢。 通过以编程方式build立断点,该方法的性能应该与正常的assert()的性能相当。

注意:正如所写,这是针对Intel x86架构的 – 其他处理器可能有不同的生成断点的指令。

有点迟了,但是你可以使用libbfb来获取文件名和linenumber,比如refdbg在symsnarf.c中做的 。 libbfb由addr2linegdb在内部使用

解决scheme之一是在失败的断言处理程序中用“bt”-script启动一个gdb。 集成这样的gdb-starting并不是很容易,但它会给你backtrace和args和demangle名字(或者你可以通过c ++ filt programm传递gdb输出)。

这两个程序(gdb和c ++ filt)都不会链接到您的应用程序中,所以GPL不会要求您开放完整的应用程序。

使用backtrace-symbols也可以使用相同的方法(执行一个GPL程序)。 只需生成%eip和exec文件(/ proc / self / maps)映射的ascii列表,并将其传递给单独的二进制文件。

你可以使用DeathHandler – 为你做的一切的小C ++类,可靠的。

我想行数与当前的eip值有关,对吗?

解决scheme1:
然后你可以使用像GetThreadContext()这样的东西,除了你正在使用linux。 我search了一下,发现了类似的东西, ptrace() :

ptrace()系统调用提供了一种方法,通过该方法父进程可以观察和控制另一个进程的执行,并检查和更改其核心映像和寄存器。 […]父母可以通过调用fork(2)来启动一个跟踪,并让生成的子进行PTRACE_TRACEME,后面(通常)由exec(3)执行。 或者,父级可以使用PTRACE_ATTACH开始对现有进程的跟踪。

现在我想,你可以做一个“主”程序,检查发送给它的孩子的信号,这是你正在做的真正的程序。 fork()之后调用waitid() :

所有这些系统调用都用于等待调用进程的subprocess中的状态更改,并获取有关状态已更改的subprocess的信息。

如果捕获到一个SIGSEGV(或类似的东西),则调用ptrace()来获取eip的值。

PS:我从来没有使用这些系统调用(实际上,我从来没有见过他们),所以我不知道是否有可能帮助你。 至less我希望这些链接是有用的。 ;)

解决scheme2:第一个解决scheme相当复杂,对吧? 我想出了一个更简单的方法:使用signal()捕获您感兴趣的信号,并调用一个简单函数来读取存储在堆栈中的eip值:

 ... signal(SIGSEGV, sig_handler); ... void sig_handler(int signum) { int eip_value; asm { push eax; mov eax, [ebp - 4] mov eip_value, eax pop eax } // now you have the address of the // **next** instruction after the // SIGSEGV was received } 

asm的语法是Borland的,只是适应了GAS 。 ;)

这是我的第三个答案 – 仍然试图利用核心转储。

在“assert-like”macros是否应该终止应用程序(断言的方式)或者在产生它们的栈跟踪之后应该继续执行的问题上,还没有完全清楚。

在这个答案中,我正在处理你想要显示堆栈跟踪并继续执行的情况。 我写了下面的coredump ()函数来生成一个核心转储, 自动从中提取堆栈跟踪 ,然后继续执行程序。

用法与assert()的用法相同。 当然,区别在于assert()终止程序,但是coredump_assert ()不会。

  #include <iostream> #include <sys/resource.h> #include <cstdio> #include <cstdlib> #include <boost/lexical_cast.hpp> #include <string> #include <sys/wait.h> #include <unistd.h> std::string exename; // expression argument is for diagnostic purposes (shows up in call-stack) void coredump( char const * expression ) { pid_t childpid = fork(); if ( childpid == 0 ) // child process generates core dump { rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY }; setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps abort(); // terminate child process and generate core dump } // give each core-file a unique name if ( childpid > 0 ) waitpid( childpid, 0, 0 ); static int count=0; using std::string; string pid = boost::lexical_cast<string>(getpid()); string newcorename = "core-"+boost::lexical_cast<string>(count++)+"."+pid; string rawcorename = "core."+boost::lexical_cast<string>(childpid); int rename_rval = rename(rawcorename.c_str(),newcorename.c_str()); // try with core.PID if ( rename_rval == -1 ) rename_rval = rename("core",newcorename.c_str()); // try with just core if ( rename_rval == -1 ) std::cerr<<"failed to capture core file\n"; #if 1 // optional: dump stack trace and delete core file string cmd = "( CMDFILE=$(mktemp); echo 'bt' >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} "+exename+" "+newcorename+" ; unlink ${CMDFILE} )"; int system_rval = system( ("bash -c '"+cmd+"'").c_str() ); if ( system_rval == -1 ) std::cerr.flush(), perror("system() failed during stack trace"), fflush(stderr); unlink( newcorename.c_str() ); #endif } #ifdef NDEBUG #define coredump_assert( expression ) ((void)(expression)) #else #define coredump_assert( expression ) do { if ( !(expression) ) { coredump( #expression ); } } while (0) #endif void recursive( int i=0 ) { coredump_assert( i < 2 ); if ( i < 4 ) recursive(i+1); } int main( int argc, char * argv[] ) { exename = argv[0]; // this is used to generate the stack trace recursive(); } 

当我运行该程序时,它显示三个堆栈跟踪…

 Core was generated by `./temp.exe'. Program terminated with signal 6, Aborted. [New process 24251] #0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6 #0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6 #1 0x00007f2818acbbc3 in abort () from /lib/libc.so.6 #2 0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29 #3 0x0000000000401f5f in recursive (i=2) at ./demo3.cpp:60 #4 0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61 #5 0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61 #6 0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66 Core was generated by `./temp.exe'. Program terminated with signal 6, Aborted. [New process 24259] #0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6 #0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6 #1 0x00007f2818acbbc3 in abort () from /lib/libc.so.6 #2 0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29 #3 0x0000000000401f5f in recursive (i=3) at ./demo3.cpp:60 #4 0x0000000000401f70 in recursive (i=2) at ./demo3.cpp:61 #5 0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61 #6 0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61 #7 0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66 Core was generated by `./temp.exe'. Program terminated with signal 6, Aborted. [New process 24267] #0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6 #0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6 #1 0x00007f2818acbbc3 in abort () from /lib/libc.so.6 #2 0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29 #3 0x0000000000401f5f in recursive (i=4) at ./demo3.cpp:60 #4 0x0000000000401f70 in recursive (i=3) at ./demo3.cpp:61 #5 0x0000000000401f70 in recursive (i=2) at ./demo3.cpp:61 #6 0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61 #7 0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61 #8 0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66