C 为什么帧指针保存在主函数的开头
假设此C代码:C 为什么帧指针保存在主函数的开头,c,assembly,x86-64,stack-frame,C,Assembly,X86 64,Stack Frame,假设此C代码: int main(){ return 0; } 在装配中的外观如下所示: main: pushq %rbp movq %rsp, %rbp movl $0, %eax popq %rbp ret 我知道帧指针fp需要通过pushq%rbp保存在函数的开头,因为它需要在返回调用函数时恢复 我的问题是为什么要在main中这样做?main的父调用方是什么?fp不是指向一个虚拟地址吗,这意味着当main终止地址时,对
int main(){
return 0;
}
在装配中的外观如下所示:
main:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
popq %rbp
ret
我知道帧指针fp
需要通过pushq%rbp
保存在函数的开头,因为它需要在返回调用函数时恢复
我的问题是为什么要在main
中这样做?main
的父调用方是什么?fp
不是指向一个虚拟地址吗,这意味着当main
终止地址时,对下一个程序不再意味着什么,对吗
fp
(甚至是sp)值是否在不同程序及其地址空间之间保持不变
main的父调用方是什么
在linux中,
main
是由\uu libc\u start\u main
调用的,在windows中,我不太确定,但也有一个\u start
事实上,一个巧妙的技巧是在不使用main
的情况下启动C程序:
#include <stdio.h>
#include <stdlib.h>
void _start()
{
printf("No main function!\n");
exit(0);
}
适用于Windows(10,gcc 8.1.0)和Ubuntu(18.04,gcc 9.2.0)
适用于MacOS(10.14.6,Xcode 11.3)
这里有一篇文章谈到另一个函数调用了
main
,因此它需要返回到另一个函数。那么在加载新程序时,如何设置sp/fp值?它的旧值是重复使用还是加载程序重置了它们?@Nic这是实现定义的行为,取决于您使用的操作系统和二进制格式。例如,在现代Linux系统上,rbp
开始时为零,而rsp
指向堆栈上的第一个空闲地址,在该地址上可以找到一些有用的数据(参数、环境、ELF aux向量)。每次启动程序时,该地址都是随机的。@fuz:在x86-64 System V ABI中,进程启动状态的RSP指向堆栈上的argc
,而不是它下面的可用空间。但是是的,所有这些都记录在ABI中,并且argv[]和envp[]都在上面。如果使用“-O2”进行编译,您将有一个main
主体,其中包含如下内容:xorl%eax,%eax;ret
,这样就消除了堆栈帧。事实上,现代调试器甚至不需要帧指针寄存器,如果启用,通常可以使用调试信息执行调试。请注意,提供自己的\u start
可能会导致libc无法正确初始化。除非您确切知道自己在做什么,否则不要这样做。@fuz,是的,您是正确的,这只是一个小示例,说明main()
不是程序启动的开始。这篇文章说明了你的观点。这并没有回答这个问题。@BrettHale,有几个问题,其中一个是“main的父调用程序是什么?”因此它确实回答了这个问题,至少部分回答了这个问题,这一点已被接受。@anastaciu:“为什么框架指针保存在main函数的开头?”-你不回答这个。此外,当问题或标记中没有提到Linux时,您会提到Linux特定的运行时。OP可以自由地接受它,我也可以自由地批评它。
gcc main.c -nostartfiles
clang -Wl,-e,-Wl,__start main.c