何时使用符号扩展重定位(如R_X86_64_32S)比使用零扩展(如R_X86_64_32)更好?——一个关于汇编语言的问题。

3
作为一个具体的例子,在GAS 2.24版本中,移动地址:
mov $s, %eax
s:

之后:

as --64 -o a.o a.S
objdump -Sr a.o

使用零扩展:

0000000000000000 <s-0x5>:
   0:   b8 00 00 00 00          mov    $0x0,%eax
                        1: R_X86_64_32  .text+0x5

但是关于内存访问:

mov s, %eax
s:

编译为符号扩展:

0000000000000000 <s-0x7>:
   0:   8b 04 25 00 00 00 00    mov    0x0,%eax
                        3: R_X86_64_32S .text+0x7

在这种特定情况下或者一般情况下,使用其中任何一种有理由吗?我不明白汇编器如何对任一情况做出更好的推测。
NASM 2.10.09在上述两种情况下都只使用R_X86_64_32。更新:nasm的6377180边缘提交在2.11之后生成了与Gas相同的输出,这似乎是一个错误,正如Ross所提到的那样。
我已经解释了我对R_X86_64_32S的理解,详见:https://dev59.com/SW025IYBdhLWcg3wSkAq#33289761

也许它只是能够看到一个值很小(尤其是零!),但不关心优化地址加载? - Bo Persson
@BoPersson,它如何知道在链接后地址是否会变小(这里是5)?如果我使用一个链接脚本,像这个例子中的 . = 0xFFFFFFFF80000000; https://dev59.com/SW025IYBdhLWcg3wSkAq#33289761,它会在 _32 上出现问题,但不会在 _32S 上出现问题。 - Ciro Santilli OurBigBook.com
2个回答

7
区别在于符号 s 允许的地址。在第一个情况下,使用R_X86_64_32,该符号必须在0x00000000'00000000到0x00000000'FFFFFFFF范围内。在第二种情况下,使用R_X86_64_32S,符号的地址必须在0xFFFFFFFF'80000000和0x00000000'7FFFFFFF之间。如果 s 的地址超出这些范围,链接器将给出错误。
这对应于CPU如何解释编码为两个指令的32位 s 值的方式。在第一条指令中,它是一个立即操作数,32位值被零扩展为RAX。在第二条指令中,32位值是内存操作数中的位移量,因此进行符号扩展以形成64位地址。
NASM不应该在第二条指令中使用无符号R_X86_64_32重定位。这不是哪一个更好的问题,这里使用R_X86_64_32是错误的。NASM将允许 s 的地址为0x00000000'80000000,但CPU最终将访问0xFFFFFFFF'80000000。

啊,好的,我忘记了mov %eax 零扩展...而且我不知道32位地址是符号扩展的! - Ciro Santilli OurBigBook.com

2

使用立即数 mov 指令时,汇编器会按照你写的内容进行操作。在 x86-64 架构中,向 32 位寄存器写入数据 总是 零扩展其上方的 32 位。这在 Intel 指令参考手册中有所说明:

  • MOV r/m64, imm32 表示:将符号扩展的 imm32 移动到 64 位的 r/m64 中。
  • MOV r/m32, imm32 表示:将 imm32 移动到 32 位的 r/m32 中。

如果你希望符号扩展与在 32 位绝对地址模式下处理 32 位地址的方式相匹配,那么应该这样写:

mov $s, %rax

32位位移总是进行符号扩展。因此,我认为Ross的答案是正确的,NASM 2.10.09存在缺陷。它显然告诉链接器地址将被零扩展,而实际上将被符号扩展。当然,RIP相对寻址需要更少的指令字节,因此在可能的情况下应优先选择绝对寻址。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接