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

问题描述 投票:0回答:10

我知道没有标准的 C 函数可以做到这一点。我想知道在 Windows 和 *nix 上有哪些技术可以做到这一点? (Windows XP 是我目前最重要的操作系统。)

c windows debugging cross-platform stack-trace
10个回答
86
投票

40
投票

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();
}

哒哒!

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


22
投票

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

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

大多数时候,人们会尝试在特殊情况下获取堆栈跟踪,例如捕获异常、断言失败,或者 - 最糟糕和最错误的 - 当你收到致命的“异常”或信号时分段违规。

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

我假设您正在尝试实现致命错误处理的目标,因为大多数人在获取堆栈跟踪时似乎都会尝试这样做。如果是这样,我将依赖调试器(在开发期间)并让进程在生产中进行核心转储(或在 Windows 上进行小型转储)。与适当的符号管理一起,您应该可以轻松地事后找出引起的指令。


6
投票

您应该使用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;
}

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

您可以在Linux上使用

addr2line
命令来获取对应PC的源函数/行号。


5
投票

对于 Windows,

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


4
投票

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

您能做的最近的事情就是运行代码而不进行优化。这样您就可以附加到进程(使用 Visual C++ 调试器或 GDB)并获取可用的堆栈跟踪。


3
投票

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


1
投票

过去几年我一直在使用 Ian Lance Taylor 的 libbacktrace。它比 GNU C 库中需要导出所有符号的函数干净得多。它为生成回溯提供了比 libunwind 更多的实用程序。最后但并非最不重要的一点是,它不会像需要外部工具(例如

addr2line
)的方法一样被 ASLR 击败。

Libbacktrace 最初是 GCC 发行版的一部分,但现在作者在 BSD 许可证下将其作为独立库提供:

https://github.com/ianlancetaylor/libbacktrace

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


0
投票

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


0
投票

Cpptrace 支持 C,并且设计为便携且易于设置。它还促进了一些不错的功能,例如信号安全堆栈跟踪。

© www.soinside.com 2019 - 2024. All rights reserved.