x86-64 中 movq 和 movabsq 的区别

Difference between movq and movabsq in x86-64

我说的是 x86-64 Intel 架构中的数据移动指令。我读到常规 movq 指令只能有直接源操作数,可以表示为 32 位二进制补码,而 movabsq 指令可以有任意 64 位立即值作为其源操作数并且只能有一个寄存器作为目标。

能否详细说明一下?这是否意味着我可以仅使用 movabsq 指令移动 64 位立即数?并且仅从立即值到寄存器?我不知道如何将 64 位立即值移动到内存中。或者也许我在这里弄错了一些重要的东西。

除非您的 64 位值可以编码为 32 位符号扩展立即数,否则您必须先将其移动到寄存器然后存储。 (或者做两个单独的 32 位存储,或其他更糟糕的解决方法来获取您想要的字节。)


在 NASM / Intel 语法中,mov r64, 0x... 根据常量选择 a MOV encoding。直接操作数有四种可供选择:

  • 5 字节 mov r32, imm32。 (zero-extended to fill the 64-bit register like always)。美国电话电报公司:mov/movl
  • 6+ 字节 mov r/m32, imm32。仅对内存目的地有用。美国电话电报公司: mov/movl
  • 7+ 字节 mov r/m64, sign-extended-imm32可以向内存存储 8 个字节,或者将 64 位寄存器设置为负值。美国电话电报公司:mov/movq
  • 10 字节 mov r64, imm64。 (这是与 mov r32, imm32 相同的无 ModRM 操作码的 REX.W=1 版本)AT&T:movabs,或 mov / movq 具有宽常数.

(字节数仅用于寄存器目的地,或不需要 SIB 字节或 disp8/disp32 的寻址模式:仅操作码 + ModR/M + imm32,如 mov dword [rdi], 123

一些 Intel 语法 assemblers(但不是 GAS,除非你使用 as -Osgcc -Wa,-Os)将优化 32 位常量,如 mov rax, 1 到 5 字节mov r32, imm32(NASM 这样做),而其他人(如 YASM)将使用 7 字节 mov r/m64, sign-extended-imm32。他们都只对大常量选择imm64编码,不需要使用特殊的助记符。

或者使用 equ 常量,YASM 有时会使用 10 字节版本,即使是小常量,不幸的是。


在 GAS 中使用 AT&T 语法

movabsq 表示机器代码编码将包含一个 64 位值:立即数或绝对内存地址。(还有另一组mov 的特殊形式的 load/store al/ax/eax/rax from/to 一个绝对地址,而它的 64 位版本使用 64 位绝对地址,而不是相对地址。AT&T 语法也调用 movabs,例如 movabs 0x123456789abc0, %eax).

即使数字很小,例如 movabs , %rax,您仍然可以获得 10 字节的版本。

what's new in x86-64 guide中使用 AT&T 语法提到了其中一些。


然而,mov 助记符(带或不带 q 操作数大小后缀)将根据立即数的大小在 mov r/m64, imm32mov r64, imm64 之间选择. (请参阅 ,因为此答案的第一个版本猜错了 GAS 对 movq 的大 assemble-时间常数所做的事情,因此存在后续行动。)

但是直到 link 时间才知道符号地址,因此当 assembler 选择编码时它们不可用。 在至少在针对 Linux ELF 目标文件时,GAS 假设如果您没有使用 movabs,您打算使用 32 位绝对值。 (YASM 对 mov rsi, string 执行相同的 R_X86_64_32 重定位,但 NASM 默认为 movabs,产生 R_X86_64_64 重定位。)

如果出于某种原因你想使用符号名称作为绝对立即数(而不是通常更好的 RIP 相对 LEA),你确实需要 movabs

(在 OS X 上的 Mach-O64 等目标上,movq $symbol, %rax 可能总是选择 imm64 编码,因为 32 位绝对地址永远无效。有一些 MacOS 关于 SO 的问答,我认为人们说他们的代码使用 movq 将数据地址放入寄存器。)


Linux/ELF 上的示例 $symbol 立即数

mov    $symbol, %rdi     # GAS assumes the address fits in 32 bits
movabs $symbol, %rdi     # GAS is forced to use an imm64


lea    symbol(%rip), %rdi  # 7 byte RIP-relative addressing, normally the best choice for position-independent code or code loaded outside the low 32 bits

mov    $symbol, %edi    # optimal in position-dependent code

用 GAS 组装成目标文件(.bss; symbol:),我们得到这些重定位。请注意 R_X86_64_32S(有符号)与 R_X86_64_32(无符号)与 R_X86_64_PC32(相对于 PC)32 位重定位之间的区别。

0000000000000000 <.text>:
   0:   48 c7 c7 00 00 00 00    mov    [=11=]x0,%rdi        3: R_X86_64_32S .bss
   7:   48 bf 00 00 00 00 00 00 00 00   movabs [=11=]x0,%rdi        9: R_X86_64_64  .bss
  11:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 18 <.text+0x18>  14: R_X86_64_PC32       .bss-0x4
  18:   bf 00 00 00 00          mov    [=11=]x0,%edi        19: R_X86_64_32 .bss

链接到非 PIE 可执行文件 (gcc -no-pie -nostdlib foo.s),我们得到:

4000d4:       48 c7 c7 f1 00 60 00      mov    [=12=]x6000f1,%rdi
4000db:       48 bf f1 00 60 00 00 00 00 00   movabs [=12=]x6000f1,%rdi
4000e5:       48 8d 3d 05 00 20 00      lea    0x200005(%rip),%rdi     # 6000f1 <__bss_start>
4000ec:       bf f1 00 60 00            mov    [=12=]x6000f1,%edi

当然这不会 link 到 PIE 可执行文件中,因为 32 位绝对重定位。 movq $symbol, %rax 在现代 Linux 发行版 上无法与正常 gcc foo.S 一起使用。 32-bit absolute addresses no longer allowed in x86-64 Linux?。 (请记住,正确的解决方案是 RIP 相关的 LEA,或者制作静态可执行文件,而不是实际使用 movabs)。


movq 总是 7 字节或 10 字节的形式,所以不要使用 mov , %rax 除非你想要一个更长的指令用于对齐目的(而不是稍后用 NOP 填充。 ).使用 mov , %eax 获取 5 字节形式。

注意 movq [=63=]xFFFFFFFF, %rax 不能使用 7 字节形式,因为它不能用 符号扩展 32 位立即数表示,并且需要imm64 编码或 %eax 目标编码。 GAS 不会为您进行此优化,因此您只能使用 10 字节编码。你肯定想要 mov [=65=]xFFFFFFFF, %eax.

movabs 直接来源始终是 imm64 形式。

movabs 也可以是具有 64 位绝对地址和 RAX 作为源或目标的 MOV encoding:如 REX.W + A3 MOV moffs64, RAX)。


I don't see how I can move a 64-bit immediate value to memory.

这是一个单独的问题,答案是:不能。 insn ref manual entry for MOV 清楚地表明:唯一具有 imm64 立即操作数的形式只有寄存器目标,而不是 r/m64。

如果您的值适合符号扩展的 32 位立即数,movq [=70=]x123456, 32(%rdi) 将对内存进行 8 字节存储。限制是高 32 位必须是位 31 的副本,因为它必须可编码为符号扩展 imm32。

相关:

  • why we can't move a 64-bit immediate value to memory? - 计算机体系结构/ISA 设计原因。
  • (使用 5 字节 mov r32, imm32 作为优化,或者在任何情况下使用 RIP 相关的 LEA,除了符号可能超过 2GiB 的大内存模型。)