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

use*_*840 32 linux memory x86-64 elf abi

文件中.27它说文本段从0x400000开始.为什么选择这个特定的地址?有什么理由吗?相同的地址被选择在GNU ldLinux:

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

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

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

我读了这个问题,讨论为什么为i386选择了0x080xxxxx地址,但它没有解释x86_64的变化.在这个问题上很难找到任何解释.有人有线索吗?

iva*_*eev 31

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


i386ABI 1

  • 堆栈位于代码之前,从下0x8048000向下增长.它提供了"堆栈略高于128 MB,文本和数据约为2 GB"(第3-22页).
  • 动态细分起始于0x80000000(2GiB),
  • 并且内核占据顶部的"保留区域",规范允许达到1GiB,至少从0xC0000000(第3-21页)开始(这是它通常做的).
  • 主程序不需要与位置无关.
  • 捕获空指针访问不需要实现(第3-21页),但可以合理地预期上面的某些堆栈空间128MiB(即288KiB)将被保留用于此目的.

amd64(其ABI被公式化为对其的修正i386(第9页))具有更大的(48位)地址空间,但大多数指令只接受32位立即操作数(包括跳转指令中的直接地址和偏移),需要更多的工作和效率更低的代码(特别是在考虑指令相互依赖性时)来处理更大的值.作者总结了一些解决这些限制的措施,他们建议使用一些"代码模型"来"让编译器生成更好的代码".(第33页)

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

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


以上解释了为什么加载地址被移动到较低的地址.现在,为什么它移动到完全0x400000(4MiB)?在这里,我空洞如此,总结了我在ABI规范中所读到的内容,我只能猜测它感觉"恰到好处":

  • 它足够大,可以捕获任何可能不正确的结构偏移,允许更大的数据单元amd64运行,但小到足以不浪费大量有价值2GiB的地址空间启动.
  • 它等于迄今为止最大的实际页面大小,是人们可以想到的所有其他虚拟内存单元大小的倍数.

1 注意实际的X32 Linux版本已经从这个布局偏离更多的随着时间的推移.但是我们在这里讨论的是ABI规范,因为amd64它正式基于它而不是任何派生的布局(参见其引用的段落).


归档时间:

查看次数:

2259 次

最近记录:

8 年,11 月 前