为什么 Linux/gnu 链接器选择地址 0x400000?

2024-10-28 08:37:00
admin
原创
51
摘要:问题描述:我正在 Linux x86_64 上试验 ELF 可执行文件和 gnu 工具链:我已经(手动)链接并剥离了一个“Hello World”测试: .global _start .text _start: mov $1, %rax ....

问题描述:

我正在 Linux x86_64 上试验 ELF 可执行文件和 gnu 工具链:

我已经(手动)链接并剥离了一个“Hello World”测试:

        .global _start
        .text
_start:
        mov     $1, %rax
        ...

变成 267 字节的 ELF64 可执行文件......

0000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
0000010: 0200 3e00 0100 0000 d400 4000 0000 0000  ..>.......@.....
0000020: 4000 0000 0000 0000 0000 0000 0000 0000  @...............
0000030: 0000 0000 4000 3800 0100 4000 0000 0000  ....@.8...@.....
0000040: 0100 0000 0500 0000 0000 0000 0000 0000  ................
0000050: 0000 4000 0000 0000 0000 4000 0000 0000  ..@.......@.....
0000060: 0b01 0000 0000 0000 0b01 0000 0000 0000  ................
0000070: 0000 2000 0000 0000 0000 0000 0000 0000  .. .............
0000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000b0: 0400 0000 1400 0000 0300 0000 474e 5500  ............GNU.
00000c0: c3b0 cbbd 0abf a73c 26ef e960 fc64 4026  .......<&..`.d@&
00000d0: e242 8bc7 48c7 c001 0000 0048 c7c7 0100  .B..H......H....
00000e0: 0000 48c7 c6fe 0040 0048 c7c2 0d00 0000  ..H....@.H......
00000f0: 0f05 48c7 c03c 0000 0048 31ff 0f05 4865  ..H..<...H1...He
0000100: 6c6c 6f2c 2057 6f72 6c64 0a              llo, World.

它有一个程序头(LOAD)并且没有部分:

There are 1 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000000010b 0x000000000000010b  R E    200000

这似乎在地址 0x400000 处加载了整个文件(文件偏移量 0 到 0x10b - elf 头和所有内容)。

入口点是:

 Entry point address:               0x4000d4

它对应于文件中的 0xd4 偏移量,我们可以看到该地址是机器代码的起始地址(mov $1, %rax1

我的问题是为什么(如何)gnu 链接器选择0x400000文件映射到的地址?


解决方案 1:

起始地址通常由链接脚本设置。

例如,在 GNU/Linux 上,/usr/lib/ldscripts/elf_x86_64.x我们可以看到:

...
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); \n    . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;

该值是该平台上0x400000该功能的默认值。SEGMENT_START()

您可以通过浏览链接器手册了解有关链接器脚本的更多信息:

% info ld Scripts

解决方案 2:

ld的默认链接器脚本已0x400000为非 PIE 可执行文件嵌入该值。

PIE(位置无关的可执行文件)没有默认基址;它们总是由内核重新定位,内核的默认值0x0000555...加上一些 ASLR 偏移量,除非为此进程或整个系统禁用 ASLR。 ld对此没有控制权。请注意,大多数现代系统将 GCC 配置-fPIE -pie为默认使用,因此它会传递-pie给,并将 C 转换为与位置无关的 asm。如果您以这种方式链接,ld手写的 asm必须遵循相同的规则。

但是什么使得0x400000(4 MiB) 成为一个好的默认值呢?

默认情况下,它必须高于mmap_min_addr= 65536 = 64K。

而且远离 0 会提供足够的空间来防止使用偏移量读取.text.data/.bss内存(array[i]其中array为 NULL)的 NULL 解除引用。即使不增加mmap_min_addr(这为不破坏可执行文件留出了空间),通常会mmap随机选择高地址,因此在实践中我们至少有 4MiB 的 NULL 解除引用保护。

2M 对齐很好

这将它放在页表的下一级页目录的开头,这意味着相同数量的 4K 页表条目将被拆分到更少的 2M 页目录条目中,从而节省内核页表内存并帮助更好地进行页面遍历硬件缓存。对于大型静态数组,靠近下一级 1G 子树的开头也是不错的选择。

我不知道为什么是 4MiB 而不是 2MiB,也不知道开发人员的理由是什么。4MiB 是没有 PAE 的 32 位大页面大小(4 字节 PTE,因此每级 10 位而不是 9 位),但 CPU 必须使用 x86-64 页表才能处于 64 位模式。

较低的起始地址允许近 2 GiB 的静态数组

(不使用更大的代码模型,其中至少大型数组必须以有时效率较低的方式来寻址。有关代码模型的详细信息,请参阅x86-64 System V ABI 文档中的第 3.5.1 节“架构约束”。)

非 PIE 可执行文件的默认代码模型(“小”)允许程序假设任何静态地址都位于虚拟地址空间的低 2GiB 中。因此,/ 中的任何绝对地址都可以.text在机器代码中用作 32 位符号扩展立即数,这样效率更高。.rodata`.data`.bss

(在 PIE 或共享库中情况并非如此:请参阅 x86-64 Linux 中不再允许使用 32 位绝对地址?以了解您/编译器在 x86-64 asm 中无法执行的操作,特别是addss xmm0, [foo + rdi*4]需要 RIP 相对 LEA 将数组起始地址放入寄存器中。x86-64 唯一的 RIP 相对寻址模式是 [RIP+rel32],没有任何通用寄存器。)

在虚拟地址空间底部附近启动可执行文件的节/段,几乎整个 2GiB 都可用于 text+data+bss 这么大。 (可能可以使用更高的默认值,并让大型可执行文件让 ld 选择较低的地址以使其适合,但这将是一个更复杂的链接器脚本。)

这包括 .bss 中的零初始化数组,这些数组不会使可执行文件变得很大,只会使进程映像在内存中变得很大。在实践中,Fortran 程序员遇到这种情况的次数比 C 和 C++ 程序员多,因为静态数组在那里很流行。例如,gfortran for dummies:mcmodel=medium 到底有什么作用?很好地解释了默认模型的构建错误small,以及由此产生的 x86-64 asm 差异medium(其中超过特定​​大小阈值的对象不被认为在代码的低 2G 或 +-2G 范围内。但代码和较小的静态数据仍然如此,因此速度损失很小。)

例如,static float arr[1UL<<28];一个 1 GiB 的数组。如果你有 3 个这样的数组,它们不可能全部从低 2 GiB开始(这可能是手写 asm 所需的全部内容),更不用说让每个元素都可以访问了。

gcc -fno-pie期望能够编译float *p = &arr[size-1];mov $arr+1073741820, %edi,即 5 字节mov $imm32。如果目标地址距离生成地址的代码超过 2GiB(或使用 从中加载),则 RIP-relative 也将不起作用movss arr+1073741820(%rip), %xmm0;即使在非 PIE 中,当没有运行时变量索引时, RIP-relative 也是加载/存储静态数据的正常方式。这就是为什么小 PIC 模型对文本+数据+bss(加上段之间的间隙)也有 2GiB 的大小限制:所有静态数据和代码都需要在可能想要到达它的任何其他数据的 2GiB 范围内。

如果您的代码仅通过运行时变量索引访问高位元素或其地址,则只需将每个数组的开头(即符号本身)置于低位 2 GiB 中。我忘记了链接器是否强制将 bss 结尾置于低位 2GiB 中;它可能会强制这样做,因为链接器脚本会将某个 CRT 启动代码可能会引用的符号放在那里。


脚注 1:对于小于 2GiB 的代码模型,没有任何有用的较小尺寸。x86-64 机器代码使用 8 位或 32 位作为立即数和寻址模式。8 位(256 字节)太小而无法使用,并且许多重要的指令(例如call rel32mov r32, imm32[rip+rel32]寻址)仅适用于 4 字节而不是 1 字节常量。

限制为低 2 GiB(而不是 4)意味着地址可以安全地进行零扩展(如mov edi, OFFSET arr)或符号扩展(如 ) 。请记住,地址不是寻址模式mov eax, [arr + rdi*4]的唯一用例;通常是有意义的,因此 x86-64 机器代码将 disp8 和 disp32 符号扩展为 64 位,而不是零扩展,这是很好的。[reg + disp32]`[rbp - 256]`

隐式零扩展至 64 位发生在写入 32 位寄存器时,就像使用mov-immediate 将地址放入寄存器一样,其中 32 位操作数大小是比 64 位操作数大小更小的机器代码指令。请参阅如何将函数或标签的地址加载到寄存器中(其中也涵盖了 RIP 相对 LEA)。


与 32 位 Windows 相关

Raymond Chen 写了一篇文章,解释了为什么32 位 Windows0x400000默认使用相同的基址。

他提到,默认情况下,DLL 会加载到高地址,而低地址则远非如此。x86-64 SysV 共享对象可以在地址空间间隙足够大的任何地方加载,内核默认加载到用户空间虚拟地址空间的顶部附近,即规范范围的顶部。但 ELF 共享对象需要完全可重定位,因此可以在任何地方正常工作。

32 位 Windows 选择 4MiB 也是出于避免使用低 64K(NULL 取消引用)以及选择旧式 32 位页表的页目录的开头的动机。(其中“大页”大小为 4M,而不是 x86-64 或 PAE 的 2M。)由于 Win95 和 Win3.1 旧式内存映射的一系列原因,至少 1MiB 或 4MiB 是部分必要的,以及解决 CPU 错误之类的问题。

解决方案 3:

任务虚拟地址空间的页面零保持未映射状态,以便可以通过导致 SIGSEGV 的页面错误异常捕获空指针引用。4 MB 适合“大页面”粒度(而不是“普通页面”粒度 4 KB) - 因此在 4 MB 页面粒度的设置上,0x000000 到 0x3FFFFF 地址范围未映射,从而使 0x400000 成为任务虚拟地址空间中的第一个有效地址。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用