位置无关代码的区别:x86与x86-64

27

我最近在构建一个针对x86-64架构的特定共享库(ELF),如下所示:

g++ -o binary.so -shared --no-undefined ... -lfoo -lbar

以下是该问题的错误信息:

重定位R_X86_64_32至“一个本地符号”时无法用于制作共享对象;请使用 -fPIC 重新编译

这意味着我需要将它重建为位置无关代码,以便可以链接到共享库中。

但是,使用完全相同的构建参数在x86上可以正常工作。所以问题是,x86与x86-64上的重定位有何不同,为什么在前者上我不需要使用 -fPIC 编译?


1
我从来没有理解过这个。如果编译器可以自动告诉你要使用哪个选项,为什么它需要你说出咒语才能使其正确运行呢?Grrr.. - Billy ONeal
@Billy ONeal,现在我相信这是一个泄漏的抽象问题。它们在加载全局数据的方式上有所不同,这会影响是否需要PIC。 - Alex B
我理解这种差异的必要性。但我不明白为什么你需要给编译器一个开关来实现它。 - Billy ONeal
1
@Billy,错误来自于链接器。 - Gearoid Murphy
@GearoidMurphy:好的,这个问题不大。编译是通过单个对g++的调用来实现的(而不是分别调用编译器和链接器),因此g++应该很容易地确定某些编译器选项是否需要相应的链接器选项。 - Billy ONeal
@BillyONeal 我同意在所有源代码同时提供和链接的特定情况下,命令行驱动程序可以处理它。然而,问题中的命令行带有 foo 和 bar 库,这些库可能只是编译时没有使用 -fPIC 的静态对象归档文件。此外,我们不知道 [...] 部分是否包含对象或源代码。对于任何非小型构建,您都需要进行单独的编译。对于快速增量重建,您需要进行单独的编译。 - Johan Boulé
3个回答

22

我找到了一篇清晰而详细的解释,其要点如下:

  1. x86-64使用IP相对偏移加载全局数据,但x86-32不能,因此它会解引用全局偏移量。
  2. IP相对偏移无法用于共享库,因为全局符号可能被覆盖,因此如果未使用PIC创建x86-64,则会出现问题。
  3. 如果使用PIC创建x86-64,则IP相对偏移解引用现在将产生GOT条目指针,然后对其进行解引用。
  4. 然而,x86-32已经使用了全局偏移量的解引用,因此它直接转换为GOT条目。

5
这是一个代码模型问题。默认情况下,静态代码构建时假定整个程序将保留在内存地址空间的较低2G部分。共享库的代码需要编译为另一种内存模型,即PIC,或使用-mcmodel=large进行编译,这样可以不做出该假设。
请注意,-mcmodel=large在旧版gcc中未实现(4.4中已实现,4.2中未实现,4.3我不知道)。

这是有道理的 - 32位绝对地址无法转换为相对重定位,因为库的加载地址可能大于2GB。 - caf
是的,我明白位置无关代码需要在计算跳转偏移量时有所不同,但我很难理解为什么在 x86 上没有使用“-fpic”就能正常工作。 - Alex B
@Alex,动态加载器能够处理一些但不是所有的重定位记录,而某些重定位记录无法处理的原因是它们假设了一个不真实的情况。只有一个非PIC 32位内存模型,并且该模型仅使用已处理的重定位记录。有几个非PIC 64位内存模型,其中一些与动态重定位兼容,一些则不兼容。如果您在gcc 4.4中使用-mcmodel = large,则不需要-fpic。 - AProgrammer

1

这只是ABI人员对我们施加的任意要求。在x86_64上,动态链接器为什么不能支持非PIC库没有逻辑上的理由。然而,由于x86_64没有像x86那样可怕的寄存器压力(并且具有更好的PIC功能),我不知道有任何重要的理由不使用PIC。


除了不支持PIC库外,该程序是可以支持非PIC库的。由于.so通常被以一种使这些重定位记录所做出的假设无效的方式加载,因此它不支持某些重定位记录。但如果您不使用它们,那就没有问题。 - AProgrammer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接