除非您的 64 位值可以编码为 32 位符号扩展立即数,否则您必须先将其移至寄存器,然后再存储。 (或者进行两个单独的 32 位存储,或者使用其他更糟糕的解决方法来将字节获取到您想要的位置。)
在 NASM / Intel 语法中,mov r64, 0x...
picks MOV编码 http://www.felixcloutier.com/x86/MOV.html基于常数。有四种立即数操作数可供选择:
- 5 byte
mov r32, imm32
. (像往常一样进行零扩展以填充 64 位寄存器 https://stackoverflow.com/questions/11177137/why-do-most-x64-instructions-zero-the-upper-part-of-a-32-bit-register)。美国电话电报公司:mov
/movl
- 6+ byte
mov r/m32, imm32
。仅对内存目的地有用。美国电话电报公司:mov
/movl
- 7+ byte
mov r/m64, sign-extended-imm32
. 可以存储8个字节到内存,或将 64 位寄存器设置为负值。美国电话电报公司:mov
/movq
- 10 byte
mov r64, imm64
。 (这是与以下相同的 no-ModRM 操作码的 REX.W=1 版本mov r32, imm32
) 美国电话电报公司:movabs
, or mov
/ movq
具有宽常数。
(字节计数仅适用于寄存器目标,或不需要 SIB 字节或 disp8/disp32 的寻址模式:只需操作码 + ModR/M + imm32,如mov dword [rdi], 123
)
一些 Intel 语法汇编程序(但不是 GAS,除非您使用as -Os
or gcc -Wa,-Os
) 将优化 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 指南中的新增内容 http://web.archive.org/web/20160609221003/http://www.x86-64.org/documentation/assembly.html使用 AT&T 语法。
但是,那mov
助记符(带或不带q
操作数大小后缀)将在之间进行选择mov r/m64, imm32
and mov r64, imm64
取决于立即数的大小。 (看,存在一个后续问题,因为这个答案的第一个版本猜测了 GAS 对大汇编时间常量所做的事情是错误的movq
.)
但是符号地址直到链接时才知道,因此当汇编器选择编码时它们不可用。至少在针对 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 位绝对地址永远无效。有一些关于 SO 的 MacOS 问答,我认为人们说他们的代码可以使用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 可执行文件。movq $symbol, %rax
无法正常工作gcc foo.S
在现代 Linux 发行版上. x86-64 Linux 中不再允许使用 32 位绝对地址? https://stackoverflow.com/questions/43367427/32-bit-absolute-addresses-no-longer-allowed-in-x86-64-linux。 (请记住,正确的解决方案是与 RIP 相关的 LEA,或者制作静态可执行文件,而不是实际使用movabs
).
movq
始终是 7 字节或 10 字节形式,所以不要使用mov $1, %rax
除非您想要更长的指令用于对齐目的(而不是稍后用 NOP 填充)。可以使用哪些方法来有效地扩展现代 x86 上的指令长度? https://stackoverflow.com/questions/48046814/what-methods-can-be-used-to-efficiently-extend-instruction-length-on-modern-x86). Use mov $1, %eax
得到5字节的形式。
请注意movq $0xFFFFFFFF, %rax
不能使用 7 字节形式,因为它不能用符号扩展32 位立即数,需要 imm64 编码或%eax
目的地编码。 GAS 不会为您进行此优化,因此您只能使用 10 字节编码。你一定想要mov $0xFFFFFFFF, %eax
.
movabs
具有直接来源的始终是 imm64 形式。
(movabs
也可以是MOV编码 http://www.felixcloutier.com/x86/MOV.html使用 64 位绝对地址和 RAX 作为源或目标:就像REX.W + A3
MOV moffs64, RAX
).
我不知道如何将 64 位立即值移至内存。
这是一个单独的问题,答案是:你不能。这MOV 的 insn ref 手动输入 http://www.felixcloutier.com/x86/MOV.html清楚地表明:具有 imm64 立即数操作数的唯一形式仅具有寄存器目标,而不是 r/m64。
如果您的值适合符号扩展的 32 位立即数,movq $0x123456, 32(%rdi)
将在内存中存储 8 字节。限制是高 32 位必须是位 31 的副本,因为它必须可编码为符号扩展的 imm32。
Related:
-
为什么我们不能将 64 位立即值移至内存? https://stackoverflow.com/questions/62771323/why-we-cant-move-a-64-bit-immediate-value-to-memory/62772299#62772299- 计算机体系结构/ISA 设计原因。
-
如何将函数或标签的地址加载到寄存器中 https://stackoverflow.com/questions/57212012/how-to-load-address-of-function-or-label-into-register(使用 5 字节
mov r32, imm32
作为一种优化,或任何情况下与 RIP 相关的 LEA,除了符号可能超过 2GiB 的大内存模型。)