You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

含内联汇编代码本地遇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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 13:17:44