为什么与 RIP 相关的 LEA 指令会产生与 PIC 不兼容的 R_X86_64_32S 重定位?

Why is a RIP-relative LEA instruction producing a PIC-incompatible R_X86_64_32S relocation?

我正在经历 x86-64 tutorial on exercism.org。我在 Linux 上使用 NASM,生成一个 ELF 二进制文件。只有一点 C 代码可以在测试工具中调用我的汇编代码。他们的构建系统在 LDFLAGS 中指定 -pie 并在 CFLAGS 中指定 -fPIE (除其他外,但我认为这些是最相关的)。因此,我需要(并且想了解)一个使用 PIC 的解决方案,它需要 RIP 相对寻址。

我有一个索引(在 rdi 中)到一个名为 values 的 8 字节(qword)值数组中。我只想获取偏移量的地址,以便 mov 它指向的值存入寄存器。或者我会直接接受 moving 值。

我试过这个:

lea rbx, [rel values + rdi * 8]

我的理解是,这将查看 values 的地址(相对于 rip),它位于 data 部分,然后它会添加正确的偏移量 (rdi * 8) 并将其放入 rbx.

但这会产生下一个错误:

/usr/bin/ld: space_age.o: relocation R_X86_64_32S against `.data' can not be used when making a PIE object; recompile with -fPIE

我知道 recompile with -fPIE 是 linker 认为代码是编译的而不是从手写汇编汇编的结果。所以看起来 NASM 正在产生一个重定位类型,它不适合它被 link 反对的东西,并且 linker 认为它不是 PIE,对吗?

所以我尝试了这个:

lea rbx, [rdi * 8]    ; first compute the offset: index * sizeof(qword value)
lea r8, [rel values]  ; then find the address of the values
add rbx, r8           ; rbx = offset of the array + address of base of the array

对我来说,这与第一条指令完全相同。我不明白这些有何不同,但是关于第一个重定位是 R_X86_64_32S 的事实似乎表明第一条指令只有一个带符号的 32 位偏移量,也许 lea r8, [rel values] 创建了一个64 位的不同重定位类型,但我只是猜测。

这可以在一条指令中完成吗?


编辑:这不是 的副本,因为它没有解释为什么 NASM 接受了我的代码,但后来却无法 link。由于已接受的答案,我现在明白了这种寻址模式不存在,但是 NASM 默默地忽略了 rel 部分。

没有lea rbx, [rel values + rdi * 8]这样的指令,换句话说lea rbx, [values + rip + rdi * 8]rip-相对寻址模式不能与索引寄存器结合使用(有或没有缩放)。参见

不幸的是,看起来 nasm 只是通过忽略 rel 和组装 lea rbx, [values + rdi * 8] 来处理这个问题,这将需要有问题的重定位。 values 的地址必须位于指令内存操作数的 32 位位移字段中,但这是不可能的,因为 values 不需要位于内存的最低或最高 2 GB 中;因此出现了令人困惑的错误消息。

但正确的解决办法是你只需要写更多的指令。您可以通过

的两条指令获得所需的效果
lea rbx, [rel values]
lea rbx, [rbx + rdi * 8]

或者如果你想实际加载:

lea rbx, [rel values]
mov rcx, [rbx + rdi * 8]

(寄存器的选择是任意的。)