考虑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以及。
1条答案
按热度按时间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。看
mov $x, %edi
。movq x(%[tmp],%[i],8), %[i])
在位置无关代码中索引数组。Q&A使用NASM语法,而不是GAS AT&T或英特尔。(使用
-fPIE
重新编译无法修复。)该错误消息假设机器码是由 * 编译器 * 从
.c
生成的,而不仅仅是从手写的asm * 汇编的。对于手写的asm,没有编译步骤,只有汇编。或者,你人类是“编译器”,所以你需要独立于位置的asm来实现存在于你大脑中的算法。(64位绝对地址也是允许的,动态链接器在加载时应用文本重定位修复,但RIP相对寻址是有效的。)脚注1:除了特殊的64位绝对寻址模式,用于累加器的加载/存储,如
movabs foo, %eax
,它不如RIP-relative紧凑,所以如果您正在构建静态数据在+-2GiB代码内的普通代码,您不需要它。编译器不使用它是有原因的。