为什么select地址0x400000作为x86_64 ABI文本段的开始?

在这个文件中。 它说文本段从0x400000开始。 为什么select这个特定的地址? 这有什么理由吗? 在Linux GNU ldselect了相同的地址:

 $ ld -verbose | grep -i text-segment PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS; 

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

 $ ld -verbose | grep -i text-segment PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x08048000)); . = SEGMENT_START("text-segment", 0x08048000) + SIZEOF_HEADERS; 

我读到这个问题 ,讨论为什么0x080xxxxx地址被选为i386,但它不解释在x86_64中的变化。 这件事很难find任何解释。 有人有线索吗?

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


i386 ABI 1

  • 堆栈位于代码之前,从0x8048000以下0x8048000增长。 它提供了“堆栈大约128 MB,文本和数据大约2 GB” (p。3-22)。
  • 动态段从0x80000000 (2GiB)开始,
  • 内核占用顶部的“保留区域”,规范允许高达1GiB ,至少从0xC0000000 (第3-21页)( 这就是通常的做法 )。
  • 主程序不要求位置独立。
  • 一个实现不需要捕获空指针访问( 128MiB ),但是期望128MiB288KiB )以上的某些栈空间将被保留用于这个目的是288KiB

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

  • 具体来说,第一个“小代码模型”建议使用地址“范围从0到2 31 -2 24 -1或从0x000000000x7effffff ,这允许一些非常有效的相对引用和数组迭代。 这是1.98GiB ,这对许多程序来说已经足够了。
  • “中等代码模型”是基于前一个模型, 数据分成上述边界下的“快速”部分和需要特殊指令访问的“更慢”剩余部分。 虽然代码仍然在边界之下。
  • 只有“大”的模型没有对大小做任何假设,要求编译器“使用movabs指令,就像在中等代码模型中一样,即使是处理文本部分的内部地址也是如此,另外,在分支到地址时需要间接分支其当前指令指针的偏移量是未知的。“ 他们继续建议将代码库分割为多个共享库,因为这些措施不适用于已知在边界内的偏移的相对引用(如“小型位置独立代码模型”中所述)。

因此,堆栈被移动到共享库空间( 0x80000000000 )下,因为它的地址永远不会是立即操作数,总是被间接地引用,或者被来自另一个引用的lea / mov引用,因此只会应用相对偏移限制。


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

  • 它足够大,可以捕获任何可能不正确的结构偏移量,从而允许amd64运行的更大的数据单元,但又足够小,不会浪费许多有价值的起始2GiB地址空间。
  • 这相当于迄今为止最大的实际页面大小,是所能想到的所有其他虚拟存储单元大小的倍数。

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