c++ GCC -fPIC选项

p8ekf7hl  于 2023-04-01  发布在  其他
关注(0)|答案(6)|浏览(312)

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

n1bvdmb6

n1bvdmb61#

位置无关代码意味着生成的机器代码不依赖于位于特定地址才能工作。
例如,跳跃将被生成为相对的而不是绝对的。
伪组件:
PIC:无论代码位于地址100还是1000,这都可以工作

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

Non-PIC:只有当代码位于地址100时才有效

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

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

aij0ehis

aij0ehis2#

我将试着用一种简单的方式来解释已经说过的话。
每当一个共享库被加载时,加载器(加载你运行的任何程序的操作系统上的代码)根据对象被加载到的位置改变代码中的一些地址。
在上面的示例中,非PIC代码中的“111”是在第一次加载时由加载程序写入的。
对于非共享对象,您可能希望它是这样的,因为编译器可以对该代码进行一些优化。
对于共享对象,如果另一个进程想要“链接”到该代码,它必须将其读取到相同的虚拟地址,否则“111”将没有意义。

bvk5enib

bvk5enib3#

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

hujrc8aj

hujrc8aj4#

动态库中函数的链接在加载库时或运行时被解析。因此,当程序运行时,可执行文件和动态库都被加载到内存中。加载动态库的内存地址不能预先确定,因为固定地址可能与需要相同地址的另一个动态库冲突。
有两种常用的方法来处理这个问题:
1.重定位。如果需要,代码中的所有指针和地址都会被修改,以适应实际的加载地址。重定位由链接器和加载器完成。
2.位置无关代码。代码中的所有地址都是相对于当前位置的。类Unix系统中的共享对象默认使用位置无关代码。如果程序长时间运行,特别是在32位模式下,这比重定位效率低。
位置无关代码”这个名称实际上意味着:

  • 代码段不包含需要重定位的绝对地址,而只包含自身相对地址。因此,代码段可以在任意内存地址加载并在多个进程之间共享。
  • 由于数据段通常包含可写数据,因此数据段不会在多个进程之间共享。因此,数据段可能包含需要重新定位的指针或地址。
  • 在Linux中,所有公共函数和公共数据都可以被重写。如果main可执行文件中的函数与共享对象中的函数同名,则main中的版本将优先,不仅从main调用时如此,而且从共享对象调用时也是如此。同样,当main中的全局变量与共享对象中的全局变量同名时,则main中的示例将被使用,即使是从共享对象访问时。这种所谓的符号插入旨在模仿静态库的行为。

共享对象有一个指向其函数的指针表,称为过程链接表(PLT),以及一个指向其变量的指针表,称为全局偏移表(GOT),以便实现这种“覆盖”功能。
所有对函数和公共变量的访问都要经过这些表。
p.s.在无法避免动态链接的情况下,有各种方法可以避免位置无关代码的耗时特性。

参考和确认

[1]在C中优化软件Windows、Linux和Mac平台的优化指南Agner Fog。丹麦技术大学。http://www.agner.org/optimize/optimizing_cpp.pdf
答案是高度基于[1]的14.11,14.12节的材料。有这么好的开发良好的一系列有用的建议来开发高质量的C
软件是非常好的。

ibps3vxo

ibps3vxo5#

添加更多…
每个进程都有相同的虚拟地址空间(如果在linux操作系统中使用标志停止虚拟地址的随机化)(更多详情Disable and re-enable address space layout randomization only for myself
因此,如果它是一个没有共享链接的exe(假设情况),那么我们总是可以给予相同的虚拟地址相同的asm指令没有任何伤害。
但是当我们想要将共享对象链接到exe时,我们不能确定分配给共享对象的起始地址,因为它取决于共享对象链接的顺序。也就是说,asm指令中的.so将始终具有不同的虚拟地址,这取决于它链接到的进程。
因此,一个进程可以在其自己的虚拟空间中给予起始地址设为0x45678910,而其他进程可以同时将起始地址设为0x12131415,如果它们不使用相对寻址,.so将根本无法工作。
因此,他们总是必须使用相对寻址模式,因此fpic选项。

42fyovps

42fyovps6#

对已经发布的答案的一个小补充:未编译为位置无关的目标文件是可重定位的;它们包含重定位表条目。
这些条目允许加载器(将程序加载到内存中的代码位)重写绝对地址,以调整虚拟地址空间中的实际加载地址。
一个操作系统将试图与链接到同一共享对象库的所有程序共享加载到内存中的“共享对象库”的单个副本。
由于代码地址空间(不像数据空间的部分)不需要是连续的,并且因为大多数链接到特定库的程序都有一个相当固定的库依赖树,这在大多数情况下都是成功的。在那些存在差异的罕见情况下,是的,可能需要在内存中有两个或多个共享对象库的副本。
显然,在程序和/或程序示例之间随机化库的加载地址的任何尝试(以便减少创建可利用模式的可能性)将使这种情况常见,而不是罕见,因此在系统启用此能力的情况下,应该尽一切努力将所有共享对象库编译为位置独立的。
由于从主程序主体对这些库的调用也是可重定位的,这使得必须复制共享库的可能性大大降低。

相关问题