为什么在x86_64 ABI中选择地址0x400000作为文本段的开始?

在本文件第页。它说文本段从
0x400000。为什么选择这个地址?有吗
原因是什么?在Linux上的GNU-ld中选择相同的地址:

$ld-verbose | grep-i文本段
提供(uuu可执行文件u start=段u start(“文本段”,0x400000));=段_开始(“文本段”,0x400000)+SIZEOF_头;

令人惊讶的是,在32位x86可执行文件中,这个地址比这个地址大:

$ld-verbose | grep-i文本段
提供(uuu可执行文件_start=段_start(“文本段”,0x08048000));=段_开始(“文本段”,0x08048000)+SIZEOF_头;

我阅读了这个问题,其中讨论了为什么选择0x0800xxxxx地址
对于i386,但它不能解释x86_64中的更改。很难找到
关于那件事的任何解释。有人有线索吗

answer=“39692117”的数据

一句话:amd64在使用大地址方面的一些技术限制建议将较低的2GiB地址空间专用于代码和数据,以提高效率。因此,堆栈已被重新定位到此范围之外。


i386ABI1

  • 堆栈位于代码之前,从刚好低于0x8048000向下扩展。它提供了略高于128 MB的
    对于堆栈,大约2GB用于文本和数据“
    (第3-22页)
  • 动态段开始于0x8000000(2GiB)
  • 内核占据了顶层的“保留区域”,规范允许该区域最多为1GiB,至少从0xc000000(第3-21页)开始(这是它通常做的)
  • 主程序不要求与位置无关
  • 捕获空指针访问(第3-21页)不需要实现,但可以合理地预期,128MiB(即288KiB)上方的一些堆栈空间将为此目的保留

amd64(其ABI是对i386one(第9页)的修订)具有更大(48位)的地址空间,但大多数指令只接受32位立即操作数(包括跳转指令中的直接地址和偏移量),需要更多的工作和效率更低的代码(特别是在考虑指令的相互依赖性时)处理较大的值。作者总结了克服这些限制的措施,介绍了一些他们建议使用的“代码模型”,以“允许编译器生成更好的代码”。(第33页)

  • 具体而言,第一个“小代码模型”建议使用地址“范围从0到231-224-1或从0x000000000x7efffff允许一些非常有效的相对引用和数组迭代。这是1.98GiB,对于许多程序来说已经足够了
  • “中间代码模型”基于上一个模型,将数据拆分为上述边界下的“快速”部分和需要特殊指令才能访问的“较慢”部分。而代码保留在边界下
  • 只有“大”模型对大小不做任何假设,要求编译器“使用movabs指令,就像在介质中一样
    代码模型,甚至用于处理文本部分内的地址。此外,当分支到其
    与当前指令指针的偏移量未知。”
    他们继续建议将代码库拆分为多个共享库,因为这些度量不适用于偏移量已知在范围内的相对引用(如“小位置独立代码模型”中所述)

因此,堆栈被移动到共享库空间下(0x800000000128GiB),因为它的地址从来都不是直接操作数,总是间接引用或从另一个引用使用lea/mov),因此只应用相对偏移限制


上面解释了为什么加载地址被移动到较低的地址。现在,为什么它被精确地移动到0x4000004MiB)?在这里,我是空的,所以总结一下我在ABI规范中读到的内容,我只能猜测它感觉“恰到好处”:

  • 它足够大,可以捕获任何可能不正确的结构偏移,允许使用较大的数据单元amd64操作,但足够小,不会浪费大量宝贵的起始2GiB地址空间
  • 它相当于迄今为止最大的实际页面大小,是人们可以想到的所有其他虚拟内存单元大小的倍数

1请注意,随着时间的推移,实际的x32 Linux已经越来越偏离此布局。但我们在这里讨论的是ABI规范,因为amd64正式基于它,而不是任何派生布局(参见其引用段落)。

发表评论