为什么ELF执行入口点虚拟地址为0x80xxxxx而不是0x0?

时间:2010-02-02 20:33:58

标签: point elf virtual-address-space

执行时,程序将从虚拟地址0x80482c0开始运行。此地址不指向我们的main()过程,而是指向由链接器创建的名为_start的过程。

到目前为止,我的谷歌研究只是让我得到了一些(含糊的)历史猜测:

  

有民间传说,0x08048000曾经是由加利福尼亚州圣克鲁斯市的一个团体颁布的* NIX到i386的端口上的STACK_TOP(也就是说,堆栈从接近0x08048000向下增长到0)。这是因为128MB的RAM很昂贵,4GB的RAM是不可想象的。

任何人都可以确认/否认这个吗?

2 个答案:

答案 0 :(得分:33)

正如Mads指出的那样,为了通过空指针捕获大多数访问,类Unix系统倾向于使地址为零的页面“未映射”。因此,访问会立即触发CPU异常,换句话说就是段错误。这比让应用程序变得流氓要好得多。但是,异常向量表可以位于任何地址,至少在x86处理器上(有一个特殊的寄存器,加载了lidt操作码)。

起点地址是描述内存布局方式的一组约定的一部分。链接器在生成可执行二进制文件时必须知道这些约定,因此它们不太可能更改。基本上,对于Linux来说,内存布局约定是从90年代早期的Linux的第一个版本继承而来的。流程必须能够访问多个区域:

  • 代码必须在包含起点的范围内。
  • 必须有一个堆栈。
  • 必须有一个堆,其限制随着brk()sbrk()系统调用而增加。
  • mmap()系统调用必须有一些空间,包括共享库加载。

如今,malloc()所在的堆由mmap()调用支持,这些调用在内核认为合适的任何地址获取内存块。但是在较老的时代,Linux就像以前的类Unix系统一样,它的堆需要一个不间断的大块区域,这可能会增加地址。因此,无论是什么约定,它都必须将代码和堆栈填充到低地址,并在给定点之后将每个地址空间块分配给堆。

但是还有堆栈,它通常很小但在某些情况下可能会非常显着。堆栈增长,当堆栈已满时,我们真的希望流程可以预测崩溃而不是覆盖一些数据。因此,堆栈必须有一个广泛的区域,在该区域的低端,是一个未映射的页面。瞧!在地址零处有一个未映射的页面,用于捕获空指针解引用。因此,定义了堆栈将获得第一个128 MB的地址空间,第一页除外。这意味着代码必须在128 MB之后,地址类似于0x080xxxxx。

迈克尔指出,“丢失”128 MB的地址空间并不是什么大问题,因为地址空间非常大,可以实际使用。那时,Linux内核将单个进程的地址空间限制为1 GB,硬件允许的最大值为4 GB,这并不是一个大问题。

答案 1 :(得分:6)

为什么不从地址0x0开始?至少有两个原因:

  • 因为地址零被称为NULL指针,并且被编程语言用于理智的检查指针。如果您要在那里执行代码,则不能使用地址值。
  • 地址0处的实际内容通常(但不总是)是异常向量表,因此在非特权模式下无法访问。请参阅特定体系结构的文档。

关于入口点_start vs main: 如果链接C运行时(C标准库),则库包装名为main的函数,因此它可以在调用main之前初始化环境。在Linux上,这些是应用程序的 argc argv 参数, env 变量,可能还有一些同步原语和锁。它还确保从主要传递返回状态代码,并调用_exit函数,这将终止进程。