【问题标题】:Difference between movq and movabsq in x86-64x86-64 中 movq 和 movabsq 的区别
【发布时间】:2024-04-21 13:55:01
【问题描述】:

我是这里的新手,刚开始学习汇编语言。所以如果我错了请纠正我,或者如果这篇文章没有任何意义我会删除。

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

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

【问题讨论】:

  • 您必须将其移至寄存器,然后移至内存。仅此而已。

标签: assembly x86-64 att instructions immediate-operand


【解决方案1】:

在 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 相同的 no-ModRM 操作码的 REX.W=1 版本) AT&T:mov / movq / movabs

(字节计数仅用于寄存器目标,或不需要 SIB 字节或 disp8/disp32 的寻址模式:只需操作码 + ModR/M + imm32。)

一些 Intel 语法汇编器(但不是 GAS)将优化 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从/到绝对地址加载/存储 al/ax/eax/rax,并且 64 位版本使用 64 位绝对地址,而不是相对地址。AT&T 语法也调用 movabs,例如 @987654353 @)。

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

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


但是,mov 助记符(有或没有q 操作数大小后缀)将根据立即数的大小在mov r/m64, imm32mov r64, imm64 之间进行选择。 (请参阅What's the difference between the x86-64 AT&T instructions movq and movabsq?,这是一个后续版本,因为该答案的第一个版本猜错了 GAS 对 movq 的大型汇编时间常数所做的事情。)

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

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

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


带有$symbol 立即数的 Linux/ELF 示例

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    $0x0,%rdi        3: R_X86_64_32S .bss
   7:   48 bf 00 00 00 00 00 00 00 00   movabs $0x0,%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    $0x0,%edi        19: R_X86_64_32 .bss

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

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

当然,这不会链接到 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 $1, %rax,除非您需要更长的指令用于对齐目的(而不是稍后使用 NOP 填充。What methods can be used to efficiently extend instruction length on modern x86?)。使用mov $1, %eax获取5字节的形式。

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

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

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


我不知道如何将 64 位立即数移动到内存中。

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

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

相关:why we can't move a 64-bit immediate value to memory? - 计算机架构/ISA 设计原因。

【讨论】:

  • 为什么movq $0xFFFFFFFF, %rax 不可编码?为什么 5 字节版本不起作用?
  • @LưuVĩnhPhúc:因为你使用了movq。要让 gas 使用 5 字节无 REX 前缀编码,您必须自己进行优化并编写 movl $0xFFFFFFFF, %eax。所以它就像 YASM,不会为你优化。