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

2024-10-28 08:37:00
admin
原创
195
摘要:问题描述:我正在 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 成为任务虚拟地址空间中的第一个有效地址。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用