x86_64调用约定和堆栈帧

我试图理解GCC(4.4.3)为在Ubuntu Linux下运行的x86_64机器生成的可执行代码。 特别是,我不明白代码如何跟踪堆栈帧。 在过去,在32位代码中,我习惯于在几乎所有function中看到这个“序幕”:

push %ebp movl %esp, %ebp 

然后,在function结束时,也会出现“结局”

 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的原始地址是理想的 – 但我无法从主要调用的任意函数访问它。 我起初以为我可以使用旧的回溯方法:追踪保存的帧指针值,直到保存的值为0 – 然后,下一个可以算作最高实际值。 (这与获取argv的地址不同,但它会 – 比如说,找出_start或任何_start调用的堆栈指针值[例如__libc_start_main]。)现在,我不知道如何获取64位代码中的等效地址。

谢谢。

我认为不同的是在amd64中更省略省略帧指针。 abi第16页的脚注说

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

我不知道GDB做了什么。 我假设在使用-g编译时,对象具有魔术调试信息,允许GDB重建它所需的内容。 我不认为我在没有调试信息的情况下在64位机器上尝试过GDB。

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

如果argv的地址是你想要的,为什么不在main中保存指向它的指针呢?
试图解开堆栈将是非常不可移植的,即使你让它工作。
即使你确实设法回到堆栈上,第一个函数的帧指针也不是很明显。 堆栈上的第一个函数不返回,但调用系统调用退出,因此从不使用其帧指针。 没有充分理由将其初始化为NULL。

假设我正在连接glibc(我正在做),看起来好像我可以用glibc全局符号__libc_stack_end来解决这个问题:

 extern void * __libc_stack_end; void myfunction(void) { /* ... */ off_t stack_hi = (off_t)__libc_stack_end; /* ... */ } 
    Interesting Posts