33

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

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

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

4

1 回答 1

27

在 NASM / Intel 语法中,根据常量mov r64, 0x...选择MOV 编码。立即操作数有四种可供选择:

  • 5 个字节mov r32, imm32。(零扩展以像往常一样填充 64 位寄存器)。美国电话电报公司:mov/movl
  • 6+ 字节mov r/m32, imm32。仅对内存目的地有用。美国电话电报公司: mov/movl
  • 7+ 字节mov r/m64, sign-extended-imm32可以将 8 个字节存储到内存中,或者将 64 位寄存器设置为负值。美国电话电报公司:mov/movq
  • 10 字节mov r64, imm64。(这是与 相同的 no-ModRM 操作码的 REX.W=1 版本mov r32, imm32) AT&T :mov//movqmovabs

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

一些英特尔语法汇编器(但不是 GAS)会将 32 位常量优化mov rax, 1为 5 字节mov r32, imm32(NASM 这样做),而其他(如 YASM)将使用 7 字节mov r/m64, sign-extended-imm32。他们都只为大常量选择 imm64 编码,而不必使用特殊的助记符。

或者对于equ常量,YASM 将使用 10 字节版本,即使是小的常量,不幸的是。


在具有 AT&T 语法的 GAS 中

movabsq意味着机器码编码将包含一个 64 位的值:一个立即数,或者一个绝对内存地址。 (还有另一组特殊形式的mov从/到绝对地址的加载/存储 al/ax/eax/rax,其 64 位版本使用 64 位绝对地址,而不是相对地址。AT&T 语法称其movabs为好吧,例如movabs 0x123456789abc0, %eax)。

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

在 x86-64 指南中使用 AT&T 语法的新特性中提到了其中的一些内容。


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

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

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

(在 OS X 上的 Mach-O64 等目标上,movq $symbol, %rax可能总是选择 imm64 编码,因为 32 位绝对地址永远无效。在 SO 上有一些 MacOS Q&A,我认为人们说他们的代码可以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    $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

当然,由于 32 位绝对重定位,这不会链接到 PIE 可执行文件。 在现代 Linux 发行版上movq $symbol, %rax无法正常工作gcc foo.Sx86-64 Linux 中不再允许使用 32 位绝对地址?. (请记住,正确的解决方案是相对于 RIP 的 LEA,或者制作静态可执行文件,而不是实际使用movabs.


movq始终是 7 字节或 10 字节的形式,所以不要使用mov $1, %rax,除非您需要更长的指令用于对齐目的(而不是稍后用 NOP 填充。 在现代 x86 上可以使用哪些方法来有效地扩展指令长度?)。用于mov $1, %eax获取 5 字节形式。

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

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

movabs也可以是MOV 编码,以 64 位绝对地址和 RAX 作为源或 dest:like REX.W + A3 MOV moffs64, RAX)。


我看不到如何将 64 位立即值移动到内存中。

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

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

相关:为什么我们不能将 64 位立即值移动到内存中?- 计算机体系结构/ISA 设计原因。

于 2016-10-29T05:42:40.563 回答