分页和 PIC 可执行文件

Paging and PIC executables

我很难理解在使用虚拟内存时需要 PIC executable。根据我的收集,每个程序都在页面 table 中分配了一个条目,因此有一种错觉,即它拥有整个内存供其使用,而分页机制负责处理可能的重定位、页面错误等 on.So如果任何程序都有拥有所有可能内存地址的错觉,为什么要使用 PIC?

两大原因:

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

  2. 安全。将特定代码出现在内存中的可预测位置是一种安全风险,因为它启用 exploits which jump to short code "gadgets" in memory 可以串在一起执行任意操作。 随机重定位代码于 应用程序启动 有助于抵御这些攻击。

我们不需要它,直到最近一两年,所有Linux可执行文件都是位置-依赖(不是 PIC)。参见 32-bit absolute addresses no longer allowed in x86-64 Linux?

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

Position-independent ELF 可执行文件开始时是一个 hack:一个带有入口点的 ELF 共享对象。但是如今,它被广泛使用并且 gcc 在大多数 Linux 发行版中都是默认设置。加载地址可以在运行时随机化。


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

例如 Linux 上的 ELF 共享对象可以包含 64 位绝对地址的重定位,因此您 可以 具有传统的跳转表(代码指针数组)或statically-initialized 代码中的指针数组(指向数据或函数)使用 gcc -fPIC 为 x86 和 x86-64 编译。


注意 gcc -fPIC 也启用了对 symbol-interposition 的支持,所以函数不能直接访问全局变量;他们必须从 GOT 加载地址,除非该符号具有 "hidden" ELF 可见性。 (当然,如果您将其设为 static 而不是全局)。

https://www.macieira.org/blog/2012/01/sorry-state-of-dynamic-libraries-on-linux/

(该博客中提出的一些想法已经实现,例如GCC支持-fno-plt。)

仅 position-independence 和 -fpie 的实际成本非常小。但是仍然 non-zero 在保证 position-dependent 可执行文件加载到虚拟地址 space 的低 32 位(例如 Linux)的操作系统上,因此您可以利用 32- 5 字节 mov r32, imm32 而不是 7 字节 RIP-relative LEA 的位绝对地址,用于将静态地址放入寄存器,或 [array + reg] 索引静态数组,其地址在 disp32 位移作为寻址模式的一部分..