GCC -fPIC选项

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

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

例如,跳转会产生为相对而非绝对的。

伪汇编:

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编译的,它适合包含在一个库中 – 库必须能够从它在内存中的首选位置重新定位到另一个地址,在你的库优先select的地址可能有另一个已经加载的库。

我会试着用一种更简单的方式来解释已经说过的话。

无论何时加载共享库,加载器(加载你运行程序的OS上的代码)都会根据加载对象的位置来改变代码中的一些地址。

在上面的例子中,非PIC代码中的“111”在加载器第一次被加载时被写入。

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

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

内置于共享库中的代码通常应该是与位置无关的代码,以便共享库可以随时加载(或多或less)在内存中的任何地址。 -fPIC选项确保GCC生成这样的代码。

进一步添加…

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

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

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

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

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

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


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

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

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


名称“与位置无关的代码 ”实际上意味着以下内容:

  • 代码部分不包含需要重定位的绝对地址,而只包含自己的相对地址。 因此,代码段可以加载到任意的内存地址并在多个进程之间共享。

  • 数据部分不在多个进程之间共享,因为它通常包含可写数据。 因此,数据部分可能包含需要重定位的指针或地址。

  • 所有公共function和公共数据都可以在Linux中重写。 如果主可执行文件中的函数与共享对象中的函数具有相同的名称,则main中的版本将优先,不仅在从main调用时,而且在从共享对象中调用时。 同样,当main中的全局variables与共享对象中的全局variables名称相同时,即使从共享对象访问,main中的实例也将被使用。


这种所谓的符号插入旨在模仿静态库的行为。

为了实现这个“覆盖”function,一个共享对象有一个指向其function的指针表,称为过程链接表(PLT)和一个指向其variables的指针表(称为全局偏移表(GOT))。 所有对函数和公共variables的访问都通过这个表格。

ps在无法避免dynamic链接的地方,有各种方法可以避免位置无关代码的耗时特性。

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

已经发布的答案的一个小增加:未被编译为位置独立的目标文件是可重定位的; 它们包含重定位表项。

这些条目允许加载程序(将程序加载到内存中的代码位)重写绝对地址,以调整虚拟地址空间中的实际加载地址。

操作系统将尝试共享加载到内存的“共享对象库”的单个副本,并链接到同一共享对象库的所有程序。

由于代码地址空间(不同于数据空间的部分)不需要是连续的,并且因为大多数链接到特定库的程序具有相当固定的库依赖性树,所以大部分时间成功了。 在这种罕见的情况下,是的,可能需要在内存中拥有两个或多个共享对象库副本。

显然,任何尝试在程序和/或程序实例之间随机化一个库的加载地址(以便减less创build可利用的模式的可能性)将使这种情况变得常见,而不是罕见,因此,在系统启用了这种能力的情况下,应该尽一切努力将所有的共享对象库编译为位置独立的。

由于从主程序主体调用这些库也会被重定位,这使共享库不太可能被复制。