问题 x86-64中movq和movabsq之间的区别


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

我在谈论x86-64英特尔架构中的数据移动指令。我读过那个常规的 movq 指令只能有直接的源操作数,可以表示为32位二进制补码数,而 movabsq 指令可以具有任意64位立即值作为其源操作数,并且只能将寄存器作为目标。

你能详细说明一下吗?这是否意味着我可以使用移动64位立即值 movabsq 仅限指导?只有立即价值到寄存器?我不知道如何将64位立即值移动到内存中。或者也许我错了一些重要的事情。


2658
2017-10-29 03:38


起源

你必须将它移动到寄存器然后移动到内存。这里的所有都是它的。 - Ross Ridge


答案:


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

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

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


在带有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 取决于眼前的大小。 (看到 x86-64 AT&T指令movq和movabsq有什么区别?,这是一个后续行动,因为这个答案的第一个版本猜错了GAS用大的汇编时间常数做了什么 movq。)

但是符号地址直到链接时才知道,因此当汇编器选择编码时它们不可用。至少在定位Linux ELF目标文件时,GAS假设您没有使用 movabs,你打算32位绝对。

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

(对于像OS X上的Mach-O64这样的目标, movq $symbol, %rax 可能总是选择imm64编码,因为32位绝对地址永远不会有效。在SO上有一些MacOS Q&As,我认为人们说他们的代码可以使用 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 (签名)vs. 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可执行文件。 movq $symbol, %rax 不会正常工作 gcc foo.S 在现代Linux发行版上x86-64 Linux中不再允许32位绝对地址?。 (请记住,正确的解决方案是RIP相对LEA,或制作静态可执行文件,而不是实际使用 movabs)。


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

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

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

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


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


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

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

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


14
2017-10-29 05:42



从64位地址加载到rax以外的其他寄存器 - phuclv
为什么 movq $0xFFFFFFFF, %rax 不可编码?为什么5字节版本不起作用? - phuclv
@LưuVĩnhPhúc:因为你用过 movq。要使气体使用5字节的无REX前缀编码,您必须自己进行优化并编写 movl $0xFFFFFFFF, %eax。所以它就像YASM,并不会为你优化。 - Peter Cordes