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

IgN*_*ite 19 x86 assembly x86-64 instruction-set att

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

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

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

Pet*_*des 19

在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.(这是REX.W = 1版本相同的无ModRM操作码作为mov r32, imm32)AT&T:mov/ movq/movabs

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

一些英特尔语法汇编器(但不是GAS)将优化32位常量,如mov rax, 15字节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字节的版本.

其中一些内容在使用AT&T语法的x86-64指南中有所提及.


然而,mov助记符(具有或不具有q操作数大小的后缀)将之间挑选mov r/m64, imm32mov r64, imm64根据即时的大小.(请参阅x86-64 AT&T指令movq和movabsq之间的区别是什么?这是一个后续版本,因为这个答案的第一个版本猜错了GAS用大的汇编时常量做了什么movq.)

但是符号地址直到链接时才知道,所以当汇编器选择编码时它们不可用. 至少在定位Linux ELF目标文件时,GAS假定如果你不使用movabs,那么你打算使用32位绝对值.(mov rsi, stringYASM 对R_X86_64_32重定位执行相同操作,但NASM默认为movabs,生成R_X86_64_64重定位.)

如果由于某种原因你想要使用符号名称作为绝对立即(而不是通常更好的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
Run Code Online (Sandbox Code Playgroud)

将GAS组装到一个目标文件(with .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
Run Code Online (Sandbox Code Playgroud)

链接到非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
Run Code Online (Sandbox Code Playgroud)

当然,由于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也可以是具有64位绝对地址的MOV编码,RAX作为源或目标:如此REX.W + A3 MOV moffs64, RAX).


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

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

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


归档时间:

查看次数:

8093 次

最近记录:

7 年,5 月 前