【发布时间】:2019-05-18 22:32:26
【问题描述】:
我很难理解在使用虚拟内存时对 PIC 可执行文件的需求。根据我收集到的信息,每个程序都在页表中分配了一个条目,因此有一种错觉,即它拥有整个内存可供使用,而分页机制负责可能的重定位、页面错误等。所以,如果任何程序有这种错觉拥有所有可能的内存地址,为什么要使用 PIC?
【问题讨论】:
标签: x86 operating-system x86-64 virtual-memory position-independent-code
我很难理解在使用虚拟内存时对 PIC 可执行文件的需求。根据我收集到的信息,每个程序都在页表中分配了一个条目,因此有一种错觉,即它拥有整个内存可供使用,而分页机制负责可能的重定位、页面错误等。所以,如果任何程序有这种错觉拥有所有可能的内存地址,为什么要使用 PIC?
【问题讨论】:
标签: x86 operating-system x86-64 virtual-memory position-independent-code
我们需要它,直到最后一两年,所有 Linux 可执行文件都是位置相关的(不是 PIC)。见32-bit absolute addresses no longer allowed in x86-64 Linux?。
您仍然可以使用gcc -fno-pie -no-pie 构建非 PIE 可执行文件,并且静态 ELF 可执行文件始终是非 PIE,其加载地址在链接时选择。通常默认将文本段的开头放在401000。
与位置无关的 ELF 可执行文件最初是一个 hack:一个带有入口点的 ELF 共享对象。但如今,它已被广泛使用,并且大多数 Linux 发行版都默认使用 gcc。加载地址可以在运行时随机化。
另请注意,许多操作系统在将可执行文件或库加载到其首选地址以外的位置时支持运行时修复。
例如,Linux 上的 ELF 共享对象可以包含 64 位绝对地址的重定位,因此您可以拥有传统的跳转表(代码指针数组)或静态初始化的指针数组(指向数据或函数)在使用 gcc -fPIC 编译的代码中,用于 x86 和 x86-64。
注意gcc -fPIC 也支持符号插入,所以函数不能直接访问全局变量;他们必须从 GOT 加载地址,除非符号具有“隐藏”的 ELF 可见性。 (或者当然,如果您将其设为 static 而不是全局)。
见https://www.macieira.org/blog/2012/01/sorry-state-of-dynamic-libraries-on-linux/
(该博客中提出的一些想法已经实现,例如GCC支持-fno-plt。)
-fpie 与位置无关的实际成本非常小。但在保证与位置相关的可执行文件加载到虚拟地址空间的低 32 位(例如 Linux)的操作系统上仍然非零,因此您可以利用 5 字节 mov r32, imm32 的 32 位绝对地址而不是7 字节 RIP 相对 LEA 将静态地址放入寄存器,或 [array + reg] 索引静态数组,其地址在 disp32 位移中作为寻址模式的一部分..
【讨论】:
两个主要原因:
共享库。不能保证库在特定地址加载——即使在 64 位系统上,也无法保证每个库都具有唯一的加载地址,不会与任何其他库或动态内存冲突分配。因此,共享库中的代码被编译为 PIC,以便可以将其加载到所需的任何地址。
安全。将特定代码存在于内存中的可预测位置是一种安全风险,因为它启用了exploits which jump to short code "gadgets" in memory,可以将它们串在一起以执行任意操作。 Relocating code randomly at application startup 有助于抵御这些攻击。
【讨论】: