内联函数出现在 std::stacktrace 中,但 main 没有出现

Jan*_*tke 6 c++ gcc stack-trace c++23

我有以下代码:

#include <stacktrace>
#include <iostream>

void bar() {
    for (const auto &entry : std::stacktrace::current()) {
        std::cout << entry << '\n';
    }
}

void foo() {
    bar();
}

int main() {
    foo();
}
Run Code Online (Sandbox Code Playgroud)

在调试版本中,这会打印出您所期望的内容:

bar() at /app/example.cpp:5
foo() at /app/example.cpp:11
main at /app/example.cpp:15
     at :0
_start at :0
Run Code Online (Sandbox Code Playgroud)

然而,我并不完全确定这个无名at :0条目代表什么。真正令人困惑的一点是当我们启用优化时-O2

bar() at /app/example.cpp:5
foo() at /app/example.cpp:11
     at :0
_start at :0
Run Code Online (Sandbox Code Playgroud)

考虑到汇编输出,我不明白这个输出是如何可能的:

main:
  sub rsp, 8
  call bar()
  xor eax, eax
  add rsp, 8
  ret
Run Code Online (Sandbox Code Playgroud)

请参阅编译器资源管理器中的实时示例

问题

  1. 如何bar()知道堆栈跟踪foo() -> bar()是否foo()内联并且在调用链中完全跳过?
  2. 如果bar()以某种方式知道其调用者(甚至是内联调用者),那么main在启用优化时如何从堆栈跟踪中消失?
  3. at :0那个没有名字的奇怪条目是什么?有没有办法轻松过滤掉它(无需字符串操作)?

Rom*_*sky 3

问题1

在某些情况下可以追踪内联函数。x86-64 ABI 指定可以省略 RBP 寄存器(帧基指针):

通过使用 %rsp(堆栈指针)索引堆栈帧,可以避免传统使用 %rbp 作为堆栈帧的帧指针。该技术在序言和尾声中保存了两条指令,并提供了一个额外的通用寄存器 (%rbp)。

调试器和 glibc 的backtrace调用通过 RSP 的值和 RIP 使用称为 CFI 的特殊调试 DWARF 部分来推断函数。对于每一行代码,它都有如何将当前状态“展开”到函数调用开始的规则。然后,另一条调试信息出现,称为DW_TAG_subprogram。每个显式标记为内联或隐式内联的函数都有一个抽象实例和每个内联扩展的具体实例。更多详细信息请参见DWARF 调试信息格式版本 5的第 3.3.8 节

并不是bar()知道谁调用了它,而是 glibc 通过使用 RSP 和 RIP 虚拟展开堆栈,然后使用内联相关的 DWARF 标签来推断出这一点。

问题2

您不应期望始终获得理想的输出。C++ 库和 glibc 尽了最大努力,但这就是他们所做的一切。这是标准提案中堆栈跟踪的定义:

堆栈跟踪是调用序列的近似 (由我标记)表示,由堆栈跟踪条目组成。堆栈跟踪条目表示堆栈跟踪中的评估。

问题3

at: 0是 x86_64 的 ABI 中最外层帧的标记。_start用汇编语言编写的函数明确地做到了这一点。这是 glibc 的start.S的摘录:

ENTRY (_start)
    /* Clearing frame pointer is insufficient, use CFI.  */
    cfi_undefined (rip)
    /* Clear the frame pointer.  The ABI suggests this be done, to mark
       the outermost frame obviously.  */
    xorl %ebp, %ebp
Run Code Online (Sandbox Code Playgroud)

这当然是 ABI 特定的。

清除此问题的方法是字符串操作,或停止末尾的两个堆栈跟踪条目。通常你对两者都不感兴趣_start()

边注

std::stacktrace截至目前,支持非常有限。GCC14 有一些支持,GCC13 无法显示堆栈跟踪,GCC12 显示堆栈跟踪的一小部分。除了 GCC 之外,Linux 上不支持std::stacktrace。C++23 标准本身仍然是一个草案 - 通常编译器需要数年时间才能采用更新的标准。