C 位置无关代码的差异:x86与x86-64

C 位置无关代码的差异:x86与x86-64,c,linux,x86,x86-64,elf,C,Linux,X86,X86 64,Elf,我最近正在构建一个针对x86-64体系结构的特定共享库(ELF),如下所示: g++ -o binary.so -shared --no-undefined ... -lfoo -lbar 此操作失败,出现以下错误: 创建共享对象时,不能使用针对“本地符号”的重新定位R_X86_64_32;用-fPIC重新编译 当然,这意味着我需要将其重建为独立于位置的代码,因此它适合链接到共享库中 但这在x86上运行得非常好,构建参数完全相同。所以问题是,x86上的重新定位与x86-64有什么不同?为什么我

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

g++ -o binary.so -shared --no-undefined ... -lfoo -lbar
此操作失败,出现以下错误:

创建共享对象时,不能使用针对“本地符号”的重新定位R_X86_64_32;用-fPIC重新编译

当然,这意味着我需要将其重建为独立于位置的代码,因此它适合链接到共享库中


但这在x86上运行得非常好,构建参数完全相同。所以问题是,x86上的重新定位与x86-64有什么不同?为什么我不需要在前者上使用
-fPIC
进行编译?

这是一个代码模型问题。默认情况下,假设整个程序将保留在内存地址空间的较低2G部分,则生成静态代码。共享库的代码需要为另一个内存模型编译,可以是PIC,也可以是-mcmodel=large,它在编译时不会做出这样的假设

请注意,-mcmodel=large在较旧的gcc版本中没有实现(它在4.4中,在4.2中没有,我不知道是否在4.3中)。
这纯粹是ABI人强加给我们的任意要求。x86_64上的动态链接器不支持非PIC库没有任何逻辑原因。然而,由于x86_64不像x86那样承受着可怕的寄存器压力(并且具有更好的PIC功能),我不知道有什么重要的理由不使用PIC。

我发现,这归结为:

  • x86-64使用IP相对偏移量加载全局数据,而x86-32不能,因此它取消了对全局偏移量的引用
  • IP相对偏移量不适用于共享库,因为全局符号可以被覆盖,所以x86-64在不使用PIC构建时会发生故障
  • 如果x86-64是用PIC构建的,那么IP相对偏移量解引用现在会生成一个指向GET条目的指针,然后对其进行解引用
  • 然而,x86-32已经使用了全局偏移量的解引用,因此它直接转换为get条目

  • 这是有道理的-32位绝对地址不能转换为相对重新定位,因为库的加载地址可能大于2GB。是的,我知道位置无关代码在计算跳转偏移量时需要不同,但我很难理解为什么它在x86上不使用
    -fpic
    @Alex,动态加载器能够处理一些但不是所有的重定位记录,而一些重定位记录不被处理的原因是它们假设了一种不正确的情况。只有一个非PIC 32位内存模型,该模型仅使用已处理的重新定位记录。有几种非PIC 64位内存型号,有些兼容动态重新定位,有些不兼容。如果在GCC4.4中使用-mcmodel=large,则不需要-fpic。除非它支持非PIC库。它不支持某些重新定位记录,因为通常。so的加载方式会导致这些重新定位记录所做的假设无效,但如果您不使用它们,则不会有问题。我从未理解这一点。如果编译器可以准确地告诉您要自动使用哪个选项,那么为什么需要您说一些神奇的话才能使其正确运行呢?Grrr.@Billy ONeal,现在我相信这就是泄漏抽象的情况。它们加载全局数据的方式不同,这会影响是否需要PIC。我理解这种差异的必要性。我不明白的是,为什么你需要给编译器一个开关,让它这样做。@Billy,错误来自linker@GearoidMurphy:好的,6个这个,半打另一个。编译是通过对g++的一次调用调用调用的(而不是单独调用编译器和链接器),因此g++应该能够很容易地判断某些编译器选项是否需要相应的链接器选项。