我试图理解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的原始地址是理想的 - 但是我无法从主要调用的任意函数访问它。我一开始以为我可以使用旧的回溯方法:追踪保存的帧指针值,直到保存的值为0 - 然后,下一个可以算作最高实际值。 (这与获取argv的地址不同,但它会 - 比如说,找出_start或任何_start调用的堆栈指针值[例如__libc_start_main]。)现在,我不知道如何获得64位代码中的等效地址。
感谢。
答案 0 :(得分:5)
我认为不同之处在于amd64更加鼓励省略帧指针。 abi第16页的脚注说
通过使用,可以避免常规使用%rbp作为堆栈帧的帧指针 %rsp(堆栈指针)索引到堆栈帧。这种技术在序言和结语中保存了两条指令,并提供了一个额外的通用寄存器(%rbp)。
我不知道GDB的作用。我假设在使用-g
编译时,对象具有魔术调试信息,允许GDB重建它所需的内容。我不认为我在没有调试信息的情况下在64位机器上尝试过GDB。
答案 1 :(得分:3)
GDB使用DWARF CFI进行展开。对于使用-g编译的未提取的二进制文件,这将在.debug_info部分中。对于剥离的x86-64二进制文件,.eh_frame部分中有展开信息。这在x86-64 ABI,第3.7节,第56页中定义。自己处理此信息非常困难,因为解析DWARF非常复杂,但我相信libunwind包含对它的支持。
答案 2 :(得分:1)
如果你想要argv的地址,为什么不在main中保存指向它的指针?
试图解开堆栈将是非常不可移植的,即使你让它工作
即使你确实设法回到堆栈上,第一个函数的帧指针也不是很明显。堆栈上的第一个函数不返回,但调用系统调用退出,因此永远不会使用它的帧指针。没有充分理由将其初始化为NULL。
答案 3 :(得分:1)
假设我正在与glibc(我正在做)进行链接,看起来好像我可以用glibc全局符号__libc_stack_end解决这个问题:
extern void * __libc_stack_end;
void myfunction(void) {
/* ... */
off_t stack_hi = (off_t)__libc_stack_end;
/* ... */
}