首页 文章

GCC -fPIC选项

提问于
浏览
347

我读过GCC's Options for Code Generation Conventions,但无法理解"Generate position-independent code (PIC)"的作用 . 请举个例子来解释一下它的含义 .

6 回答

  • 17

    对已经发布的答案的一个小补充:未编译为位置无关的目标文件是可重定位的;它们包含重定位表条目 .

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

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

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

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

    由于从主程序主体调用这些库也将可以重定位,因此不太可能需要复制共享库 .

  • 5

    位置无关代码意味着生成的机器代码不依赖于位于特定地址以便工作 .

    例如 . 跳跃将生成为相对而非绝对 .

    伪汇编:

    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编译的,那么它适合包含在库中 - 库必须能够从其在内存中的首选位置重新定位到另一个地址,在您的库更喜欢的地址可能有另一个已加载的库 .

  • 9

    进一步添加......

    每个进程都有相同的虚拟地址空间(如果通过在linux OS中使用标志来停止虚拟地址的随机化)(更多详细信息Disable and re-enable address space layout randomization only for myself

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

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

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

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

  • 33

    我将尝试以更简单的方式解释已经说过的内容 .

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

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

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

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

  • 409

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


    有两种常用的方法可以解决这个问题:

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

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


    名称“ position-independent code ”实际上意味着以下内容:

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

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

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


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

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

    附:在无法避免动态链接的情况下,有多种方法可以避免与位置无关的代码的耗时特征 .

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

  • 47

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

相关问题