2023-08-05 10:00:07

GCC -fPIC选项

我读过GCC的代码生成约定选项,但不明白“生成位置无关代码(PIC)”是做什么的。请举个例子给我解释一下是什么意思。


当前回答

添加更多…

每个进程都有相同的虚拟地址空间(如果在linux操作系统中使用标志停止虚拟地址的随机化) (有关详细信息,仅为自己禁用和重新启用地址空间布局随机化)

因此,如果它是一个没有共享链接的exe(假设场景),那么我们可以始终给相同的asm指令相同的虚拟地址而不会有任何损害。

但是当我们想要将共享对象链接到exe时,我们不确定分配给共享对象的起始地址,因为它将取决于共享对象被链接的顺序。也就是说,.so内部的asm指令总是有不同的虚拟地址,这取决于它所链接的进程。

因此,一个进程可以在自己的虚拟空间中给出起始地址为。So . as 0x45678910,而其他进程同时可以给出起始地址为0x12131415,如果它们不使用相对寻址,. So将根本不起作用。

所以他们总是必须使用相对寻址模式,因此有了fpic选项。

其他回答

位置独立代码意味着生成的机器代码不依赖于位于特定地址才能工作。

例如,跳跃是相对的,而不是绝对的。

Pseudo-assembly:

PIC:无论代码在地址100还是1000,这都有效

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP

非pic:这将只在代码在地址100的情况下工作

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP

编辑:回应评论。

如果你的代码是用- fpic编译的,它适合包含在库中——库必须能够从内存中的首选位置重新定位到另一个地址,在你的库首选的地址可能有另一个已经加载的库。

到动态库中函数的链接在库加载时或运行时解析。因此,当程序运行时,可执行文件和动态库都加载到内存中。 无法确定加载动态库的内存地址 因为固定地址可能与另一个需要相同地址的动态库冲突。


有两种常用的方法来处理这个问题:

1.搬迁。如果需要,代码中的所有指针和地址都会被修改,以适应实际的加载地址。重定位是由链接器和加载器完成的。

2.位置无关代码。代码中的所有地址都相对于当前位置。类unix系统中的共享对象默认使用位置无关代码。如果程序运行很长时间,这比重定位效率低,特别是在32位模式下。


“位置无关代码”的名称实际上意味着:

The code section contains no absolute addresses that need relocation, but only self relative addresses. Therefore, the code section can be loaded at an arbitrary memory address and shared between multiple processes. The data section is not shared between multiple processes because it often contains writeable data. Therefore, the data section may contain pointers or addresses that need relocation. All public functions and public data can be overridden in Linux. If a function in the main executable has the same name as a function in a shared object, then the the version in main will take precedence, not only when called from main, but also when called from the shared object. Likewise, when a global variable in the main has the same name as a global variable in the shared object, then the instance in main will be used, even when accessed from the shared object. This so-called symbol interposition is intended to mimic the behavior of static libraries.


共享对象有一个指向其函数的指针表,称为过程链接表(PLT)和一个表 为了实现这个“覆盖”特性,我们需要使用全局偏移表(GOT)。

所有对函数和公共变量的访问都要经过这些表。

p.s.在无法避免动态链接的情况下,有各种方法可以避免位置无关代码的耗时特性。

你可以从这篇文章中阅读更多:http://www.agner.org/optimize/optimizing_cpp.pdf

我将试着用更简单的方式解释已经说过的话。

无论何时加载共享库,加载器(在操作系统上加载任何你运行的程序的代码)都会根据对象加载到的位置更改代码中的一些地址。

在上面的例子中,非pic代码中的“111”是加载程序在第一次加载时编写的。

对于非共享对象,您可能希望它是这样的,因为编译器可以对该代码进行一些优化。

对于共享对象,如果另一个进程想要“链接”到该代码,它必须将其读取到相同的虚拟地址,否则“111”将没有意义。但是虚拟空间可能已经在第二个过程中被使用了。

构建在共享库中的代码通常应该是与位置无关的代码,这样共享库就可以轻松地(或多或少)加载到内存中的任何地址。-fPIC选项确保GCC生成这样的代码。

添加更多…

每个进程都有相同的虚拟地址空间(如果在linux操作系统中使用标志停止虚拟地址的随机化) (有关详细信息,仅为自己禁用和重新启用地址空间布局随机化)

因此,如果它是一个没有共享链接的exe(假设场景),那么我们可以始终给相同的asm指令相同的虚拟地址而不会有任何损害。

但是当我们想要将共享对象链接到exe时,我们不确定分配给共享对象的起始地址,因为它将取决于共享对象被链接的顺序。也就是说,.so内部的asm指令总是有不同的虚拟地址,这取决于它所链接的进程。

因此,一个进程可以在自己的虚拟空间中给出起始地址为。So . as 0x45678910,而其他进程同时可以给出起始地址为0x12131415,如果它们不使用相对寻址,. So将根本不起作用。

所以他们总是必须使用相对寻址模式,因此有了fpic选项。