如何在调用某个函数时打印堆栈跟踪

发布于 2024-09-26 16:13:43 字数 766 浏览 4 评论 0原文

有没有办法在每次调用某个函数时转储 C 或 C++ 正在运行的进程中的调用堆栈?我的想法是这样的:

void foo()
{
   print_stack_trace();

   // foo's body

   return
}

print_stack_trace 的工作方式类似于 < Perl 中的代码>调用者

或者类似这样的内容:

int main (void)
{
    // will print out debug info every time foo() is called
    register_stack_trace_function(foo); 

    // etc...
}

其中 register_stack_trace_function 放置某种内部断点,每当调用 foo 时,都会导致打印堆栈跟踪。

标准 C 库中是否存在类似的东西?

我正在 Linux 上工作,使用 GCC。


背景

我有一个测试运行,它的行为基于一些不应该影响此行为的命令行开关。我的代码有一个伪随机数生成器,我假设根据这些开关以不同的方式调用它。我希望能够使用每组开关运行测试,并查看每组开关的随机数生成器调用是否不同。

Is there any way to dump the call stack in a running process in C or C++ every time a certain function is called? What I have in mind is something like this:

void foo()
{
   print_stack_trace();

   // foo's body

   return
}

Where print_stack_trace works similarly to caller in Perl.

Or something like this:

int main (void)
{
    // will print out debug info every time foo() is called
    register_stack_trace_function(foo); 

    // etc...
}

where register_stack_trace_function puts some sort of internal breakpoint that will cause a stack trace to be printed whenever foo is called.

Does anything like this exist in some standard C library?

I am working on Linux, using GCC.


Background

I have a test run that behaves differently based on some commandline switches that shouldn't affect this behavior. My code has a pseudo-random number generator that I assume is being called differently based on these switches. I want to be able to run the test with each set of switches and see if the random number generator is called differently for each one.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

影子是时光的心 2024-10-03 16:13:43

C/C++ 回溯方法调查

在这个答案中,我将尝试对一堆解决方案运行一个基准测试,看看哪一个运行得更快,同时还考虑其他点,例如功能和可移植性。

工具时间/调用 行函数名称C++ 分解 重新编译信号安全As stringC
C++23 GCC 12.17 usyyyynyn
Boost 1.74 stacktrace()5 usyyyynyn
Boost 1.74 stacktrace::safe_dump_toy(已弃用)nn
glibc backtrace_symbols_fd25 usn-rdynamichacksynny
glibc backtrace_symbols21 usn-rdynamic< /code>hacksynyy
GDB 脚本600 usyynynyGDB
代码注入 nnylibunwind
ylibdwfl
4msny
libbacktracey
cpptraceyyynyyy

空单元格表示“TODO”,而不是“不”。

  • 我们:微秒

  • 行号:显示实际行号,而不仅仅是函数名称+内存地址。

    通常可以在事后使用addr2line从地址手动恢复行号。但这是一种痛苦。

  • 重新编译:需要重新编译程序才能获取痕迹。不重新编译更好!

  • 信号安全:对于“在发生段错误时获取堆栈跟踪”的重要用例至关重要:如何在程序崩溃时自动生成堆栈跟踪

  • 作为字符串:您可以在程序本身中以字符串形式获取堆栈跟踪,而不是例如只是打印到标准输出。通常意味着信号不安全,因为我们事先不知道堆栈跟踪字符串的大小,因此需要非异步信号安全的 malloc。

  • C:它可以在纯 C 项目上运行吗(是的,那里仍然有可怜的灵魂),还是需要 C++?

测试设置

所有基准测试都将运行以下

main.cpp

#include <cstdlib> // strtoul

#include <mystacktrace.h>

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
    }
}

此输入旨在测试自 my_func_1(int)my_func_1(float) 以来的 C++ 名称分解> 作为实现 C++ 的一种方式,必然会被破坏函数重载

我们通过使用不同的 -I 包含来指向 print_stacktrace() 的不同实现来区分基准。

每个基准测试都是通过以下形式的命令完成的:

time ./stacktrace.out 100000 &>/dev/null

针对每个实现调整迭代次数,以产生该基准测试的 1 秒左右的总运行时间。

除非另有说明,否则以下所有测试均使用 -O0。堆栈跟踪可能会因某些优化而受到不可挽回的损害。尾部调用优化就是一个著名的例子:什么是尾部调用优化? 我们对此无能为力。

C++23

此方法之前曾在以下位置提到过:https ://stackoverflow.com/a/69384663/895245请考虑投票赞成该答案。

这是最好的解决方案...它便携、快速、显示行号并分解 C++ 符号。一旦该选项变得更广泛可用,它就会取代所有其他替代方案,但 GDB 可能只是一次性的例外,无需或重新编译。

cpp20_stacktrace/mystacktrace.h

#include <iostream>
#include <stacktrace>

void print_stacktrace() {
    std::cout << std::stacktrace::current();
}

Ubuntu 22.04 中的 GCC 12.1.0 不支持编译,所以现在我按照以下方式从源代码构建它: 如何编辑和重新构建 GCC libstdc++ C++ 标准库源? 并设置 < code>--enable-libstdcxx-backtrace=yes,它成功了!

编译:

g++ -O0 -ggdb3 -Wall -Wextra -pedantic -std=c++23 -o cpp20_stacktrace.out main.cpp -lstdc++_libbacktrace

示例输出:

   0# print_stacktrace() at cpp20_stacktrace/mystacktrace.h:5
   1# my_func_2() at /home/ciro/main.cpp:6
   2# my_func_1(int) at /home/ciro/main.cpp:16
   3#      at /home/ciro/main.cpp:27
   4#      at :0
   5#      at :0
   6#      at :0
   7#

如果我们尝试使用 Ubuntu 22.04 中的 GCC 12.1.0:

sudo apt install g++-12
g++-12 -ggdb3 -O2 -std=c++23 -Wall -Wextra -pedantic -o stacktrace.out stacktrace.cpp -lstdc++_libbacktrace

它会失败:

stacktrace.cpp: In function ‘void my_func_2()’:
stacktrace.cpp:6:23: error: ‘std::stacktrace’ has not been declared
    6 |     std::cout << std::stacktrace::current();
      |                       ^~~~~~~~~~

检查构建选项:

g++-12 -v

不显示:

--enable-libstdcxx-backtrace=yes

所以它没有被编译。 参考书目:

它不会在包含时失败,因为头文件:

/usr/include/c++/12

具有功能检查:

#if __cplusplus > 202002L && _GLIBCXX_HAVE_STACKTRACE

Boost stacktrace

库在 Ubuntu 22.04 周围发生了很大变化,因此请确保您的版本匹配: Boost 堆栈跟踪不显示函数名称和行号

该库几乎已被更可移植的 C++23 实现所取代,但对于那些不符合该标准的人来说仍然是一个非常好的选择版本还没有,但已经有“Boost 许可”。

记录于: https:// www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack

在Ubuntu 22.04,boost 1.74.0上测试,你应该这样做:

boost_stacktrace/mystacktrace.h

#include <iostream>

#define BOOST_STACKTRACE_LINK
#include <boost/stacktrace.hpp>

void print_stacktrace(void) {
    std::cout << boost::stacktrace::stacktrace();
}

在 Ubuntu 19.10 boost 1.67.0 上,为了获取行号,我们必须改为:

#include <iostream>

#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>

void print_stacktrace(void) {
    std::cout << boost::stacktrace::stacktrace();
}

这将调用 addr2line 可执行文件,并且比新的 Boost 版本慢 1000 倍。

Ubuntu 16.04 上根本不存在 libboost-stacktrace-dev 包。

本节的其余部分仅考虑 Ubuntu 22.04、boost 1.74 的行为。

编译:

sudo apt-get install libboost-stacktrace-dev
g++ -O0 -ggdb3 -Wall -Wextra -pedantic -std=c++11 -o boost_stacktrace.out main.cpp -lboost_stacktrace_backtrace

示例输出:

 0# print_stacktrace() at boost_stacktrace/mystacktrace.h:7
 1# my_func_2() at /home/ciro/main.cpp:7
 2# my_func_1(int) at /home/ciro/main.cpp:17
 3# main at /home/ciro/main.cpp:26
 4# __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
 5# __libc_start_main at ../csu/libc-start.c:379
 6# _start in ./boost_stacktrace.out

请注意,各行相差一行。 评论中建议这是因为正在考虑以下指令地址。

仅 Boost stacktrace 标头

BOOST_STACKTRACE_LINK 的作用是在链接时需要 -lboost_stacktrace_backtrace,所以我们想象没有它它会起作用的。对于没有“Boost 许可”的开发人员来说,这将是一个不错的选择,可以一次性添加进行调试。

TODO 不幸的是,它对我来说不太好:

#include <iostream>

#include <boost/stacktrace.hpp>

void print_stacktrace(void) {
    std::cout << boost::stacktrace::stacktrace();
}

then:

g++ -O0 -ggdb3 -Wall -Wextra -pedantic -std=c++11 -o boost_stacktrace_header_only.out main.cpp

包含过短的输出:

 0# 0x000055FF74AFB601 in ./boost_stacktrace_header_only.out
 1# 0x000055FF74AFB66C in ./boost_stacktrace_header_only.out
 2# 0x000055FF74AFB69C in ./boost_stacktrace_header_only.out
 3# 0x000055FF74AFB6F7 in ./boost_stacktrace_header_only.out
 4# 0x00007F0176E7BD90 in /lib/x86_64-linux-gnu/libc.so.6
 5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 6# 0x000055FF74AFB4E5 in ./boost_stacktrace_header_only.out

我们甚至不能与 addr2line 一起使用。也许我们必须传递一些其他定义: https:// /www.boost.org/doc/libs/1_80_0/doc/html/stacktrace/configuration_and_build.html

在 Ubuntu 22.04 上测试。提升1.74。

Boost boost::stacktrace::safe_dump_to

这是 boost::stacktrace::stacktrace 的一个有趣的替代方案,因为它将堆栈跟踪写入对文件的异步信号安全方式,这使得它成为自动转储段错误上的堆栈跟踪的好选择,这是一个超级常见的用例:如何在程序崩溃时自动生成堆栈跟踪

记录于:https://www.boost.org/doc/libs/1_70_0/doc/html/boost/stacktrace /safe_dump_1_3_38_7_6_2_1_6.html

TODO 使其正常工作。我每次看到的都是一堆随机字节。我的尝试:

boost_stacktrace_safe/mystacktrace.h

#include <unistd.h>

#define BOOST_STACKTRACE_LINK
#include <boost/stacktrace.hpp>

void print_stacktrace(void) {
    boost::stacktrace::safe_dump_to(0, 1024, STDOUT_FILENO);
}

示例输出:

1[FU1[FU"2[FU}2[FUm1@n10[FU

每次都会发生巨大变化,表明它是随机内存地址。

在 Ubuntu 22.04、boost 1.74.0 上测试。

glibc backtrace

这个方法非常便携,因为它是 glibc 本身自带的。记录于: https://www.gnu.org/software/libc/ Manual/html_node/Backtraces.html

在 Ubuntu 22.04、glibc 2.35 上测试。

glibc_backtrace_symbols_fd/mystacktrace.h

#include <execinfo.h> /* backtrace, backtrace_symbols_fd */
#include <unistd.h> /* STDOUT_FILENO */

void print_stacktrace(void) {
    size_t size;
    enum Constexpr { MAX_SIZE = 1024 };
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
    backtrace_symbols_fd(array, size, STDOUT_FILENO);
}

编译:

g++ -O0 -ggdb3 -Wall -Wextra -pedantic -rdynamic -std=c++11 -o glibc_backtrace_symbols_fd.out main.cpp

使用 -rdynamic 的示例输出:

./glibc_backtrace_symbols.out(_Z16print_stacktracev+0x47) [0x556e6a131230]
./glibc_backtrace_symbols.out(_Z9my_func_2v+0xd) [0x556e6a1312d6]
./glibc_backtrace_symbols.out(_Z9my_func_1i+0x14) [0x556e6a131306]
./glibc_backtrace_symbols.out(main+0x58) [0x556e6a131361]
/lib/x86_64-linux-gnu/libc.so.6(+0x29d90) [0x7f175e7bdd90]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80) [0x7f175e7bde40]
./glibc_backtrace_symbols.out(_start+0x25) [0x556e6a131125]

不使用 -rdynamic 的示例输出:

./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x11f0)[0x556bd40461f0]
./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x123c)[0x556bd404623c]
./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x126c)[0x556bd404626c]
./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x12c7)[0x556bd40462c7]
/lib/x86_64-linux-gnu/libc.so.6(+0x29d90)[0x7f0da2b70d90]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80)[0x7f0da2b70e40]
./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x10e5)[0x556bd40460e5]

要获取不使用 -rdynamic 的行号,我们可以使用addr2line

addr2line -C -e glibc_backtrace_symbols_fd_no_rdynamic.out 0x11f0 0x123c 0x126c 0x12c7

不幸的是,当我们不使用-rdynamic时,addr2line无法处理函数格式中的函数名+偏移量,例如_Z9my_func_2v+0xd

然而,GDB 可以:

gdb -nh -batch -ex 'info line *(_Z9my_func_2v+0xd)' -ex 'info line *(_Z9my_func_1i+0x14)' glibc_backtrace_symbols.out
Line 7 of "main.cpp" starts at address 0x12d6 <_Z9my_func_2v+13> and ends at 0x12d9 <_Z9my_func_1d>.
Line 17 of "main.cpp" starts at address 0x1306 <_Z9my_func_1i+20> and ends at 0x1309 <main(int, char**)>.

一个让它更容易忍受的助手:

addr2lines() (
  perl -ne '$m = s/(.*).*\(([^)]*)\).*/gdb -nh -q -batch -ex "info line *\2" \1/;print $_ if $m' | bash
)

用法:

xsel -b | addr2lines

glibc backtrace_symbols

backtrace_symbols_fd 的一个版本,它返回一个字符串而不是打印到文件句柄。

glibc_backtrace_symbols/mystacktrace.h

#include <execinfo.h> /* backtrace, backtrace_symbols */
#include <stdio.h> /* printf */

void print_stacktrace(void) {
    char **strings;
    size_t i, size;
    enum Constexpr { MAX_SIZE = 1024 };
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
    strings = backtrace_symbols(array, size);
    for (i = 0; i < size; i++)
        printf("%s\n", strings[i]);
    free(strings);
}

glibc backtrace with C++ demangling hack 1: -export-dynamic + dladdr

我不能'找不到一种简单的方法来使用 glibc backtrace 自动消除 C++ 符号的角度。

改编自:https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01 d3

这是一个“黑客”,因为它需要使用 -export-dynamic 更改 ELF。

glibc_ldl.cpp

#include <dlfcn.h>     // for dladdr
#include <cxxabi.h>    // for __cxa_demangle

#include <cstdio>
#include <string>
#include <sstream>
#include <iostream>

// This function produces a stack backtrace with demangled function & method names.
std::string backtrace(int skip = 1)
{
    void *callstack[128];
    const int nMaxFrames = sizeof(callstack) / sizeof(callstack[0]);
    char buf[1024];
    int nFrames = backtrace(callstack, nMaxFrames);
    char **symbols = backtrace_symbols(callstack, nFrames);

    std::ostringstream trace_buf;
    for (int i = skip; i < nFrames; i++) {
        Dl_info info;
        if (dladdr(callstack[i], &info)) {
            char *demangled = NULL;
            int status;
            demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
            std::snprintf(
                buf,
                sizeof(buf),
                "%-3d %*p %s + %zd\n",
                i,
                (int)(2 + sizeof(void*) * 2),
                callstack[i],
                status == 0 ? demangled : info.dli_sname,
                (char *)callstack[i] - (char *)info.dli_saddr
            );
            free(demangled);
        } else {
            std::snprintf(buf, sizeof(buf), "%-3d %*p\n",
                i, (int)(2 + sizeof(void*) * 2), callstack[i]);
        }
        trace_buf << buf;
        std::snprintf(buf, sizeof(buf), "%s\n", symbols[i]);
        trace_buf << buf;
    }
    free(symbols);
    if (nFrames == nMaxFrames)
        trace_buf << "[truncated]\n";
    return trace_buf.str();
}

void my_func_2(void) {
    std::cout << backtrace() << std::endl;
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

编译并运行:

g++ -fno-pie -ggdb3 -O0 -no-pie -o glibc_ldl.out -std=c++11 -Wall -Wextra \
  -pedantic-errors -fpic glibc_ldl.cpp -export-dynamic -ldl
./glibc_ldl.out 

输出:

1             0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2             0x40139e my_func_1(int) + 16
./glibc_ldl.out(_Z9my_func_1i+0x10) [0x40139e]
3             0x4013b3 main + 18
./glibc_ldl.out(main+0x12) [0x4013b3]
4       0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5             0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]

1             0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2             0x40138b my_func_1(double) + 18
./glibc_ldl.out(_Z9my_func_1d+0x12) [0x40138b]
3             0x4013c8 main + 39
./glibc_ldl.out(main+0x27) [0x4013c8]
4       0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5             0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]

在 Ubuntu 18.04 上测试。

glibc backtrace with C++ demangled hack 2:解析回溯输出

显示于:https://panthema.net/2008/0901-stacktrace-demangled/

这是一个 hack,因为它需要解析。

TODO 对其进行编译并在此处显示。

GDB 脚本

我们还可以使用 GDB 来执行此操作,而无需重新编译:当 GDB 中某个断点被击中时,如何执行特定操作?

我们为测试设置了一个空的回溯函数:

gdb/mystacktrace。 h

void print_stacktrace(void) {}

然后使用:

main.gdb

start
break print_stacktrace
commands
  silent
  backtrace
  printf "\n"
  continue
end
continue

我们可以运行:

gdb -nh -batch -x main.gdb --args gdb.out

示例输出:

Temporary breakpoint 1 at 0x11a7: file main.cpp, line 21.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Temporary breakpoint 1, main (argc=1, argv=0x7fffffffc3e8) at main.cpp:21
warning: Source file is more recent than executable.
21          if (argc > 1) {
Breakpoint 2 at 0x555555555151: file gdb/mystacktrace.h, line 1.
#0  print_stacktrace () at gdb/mystacktrace.h:1
#1  0x0000555555555161 in my_func_2 () at main.cpp:6
#2  0x0000555555555191 in my_func_1 (i=1) at main.cpp:16
#3  0x00005555555551ec in main (argc=1, argv=0x7fffffffc3e8) at main.cpp:27

[Inferior 1 (process 165453) exited normally]

使用以下 Bash 函数可以使上面的内容更加有用:

gdbbt() (
  tmpfile=$(mktemp /tmp/gdbbt.XXXXXX)
  fn="$1"
  shift
  printf '%s' "
start
break $fn
commands
  silent
  backtrace
  printf \"\n\"
  continue
end
continue
" > "$tmpfile"
  gdb -nh -batch -x "$tmpfile" -args "$@"
  rm -f "$tmpfile"
)

用法:

gdbbt print_stacktrace gdb.out 2

我不知道如何使用 制作 commands -ex 没有临时文件: 使用 ex 命令从命令行添加断点时出现问题

在 Ubuntu 22.04、GDB 12.0.90 中测试。

GDB 代码注入

TODO 这就是梦想!它可能允许两种类似编译的速度,但不需要重新编译!要么:

libunwind

TODO 这比 glibc 回溯有什么优势吗?非常相似的输出,也需要修改构建命令,但不是 glibc 的一部分,因此需要额外的软件包安装。

代码改编自:https://eli。 thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

main.c

/* This must be on top. */
#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>

/* Paste this on the file you want to debug. */
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <stdio.h>
void print_trace() {
    char sym[256];
    unw_context_t context;
    unw_cursor_t cursor;
    unw_getcontext(&context);
    unw_init_local(&cursor, &context);
    while (unw_step(&cursor) > 0) {
        unw_word_t offset, pc;
        unw_get_reg(&cursor, UNW_REG_IP, &pc);
        if (pc == 0) {
            break;
        }
        printf("0x%lx:", pc);
        if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
            printf(" (%s+0x%lx)\n", sym, offset);
        } else {
            printf(" -- error: unable to obtain symbol name for this frame\n");
        }
    }
    puts("");
}

void my_func_3(void) {
    print_trace();
}

void my_func_2(void) {
    my_func_3();
}

void my_func_1(void) {
    my_func_3();
}

int main(void) {
    my_func_1(); /* line 46 */
    my_func_2(); /* line 47 */
    return 0;
}

编译并运行:

sudo apt-get install libunwind-dev
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -std=c99 \
  -Wall -Wextra -pedantic-errors main.c -lunwind

#define _XOPEN_SOURCE 700 必须位于顶部,或者我们必须使用 -std=gnu99

运行:

./main.out

输出:

0x4007db: (main+0xb)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)

0x4007e2: (main+0x12)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)

and:

addr2line -e main.out 0x4007db 0x4007e2

给出:

/home/ciro/main.c:34
/home/ciro/main.c:49

使用 -O0:

0x4009cf: (my_func_3+0xe)
0x4009e7: (my_func_1+0x9)
0x4009f3: (main+0x9)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)

0x4009cf: (my_func_3+0xe)
0x4009db: (my_func_2+0x9)
0x4009f8: (main+0xe)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)

and:

addr2line -e main.out 0x4009f3 0x4009f8

给出:

/home/ciro/main.c:47
/home/ciro/main.c:48

在 Ubuntu 16.04、GCC 6.4.0、libunwind 1.1 上测试。

带有 C++ 名称重组的 libunwind

代码改编自:https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

unwind.cpp

#define UNW_LOCAL_ONLY
#include <cxxabi.h>
#include <libunwind.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>

void backtrace() {
  unw_cursor_t cursor;
  unw_context_t context;

  // Initialize cursor to current frame for local unwinding.
  unw_getcontext(&context);
  unw_init_local(&cursor, &context);

  // Unwind frames one by one, going up the frame stack.
  while (unw_step(&cursor) > 0) {
    unw_word_t offset, pc;
    unw_get_reg(&cursor, UNW_REG_IP, &pc);
    if (pc == 0) {
      break;
    }
    std::printf("0x%lx:", pc);

    char sym[256];
    if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
      char* nameptr = sym;
      int status;
      char* demangled = abi::__cxa_demangle(sym, nullptr, nullptr, &status);
      if (status == 0) {
        nameptr = demangled;
      }
      std::printf(" (%s+0x%lx)\n", nameptr, offset);
      std::free(demangled);
    } else {
      std::printf(" -- error: unable to obtain symbol name for this frame\n");
    }
  }
}

void my_func_2(void) {
    backtrace();
    std::cout << std::endl; // line 43
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}  // line 54

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

编译并运行:

sudo apt-get install libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o unwind.out -std=c++11 \
  -Wall -Wextra -pedantic-errors unwind.cpp -lunwind -pthread
./unwind.out

输出:

0x400c80: (my_func_2()+0x9)
0x400cb7: (my_func_1(int)+0x10)
0x400ccc: (main+0x12)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)

0x400c80: (my_func_2()+0x9)
0x400ca4: (my_func_1(double)+0x12)
0x400ce1: (main+0x27)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)

然后我们可以找到 my_func_2my_func_1(int) 的行:其中

addr2line -e unwind.out 0x400c80 0x400cb7

给出:

/home/ciro/test/unwind.cpp:43
/home/ciro/test/unwind.cpp:54

TODO:为什么这些行相差一位?

在 Ubuntu 18.04、GCC 7.4.0、libunwind 1.2.1 上测试。

Linux 内核

如何打印Linux内核中当前线程堆栈跟踪?

libdwfl

这最初是在:https://stackoverflow.com/a/60713161/895245 这可能是最好的方法,但我必须进行更多基准测试,但请对这个答案进行投票。

TODO:我试图将该答案中有效的代码最小化为单个函数,但它存在段错误,如果有人能找到原因,请告诉我。

dwfl.cpp:答案达到了 30k 个字符,这是最简单的削减: https://gist.github.com/ cirosantilli/f1dd3ee5d324b9d24e40f855723544ac

编译并运行:

sudo apt install libdw-dev libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw -lunwind
./dwfl.out

我们还需要 libunwind,因为这使得结果更加正确。如果你没有它,它会运行,但你会发现有些行有点错误。

输出:

0: 0x402b72 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402cda my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d76 my_func_1(int) at /home/ciro/test/dwfl.cpp:111
3: 0x402dd1 main at /home/ciro/test/dwfl.cpp:122
4: 0x7ff227ea0d8f __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
5: 0x7ff227ea0e3f __libc_start_main@@GLIBC_2.34 at ../csu/libc-start.c:392
6: 0x402534 _start at ../csu/libc-start.c:-1

0: 0x402b72 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402cda my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d5f my_func_1(double) at /home/ciro/test/dwfl.cpp:106
3: 0x402de2 main at /home/ciro/test/dwfl.cpp:123
4: 0x7ff227ea0d8f __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
5: 0x7ff227ea0e3f __libc_start_main@@GLIBC_2.34 at ../csu/libc-start.c:392
6: 0x402534 _start at ../csu/libc-start.c:-1

基准测试运行:

g++ -fno-pie -ggdb3 -O3 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
time ./dwfl.out 1000 >/dev/null

输出:

real    0m3.751s
user    0m2.822s
sys     0m0.928s

所以我们看到这个方法比 Boost 的堆栈跟踪快 10 倍,因此可能适用于更多用例。

在 Ubuntu 22.04 amd64、libdw-dev 0.186、libunwind 1.3.2 中测试。

libbacktrace

https://github.com/ianlancetaylor/libbacktrace

考虑 h​​arcore 库作者,值得一试,也许就是The One。 TODO 检查一下。

可以链接到 C/C++ 程序以生成符号回溯的 AC 库

截至 2020 年 10 月,libbacktrace 支持带有 DWARF 调试信息的 ELF、PE/COFF、Mach-O 和 XCOFF 可执行文件。换句话说,它支持 GNU/Linux、*BSD、macOS、Windows 和 AIX。该库的编写目的是让添加对其他目标文件和调试格式的支持变得简单。

该库依赖于 https 中定义的 C++ unwind API: //itanium-cxx-abi.github.io/cxx-abi/abi-eh.html 该API由GCC和clang提供。

另请参阅

cpptrace

Cpptrace 简单且可移植,支持 C++11和更新的。与其他解决方案不同,它支持所有主要平台和编译器,并且几乎完全独立。

cpptrace::generate_trace().print();

示例输出:

Stack trace (most recent call first):
#0 0x000055fb1305f235 in my_func_2() at /path/to/main.cpp:6
#1 0x000055fb1305f2b5 in my_func_1(int) at /path/to/main.cpp:16
#2 0x000055fb1305f310 in main at /path/to/main.cpp:27
#3 0x00007f648990cd8f in __libc_start_call_main at ./csu/../sysdeps/nptl/libc_start_call_main.h:58
#4 0x00007f648990ce3f in __libc_start_main_impl at ./csu/../csu/libc-start.c:392
#5 0x000055fb1305f144 in _start at ./stacktrace.out

除了一般的堆栈跟踪生成之外,它还包括一个跟踪的异常对象,该对象在抛出时生成跟踪,并允许生成可以稍后解析的原始堆栈跟踪。

更多信息可以在此处找到。

Survey of C/C++ backtrace methods

In this answer I will try to run a single benchmark for a bunch of solutions to see which one runs faster, while also considering other points such as features and portability.

ToolTime / callLine numberFunction nameC++ demanglingRecompileSignal safeAs stringC
C++23 <stacktrace> GCC 12.17 usyyyynyn
Boost 1.74 stacktrace()5 usyyyynyn
Boost 1.74 stacktrace::safe_dump_toy (deprecated)nn
glibc backtrace_symbols_fd25 usn-rdynamichacksynny
glibc backtrace_symbols21 usn-rdynamichacksynyy
GDB scripting600 usyyynyny
GDB code injectionnny
libunwindy
libdwfl4 msny
libbacktracey
cpptraceyyynyyy

Empty cells mean "TODO", not "no".

  • us: microsecond

  • Line number: shows actual line number, not just function name + a memory address.

    It is usually possible to recover the line number from an address manually after the fact with addr2line. But it is a pain.

  • Recompile: requires recompiling the program to get your traces. Not recompiling is better!

  • Signal safe: crucial for the important uses case of "getting a stack trace in case of segfault": How to automatically generate a stacktrace when my program crashes

  • As string: you get the stack trace as a string in the program itself, as opposed to e.g. just printing to stdout. Usually implies not signal safe, as we don't know the size of the stack trace string size in advance, and therefore requires malloc which is not async signal safe.

  • C: does it work on a plain-C project (yes, there are still poor souls out there), or is C++ required?

Test setup

All benchmarks will run the following

main.cpp

#include <cstdlib> // strtoul

#include <mystacktrace.h>

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
    }
}

This input is designed to test C++ name demangling since my_func_1(int) and my_func_1(float) are necessarily mangled as a way to implement C++ function overload.

We differentiate between the benchmarks by using different -I includes to point to different implementations of print_stacktrace().

Each benchmark is done with a command of form:

time ./stacktrace.out 100000 &>/dev/null

The number of iterations is adjusted for each implementation to produce a total runtime of the order of 1s for that benchmark.

-O0 is used on all tests below unless noted. Stack traces may be irreparably mutilated by certain optimizations. Tail call optimization is a notable example of that: What is tail call optimization? There's nothing we can do about it.

C++23 <stacktrace>

This method was previously mentioned at: https://stackoverflow.com/a/69384663/895245 please consider upvoting that answer.

This is the best solution... it's portable, fast, shows line numbers and demangles C++ symbols. This option will displace every other alternative as soon as it becomes more widely available, with the exception perhaps only of GDB for one-offs without the need or recompilation.

cpp20_stacktrace/mystacktrace.h

#include <iostream>
#include <stacktrace>

void print_stacktrace() {
    std::cout << std::stacktrace::current();
}

GCC 12.1.0 from Ubuntu 22.04 does not have support compiled in, so for now I built it from source as per: How to edit and re-build the GCC libstdc++ C++ standard library source? and set --enable-libstdcxx-backtrace=yes, and it worked!

Compile with:

g++ -O0 -ggdb3 -Wall -Wextra -pedantic -std=c++23 -o cpp20_stacktrace.out main.cpp -lstdc++_libbacktrace

Sample output:

   0# print_stacktrace() at cpp20_stacktrace/mystacktrace.h:5
   1# my_func_2() at /home/ciro/main.cpp:6
   2# my_func_1(int) at /home/ciro/main.cpp:16
   3#      at /home/ciro/main.cpp:27
   4#      at :0
   5#      at :0
   6#      at :0
   7#

If we try to use GCC 12.1.0 from Ubuntu 22.04:

sudo apt install g++-12
g++-12 -ggdb3 -O2 -std=c++23 -Wall -Wextra -pedantic -o stacktrace.out stacktrace.cpp -lstdc++_libbacktrace

It fails with:

stacktrace.cpp: In function ‘void my_func_2()’:
stacktrace.cpp:6:23: error: ‘std::stacktrace’ has not been declared
    6 |     std::cout << std::stacktrace::current();
      |                       ^~~~~~~~~~

Checking build options with:

g++-12 -v

does not show:

--enable-libstdcxx-backtrace=yes

so it wasn't compiled in. Bibliography:

It does not fail on the include because the header file:

/usr/include/c++/12

has a feature check:

#if __cplusplus > 202002L && _GLIBCXX_HAVE_STACKTRACE

Boost stacktrace

The library has changed quite a lot around Ubuntu 22.04, so make sure your version matches: Boost stack-trace not showing function names and line numbers

The library is pretty much superseded by the more portable C++23 implementation, but remains a very good option for those that are not at that standard version yet, but already have a "Boost clearance".

Documented at: https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack

Tested on Ubuntu 22.04, boost 1.74.0, you should do:

boost_stacktrace/mystacktrace.h

#include <iostream>

#define BOOST_STACKTRACE_LINK
#include <boost/stacktrace.hpp>

void print_stacktrace(void) {
    std::cout << boost::stacktrace::stacktrace();
}

On Ubuntu 19.10 boost 1.67.0 to get the line numbers we had to instead:

#include <iostream>

#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>

void print_stacktrace(void) {
    std::cout << boost::stacktrace::stacktrace();
}

which would call out to the addr2line executable and be 1000x slower than the newer Boost version.

The package libboost-stacktrace-dev did not exist at all on Ubuntu 16.04.

The rest of this section considers only the Ubuntu 22.04, boost 1.74 behaviour.

Compile:

sudo apt-get install libboost-stacktrace-dev
g++ -O0 -ggdb3 -Wall -Wextra -pedantic -std=c++11 -o boost_stacktrace.out main.cpp -lboost_stacktrace_backtrace

Sample output:

 0# print_stacktrace() at boost_stacktrace/mystacktrace.h:7
 1# my_func_2() at /home/ciro/main.cpp:7
 2# my_func_1(int) at /home/ciro/main.cpp:17
 3# main at /home/ciro/main.cpp:26
 4# __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
 5# __libc_start_main at ../csu/libc-start.c:379
 6# _start in ./boost_stacktrace.out

Note that the lines are off by one line. It was suggested in the comments that this is because the following instruction address is being considered.

Boost stacktrace header only

What the BOOST_STACKTRACE_LINK does is to require -lboost_stacktrace_backtrace at link time, so we imagine without that it will just work. This would be a good option for devs who don't have the "Boost clearance" to just add as one offs to debug.

TODO unfortunately it didn't so well for me:

#include <iostream>

#include <boost/stacktrace.hpp>

void print_stacktrace(void) {
    std::cout << boost::stacktrace::stacktrace();
}

then:

g++ -O0 -ggdb3 -Wall -Wextra -pedantic -std=c++11 -o boost_stacktrace_header_only.out main.cpp

contains the overly short output:

 0# 0x000055FF74AFB601 in ./boost_stacktrace_header_only.out
 1# 0x000055FF74AFB66C in ./boost_stacktrace_header_only.out
 2# 0x000055FF74AFB69C in ./boost_stacktrace_header_only.out
 3# 0x000055FF74AFB6F7 in ./boost_stacktrace_header_only.out
 4# 0x00007F0176E7BD90 in /lib/x86_64-linux-gnu/libc.so.6
 5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 6# 0x000055FF74AFB4E5 in ./boost_stacktrace_header_only.out

which we can't even use with addr2line. Maybe we have to pass some other define from: https://www.boost.org/doc/libs/1_80_0/doc/html/stacktrace/configuration_and_build.html ?

Tested on Ubuntu 22.04. boost 1.74.

Boost boost::stacktrace::safe_dump_to

This is an interesting alternative to boost::stacktrace::stacktrace as it writes the stack trace in a async signal safe manner to a file, which makes it a good option for automatically dumping stack traces on segfaults which is a super common use case: How to automatically generate a stacktrace when my program crashes

Documented at: https://www.boost.org/doc/libs/1_70_0/doc/html/boost/stacktrace/safe_dump_1_3_38_7_6_2_1_6.html

TODO get it to work. All I see each time is a bunch of random bytes. My attempt:

boost_stacktrace_safe/mystacktrace.h

#include <unistd.h>

#define BOOST_STACKTRACE_LINK
#include <boost/stacktrace.hpp>

void print_stacktrace(void) {
    boost::stacktrace::safe_dump_to(0, 1024, STDOUT_FILENO);
}

Sample output:

1[FU1[FU"2[FU}2[FUm1@n10[FU

Changes drastically each time, suggesting it is random memory addresses.

Tested on Ubuntu 22.04, boost 1.74.0.

glibc backtrace

This method is quite portable as it comes with glibc itself. Documented at: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html

Tested on Ubuntu 22.04, glibc 2.35.

glibc_backtrace_symbols_fd/mystacktrace.h

#include <execinfo.h> /* backtrace, backtrace_symbols_fd */
#include <unistd.h> /* STDOUT_FILENO */

void print_stacktrace(void) {
    size_t size;
    enum Constexpr { MAX_SIZE = 1024 };
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
    backtrace_symbols_fd(array, size, STDOUT_FILENO);
}

Compile with:

g++ -O0 -ggdb3 -Wall -Wextra -pedantic -rdynamic -std=c++11 -o glibc_backtrace_symbols_fd.out main.cpp

Sample output with -rdynamic:

./glibc_backtrace_symbols.out(_Z16print_stacktracev+0x47) [0x556e6a131230]
./glibc_backtrace_symbols.out(_Z9my_func_2v+0xd) [0x556e6a1312d6]
./glibc_backtrace_symbols.out(_Z9my_func_1i+0x14) [0x556e6a131306]
./glibc_backtrace_symbols.out(main+0x58) [0x556e6a131361]
/lib/x86_64-linux-gnu/libc.so.6(+0x29d90) [0x7f175e7bdd90]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80) [0x7f175e7bde40]
./glibc_backtrace_symbols.out(_start+0x25) [0x556e6a131125]

Sample output without -rdynamic:

./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x11f0)[0x556bd40461f0]
./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x123c)[0x556bd404623c]
./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x126c)[0x556bd404626c]
./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x12c7)[0x556bd40462c7]
/lib/x86_64-linux-gnu/libc.so.6(+0x29d90)[0x7f0da2b70d90]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80)[0x7f0da2b70e40]
./glibc_backtrace_symbols_fd_no_rdynamic.out(+0x10e5)[0x556bd40460e5]

To get the line numbers without -rdynamic we can use addr2line:

addr2line -C -e glibc_backtrace_symbols_fd_no_rdynamic.out 0x11f0 0x123c 0x126c 0x12c7

addr2line cannot unfortunately handle the function name + offset in function format of when we are not using -rdynamic, e.g. _Z9my_func_2v+0xd.

GDB can however:

gdb -nh -batch -ex 'info line *(_Z9my_func_2v+0xd)' -ex 'info line *(_Z9my_func_1i+0x14)' glibc_backtrace_symbols.out
Line 7 of "main.cpp" starts at address 0x12d6 <_Z9my_func_2v+13> and ends at 0x12d9 <_Z9my_func_1d>.
Line 17 of "main.cpp" starts at address 0x1306 <_Z9my_func_1i+20> and ends at 0x1309 <main(int, char**)>.

A helper to make it more bearable:

addr2lines() (
  perl -ne '$m = s/(.*).*\(([^)]*)\).*/gdb -nh -q -batch -ex "info line *\2" \1/;print $_ if $m' | bash
)

Usage:

xsel -b | addr2lines

glibc backtrace_symbols

A version of backtrace_symbols_fd that returns a string rather than printing to a file handle.

glibc_backtrace_symbols/mystacktrace.h

#include <execinfo.h> /* backtrace, backtrace_symbols */
#include <stdio.h> /* printf */

void print_stacktrace(void) {
    char **strings;
    size_t i, size;
    enum Constexpr { MAX_SIZE = 1024 };
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
    strings = backtrace_symbols(array, size);
    for (i = 0; i < size; i++)
        printf("%s\n", strings[i]);
    free(strings);
}

glibc backtrace with C++ demangling hack 1: -export-dynamic + dladdr

I couldn't find a simple way to automatically demangle C++ symbols with glibc backtrace.

Adapted from: https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3

This is a "hack" because it requires changing the ELF with -export-dynamic.

glibc_ldl.cpp

#include <dlfcn.h>     // for dladdr
#include <cxxabi.h>    // for __cxa_demangle

#include <cstdio>
#include <string>
#include <sstream>
#include <iostream>

// This function produces a stack backtrace with demangled function & method names.
std::string backtrace(int skip = 1)
{
    void *callstack[128];
    const int nMaxFrames = sizeof(callstack) / sizeof(callstack[0]);
    char buf[1024];
    int nFrames = backtrace(callstack, nMaxFrames);
    char **symbols = backtrace_symbols(callstack, nFrames);

    std::ostringstream trace_buf;
    for (int i = skip; i < nFrames; i++) {
        Dl_info info;
        if (dladdr(callstack[i], &info)) {
            char *demangled = NULL;
            int status;
            demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
            std::snprintf(
                buf,
                sizeof(buf),
                "%-3d %*p %s + %zd\n",
                i,
                (int)(2 + sizeof(void*) * 2),
                callstack[i],
                status == 0 ? demangled : info.dli_sname,
                (char *)callstack[i] - (char *)info.dli_saddr
            );
            free(demangled);
        } else {
            std::snprintf(buf, sizeof(buf), "%-3d %*p\n",
                i, (int)(2 + sizeof(void*) * 2), callstack[i]);
        }
        trace_buf << buf;
        std::snprintf(buf, sizeof(buf), "%s\n", symbols[i]);
        trace_buf << buf;
    }
    free(symbols);
    if (nFrames == nMaxFrames)
        trace_buf << "[truncated]\n";
    return trace_buf.str();
}

void my_func_2(void) {
    std::cout << backtrace() << std::endl;
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

Compile and run:

g++ -fno-pie -ggdb3 -O0 -no-pie -o glibc_ldl.out -std=c++11 -Wall -Wextra \
  -pedantic-errors -fpic glibc_ldl.cpp -export-dynamic -ldl
./glibc_ldl.out 

output:

1             0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2             0x40139e my_func_1(int) + 16
./glibc_ldl.out(_Z9my_func_1i+0x10) [0x40139e]
3             0x4013b3 main + 18
./glibc_ldl.out(main+0x12) [0x4013b3]
4       0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5             0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]

1             0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2             0x40138b my_func_1(double) + 18
./glibc_ldl.out(_Z9my_func_1d+0x12) [0x40138b]
3             0x4013c8 main + 39
./glibc_ldl.out(main+0x27) [0x4013c8]
4       0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5             0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]

Tested on Ubuntu 18.04.

glibc backtrace with C++ demangling hack 2: parse backtrace output

Shown at: https://panthema.net/2008/0901-stacktrace-demangled/

This is a hack because it requires parsing.

TODO get it to compile and show it here.

GDB scripting

We can also do this with GDB without recompiling by using: How to do an specific action when a certain breakpoint is hit in GDB?

We setup an empty backtrace function for our testing:

gdb/mystacktrace.h

void print_stacktrace(void) {}

and then with:

main.gdb

start
break print_stacktrace
commands
  silent
  backtrace
  printf "\n"
  continue
end
continue

we can run:

gdb -nh -batch -x main.gdb --args gdb.out

Sample output:

Temporary breakpoint 1 at 0x11a7: file main.cpp, line 21.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Temporary breakpoint 1, main (argc=1, argv=0x7fffffffc3e8) at main.cpp:21
warning: Source file is more recent than executable.
21          if (argc > 1) {
Breakpoint 2 at 0x555555555151: file gdb/mystacktrace.h, line 1.
#0  print_stacktrace () at gdb/mystacktrace.h:1
#1  0x0000555555555161 in my_func_2 () at main.cpp:6
#2  0x0000555555555191 in my_func_1 (i=1) at main.cpp:16
#3  0x00005555555551ec in main (argc=1, argv=0x7fffffffc3e8) at main.cpp:27

[Inferior 1 (process 165453) exited normally]

The above can be made more usable with the following Bash function:

gdbbt() (
  tmpfile=$(mktemp /tmp/gdbbt.XXXXXX)
  fn="$1"
  shift
  printf '%s' "
start
break $fn
commands
  silent
  backtrace
  printf \"\n\"
  continue
end
continue
" > "$tmpfile"
  gdb -nh -batch -x "$tmpfile" -args "$@"
  rm -f "$tmpfile"
)

Usage:

gdbbt print_stacktrace gdb.out 2

I don't know how to make commands with -ex without the temporary file: Problems adding a breakpoint with commands from command line with ex command

Tested in Ubuntu 22.04, GDB 12.0.90.

GDB code injection

TODO this is the dream! It might allow for both compiled-liked speeds, but without the need to recompile! Either:

libunwind

TODO does this have any advantage over glibc backtrace? Very similar output, also requires modifying the build command, but not part of glibc so requires an extra package installation.

Code adapted from: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

main.c

/* This must be on top. */
#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>

/* Paste this on the file you want to debug. */
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <stdio.h>
void print_trace() {
    char sym[256];
    unw_context_t context;
    unw_cursor_t cursor;
    unw_getcontext(&context);
    unw_init_local(&cursor, &context);
    while (unw_step(&cursor) > 0) {
        unw_word_t offset, pc;
        unw_get_reg(&cursor, UNW_REG_IP, &pc);
        if (pc == 0) {
            break;
        }
        printf("0x%lx:", pc);
        if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
            printf(" (%s+0x%lx)\n", sym, offset);
        } else {
            printf(" -- error: unable to obtain symbol name for this frame\n");
        }
    }
    puts("");
}

void my_func_3(void) {
    print_trace();
}

void my_func_2(void) {
    my_func_3();
}

void my_func_1(void) {
    my_func_3();
}

int main(void) {
    my_func_1(); /* line 46 */
    my_func_2(); /* line 47 */
    return 0;
}

Compile and run:

sudo apt-get install libunwind-dev
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -std=c99 \
  -Wall -Wextra -pedantic-errors main.c -lunwind

Either #define _XOPEN_SOURCE 700 must be on top, or we must use -std=gnu99:

Run:

./main.out

Output:

0x4007db: (main+0xb)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)

0x4007e2: (main+0x12)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)

and:

addr2line -e main.out 0x4007db 0x4007e2

gives:

/home/ciro/main.c:34
/home/ciro/main.c:49

With -O0:

0x4009cf: (my_func_3+0xe)
0x4009e7: (my_func_1+0x9)
0x4009f3: (main+0x9)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)

0x4009cf: (my_func_3+0xe)
0x4009db: (my_func_2+0x9)
0x4009f8: (main+0xe)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)

and:

addr2line -e main.out 0x4009f3 0x4009f8

gives:

/home/ciro/main.c:47
/home/ciro/main.c:48

Tested on Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.

libunwind with C++ name demangling

Code adapted from: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

unwind.cpp

#define UNW_LOCAL_ONLY
#include <cxxabi.h>
#include <libunwind.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>

void backtrace() {
  unw_cursor_t cursor;
  unw_context_t context;

  // Initialize cursor to current frame for local unwinding.
  unw_getcontext(&context);
  unw_init_local(&cursor, &context);

  // Unwind frames one by one, going up the frame stack.
  while (unw_step(&cursor) > 0) {
    unw_word_t offset, pc;
    unw_get_reg(&cursor, UNW_REG_IP, &pc);
    if (pc == 0) {
      break;
    }
    std::printf("0x%lx:", pc);

    char sym[256];
    if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
      char* nameptr = sym;
      int status;
      char* demangled = abi::__cxa_demangle(sym, nullptr, nullptr, &status);
      if (status == 0) {
        nameptr = demangled;
      }
      std::printf(" (%s+0x%lx)\n", nameptr, offset);
      std::free(demangled);
    } else {
      std::printf(" -- error: unable to obtain symbol name for this frame\n");
    }
  }
}

void my_func_2(void) {
    backtrace();
    std::cout << std::endl; // line 43
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}  // line 54

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

Compile and run:

sudo apt-get install libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o unwind.out -std=c++11 \
  -Wall -Wextra -pedantic-errors unwind.cpp -lunwind -pthread
./unwind.out

Output:

0x400c80: (my_func_2()+0x9)
0x400cb7: (my_func_1(int)+0x10)
0x400ccc: (main+0x12)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)

0x400c80: (my_func_2()+0x9)
0x400ca4: (my_func_1(double)+0x12)
0x400ce1: (main+0x27)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)

and then we can find the lines of my_func_2 and my_func_1(int) with:

addr2line -e unwind.out 0x400c80 0x400cb7

which gives:

/home/ciro/test/unwind.cpp:43
/home/ciro/test/unwind.cpp:54

TODO: why are the lines off by one?

Tested on Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.

Linux kernel

How to print the current thread stack trace inside the Linux kernel?

libdwfl

This was originally mentioned at: https://stackoverflow.com/a/60713161/895245 and it might be the best method, but I have to benchmark a bit more, but please go upvote that answer.

TODO: I tried to minimize the code in that answer, which was working, to a single function, but it is segfaulting, let me know if anyone can find why.

dwfl.cpp: answer reached 30k chars and this was the easiest cut: https://gist.github.com/cirosantilli/f1dd3ee5d324b9d24e40f855723544ac

Compile and run:

sudo apt install libdw-dev libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw -lunwind
./dwfl.out

We also need libunwind as that makes results more correct. If you do without it, it runs, but you will see that some of the lines are a bit wrong.

Output:

0: 0x402b72 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402cda my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d76 my_func_1(int) at /home/ciro/test/dwfl.cpp:111
3: 0x402dd1 main at /home/ciro/test/dwfl.cpp:122
4: 0x7ff227ea0d8f __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
5: 0x7ff227ea0e3f __libc_start_main@@GLIBC_2.34 at ../csu/libc-start.c:392
6: 0x402534 _start at ../csu/libc-start.c:-1

0: 0x402b72 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402cda my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d5f my_func_1(double) at /home/ciro/test/dwfl.cpp:106
3: 0x402de2 main at /home/ciro/test/dwfl.cpp:123
4: 0x7ff227ea0d8f __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
5: 0x7ff227ea0e3f __libc_start_main@@GLIBC_2.34 at ../csu/libc-start.c:392
6: 0x402534 _start at ../csu/libc-start.c:-1

Benchmark run:

g++ -fno-pie -ggdb3 -O3 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
time ./dwfl.out 1000 >/dev/null

Output:

real    0m3.751s
user    0m2.822s
sys     0m0.928s

So we see that this method is 10x faster than Boost's stacktrace, and might therefore be applicable to more use cases.

Tested in Ubuntu 22.04 amd64, libdw-dev 0.186, libunwind 1.3.2.

libbacktrace

https://github.com/ianlancetaylor/libbacktrace

Considering the harcore library author, it is worth trying this out, maybe it is The One. TODO check it out.

A C library that may be linked into a C/C++ program to produce symbolic backtraces

As of October 2020, libbacktrace supports ELF, PE/COFF, Mach-O, and XCOFF executables with DWARF debugging information. In other words, it supports GNU/Linux, *BSD, macOS, Windows, and AIX. The library is written to make it straightforward to add support for other object file and debugging formats.

The library relies on the C++ unwind API defined at https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html This API is provided by GCC and clang.

See also

cpptrace

Cpptrace is simple and portable supporting C++11 and newer. Unlike other solutions it supports all major platforms and compilers and is almost entirely self-contained.

cpptrace::generate_trace().print();

Sample output:

Stack trace (most recent call first):
#0 0x000055fb1305f235 in my_func_2() at /path/to/main.cpp:6
#1 0x000055fb1305f2b5 in my_func_1(int) at /path/to/main.cpp:16
#2 0x000055fb1305f310 in main at /path/to/main.cpp:27
#3 0x00007f648990cd8f in __libc_start_call_main at ./csu/../sysdeps/nptl/libc_start_call_main.h:58
#4 0x00007f648990ce3f in __libc_start_main_impl at ./csu/../csu/libc-start.c:392
#5 0x000055fb1305f144 in _start at ./stacktrace.out

In addition to general stacktrace generation it includes a traced exception object that generates a trace when thrown and allows for generation of raw stack traces that can be resolved later.

More information can be found here.

番薯 2024-10-03 16:13:43

对于仅限 Linux 的解决方案,您可以使用 backtrace( 3) 简单地返回一个 void * 数组(事实上,每个数组都指向相应堆栈帧的返回地址)。要将这些转换为有用的东西,可以使用 backtrace_symbols(3)

注意 backtrace 中的 注释部分( 3)

符号名称可能不可用
无需使用特殊的链接器
选项。
对于使用 GNU 链接器的系统,有必要使用
-r动态链接器
选项。请注意,“静态”函数的名称不会公开,
并且不会是
在回溯中可用。

For a linux-only solution you can use backtrace(3) that simply returns an array of void * (in fact each of these point to the return address from the corresponding stack frame). To translate these to something of use, there's backtrace_symbols(3).

Pay attention to the notes section in backtrace(3):

The symbol names may be unavailable
without the use of special linker
options.
For systems using the GNU linker, it is necessary to use the
-rdynamic linker
option. Note that names of "static" functions are not exposed,
and won't be
available in the backtrace.

伪装你 2024-10-03 16:13:43

在C++23中,会有,然后你可以这样做:

#include <stacktrace>

/* ... */

std::cout << std::stacktrace::current();

更多细节:
  • https://en.cppreference.com/w/cpp/header/堆栈跟踪
  • https://en.cppreference.com/w/cpp/实用程序/basic_stacktrace/operator_ltlt

In C++23, there will be <stacktrace>, and then you can do:

#include <stacktrace>

/* ... */

std::cout << std::stacktrace::current();

Further details:
  • https://en.cppreference.com/w/cpp/header/stacktrace
  • https://en.cppreference.com/w/cpp/utility/basic_stacktrace/operator_ltlt

萌吟 2024-10-03 16:13:43

对旧线程的另一个答案。

当我需要这样做时,我通常只使用 system()pstack

所以像这样:

#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <sstream>
#include <cstdlib>

void f()
{
    pid_t myPid = getpid();
    std::string pstackCommand = "pstack ";
    std::stringstream ss;
    ss << myPid;
    pstackCommand += ss.str();
    system(pstackCommand.c_str());
}

void g()
{
   f();
}


void h()
{
   g();
}

int main()
{
   h();
}

这个输出

#0  0x00002aaaab62d61e in waitpid () from /lib64/libc.so.6
#1  0x00002aaaab5bf609 in do_system () from /lib64/libc.so.6
#2  0x0000000000400c3c in f() ()
#3  0x0000000000400cc5 in g() ()
#4  0x0000000000400cd1 in h() ()
#5  0x0000000000400cdd in main ()

这应该在 Linux、FreeBSD 和 Solaris 上工作(在 FreeBSD 上“bstack”端口比“pstack”工作得更好)。我不认为 macOS 有 pstack 或简单的等效项,但是这个 线程似乎有一个替代方案

如果您使用的是C,那么您将需要使用C字符串函数。

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

void f()
{
    pid_t myPid = getpid();
    /*
      length of command 7 for 'pstack ', 7 for the PID, 1 for nul
    */
    char pstackCommand[7+7+1];
    sprintf(pstackCommand, "pstack %d", (int)myPid);
    system(pstackCommand);
}

我使用 7 作为 PID 中的最大位数,基于 这篇文章

Another answer to an old thread.

When I need to do this, I usually just use system() and pstack

So something like this:

#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <sstream>
#include <cstdlib>

void f()
{
    pid_t myPid = getpid();
    std::string pstackCommand = "pstack ";
    std::stringstream ss;
    ss << myPid;
    pstackCommand += ss.str();
    system(pstackCommand.c_str());
}

void g()
{
   f();
}


void h()
{
   g();
}

int main()
{
   h();
}

This outputs

#0  0x00002aaaab62d61e in waitpid () from /lib64/libc.so.6
#1  0x00002aaaab5bf609 in do_system () from /lib64/libc.so.6
#2  0x0000000000400c3c in f() ()
#3  0x0000000000400cc5 in g() ()
#4  0x0000000000400cd1 in h() ()
#5  0x0000000000400cdd in main ()

This should work on Linux, FreeBSD and Solaris (on FreeBSD the "bstack" port works better than "pstack"). I don't think that macOS has pstack or a simple equivalent, but this thread seems to have an alternative.

If you are using C, then you will need to use C string functions.

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

void f()
{
    pid_t myPid = getpid();
    /*
      length of command 7 for 'pstack ', 7 for the PID, 1 for nul
    */
    char pstackCommand[7+7+1];
    sprintf(pstackCommand, "pstack %d", (int)myPid);
    system(pstackCommand);
}

I've used 7 for the max number of digits in the PID, based on this post.

眼前雾蒙蒙 2024-10-03 16:13:43

Linux 特定,TLDR:

  1. backtrace-lunwind 时,glibc 中的 > 才会生成准确的堆栈跟踪(未记录的特定于平台的功能)。
  2. 要输出函数名称源文件行号,请使用#include (该库仅在其头文件中记录)。 backtrace_symbolsbacktrace_symbolsd_fd 提供的信息最少。

在现代 Linux 上,您可以使用函数 backtrace< 获取堆栈跟踪地址/代码>。使 backtrace 在流行平台上生成更准确地址的未记录方法是链接 -lunwind(Ubuntu 18.04 上的 libunwind-dev)(请参阅下面的示例输出)。 backtrace 使用函数_Unwind_Backtrace,默认情况下后者来自libgcc_s.so.1,并且该实现是最可移植的。当链接 -lunwind 时,它提供了更准确的 _Unwind_Backtrace 版本,但该库的可移植性较差(请参阅 libunwind/src)。

不幸的是,配套的 backtrace_symbolsdbacktrace_symbols_fd 函数大约十年来一直无法将堆栈跟踪地址解析为具有源文件名和行号的函数名称(请参阅示例输出如下)。

但是,还有另一种方法可以将地址解析为符号,它会生成最有用的跟踪,其中包含函数名称源文件行号。方法是 #include 并与 -ldw 链接(Ubuntu 18.04 上为 libdw-dev)。

工作 C++ 示例 (test.cc):

#include <stdexcept>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <string>

#include <boost/core/demangle.hpp>

#include <execinfo.h>
#include <elfutils/libdwfl.h>

struct DebugInfoSession {
    Dwfl_Callbacks callbacks = {};
    char* debuginfo_path = nullptr;
    Dwfl* dwfl = nullptr;

    DebugInfoSession() {
        callbacks.find_elf = dwfl_linux_proc_find_elf;
        callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
        callbacks.debuginfo_path = &debuginfo_path;

        dwfl = dwfl_begin(&callbacks);
        assert(dwfl);

        int r;
        r = dwfl_linux_proc_report(dwfl, getpid());
        assert(!r);
        r = dwfl_report_end(dwfl, nullptr, nullptr);
        assert(!r);
        static_cast<void>(r);
    }

    ~DebugInfoSession() {
        dwfl_end(dwfl);
    }

    DebugInfoSession(DebugInfoSession const&) = delete;
    DebugInfoSession& operator=(DebugInfoSession const&) = delete;
};

struct DebugInfo {
    void* ip;
    std::string function;
    char const* file;
    int line;

    DebugInfo(DebugInfoSession const& dis, void* ip)
        : ip(ip)
        , file()
        , line(-1)
    {
        // Get function name.
        uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
        Dwfl_Module* module = dwfl_addrmodule(dis.dwfl, ip2);
        char const* name = dwfl_module_addrname(module, ip2);
        function = name ? boost::core::demangle(name) : "<unknown>";

        // Get source filename and line number.
        if(Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
            Dwarf_Addr addr;
            file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
        }
    }
};

std::ostream& operator<<(std::ostream& s, DebugInfo const& di) {
    s << di.ip << ' ' << di.function;
    if(di.file)
        s << " at " << di.file << ':' << di.line;
    return s;
}

void terminate_with_stacktrace() {
    void* stack[512];
    int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);

    // Print the exception info, if any.
    if(auto ex = std::current_exception()) {
        try {
            std::rethrow_exception(ex);
        }
        catch(std::exception& e) {
            std::cerr << "Fatal exception " << boost::core::demangle(typeid(e).name()) << ": " << e.what() << ".\n";
        }
        catch(...) {
            std::cerr << "Fatal unknown exception.\n";
        }
    }

    DebugInfoSession dis;
    std::cerr << "Stacktrace of " << stack_size << " frames:\n";
    for(int i = 0; i < stack_size; ++i) {
        std::cerr << i << ": " << DebugInfo(dis, stack[i]) << '\n';
    }
    std::cerr.flush();

    std::_Exit(EXIT_FAILURE);
}

int main() {
    std::set_terminate(terminate_with_stacktrace);
    throw std::runtime_error("test exception");
}

在 Ubuntu 18.04.4 LTS 上使用 gcc-8.3 编译:

g++ -o test.o -c -m{arch,tune}=native -std=gnu++17 -W{all,extra,error} -g -Og -fstack-protector-all test.cc
g++ -o test -g test.o -ldw -lunwind

输出:

Fatal exception std::runtime_error: test exception.
Stacktrace of 7 frames:
0: 0x55f3837c1a8c terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7fbc1c845ae5 <unknown>
2: 0x7fbc1c845b20 std::terminate()
3: 0x7fbc1c845d53 __cxa_throw
4: 0x55f3837c1a43 main at /home/max/src/test/test.cc:103
5: 0x7fbc1c3e3b96 __libc_start_main at ../csu/libc-start.c:310
6: 0x55f3837c17e9 _start

当没有链接 -lunwind 时,它会产生不太准确的堆栈跟踪:

0: 0x5591dd9d1a4d terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7f3c18ad6ae6 <unknown>
2: 0x7f3c18ad6b21 <unknown>
3: 0x7f3c18ad6d54 <unknown>
4: 0x5591dd9d1a04 main at /home/max/src/test/test.cc:103
5: 0x7f3c1845cb97 __libc_start_main at ../csu/libc-start.c:344
6: 0x5591dd9d17aa _start

为了进行比较,同一堆栈跟踪的 backtrace_symbols_fd 输出信息量最少:

/home/max/src/test/debug/gcc/test(+0x192f)[0x5601c5a2092f]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(+0x92ae5)[0x7f95184f5ae5]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(_ZSt9terminatev+0x10)[0x7f95184f5b20]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(__cxa_throw+0x43)[0x7f95184f5d53]
/home/max/src/test/debug/gcc/test(+0x1ae7)[0x5601c5a20ae7]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe6)[0x7f9518093b96]
/home/max/src/test/debug/gcc/test(+0x1849)[0x5601c5a20849]

在生产版本(以及 C 语言版本)中,您可能希望通过替换 boost: 来使此代码更加健壮: core::demanglestd::stringstd::cout 及其底层调用。

您还可以重写 __cxa_throw 来捕获抛出异常时的堆栈跟踪,并在捕获异常时打印它。当它进入catch块时,堆栈已经展开,因此调用backtrace为时已晚,这就是为什么必须在throw上捕获堆栈的原因 由函数 __cxa_throw。请注意,在多线程程序中,__cxa_throw 可以由多个线程同时调用,因此,如果它将堆栈跟踪捕获到全局数组中,则该数组必须是 thread_local

您还可以使堆栈跟踪打印功能异步信号安全< /a>,以便您可以直接从 SIGSEGVSIGBUS 信号处理程序(应使用自己的堆栈以保证稳健性)调用它。使用 libdwfl 从信号处理程序获取函数名称源文件行号可能会失败,因为它不是异步的-信号安全或者进程的地址空间已被严重损坏,但实际上它在 99% 的时间内成功(我还没有看到它失败)。


总而言之,用于自动堆栈跟踪输出的完整生产就绪库应该:

  1. 上的堆栈跟踪捕获到线程特定的存储中。
  2. 自动打印未处理异常的堆栈跟踪。
  3. 以异步信号安全的方式打印堆栈跟踪。
  4. 提供一个强大的信号处理函数,该函数使用自己的堆栈以异步信号安全的方式打印堆栈跟踪。用户可以安装此函数作为 SIGSEGVSIGBUSSIGFPE 等的信号处理程序。
  5. 信号处理程序也可以打印这些值来自 ucontext_t 信号函数参数的故障点的所有 CPU 寄存器(可能不包括向量寄存器),a-la Linux 内核 oops 日志消息。

Linux specific, TLDR:

  1. backtrace in glibc produces accurate stacktraces only when -lunwind is linked (undocumented platform-specific feature).
  2. To output function name, source file and line number use #include <elfutils/libdwfl.h> (this library is documented only in its header file). backtrace_symbols and backtrace_symbolsd_fd are least informative.

On modern Linux your can get the stacktrace addresses using function backtrace. The undocumented way to make backtrace produce more accurate addresses on popular platforms is to link with -lunwind (libunwind-dev on Ubuntu 18.04) (see the example output below). backtrace uses function _Unwind_Backtrace and by default the latter comes from libgcc_s.so.1 and that implementation is most portable. When -lunwind is linked it provides a more accurate version of _Unwind_Backtrace but this library is less portable (see supported architectures in libunwind/src).

Unfortunately, the companion backtrace_symbolsd and backtrace_symbols_fd functions have not been able to resolve the stacktrace addresses to function names with source file name and line number for probably a decade now (see the example output below).

However, there is another method to resolve addresses to symbols and it produces the most useful traces with function name, source file and line number. The method is to #include <elfutils/libdwfl.h>and link with -ldw (libdw-dev on Ubuntu 18.04).

Working C++ example (test.cc):

#include <stdexcept>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <string>

#include <boost/core/demangle.hpp>

#include <execinfo.h>
#include <elfutils/libdwfl.h>

struct DebugInfoSession {
    Dwfl_Callbacks callbacks = {};
    char* debuginfo_path = nullptr;
    Dwfl* dwfl = nullptr;

    DebugInfoSession() {
        callbacks.find_elf = dwfl_linux_proc_find_elf;
        callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
        callbacks.debuginfo_path = &debuginfo_path;

        dwfl = dwfl_begin(&callbacks);
        assert(dwfl);

        int r;
        r = dwfl_linux_proc_report(dwfl, getpid());
        assert(!r);
        r = dwfl_report_end(dwfl, nullptr, nullptr);
        assert(!r);
        static_cast<void>(r);
    }

    ~DebugInfoSession() {
        dwfl_end(dwfl);
    }

    DebugInfoSession(DebugInfoSession const&) = delete;
    DebugInfoSession& operator=(DebugInfoSession const&) = delete;
};

struct DebugInfo {
    void* ip;
    std::string function;
    char const* file;
    int line;

    DebugInfo(DebugInfoSession const& dis, void* ip)
        : ip(ip)
        , file()
        , line(-1)
    {
        // Get function name.
        uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
        Dwfl_Module* module = dwfl_addrmodule(dis.dwfl, ip2);
        char const* name = dwfl_module_addrname(module, ip2);
        function = name ? boost::core::demangle(name) : "<unknown>";

        // Get source filename and line number.
        if(Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
            Dwarf_Addr addr;
            file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
        }
    }
};

std::ostream& operator<<(std::ostream& s, DebugInfo const& di) {
    s << di.ip << ' ' << di.function;
    if(di.file)
        s << " at " << di.file << ':' << di.line;
    return s;
}

void terminate_with_stacktrace() {
    void* stack[512];
    int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);

    // Print the exception info, if any.
    if(auto ex = std::current_exception()) {
        try {
            std::rethrow_exception(ex);
        }
        catch(std::exception& e) {
            std::cerr << "Fatal exception " << boost::core::demangle(typeid(e).name()) << ": " << e.what() << ".\n";
        }
        catch(...) {
            std::cerr << "Fatal unknown exception.\n";
        }
    }

    DebugInfoSession dis;
    std::cerr << "Stacktrace of " << stack_size << " frames:\n";
    for(int i = 0; i < stack_size; ++i) {
        std::cerr << i << ": " << DebugInfo(dis, stack[i]) << '\n';
    }
    std::cerr.flush();

    std::_Exit(EXIT_FAILURE);
}

int main() {
    std::set_terminate(terminate_with_stacktrace);
    throw std::runtime_error("test exception");
}

Compiled on Ubuntu 18.04.4 LTS with gcc-8.3:

g++ -o test.o -c -m{arch,tune}=native -std=gnu++17 -W{all,extra,error} -g -Og -fstack-protector-all test.cc
g++ -o test -g test.o -ldw -lunwind

Outputs:

Fatal exception std::runtime_error: test exception.
Stacktrace of 7 frames:
0: 0x55f3837c1a8c terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7fbc1c845ae5 <unknown>
2: 0x7fbc1c845b20 std::terminate()
3: 0x7fbc1c845d53 __cxa_throw
4: 0x55f3837c1a43 main at /home/max/src/test/test.cc:103
5: 0x7fbc1c3e3b96 __libc_start_main at ../csu/libc-start.c:310
6: 0x55f3837c17e9 _start

When no -lunwind is linked, it produces a less accurate stacktrace:

0: 0x5591dd9d1a4d terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7f3c18ad6ae6 <unknown>
2: 0x7f3c18ad6b21 <unknown>
3: 0x7f3c18ad6d54 <unknown>
4: 0x5591dd9d1a04 main at /home/max/src/test/test.cc:103
5: 0x7f3c1845cb97 __libc_start_main at ../csu/libc-start.c:344
6: 0x5591dd9d17aa _start

For comparison, backtrace_symbols_fd output for the same stacktrace is least informative:

/home/max/src/test/debug/gcc/test(+0x192f)[0x5601c5a2092f]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(+0x92ae5)[0x7f95184f5ae5]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(_ZSt9terminatev+0x10)[0x7f95184f5b20]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(__cxa_throw+0x43)[0x7f95184f5d53]
/home/max/src/test/debug/gcc/test(+0x1ae7)[0x5601c5a20ae7]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe6)[0x7f9518093b96]
/home/max/src/test/debug/gcc/test(+0x1849)[0x5601c5a20849]

In a production version (as well as C language version) you may like to make this code extra robust by replacing boost::core::demangle, std::string and std::cout with their underlying calls.

You can also override __cxa_throw to capture the stacktrace when an exception is thrown and print it when the exception is caught. By the time it enters catch block the stack has been unwound, so it is too late to call backtrace, and this is why the stack must be captured on throw which is implemented by function __cxa_throw. Note that in a multi-threaded program __cxa_throw can be called simultaneously by multiple threads, so that if it captures the stacktrace into a global array that must be thread_local.

You can also make the stack trace printing function async-signal safe, so that you can invoke it directly from your SIGSEGV, SIGBUS signal handlers (which should use their own stacks for robustness). Obtaining function name, source file and line number using libdwfl from a signal handler may fail because it is not async-signal safe or if the address space of the process has been substantially corrupted, but in practice it succeeds 99% of the time (I haven't seen it fail).


To summarize, a complete production-ready library for automatic stacktrace output should:

  1. Capture the stacktrace on throw into thread-specific storage.
  2. Automatically print the stacktrace on unhandled exceptions.
  3. Print the stacktrace in async-signal-safe manner.
  4. Provide a robust signal handler function which uses its own stack that prints the stacktrace in a async-signal-safe manner. The user can install this function as a signal handler for SIGSEGV, SIGBUS, SIGFPE, etc..
  5. The signal handler may as well print the values of all CPU registers at the point of the fault from ucontext_t signal function argument (may be excluding vector registers), a-la Linux kernel oops log messages.
停滞 2024-10-03 16:13:43

有没有办法在每次调用某个函数时转储 C 或 C++ 中正在运行的进程中的调用堆栈?

您可以在特定函数中使用宏函数来代替 return 语句。

例如,

int foo(...)
{
    if (error happened)
        return -1;

    ... do something ...

    return 0
}

您可以使用宏函数,而不是使用 return。

#include "c-callstack.h"

int foo(...)
{
    if (error happened)
        NL_RETURN(-1);

    ... do something ...

    NL_RETURN(0);
}

每当函数发生错误时,您都会看到 Java 风格的调用堆栈,如下所示。

Error(code:-1) at : so_topless_ranking_server (sample.c:23)
Error(code:-1) at : nanolat_database (sample.c:31)
Error(code:-1) at : nanolat_message_queue (sample.c:39)
Error(code:-1) at : main (sample.c:47)

完整的源代码可在此处获取。

c-callstack 位于 https://github.com/Nanolat

Is there any way to dump the call stack in a running process in C or C++ every time a certain function is called?

You can use a macro function instead of return statement in the specific function.

For example, instead of using return,

int foo(...)
{
    if (error happened)
        return -1;

    ... do something ...

    return 0
}

You can use a macro function.

#include "c-callstack.h"

int foo(...)
{
    if (error happened)
        NL_RETURN(-1);

    ... do something ...

    NL_RETURN(0);
}

Whenever an error happens in a function, you will see Java-style call stack as shown below.

Error(code:-1) at : so_topless_ranking_server (sample.c:23)
Error(code:-1) at : nanolat_database (sample.c:31)
Error(code:-1) at : nanolat_message_queue (sample.c:39)
Error(code:-1) at : main (sample.c:47)

Full source code is available here.

c-callstack at https://github.com/Nanolat

陌伤ぢ 2024-10-03 16:13:43

没有标准化的方法可以做到这一点。对于 Windows,该功能在 DbgHelp

There is no standardized way to do that. For windows the functionality is provided in the DbgHelp library

猫七 2024-10-03 16:13:43

您可以使用 Boost 库来打印当前的调用堆栈。

#include <boost/stacktrace.hpp>

// ... somewhere inside the `bar(int)` function that is called recursively:
std::cout << boost::stacktrace::stacktrace();

人在这里: https://www.boost.org/doc/ libs/1_65_1/doc/html/stacktrace.html

You can use the Boost libraries to print the current callstack.

#include <boost/stacktrace.hpp>

// ... somewhere inside the `bar(int)` function that is called recursively:
std::cout << boost::stacktrace::stacktrace();

Man here: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html

尐偏执 2024-10-03 16:13:43

我知道这个线程很旧,但我认为它对其他人可能有用。如果您使用 gcc,则可以使用其仪器功能(-finstrument-functions 选项)来记录任何函数调用(进入和退出)。查看此内容以获取更多信息: http://hacktalks.blogspot .fr/2013/08/gcc-instrument-functions.html

因此,您可以将每个调用推送和弹出到堆栈中,当您想要打印它时,您只需查看堆栈中的内容即可。

我已经测试过它,它工作完美并且非常方便

更新:您还可以在有关仪器选项的 GCC 文档中找到有关 -finstrument-functions 编译选项的信息:​​https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html

I know this thread is old, but I think it can be useful for other people. If you are using gcc, you can use its instrument features (-finstrument-functions option) to log any function call (entry and exit). Have a look at this for more information: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html

You can thus for instance push and pop every calls into a stack, and when you want to print it, you just look at what you have in your stack.

I've tested it, it works perfectly and is very handy

UPDATE: you can also find information about the -finstrument-functions compile option in the GCC doc concerning the Instrumentation options: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html

如果没有你 2024-10-03 16:13:43

您可以自己实现该功能:

使用全局(字符串)堆栈,并在每个函数开始时将函数名称和其他值(例如参数)压入该堆栈;在函数退出时再次弹出它。

编写一个函数,在调用时打印出堆栈内容,并在要查看调用堆栈的函数中使用它。

这听起来可能需要做很多工作,但非常有用。

You can implement the functionality yourself:

Use a global (string)stack and at start of each function push the function name and such other values (eg parameters) onto this stack; at exit of function pop it again.

Write a function that will printout the stack content when it is called, and use this in the function where you want to see the callstack.

This may sound like a lot of work but is quite useful.

初吻给了烟 2024-10-03 16:13:43

当然,下一个问题是:这足够了吗?

堆栈跟踪的主要缺点是,为什么您拥有被调用的精确函数,您没有其他任何东西,例如其参数的值,这对于调试非常有用。

如果您可以访问 gcc 和 gdb,我建议使用 assert 检查特定条件,如果不满足则生成内存转储。当然,这意味着该过程将停止,但您将获得完整的报告,而不仅仅是堆栈跟踪。

如果您希望采用一种不那么唐突的方式,您始终可以使用日志记录。有非常高效的日志记录工具,例如 Pantheios。这再次可以让您更准确地了解正在发生的事情。

Of course the next question is: will this be enough ?

The main disadvantage of stack-traces is that why you have the precise function being called you do not have anything else, like the value of its arguments, which is very useful for debugging.

If you have access to gcc and gdb, I would suggest using assert to check for a specific condition, and produce a memory dump if it is not met. Of course this means the process will stop, but you'll have a full fledged report instead of a mere stack-trace.

If you wish for a less obtrusive way, you can always use logging. There are very efficient logging facilities out there, like Pantheios for example. Which once again could give you a much more accurate image of what is going on.

乞讨 2024-10-03 16:13:43

您可以使用 Poppy 来实现此目的。它通常用于在崩溃期间收集堆栈跟踪,但它也可以为正在运行的程序输出它。

现在这是好的部分:它可以输出堆栈上每个函数的实际参数值,甚至局部变量、循环计数器等。

You can use Poppy for this. It is normally used to gather the stack trace during a crash but it can also output it for a running program as well.

Now here's the good part: it can output the actual parameter values for each function on the stack, and even local variables, loop counters, etc.

时光是把杀猪刀 2024-10-03 16:13:43

您可以使用 GNU 分析器。它还显示了调用图!命令是 gprof,您需要使用某些选项来编译代码。

You can use the GNU profiler. It shows the call-graph as well! the command is gprof and you need to compile your code with some option.

撩心不撩汉 2024-10-03 16:13:43

对于 QT,这是一种在输出中打印内容的选择性方法。

ccallstack.h

`#define CALLSTACK CCallStack c(QDateTime::currentDateTime(),__FILE__,__LINE__,__FUNCTION__,"")
#define CALLSTACK2(comment) CCallStack c(QDateTime::currentDateTime(),__FILE__,__LINE__,__FUNCTION__,comment)
/**************************************************/
class CCallStack
{
    private:
        QString FunctionName;
        static QStringList Stack;
    public:
        CCallStack(QDateTime date,cstr file,int line,cstr functionName,cstr comment);
        CCallStack(cstr functionName);
        ~CCallStack();
        static void print();
};

`

ccallstack.cpp

 #include "ccallstack.h"
QStringList CCallStack::Stack;

CCallStack::CCallStack(QDateTime date,cstr file,int line,cstr functionName,cstr comment)
{
    QString s =
        QString("%1 file://%2:%3\033[35m %4\033[0m \033[34m:%5").arg(date.toString("HH:mm:ss:zzz")).arg(file).arg(
                line).arg(
                functionName).arg(comment);
    FunctionName = s;
    Stack.append(s);
}

CCallStack::CCallStack(cstr functionName):
    FunctionName(functionName)
{
    Stack.append(functionName);
}

CCallStack::~CCallStack()
{
    QString lastName = Stack.last();
    if (lastName!=FunctionName)
    {
        qWarning() << "There is a thread or Event Order conflict.";
        print();
    }
    Stack.removeLast();
}
void CCallStack::print()
{
    int index = 0;
    qDebug().nospace().noquote() << "STACKTRACE OUTPUT Stack Count:" << Stack.count();
    foreach(QString name,Stack)
    {
        qDebug().nospace().noquote() << index << ":" << name;
        index++;
    }
    qDebug().nospace().noquote() << "END OF STACKTRACE OUTPUT";
}

用法:
使用以下方法注册一个函数:

foo (){
   CALLSTACK
   ...
}

或者

CALLSTACK2("this is a function to trace");

然后只需调用:

CException::CException(cstr err):
{
    CCallStack::print();
}

输出:

STACKTRACE OUTPUT Stack Count:6
0:09:18:26:505 file://..\main_app\entities\cdesktopsession.cpp:1034 CDesktopSession::sl_updateTimer_triggerred :
1:09:18:26:505 file://..\main_app\entities\cdesktopentity.cpp:161 CDesktopEntity::processTimeTick :Freeze_Frame_Index_Number
2:09:18:26:505 file://..\main_app\entities\cdesktopfield.cpp:154 CDesktopField::updateEntity :Freeze_Frame_Index_Number
3:09:18:26:505 file://..\main_app\entities\cdesktopa2lsinglevalue.cpp:299 CDesktopA2LSingleValue::getA2LValue :Freeze_Frame_Index_Number
4:09:18:26:505 file://..\main_app\device\ccandevicexcp.cpp:302 CCanDeviceXCP::requestAddressValue_internal :
5:09:18:26:505 file://..\main_app\device\ccandevice.cpp:72 CCanDevice::sendFrame_internal :
END OF STACKTRACE OUTPUT

简单而有用...

With QT, this is a selective approach what to print in output.

ccallstack.h

`#define CALLSTACK CCallStack c(QDateTime::currentDateTime(),__FILE__,__LINE__,__FUNCTION__,"")
#define CALLSTACK2(comment) CCallStack c(QDateTime::currentDateTime(),__FILE__,__LINE__,__FUNCTION__,comment)
/**************************************************/
class CCallStack
{
    private:
        QString FunctionName;
        static QStringList Stack;
    public:
        CCallStack(QDateTime date,cstr file,int line,cstr functionName,cstr comment);
        CCallStack(cstr functionName);
        ~CCallStack();
        static void print();
};

`

ccallstack.cpp

 #include "ccallstack.h"
QStringList CCallStack::Stack;

CCallStack::CCallStack(QDateTime date,cstr file,int line,cstr functionName,cstr comment)
{
    QString s =
        QString("%1 file://%2:%3\033[35m %4\033[0m \033[34m:%5").arg(date.toString("HH:mm:ss:zzz")).arg(file).arg(
                line).arg(
                functionName).arg(comment);
    FunctionName = s;
    Stack.append(s);
}

CCallStack::CCallStack(cstr functionName):
    FunctionName(functionName)
{
    Stack.append(functionName);
}

CCallStack::~CCallStack()
{
    QString lastName = Stack.last();
    if (lastName!=FunctionName)
    {
        qWarning() << "There is a thread or Event Order conflict.";
        print();
    }
    Stack.removeLast();
}
void CCallStack::print()
{
    int index = 0;
    qDebug().nospace().noquote() << "STACKTRACE OUTPUT Stack Count:" << Stack.count();
    foreach(QString name,Stack)
    {
        qDebug().nospace().noquote() << index << ":" << name;
        index++;
    }
    qDebug().nospace().noquote() << "END OF STACKTRACE OUTPUT";
}

usage:
register a function using:

foo (){
   CALLSTACK
   ...
}

or

CALLSTACK2("this is a function to trace");

Then just call like:

CException::CException(cstr err):
{
    CCallStack::print();
}

outputs:

STACKTRACE OUTPUT Stack Count:6
0:09:18:26:505 file://..\main_app\entities\cdesktopsession.cpp:1034 CDesktopSession::sl_updateTimer_triggerred :
1:09:18:26:505 file://..\main_app\entities\cdesktopentity.cpp:161 CDesktopEntity::processTimeTick :Freeze_Frame_Index_Number
2:09:18:26:505 file://..\main_app\entities\cdesktopfield.cpp:154 CDesktopField::updateEntity :Freeze_Frame_Index_Number
3:09:18:26:505 file://..\main_app\entities\cdesktopa2lsinglevalue.cpp:299 CDesktopA2LSingleValue::getA2LValue :Freeze_Frame_Index_Number
4:09:18:26:505 file://..\main_app\device\ccandevicexcp.cpp:302 CCanDeviceXCP::requestAddressValue_internal :
5:09:18:26:505 file://..\main_app\device\ccandevice.cpp:72 CCanDevice::sendFrame_internal :
END OF STACKTRACE OUTPUT

Simple and usefull...

孤千羽 2024-10-03 16:13:43

有没有办法在每次调用某个函数时转储 C 或 C++ 中正在运行的进程中的调用堆栈?

不,不存在,尽管可能存在依赖于平台的解决方案。

Is there any way to dump the call stack in a running process in C or C++ every time a certain function is called?

No there is not, although platform-dependent solutions might exist.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文