含内联汇编代码本地遇Relocation错误但Godbolt正常的原因与解决
内联汇编与PIE编译的问题与解决
原程序代码
#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 - 本地使用同版本GCC,执行
g++ -O3 main.cpp编译时也会出现上述错误,即使添加-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(),所有编译器(本地及在线环境)均可正常编译运行。
问题与解答
1. 为何GCC在线环境可运行但本地不行?
核心原因是编译默认选项差异:
- 在线编译环境默认采用
-no-pie选项,生成非位置独立可执行文件(non-PIE),此时允许使用32位绝对地址重定位(R_X86_64_32S),硬编码_ZL1x符号的写法可以正常工作。 - 多数现代Linux发行版的本地GCC默认开启
-pie选项,强制生成位置独立可执行文件(PIE)。PIE要求使用相对地址或64位重定位,不允许32位绝对地址重定位,因此硬编码符号的内联汇编触发链接错误。添加-fPIE仅控制编译阶段生成位置独立代码,但链接阶段仍会生成PIE,无法解决重定位类型不兼容的问题。
2. 如何让代码本地正常编译且兼容Clang?
有两种可行方案:
方案1:显式关闭PIE编译
编译时添加-no-pie选项,强制生成非PIE可执行文件,避开重定位限制:
g++ -O3 -no-pie main.cpp # 或Clang clang++ -O3 -no-pie main.cpp
此方案简单直接,但不符合现代安全编译的默认趋势(PIE可提升程序安全性)。
方案2:规范内联汇编写法(推荐)
避免硬编码编译器生成的符号名,改为让编译器自动处理数组地址的传递,从根本上解决重定位和兼容性问题:
修改f()函数如下:
int64_t f() { int64_t i; asm volatile ( "xor %[i], %[i]\n\t" "movq (%[x_base], %[i], 8), %[i]" : [i]"=r"(i) : [x_base]"r"(x)); // 将数组x的地址作为输入约束传入 return i; }
- 编译器会自动处理数组
x的地址引用,无论是否开启PIE,都会生成合法的重定位代码。 - 避免依赖编译器的符号修饰规则(如
_ZL1x是GCC对内部链接变量的修饰名,不同编译器可能存在差异),完全兼容GCC和Clang。
内容的提问来源于stack exchange,提问作者Cassio Neri
相关产品推荐
相关产品推荐

