如何在C中获取堆栈跟踪?


Answers:


81

6
glibc FTW ...再次。(这也是为什么我认为glibc是C编程(和它附带的编译器)的绝对黄金标准的另一个原因。)
Trevor Boyd Smith Smith,

4
但是,还有更多!backtrace()函数仅提供一个void *指针数组,它们表示调用栈函数。“那不是很有用。arg。” 不要害怕!glibc提供了一个将所有void *地址(调用堆栈函数地址)转换为人类可读的字符串符号的函数。char ** backtrace_symbols (void *const *buffer, int size)
特雷弗·博伊德·史密斯

1
注意:我认为仅适用于C函数。@Trevor:它只是通过ELF表中的地址查找syms。
康拉德·迈尔

2
void backtrace_symbols_fd(void *const *buffer, int size, int fd)可以将输出直接发送到例如stdout / err。
wkz 2012年

2
backtrace_symbols()很烂。它需要导出所有符号,并且不支持DWARF(调试)符号。在许多(大多数)情况下,libbacktrace是一个更好的选择。
Erwan Legrand

29

有backtrace()和backtrace_symbols():

从手册页:

#include <execinfo.h>
#include <stdio.h>
...
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
for (i = 0; i < frames; ++i) {
    printf("%s\n", strs[i]);
}
free(strs);
...

一种更方便/ OOP的方式使用此方法的方法是将backtrace_symbols()的结果保存在异常类构造函数中。因此,无论何时抛出这种类型的异常,您都有堆栈跟踪。然后,只需提供将其打印出来的功能即可。例如:

class MyException : public std::exception {

    char ** strs;
    MyException( const std::string & message ) {
         int i, frames = backtrace(callstack, 128);
         strs = backtrace_symbols(callstack, frames);
    }

    void printStackTrace() {
        for (i = 0; i < frames; ++i) {
            printf("%s\n", strs[i]);
        }
        free(strs);
    }
};

...

try {
   throw MyException("Oops!");
} catch ( MyException e ) {
    e.printStackTrace();
}

da!

注意:启用优化标志可能会使生成的堆栈跟踪不准确。理想情况下,可以在调试标志打开和优化标志关闭的情况下使用此功能。


@shuckc仅用于将地址转换为符号字符串,可以根据需要使用其他工具从外部完成。
Woodrow Barlow

22

对于Windows,请检查StackWalk64()API(也在32位Windows上)。对于UNIX,您应该使用操作系统的本机方式执行此操作,如果可用,则应使用glibc的backtrace()。

但是请注意,在本机代码中使用Stacktrace几乎不是一个好主意-不是因为不可能,而是因为您通常试图实现错误的目标。

在大多数情况下,人们尝试在异常情况下获取堆栈跟踪,例如当捕获到异常,断言失败或-当所有情况中最严重和最错误的情况-当您收到致命的“异常”或类似信号时,细分违规。

考虑到最后一个问题,大多数API都将要求您显式分配内存或可以在内部进行分配。在您的程序可能处于当前脆弱的状态下这样做可能会使情况变得更糟。例如,崩溃报告(或coredump)不会反映问题的实际原因,但是您处理该问题的尝试失败。

我认为您正在尝试实现致命错误处理,因为大多数人在获取堆栈跟踪时似乎都尝试这样做。如果是这样,我将依靠调试器(在开发过程中)并使进程在生产环境中转储(或在Windows上进行小型转储)。结合适当的符号管理,您应该可以容易地确定事后验明指令。


2
您是对的,因为它在信号或异常处理程序中尝试分配内存非常脆弱。一种可能的解决方法是在程序启动时分配固定数量的“紧急”空间,或使用静态缓冲区。
j_random_hacker

另一种解决方法是创建一个独立运行的coredump服务
Kobor42

5

您应该使用unwind库

unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
unsigned long a[100];
int ctr = 0;

while (unw_step(&cursor) > 0) {
  unw_get_reg(&cursor, UNW_REG_IP, &ip);
  unw_get_reg(&cursor, UNW_REG_SP, &sp);
  if (ctr >= 10) break;
  a[ctr++] = ip;
}

除非您从共享库进行调用,否则您的方法也可以正常工作。

您可以addr2line在Linux上使用该命令来获取相应PC的源功能/行号。


“源功能/行号”?如果优化链接以减少代码大小怎么办?不过,我会说这看起来像是一个有用的项目。可惜没有办法获得登记册。我一定会调查一下。您知道它绝对独立于处理器吗?可以在具有C编译器的任何东西上工作吗?
Mawg说恢复Monica 2010年

1
好的,仅因为提到了有用的addr2line命令,此注释才值得!
Ogre Psalm10年

addr2line无法在具有ASLR的系统上重定位代码(即,过去十年中人们一直在使用的大多数代码)。
Erwan Legrand

4

没有平台独立的方法可以做到这一点。

您可以做的最接近的事情是无需优化即可运行代码。这样,您可以附加到进程(使用可视c ++调试器或GDB)并获得可用的堆栈跟踪。


当现场的嵌入式计算机发生崩溃时,这对我没有帮助。:(
凯文

@Kevin:即使在嵌入式计算机上,通常也有一种获取远程调试器存根或至少一个核心转储的方法。不过,也许一旦部署到现场,就不会……
短暂的

如果您在选择的Windows / Linux / mac平台上使用gcc-glibc运行,则backtrace()和backtrace_symbols()将在所有三个平台上运行。鉴于这一说法,我将使用“没有[便携式]方法”这样的字眼。
特雷弗·博伊德·史密斯

4

对于Windows,CaptureStackBackTrace()这也是一个选择,与最终用户相比,StackWalk64()它在用户端需要的准备代码更少。(此外,对于我所遇到的类似情况,CaptureStackBackTrace()最终工作得比更好(更可靠)StackWalk64()。)


2

Solaris具有pstack命令,该命令也已复制到Linux中。


1
有用,但不是真正的C(它是一个外部实用程序)。
短暂

1
另外,从描述(部分:限制)开始:“:pstack当前仅在Linux上运行,仅在运行32位ELF二进制文件(不支持64位)的x86机器上运行”
Ciro Costa

0

您可以通过向后移动堆栈来实现。但是,实际上,在每个函数的开头将标识符添加到调用堆栈中并在结尾将其弹出通常会更容易,然后只需遍历打印内容即可。它有点像PITA,但是效果很好,最终可以节省您的时间。


1
您能否更详尽地解释“向后走栈”?
Spidey 2012年

@Spidey在嵌入式系统上,有时只有这些-我猜这是被否决的,因为平台是WinXP。但是,如果没有支持堆栈遍历的libc,则基本上必须“遍历”堆栈。从当前的基本指针开始(在x86上,这是RBP reg的内容)。这需要您使用1.指向已保存的先前的RBP(即使堆栈继续前进的方式),以及2.调用/分支返回地址(调用函数的已保存RIP reg)来指向堆栈,该地址告诉您该函数是什么。然后,如果您可以访问符号表,则可以查找函数地址。
特德·米德尔顿

0

在过去的几年中,我一直在使用Ian Lance Taylor的libbacktrace。它比需要导出所有符号的GNU C库中的函数干净得多。它比libunwind提供了更多的回溯生成工具。最后但并非最不重要的一点是,它并没有像ASLR这样需要外部工具的方法所击败addr2line

Libbacktrace最初是GCC发行版的一部分,但现在由BSD许可的作者以独立库的形式提供:

https://github.com/ianlancetaylor/libbacktrace

在撰写本文时,除非需要在libbacktrace不支持的平台上生成回溯,否则我不会使用其他任何东西。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.