GCC -fPIC选项


Answers:


526

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

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

伪组装:

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编译的,则适合包含在库中-该库必须能够从其在内存中的首选位置重定位到另一个地址,在您希望的地址处可能已经有另一个已加载的库。


36
这个示例很清楚,但是作为用户,如果我创建不带选项的共享库(.so)文件,会有什么不同?在某些情况下,没有-fPIC,我的库将无效吗?
Narek

16
是的,构建不是PIC的共享库可能会出错。
John Zwinck

92
更具体地说,共享库应该在进程之间共享,但是可能无法始终在两个库中的相同地址处加载该库。如果代码不是位置无关的,则每个过程将需要自己的副本。
西蒙·里希特

19
@Narek:如果一个进程要在同一虚拟地址上加载多个共享库,则会发生错误。由于库无法预测可以加载哪些其他库,因此传统的共享库概念无法避免此问题。虚拟地址空间在这里无济于事。
菲利普

6
您可以-fPIC在编译程序或静态库时省略,因为一个进程中将只存在一个主程序,因此不需要运行时重定位。在某些系统上,程序仍保持位置独立,以增强安全性。
西蒙·里希特

61

我将尝试以一种更简单的方式来解释所讲的内容。

每当加载共享库时,加载程序(OS上加载您运行的任何程序的代码)都会根据对象的加载位置更改代码中的某些地址。

在上面的示例中,非PIC代码中的“ 111”由加载程序在首次加载时写入。

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

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


Whenever a shared lib is loaded, the loader changes some addresses in the code depending on where the object was loaded to.我认为这是不正确的,如果使用-fpic进行编译以及存在-fpic的原因(即出于性能原因)或因为您无法重定位的加载程序,或者因为您需要在不同位置放置多个副本或出于其他原因而导致。
抢劫

为什么不总是使用-fpic?
周杰伦

1
@Jay-因为将需要为每个函数调用再进行一次计算(函数地址)。因此,从性能角度考虑,如果不需要,最好不要使用它。
Roee Gavirel

45

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


为什么不打开-fPIC标志就不将共享库加载到内存中的任何地址?它没有链接到程序吗?程序运行时,操作系统将其上载到内存。我想念什么吗?
Tony Tannous,

1
是否使用该-fPIC标志以确保可以将该lib加载到链接它的进程中的任何虚拟地址?对不起,您有5分钟的重复评论无法编辑上一个。
Tony Tannous

1
在构建共享库(创建libwotnot.so)和与其链接(-lwotnot)之间进行区分。链接时,您无需大惊小怪-fPIC。过去的情况是,在构建共享库时,您需要确保已将-fPIC所有要构建到共享库中的目标文件使用。这些天,规则可能已更改,因为默认情况下,编译器使用PIC代码进行编译。因此,我相信20年前至关重要的事情,而7年前可能已经重要的事情,如今已不那么重要了。o / s内核之外的地址是“总是”虚拟地址。
乔纳森·莱夫勒

因此,以前您必须添加-fPIC。在不传递此标志的情况下,构建.so时生成的代码需要加载到可能正在使用的特定虚拟地址吗?
Tony Tannous

1
是的,因为如果您不使用PIC标志,则无法可靠地重定位代码。如果代码不是PIC(或者至少很难实现以至于实际上是不可能的),则不可能实现ASLR(地址空间布局随机化)之类的事情。
乔纳森·莱夫勒

21

进一步添加...

每个进程都具有相同的虚拟地址空间(如果通过在Linux OS中使用标志来停止虚拟地址的随机化)(有关更多详细信息,请仅对本人禁用和重新启用地址空间布局随机化

因此,如果它的一个exe没有共享链接(假设情况),那么我们总是可以将相同的虚拟地址赋予相同的asm指令,而不会造成任何损害。

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

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

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


1
感谢您对虚拟地址的解释。
Hot.PxL 2014年

2
谁能解释这对于静态库来说不是问题,为什么您不必在静态库上使用-fPIC?我知道链接是在编译时完成的(或者实际上是在编译之后),但是如果您有2个带有位置相关代码的静态库,将如何链接它们?
Michael P

3
@MichaelP对象文件具有一个位置依赖的标签表,当链接特定的obj文件时,所有标签也会相应地更新。无法对共享库执行此操作。
Slava

16

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


有两种常用的方法来解决此问题:

1.搬迁 如有必要,可以修改代码中的所有指针和地址,以适合实际的加载地址。重定位由链接器和加载器完成。

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


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

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

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

  • 在Linux中可以覆盖所有公共功能和公共数据。如果主可执行文件中的函数与共享库中的函数具有相同的名称,则main中的版本将优先,不仅是从main调用时,还是从共享库调用时。同样,当main中的全局变量与共享库中的全局变量具有相同的名称时,即使从共享库访问,main中的实例也将被使用。


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

共享对象有一个指向其功能的指针表,称为过程链接表(PLT),并有一个指向其变量的指针表,称为全局偏移量表(GOT),以实现此“替代”功能。对函数和公共变量的所有访问都通过此表。

ps在无法避免动态链接的地方,有多种方法可以避免位置无关代码的耗时功能。

您可以从本文中了解更多信息:http : //www.agner.org/optimize/optimizing_cpp.pdf


9

除了已经发布的答案外,还有一个小小的补充:可重定位的,未编译为与位置无关的目标文件;它们包含重定位表条目。

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

操作系统将尝试与链接到同一共享库的所有程序共享共享的“共享库”的单个副本。

由于代码地址空间(与数据空间的各个部分不同)不必是连续的,并且由于链接到特定库的大多数程序都具有相当固定的库依赖关系树,因此大多数情况下这都是成功的。是的,在极少数情况下有差异的情况下,是的,可能需要在内存中有两个或更多共享对象库的副本。

显然,任何试图在程序和/或程序实例之间随机分配库的加载地址(以减少创建可利用模式的可能性)的尝试都会使这种情况司空见惯,因此在系统启用了此功能的情况下,应该尽一切努力将所有共享库编译为位置无关的。

由于从主程序的主体对这些库的调用也将变得可重定位,因此,减少了必须复制共享库的可能性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.