gcc 重新定位错误发生在本地,但不在godbolt中

kpbwa7wx  于 2023-06-23  发布在  Go
关注(0)|答案(1)|浏览(154)

考虑godbolt上的这个程序:

#include <cassert>
#include <cstdint>

int64_t const x[] = { 42, 2, 3, 4 };

int64_t f() {
  int64_t i;
  asm volatile (
    "xor %[i], %[i]\n\t"
    "movq _ZL1x(,%[i],8), %[i]"
  : [i]"=r"(i));
  return i;
}

int64_t g() {
  int64_t i;
  asm volatile (
    "xor %[i], %[i]"
  : [i]"=r"(i));
  i = x[i];
  return i;
}

int main() {
  assert(f() == 42);
}

它在gcc 13.1上编译、链接和运行都很好,但是clang 16.0给出了一个链接错误:

[...]: relocation R_X86_64_32S against `.rodata.cst32' can not be used when making a PIE object; recompile with -fPIE

如果我在本地尝试相同的代码并使用g++ -O3 main.cpp(相同的gcc版本)编译,我会得到上面相同的错误。(使用-fPIE重新编译无法修复。)
值得注意的是,gcc为f()g()生成的代码是相同的:

_Z1fv:
  xor %rax, %rax
  movq _ZL1x(,%rax,8), %rax
  ret
_Z1gv:
  xor %rax, %rax
  movq _ZL1x(,%rax,8), %rax
  ret

如果我从源文件中删除f,并在main中使用g,那么所有编译器都在本地和godbolt上运行。
我知道直接在内联程序集中编写_ZL1x是很奇怪的,而且很可能是一种糟糕的做法,但这不是这里的重点。我想知道为什么gcc在godbolt上很高兴,而不是本地的,更重要的是,我如何才能使它在本地工作,(如果可能的话)为clang以及。

at0kjp5o

at0kjp5o1#

大多数Linux发行版默认使用-fPIE -pie -fstack-protector-strong配置GCC和Clang,可能还有-fno-plt。因此,他们正在制作位置无关的可执行文件,这些可执行文件必须可重定位到64位地址空间中的任何位置。

Matt Godbolt的编译器资源管理器安装了带有普通配置的编译器,默认情况下没有这些选项,因此他们正在制作传统的可执行文件,这些可执行文件链接到低31位虚拟地址空间中的特定地址。(x86-64 Clang 16.0除外,它至少启用了-fPIE。这完全取决于https://godbolt.org/的系统管理员在从源代码构建编译器时使用默认配置选项。
symbol(%rip)以外的任何寻址模式最多使用32位符号扩展位移1,因此绝对地址必须适合该模式。这只适用于Linux非PIE可执行文件,我认为是Windows非LargeAddressAware可执行文件或dll。在MachO 64中,无论你如何链接它。

如果您在本地执行gcc -O3 -S,您可以看到它为您的g()函数创建的兼容PIE的asm,首先使用RIP相对LEA。

(使用-fPIE重新编译无法修复。)
该错误消息假设机器码是由 * 编译器 * 从.c生成的,而不仅仅是从手写的asm * 汇编的。对于手写的asm,没有编译步骤,只有汇编。或者,你人类是“编译器”,所以你需要独立于位置的asm来实现存在于你大脑中的算法。(64位绝对地址也是允许的,动态链接器在加载时应用文本重定位修复,但RIP相对寻址是有效的。)
脚注1:除了特殊的64位绝对寻址模式,用于累加器的加载/存储,如movabs foo, %eax,它不如RIP-relative紧凑,所以如果您正在构建静态数据在+-2GiB代码内的普通代码,您不需要它。编译器不使用它是有原因的。

相关问题