C x86_64调用约定和堆栈帧
我试图理解GCC(4.4.3)为运行在Ubuntu Linux下的x86_64机器生成的可执行代码。特别是,我不明白代码是如何跟踪堆栈帧的。在过去,在32位代码中,我习惯于在几乎每个函数中看到这个“开场白”:C x86_64调用约定和堆栈帧,c,stack,x86-64,calling-convention,backtrace,C,Stack,X86 64,Calling Convention,Backtrace,我试图理解GCC(4.4.3)为运行在Ubuntu Linux下的x86_64机器生成的可执行代码。特别是,我不明白代码是如何跟踪堆栈帧的。在过去,在32位代码中,我习惯于在几乎每个函数中看到这个“开场白”: push %ebp movl %esp, %ebp 然后,在功能结束时,也会出现“尾声” 或者干脆 leave ret 它完成了同样的事情: 将堆栈指针设置为当前帧的顶部,就在 回信地址 恢复旧的帧指针值 在64位代码中,正如我通过objdump反汇编所看到的,许多函数不遵循此约定
push %ebp
movl %esp, %ebp
然后,在功能结束时,也会出现“尾声”
或者干脆
leave
ret
它完成了同样的事情:
- 将堆栈指针设置为当前帧的顶部,就在 回信地址
- 恢复旧的帧指针值
谢谢。我认为区别在于amd64更鼓励省略帧指针。报告第16页的脚注中写道: 使用%rbp作为堆栈帧的帧指针可以避免传统的使用 %rsp(堆栈指针),用于索引到堆栈帧中。此技术在序言和尾声中保存了两条指令,并使一个额外的通用寄存器(%rbp)可用
我不知道GDB做什么。我假设当使用
-g
编译时,对象具有神奇的调试信息,允许GDB重构它所需要的内容。我想我没有在没有调试信息的64位机器上尝试过GDB。如果argv的地址是您想要的,为什么不在main中保存指向它的指针呢?即使您让堆栈正常工作,尝试展开堆栈也是非常不可移植的。
即使您设法返回堆栈,第一个函数的帧指针是否为空也不是很明显。堆栈上的第一个函数不返回,而是调用系统调用退出,因此它的帧指针永远不会被使用。没有充分的理由将其初始化为NULL。假设我正在与glibc链接(我正在这样做),似乎我可以使用glibc全局符号\uuuu libc\u stack\u end解决此问题:
extern void * __libc_stack_end;
void myfunction(void) {
/* ... */
off_t stack_hi = (off_t)__libc_stack_end;
/* ... */
}
GDB使用DWARF CFI进行退绕。对于使用-g编译的非压缩二进制文件,这将出现在.debug_info部分。对于精简的x86-64二进制文件,.eh_frame部分中有展开信息。这在第56页第3.7节中有定义。自己处理这些信息相当困难,因为解析DWARF非常复杂,但我相信它包含对它的支持。Hm确实如此。而且不仅仅是使用
-fomit帧指针
。您是否尝试过-fno省略帧指针?你能用这个标志编译其他代码吗?libunwind的源代码可能很有用。谢谢所有这三条注释。我认为这里的问题是,我的库实际上是GCC libgomp的一个修改版本,所以我使用Gnu构建系统构建它,并尽可能避免更改默认值。我相信GCC在默认情况下使用-O2编译,我很确定它包含-fomit帧指针。发帖后,但在我看到Firoze的评论之前,我确实看了glibc的debug/backtrace.c的代码,这让我开始寻找“libc\u stack\u end”,这就是我如何找到一个稍微合理且通用的解决方案的原因。sub$xx,%esp
是序言的一部分。它在堆栈上保留空间。尾声添加$xx,%esp返回堆栈指针,指向需要弹出的对象。(或者在简单的情况下,您可以在不调整ESP的情况下使用它。)我使用x86-64的经验表明,调试器使用额外的信息来了解堆栈帧大小,这保存了指令,但使调试和展开变得困难。是的,正如我所怀疑的。当编译可执行文件时没有调试信息,这一切都会停止吗?谢谢。ABI中的建议确实说明了发生了什么——但它仍然让我想知道如何解决我的问题。粗略地说,我需要从调用图中main之后的任意函数中获取当执行进入main时堆栈指针的值。该值可以高于main堆栈顶部框架的实际值,只要它位于进程堆栈内,但越靠近main堆栈顶部框架越好。谢谢。唉,不行,我无法在main中保存指针。我正在编写一个用户级库来链接任意代码,因此我不能接触原始代码(除了添加一个#include)——或者如果可能的话,我宁愿避免这样做。至于你的第二点,我的印象是,像Linux这样的内核确实遵循这样的惯例,即在将控制权传递给用户进程之前,将帧指针设置为NULL,这正是出于这个目的。但也许这只是一个古老的惯例,并非所有系统都遵循它。我很确定它总是在.eh_frame
部分,这就是为什么它在剥离后仍然存在的原因。按照您描述的方式,strip
必须找到该信息
extern void * __libc_stack_end;
void myfunction(void) {
/* ... */
off_t stack_hi = (off_t)__libc_stack_end;
/* ... */
}