在 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
//movq
movabs
(字节计数仅用于寄存器目标,或不需要 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, string
YASM 对 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.S
。 x86-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 设计原因。