我是这里的新手,刚刚开始学习汇编语言。所以如果我错了请纠正我,或者如果这篇文章没有任何意义我会删除。
我在谈论x86-64英特尔架构中的数据移动指令。我读过那个常规的 movq
指令只能有直接的源操作数,可以表示为32位二进制补码数,而 movabsq
指令可以具有任意64位立即值作为其源操作数,并且只能将寄存器作为目标。
你能详细说明一下吗?这是否意味着我可以使用移动64位立即值 movabsq
仅限指导?只有立即价值到寄存器?我不知道如何将64位立即值移动到内存中。或者也许我错了一些重要的事情。
在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。