我是这里的新手,刚开始学习汇编语言。因此,如果我错了,或者如果这篇帖子没有任何意义,请删除我。
我说的是x86-64 Intel架构中的数据移动指令。我已经读过,常规movq
指令只能有可以表示为32位二进制补码的立即源操作数,而movabsq
指令可以具有任意64位立即数作为其源操作数,并且只能具有一个寄存器作为一个目的地。
您能详细说明一下吗?这是否意味着我只能使用movabsq
指令移动64位立即数?并且仅从立即值到寄存器?我看不到如何将64位立即数移到内存中。也许我在这里弄错了一些重要的东西。
最佳答案
在NASM / Intel语法中,mov r64, 0x...
根据常量选择a MOV encoding。即时操作数有四种可供选择:
mov r32, imm32
。 (zero-extended to fill the 64-bit register like always)。 AT&T:mov
/ movl
mov r/m32, imm32
。仅对内存目标有用。 AT&T:mov
/ movl
mov r/m64, sign-extended-imm32
。 可以将8个字节存储到内存中,或将64位寄存器设置为负值。 AT&T:mov
/ movq
mov r64, imm64
。 (这是与mov r32, imm32
相同的no-ModRM操作码的REX.W = 1版本)AT&T:mov
/ movq
/ movabs
(字节计数仅用于寄存器目标,或不需要SIB字节或disp8 / disp32的寻址模式:只需操作码+ ModR / M + imm32。)
某些Intel语法汇编程序(而非GAS)会将
mov rax, 1
之类的32位常量优化为5字节的mov r32, imm32
(NASM这样做),而其他人(如YASM)将使用7字节的mov r/m64, sign-extended-imm32
。他们俩都只为大常量选择了imm64编码,而不必使用特殊的助记符。或使用
equ
常量,不幸的是,YASM将使用10字节版本,即使使用较小的常量也是如此。在GAS中使用AT&T语法
movabsq
表示机器代码编码将包含64位值:立即数或绝对内存地址。 (另一组特殊形式的mov
从绝对地址加载/存储al / ax / eax / rax到绝对地址,并且64位版本使用的是64位绝对地址,而不是相对地址。AT&T语法调用该movabs
,例如movabs 0x123456789abc0, %eax
)。即使数量很少,例如
movabs $1, %rax
,您仍然可以获得10字节的版本。在此what's new in x86-64 guide中使用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位绝对值。 (YASM对具有R_X86_64_32重定位的mov rsi, string
进行相同的操作,但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与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
在现代Linux发行版上无法与普通gcc foo.S
一起使用。 32-bit absolute addresses no longer allowed in x86-64 Linux?。 (请记住,正确的解决方案是相对于RIP的LEA,或者制作一个静态可执行文件,而不实际使用movabs
)。movq
始终为7字节或10字节的格式,因此,除非您需要更长的指令用于对齐目的(而不是稍后使用NOP填充。What methods can be used to efficiently extend instruction length on modern x86?),否则请勿使用mov $1, %rax
。使用mov $1, %eax
获取5字节格式。请注意,
movq $0xFFFFFFFF, %rax
不能使用7字节格式,因为它不能用符号扩展的32位立即数表示,并且需要imm64编码或%eax
目标编码。 GAS不会为您执行此优化操作,因此您只能使用10字节编码。您一定要mov $0xFFFFFFFF, %eax
。具有直接来源的
movabs
始终是imm64形式。(
movabs
也可以是具有64位绝对地址并以RAX作为源或dest的MOV encoding:例如REX.W + A3
MOV moffs64, RAX
)。那是一个单独的问题,答案是:你不能。 insn ref manual entry for MOV明确了这一点:唯一具有imm64立即操作数的形式仅具有寄存器目标,而不是r / m64。
如果您的值适合符号扩展的32位立即数,那么
movq $0x123456, 32(%rdi)
会将8字节存储到内存中。限制是高32位必须是位31的副本,因为它必须可编码为符号扩展imm32。相关:why we can't move a 64-bit immediate value to memory?-计算机体系结构/ ISA设计的原因。
关于assembly - x86-64中movq和movabsq之间的区别,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40315803/