【问题标题】:Finding the return address of the caller of the current function查找当前函数调用者的返回地址
【发布时间】:2021-01-17 00:42:47
【问题描述】:

GNU return address documentation 声明 __builtin_return_address(1) 产生当前函数调用者的返回地址。

有人可以详细说明这个描述的含义吗?在做了一些测试后,我意识到它似乎并没有按照我的预期做(所以我可能没有正确理解它)。

例如,我制作了以下非常简单的测试代码,以进一步了解此功能(以及其他功能)的工作原理:

#include <stdio.h> 
#include <stdint.h>
void foo(uint64_t x, uint64_t y){
  void *ptr  = __builtin_extract_return_addr(__builtin_return_address(0));
  void *ptr2 = __builtin_extract_return_addr(__builtin_return_address(1));

printf("ret_addr(0)=%p\nret_addr(1)=%p\n", ptr, ptr2);
  return;
}

int main(int argc, char **argv)
{
  foo(1,1);
  foo(1,1);
}

反汇编代码(供参考)

0000000000400536 <main>:
  400536:       55                      push   %rbp
             ------- skipped -------
  40054f:       e8 93 ff ff ff          callq  4004e7 <foo>
  400554:       be 01 00 00 00          mov    $0x1,%esi
             ------- skipped -------
  40055e:       e8 84 ff ff ff          callq  4004e7 <foo>
  400563:       b8 00 00 00 00          mov    $0x0,%eax
             ------- skipped -------

执行此代码后,会输出以下内容:

ret_addr(0)=0x400554
ret_addr(1)=0x7f609c67cb97

ret_addr(0)=0x400563
ret_addr(1)=0x7f609c67cb97

因此,我有点困惑,想请教一下。

我可以看到 __builtin_return_address(0) [ret_addr(0)] 工作正常,因为它返回正确的返回地址值 0x4005540x400563

但是,对于__bulitin_return_address(1) [ret_addr(1)],返回值不应该是0x40054f0x40055e吗?因为这两个地址是反汇编代码中显示的callq 4004e7 &lt;foo&gt;指令(这是我从描述中理解的)。

相反,我得到了一些 0x7f609c67cb97 的垃圾值,这两个 foo 函数的值相同,即使对于垃圾值,我也希望两者不同。

所以总结一下,__builtin_return_address(1) 函数的目的是什么?是否应该返回当前函数调用者的确切地址? (而不是简单地找到返回地址)。如果没有,是否有可能找到这样的地址? (我觉得这可能有点太难了)

我相信我的问题与此类似:Getting the caller's Return Address

【问题讨论】:

  • 此函数仅在所有涉及的堆栈帧都使用帧指针设置时才能正常工作(即,如果您的代码使用-fno-omit-frame-pointer 编译)。如果不能保证,则需要使用 libunwind 之类的工具来评估程序元数据以查找堆栈帧。除此之外,您还可以使用 glibc 中的 backtrace 函数。
  • 参数是嵌套级别。 (1) 为您提供调用者的调用者,即在调用您的 main 的 C 库函数中,因此两次调用都是相同的。手册说:“值为 0 产生当前函数的返回地址,值为 1 产生当前函数调用者的返回地址,依此类推。”.
  • @fuz 感谢您对 libunwind 和 backtrace 的建议,我会看看它们。
  • @Jester 啊,我明白我在哪里误解了。非常感谢您的澄清,我在我的 foo 函数中添加了另一个 bar 的调用函数(现在在 foo 中调用 bar),我能够理解它的含义。

标签: c assembly gcc x86


【解决方案1】:

__builtin_return_address(N) 将返回地址返回给第 N 个调用者。在您的情况下,__builtin_return_address(1) 将返回foo 的调用者的调用者地址,即main 的调用者,即Glibc 启动代码。

N = 0 案例表示直接调用者并且始终有效,正如您在示例中看到的那样。其他值 (N &gt; 0) 通常依赖于存在的帧指针,这些指针仅在使用 -fno-omit-frame-pointer 标志编译时可用。当帧指针不可用时,为__builtin_return_address(N) 生成的代码将为非零N's 返回垃圾甚至崩溃。

【讨论】:

  • 通常会依赖帧指针 - 你确定吗?我预计(但尚未检查)它将使用任何标准的堆栈展开机制;例如.eh_frame 现代 GNU/Linux 上的元数据,与 GDB 用于回溯相同,而 libc backtrace 使用:How does glibc backtrace() determine which stack memory are the return addresses?。请注意,几年来,GCC 现在启用 -fomit-frame-pointer 作为 -O2 的一部分,即使在 32 位代码中也是如此,因此“x86”通常不会有帧指针。
  • @PeterCordes “我认为你留下了一个未完成的句子” - 谢谢,那是一个未完成的重构......
  • @PeterCordes “你确定吗?” - 是的!这实际上是__builtin_return_address 在实践中如此无用的原因。这确实与gdbbacktrace(3) 的工作方式不同(它们在.eh_frame 中使用展开信息,或者在gdb 的情况下使用.debug_frame)。
  • @PeterCordes "几年来,GCC 现在启用了 -fomit-frame-pointer" - 嗯,感谢您的更新。那么__builtin_return_address 更多地取决于-fno-omit-frame-pointerCFLAGS 中的存在,而不是特定的平台。我已经更新了答案。
  • 哦,你是对的,这太糟糕了。它启用了一个帧指针用于该函数,然后假设调用者做了同样的事情。 godbolt.org/z/vK6jbK 显示 gcc -O1 -m32 启用 -fomit-frame-pointer,并显示 __builtin_return_address(1) 的结果 - 碰巧在 32 位构建中不会崩溃,但会打印保存的 EBP 指向的虚假垃圾。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-24
  • 1970-01-01
  • 1970-01-01
  • 2012-03-03
  • 2019-09-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多