3

当使用虚拟内存时,我很难理解对 PIC 可执行文件的需求。根据我收集到的信息,每个程序都在页表中分配了一个条目,因此有一种错觉,即它拥有整个内存可供使用,而分页机制负责可能的重定位、页面错误等。所以,如果任何程序有这种错觉拥有所有可能的内存地址,为什么要使用 PIC?

4

2 回答 2

3

两个主要原因:

  1. 共享库。不能保证库在特定地址加载——即使在 64 位系统上,也无法保证每个库都具有唯一的加载地址,不会与任何其他库或动态内存冲突分配。因此,共享库中的代码被编译为 PIC,以便可以将其加载到所需的任何地址。

  2. 安全。将特定代码存在于内存中可预测的位置是一种安全风险,因为它允许利用跳转到内存中的短代码“小工具”,这些小工具可以串在一起执行任意操作。在应用程序启动时随机重定位代码有助于抵御这些攻击。

于 2019-05-18T22:40:32.353 回答
3

我们不需要它,直到最后一两年,所有 Linux 可执行文件都是位置相关的(不是 PIC)。看到x86-64 Linux 中不再允许使用 32 位绝对地址了吗?.

您仍然可以使用 构建非 PIE 可执行文件gcc -fno-pie -no-pie,并且静态 ELF 可执行文件始终是非 PIE,其加载地址是在链接时选择的。通常默认将文本段的开头放在401000.

与位置无关的 ELF 可执行文件最初是一个 hack:一个带有入口点的 ELF 共享对象。但如今,这已被广泛使用,并且是gcc大多数 Linux 发行版的默认设置。加载地址可以在运行时随机化。


另请注意,许多操作系统在将可执行文件或库加载到其首选地址以外的位置时支持运行时修复。

例如,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)的操作系统上仍然非零,因此您可以利用 32 位绝对地址来获取 5 字节mov r32, imm32而不是 7 字节RIP-relative LEA 将静态地址放入寄存器,或[array + reg]使用其地址disp32作为寻址模式的一部分的位移来索引静态数组。

于 2019-05-19T03:24:07.187 回答