如何使用带有行号信息的gcc获取C++的堆栈跟踪?

dim*_*mba 57 c++ linux gcc stack-trace

我们在专有的assert宏中使用堆栈跟踪来捕获开发人员的错误 - 当捕获错误时,打印堆栈跟踪.

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

  1. 名称被破坏了
  2. 没有行信息

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

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

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

任何建议?

PS

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

PS 2

类似的问题(感谢nobar)

nob*_*bar 37

因此,您需要一个独立的函数来打印堆栈跟踪,其中包含gdb堆栈跟踪所具有的所有功能,并且不会终止您的应用程序.答案是在非交互模式下自动启动gdb,以执行您想要的任务.

这是通过使用fork()在子进程中执行gdb,并在应用程序等待它完成时编写脚本来显示堆栈跟踪来完成的.这可以在不使用核心转储且不中止应用程序的情况下执行.我从这个问题中学到了如何做到这一点:如何从程序中调用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如引用的问题所示,gdb提供了您可以使用的其他选项.例如,使用"bt full"而不是"bt"会生成更详细的报告(局部变量包含在输出中).gdb的联机帮助页很轻松,但这里提供完整的文档.

由于这是基于gdb,因此输出包括demangled名称,行号,函数参数以及可选的局部变量.此外,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
Run Code Online (Sandbox Code Playgroud)

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

  • 而且情况正在恶化:现在已经不再[允许]了解父母(https://lists.ubuntu.com/archives/kernel-team/2010-May/010499.html).但也许你可以用`prctl`设置一个标志? (5认同)
  • 不要使用这个!我在我的程序中逐字使用了上述函数,在Ubuntu 12.04上它完全崩溃了X服务器. (4认同)
  • 您可以使用`#include <sys/prctl.h>``prctl(PR_SET_PTRACER,PR_SET_PTRACER_ANY,0,0,0);```fork()`之前绕过它. (4认同)
  • @BeniBela,你在运行什么样的程序?是低级的东西吗?这种方法在 Fedora 17 中对我来说很好用。 (2认同)

kar*_*lip 28

不久前,我回答了类似的问题.您应该查看方法#4上可用的源代码,它还可以打印行号和文件名.

  • 方法#4:

我在方法#3上做了一个很小的改进来打印行号.这也可以复制到方法#2上.

基本上,它使用addr2line将地址转换为文件名和行号.

下面的源代码打印所有本地函数的行号.如果调用另一个库中的函数,您可能会看到一些??:0而不是文件名.

#include <stdio.h>
#include <signal.h>
#include <stdio.h>
#include <signal.h>
#include <execinfo.h>

void bt_sighandler(int sig, struct sigcontext ctx) {

  void *trace[16];
  char **messages = (char **)NULL;
  int i, trace_size = 0;

  if (sig == SIGSEGV)
    printf("Got signal %d, faulty address is %p, "
           "from %p\n", sig, ctx.cr2, ctx.eip);
  else
    printf("Got signal %d\n", sig);

  trace_size = backtrace(trace, 16);
  /* overwrite sigaction with caller's address */
  trace[1] = (void *)ctx.eip;
  messages = backtrace_symbols(trace, trace_size);
  /* skip first stack frame (points here) */
  printf("[bt] Execution path:\n");
  for (i=1; i<trace_size; ++i)
  {
    printf("[bt] #%d %s\n", i, messages[i]);

    /* find first occurence of '(' or ' ' in message[i] and assume
     * everything before that is the file name. (Don't go beyond 0 though
     * (string terminator)*/
    size_t p = 0;
    while(messages[i][p] != '(' && messages[i][p] != ' '
            && messages[i][p] != 0)
        ++p;

    char syscom[256];
    sprintf(syscom,"addr2line %p -e %.*s", trace[i], p, messages[i]);
        //last parameter is the file name of the symbol
    system(syscom);
  }

  exit(0);
}


int func_a(int a, char b) {

  char *p = (char *)0xdeadbeef;

  a = a + b;
  *p = 10;  /* CRASH here!! */

  return 2*a;
}


int func_b() {

  int res, a = 5;

  res = 5 + func_a(a, 't');

  return res;
}


int main() {

  /* Install our signal handler */
  struct sigaction sa;

  sa.sa_handler = (void *)bt_sighandler;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_RESTART;

  sigaction(SIGSEGV, &sa, NULL);
  sigaction(SIGUSR1, &sa, NULL);
  /* ... add any other signal here */

  /* Do something */
  printf("%d\n", func_b());
}
Run Code Online (Sandbox Code Playgroud)

此代码应编译为: gcc sighandler.c -o sighandler -rdynamic

该方案产出:

Got signal 11, faulty address is 0xdeadbeef, from 0x8048975
[bt] Execution path:
[bt] #1 ./sighandler(func_a+0x1d) [0x8048975]
/home/karl/workspace/stacktrace/sighandler.c:44
[bt] #2 ./sighandler(func_b+0x20) [0x804899f]
/home/karl/workspace/stacktrace/sighandler.c:54
[bt] #3 ./sighandler(main+0x6c) [0x8048a16]
/home/karl/workspace/stacktrace/sighandler.c:74
[bt] #4 /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x3fdbd6]
??:0
[bt] #5 ./sighandler() [0x8048781]
??:0
Run Code Online (Sandbox Code Playgroud)

  • 请记住使用* -rdynamic *编译您的应用程序。 (2认同)

nob*_*bar 11

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

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

不同的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
Run Code Online (Sandbox Code Playgroud)

崩溃后,运行您最喜欢的调试器来检查程序状态.

$ kdbg executable core
Run Code Online (Sandbox Code Playgroud)

这是一些示例输出......

替代文字

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

$ ( 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
Run Code Online (Sandbox Code Playgroud)

  • gdb用于死后分析.我正在寻找更多如何从内部代码接收信息.也许我想打印回溯不是在SIGSEV的情况下 - 例如,看看从未处理的C++异常抛出的位置. (2认同)

Ind*_*ant 6

使用google glog库.它有新的BSD许可证.

它包含stacktrace.h文件中的GetStackTrace函数.

编辑

我在这里找到http://blog.bigpixel.ro/2010/09/09/stack-unwinding-stack-trace-with-gcc/,有一个名为addr2line的实用程序,它将程序地址转换为文件名和行号.

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

  • Glog具有堆栈跟踪打印功能 (5认同)
  • 确实glog有堆栈跟踪(http://google-glog.googlecode.com/svn/trunk/doc/glog.html故障信号处理程序部分),但它没有代码行信息. (2认同)
  • google-glog是backtrace和backtrace_symbols的瘦包装器.它不会给你文件名和行号 (2认同)

Kei*_*thB 6

由于GPL许可代码旨在帮助您在开发过程中,您可以不将其包含在最终产品中.GPL限制您分发与非GPL兼容代码链接的GPL许可证代码.只要您在内部使用GPL代码,您应该没问题.


nob*_*bar 5

这是另一种方法.甲debug_assert()宏编程设置一个条件断点.如果您在调试器中运行,则在断言表达式为false时将触发断点 - 并且您可以分析实时堆栈(程序不会终止).如果您没有在调试器中运行,则失败的debug_assert()会导致程序中止,并且您将获得一个核心转储,您可以从中分析堆栈(请参阅我之前的回答).

与普通断言相比,此方法的优点是,您可以在触发debug_assert后(在调试器中运行时)继续运行程序.换句话说,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();
   }
Run Code Online (Sandbox Code Playgroud)

注意:有时在调试器中设置"条件断点"可能会很慢.通过以编程方式建立断点,此方法的性能应与正常的assert()相当.

注意:如上所述,这是针对Intel x86架构的 - 其他处理器可能有不同的生成断点的指令.


rve*_*rve 5

有点晚了,但是您可以使用libbfb来获取文件名和行号,就像 refdbg 在symsnarf.c 中所做的那样。libbfb 由addr2line和在内部使用gdb


chi*_*nce 5

这是我的解决方案:

#include <execinfo.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <zconf.h>
#include "regex"

std::string getexepath() {
    char result[PATH_MAX];
    ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
    return std::string(result, (count > 0) ? count : 0);
}

std::string sh(std::string cmd) {
    std::array<char, 128> buffer;
    std::string result;
    std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
    if (!pipe) throw std::runtime_error("popen() failed!");
    while (!feof(pipe.get())) {
        if (fgets(buffer.data(), 128, pipe.get()) != nullptr) {
            result += buffer.data();
        }
    }
    return result;
}


void print_backtrace(void) {
    void *bt[1024];
    int bt_size;
    char **bt_syms;
    int i;

    bt_size = backtrace(bt, 1024);
    bt_syms = backtrace_symbols(bt, bt_size);
    std::regex re("\\[(.+)\\]");
    auto exec_path = getexepath();
    for (i = 1; i < bt_size; i++) {
        std::string sym = bt_syms[i];
        std::smatch ms;
        if (std::regex_search(sym, ms, re)) {
            std::string addr = ms[1];
            std::string cmd = "addr2line -e " + exec_path + " -f -C " + addr;
            auto r = sh(cmd);
            std::regex re2("\\n$");
            auto r2 = std::regex_replace(r, re2, "");
            std::cout << r2 << std::endl;
        }
    }
    free(bt_syms);
}

void test_m() {
    print_backtrace();
}

int main() {
    test_m();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

/home/roroco/Dropbox/c/ro-c/cmake-build-debug/ex/test_backtrace_with_line_number
test_m()
/home/roroco/Dropbox/c/ro-c/ex/test_backtrace_with_line_number.cpp:57
main
/home/roroco/Dropbox/c/ro-c/ex/test_backtrace_with_line_number.cpp:61
??
??:0
Run Code Online (Sandbox Code Playgroud)

“??” 和“??:0”,因为此跟踪在 libc 中,而不是在我的源中


归档时间:

查看次数:

63758 次

最近记录:

6 年,7 月 前