我是这里的新手,刚开始学习汇编语言。因此,如果我错了,或者如果这篇帖子没有任何意义,请删除我。

我说的是x86-64 Intel架构中的数据移动指令。我已经读过,常规movq指令只能有可以表示为32位二进制补码的立即源操作数,而movabsq指令可以具有任意64位立即数作为其源操作数,并且只能具有一个寄存器作为一个目的地。

您能详细说明一下吗?这是否意味着我只能使用movabsq指令移动64位立即数?并且仅从立即值到寄存器?我看不到如何将64位立即数移到内存中。也许我在这里弄错了一些重要的东西。

最佳答案

在NASM / Intel语法中,mov r64, 0x...根据常量选择a MOV encoding。即时操作数有四种可供选择:

  • 5字节mov r32, imm32。 (zero-extended to fill the 64-bit register like always)。 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。 (这是与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, imm32mov 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/

    10-11 15:15