在C或C++应用程序中,有没有一种方法可以将函数调用堆栈作为文本输出获取?
在C或C++应用程序中,有没有一种方法可以将函数调用堆栈作为文本输出获取?
在C或C++中,有没有办法在运行过程中转储调用堆栈,每次调用特定函数时都转储?我心目中的想法是这样的:
void foo() { print_stack_trace(); // foo的代码 return; }
其中的`print_stack_trace`类似于Perl中的`caller`函数。
或者是这样的:
int main (void) { // 每次调用foo()时都打印出调试信息 register_stack_trace_function(foo); // 等等... }
其中的`register_stack_trace_function`会设置某种内部断点,每次调用`foo`时都会打印出堆栈跟踪信息。
在某个标准C库中是否存在类似的功能?
我正在Linux上使用GCC进行开发。
背景
我有一个测试运行,根据一些不应影响此行为的命令行开关而表现出不同的行为。我的代码中有一个伪随机数生成器,我假设根据这些开关以不同的方式调用它。我想能够对每组开关运行测试,并查看随机数生成器是否在每个测试中以不同的方式被调用。
在C或C++应用程序中,有没有一种方法可以将函数调用堆栈作为文本输出?这个问题的出现是因为开发者需要获取函数调用堆栈信息,以便在调试和错误追踪时进行分析和定位。
在Linux系统中,可以使用backtrace(3)函数来获取函数调用堆栈信息,它返回一个指向void指针数组的指针,每个指针指向对应堆栈帧的返回地址。为了将这些信息转化为有用的内容,可以使用backtrace_symbols(3)函数。
需要注意的是,backtrace(3)函数的文档中提到,如果不使用特殊的链接器选项,可能无法获取到符号名称。对于使用GNU链接器的系统,需要使用-rdynamic链接器选项。需要注意的是,静态函数的名称不会被公开,因此在函数调用堆栈中也无法获取到。
值得一提的是,Mac OS X系统上也存在类似的功能,可以参考developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/backtrace.3.html。
而在Windows系统中,可以使用CaptureStackBackTrace函数来获取函数调用堆栈信息。
然而,在Linux系统中使用glibc时,backtrace_symbols函数无法提供函数名称、源文件名和行号的信息。
除了使用-rdynamic选项之外,还要确保构建系统没有添加-fvisibility=hidden选项,因为这会完全丢弃-rdynamic选项的效果。
在Mac系统上,可以使用atos工具将地址映射到文件名和行号,只需要将crash报告中的第一个地址传递给atos的-l参数即可。注意,需要先使用clang++编译时使用-g和-O0选项生成a.out.dSYM文件。
开发者可以根据自己的需求选择适合的方法来获取函数调用堆栈信息,以便进行调试和错误追踪。
C++23引入的<stacktrace>
库可以用于获取函数调用堆栈的文本输出。以下是使用方法:
#include/* ... */ std::cout << std::stacktrace::current();
进一步的细节:
• https://en.cppreference.com/w/cpp/header/stacktrace
• https://en.cppreference.com/w/cpp/utility/basic_stacktrace/operator_ltlt
函数调用堆栈是指在程序运行过程中,每个函数调用都会在堆栈中创建一个帧。当程序发生错误或异常时,了解函数调用堆栈可以帮助我们追踪错误的来源和调试代码。然而,在C和C++中,默认情况下是没有直接的方法来获取函数调用堆栈的文本输出的。
为了解决这个问题,C++23引入了<stacktrace>
库。通过包含该头文件,并使用std::stacktrace::current()
函数,我们可以获得当前的函数调用堆栈,并将其以文本形式输出到控制台。
以上代码片段中的#include <stacktrace>
语句用于包含<stacktrace>
库的头文件。然后,使用std::cout << std::stacktrace::current();
语句将当前的函数调用堆栈输出到标准输出流。
通过使用<stacktrace>
库,我们可以方便地获取函数调用堆栈的文本输出,从而更好地了解程序的执行流程和错误来源。这对于调试和排查问题非常有帮助。
调查C/C++回溯方法的调查结果
在本文中,我将尝试对一些解决方案进行基准测试,以查看哪个运行速度更快,同时考虑其他因素,如功能和可移植性。
调查结果如下:
| 工具名称 | 每次调用时间 | 行号 | 函数名 | C++解缠 | 重新编译 | 信号安全 | 作为字符串 | C |
|-----------------------------|--------------|------|--------------|-------------|------------|-----------|--------------|----|
| C++23
| Boost 1.74 stacktrace() | 5微秒 | y | y | y | y | n | y | n |
| Boost 1.74 stacktrace::safe_dump_to | | | | | | y | n | n |
| glibc backtrace_symbols_fd | 25微秒 | n | -rdynamic | hacks | y | y | n | y |
| glibc backtrace_symbols | 21微秒 | n | -rdynamic | hacks | y | n | y | y |
| GDB scripting | 600微秒 | y | y | y | n | y | n | y |
| GDB code injection | | | | | n | | n | y |
| libunwind | | | | | y | | | |
| libdwfl | 4毫秒 | n | | | y | | | |
| libbacktrace | | | | | y | | | |
空单元格表示"TODO",而不是"no"。
其中,我测试了每个解决方案的性能、功能和可移植性。可以看出,C++23的
测试设置的源代码如下:
#include// strtoul #include void my_func_2(void) { print_stacktrace(); // line 6 } void my_func_1(double f) { (void)f; my_func_2(); } void my_func_1(int i) { (void)i; my_func_2(); // line 16 } int main(int argc, char **argv) { long long unsigned int n; if (argc > 1) { n = std::strtoul(argv[1], NULL, 0); } else { n = 1; } for (long long unsigned int i = 0; i < n; ++i) { my_func_1(1); // line 27 } }
这个程序用于测试C++的名称解缠功能,因此具有多个重载的函数。在测试中,我根据具体的测试需求进行了调整,并对每个解决方案进行了基准测试,以获得性能数据。
根据调查结果,C++23的