C - x86_64调用约定和堆栈帧



我试图弄明白GCC(4.4.3)为运行在Ubuntu Linux下的x86_64机器生成的可执行代码。特别是,我不明白代码是如何跟踪堆栈帧的。在过去,在32位代码中,我习惯在几乎每个函数中看到这个"序言":

push %ebp
movl %esp, %ebp

然后,在函数的末尾,会有一个"尾声",或者

sub $xx, %esp   # Where xx is a number based on GCC's accounting.
pop %ebp
ret

leave
ret

完成同样的事情:

  • 设置堆栈指针到当前帧的顶部,就在返回地址
  • 恢复旧的帧指针值

在64位代码中,正如我通过objdump反汇编看到的那样,许多函数不遵循此约定——它们不推入%rbp,然后保存%rsp到%rbp,像GDB这样的调试器如何构建反向跟踪?

我在这里的真正目标是试图找出一个合理的地址,当执行到程序中任意函数的开始时,可能堆栈指针已经向下移动,可以考虑作为用户堆栈的顶部(最高地址)。例如,对于"top",argv的原始地址将是理想的——但是我无法从main调用的任意函数访问它。我最初认为我可以使用旧的回溯方法:追踪保存的帧指针值,直到保存的值为0——然后,在此之后的下一个值可以算作最高的实用值。(这与获取argv的地址不一样,但它会——比如说,找出_start或_start调用的堆栈指针的值[例如,__libc_start_main]。)现在,我不知道如何在64位代码中获得等效的地址。

谢谢。

我认为区别在于在amd64中更鼓励省略帧指针。abi第16页的一个脚注说

使用%rbp作为堆栈帧的帧指针可以避免使用%rsp(堆栈指针)索引到堆栈帧。这种技术节省了序言和尾声中的两条指令,并使一个额外的通用寄存器(%rbp)可用。

我不知道GDB是做什么的。我假设在使用-g编译时,对象具有允许GDB重构其所需的神奇调试信息。我想我没有在64位机器上尝试过GDB,没有调试信息。

GDB使用DWARF CFI进行unwind。对于使用-g编译的未剥离二进制文件,它将位于.debug_info节中。对于剥离的x86-64二进制文件,在.eh_frame部分中有unwind信息。这是在第56页的x86-64 ABI第3.7节中定义的。自己处理这些信息是相当困难的,因为解析DWARF非常复杂,但我相信libwind包含对它的支持。

如果argv的地址是你想要的,为什么不在main中保存一个指向它的指针呢?
试图展开堆栈将是高度不可移植的,即使你让它工作。
即使您设法返回堆栈,也不明显第一个函数的帧指针将为NULL。堆栈上的第一个函数不返回,而是调用系统调用退出,因此它的帧指针永远不会被使用。没有理由将它初始化为NULL

假设我正在与glibc链接(我正在这样做),看起来好像我可以用glibc全局符号__libc_stack_end:

解决这个问题。
extern void * __libc_stack_end;
void myfunction(void) {
  /* ... */
  off_t stack_hi = (off_t)__libc_stack_end;
  /* ... */
}

最新更新