x86-64 AT&T指令movq和movabsq有什么区别?

时间:2018-09-20 22:14:31

标签: assembly x86-64 att

在阅读this stack overflow answerthis document之后,我仍然不了解movqmovabsq之间的区别。

我目前的理解是,在movabsq中,第一个操作数是64位立即数,而movq符号扩展了32位的立即数。从上面引用的第二份文档中:

  

可以通过movq指令将立即数据移动到64位寄存器中,该指令将对32位立即数进行符号扩展,也可以使用movabsq指令将64位完整寄存器中的数据立即进行扩展。位必须为立即数。

在第一个参考文献中,彼得指出:

  

有趣的实验:movq $0xFFFFFFFF, %rax可能无法编码,因为它不能用符号扩展的32位立即数表示,并且需要imm64编码或%eax目标编码。

但是,当我汇编/运行此程序时,它似乎运行良好:

        .section .rodata
str:
        .string "0x%lx\n"
        .text
        .globl  main
main:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $str, %edi
        movq    $0xFFFFFFFF, %rsi
        xorl    %eax, %eax
        call    printf
        xorl    %eax, %eax
        popq    %rbp
        ret

$ clang file.s -o file && ./file

打印0xffffffff。 (这对于较大的值也类似,例如,如果您添加一些额外的“ F”,则该方法也是如此)。 movabsq产生相同的输出。

Clang是否在推断我想要什么?如果是这样,movabsq仍然比movq有好处吗?

我错过了什么吗?

1 个答案:

答案 0 :(得分:6)

有三种方法可以填充64位寄存器:

  1. 移至低32位部分B8 +rd id,5个字节
    示例:mov eax, 241 / mov[l] $241, %eax
    移至低32位部分会将高位部分归零。

  2. 以64位立即移动48 B8 +rd io,10字节
    示例:mov rax, 0xf1f1f1f1f1f1f1f1 / mov[abs][q] $0xf1f1f1f1f1f1f1f1, %rax
    立即移动完整的64位。

  3. 以符号扩展的32位立即数进行移动48 C7 /0 id,7个字节
    示例:mov rax, 0xffffffffffffffff / mov[q] $0xffffffffffffffff, %rax 将带符号的32位立即数移到完整的64位寄存器中。

请注意在组装级别如何使用room for ambiguity,第二和第三种情况下使用movq

对于每个立即数,我们有:

  • (a) [0,0x7fff_ffff] 中的值可以用(1),(2)和(3)进行编码。
  • (b) [0x8000_0000,0xffff_ffff] 中的值可以用(1)和(2)进行编码。
  • (c) [0x1_0000_0000,0xffff_ffff_7fff_ffff] 中的值可以用(2)编码
  • (d) [0xffff_ffff_8000_0000,0xffff_ffff_ffff_ffff] 中的值可以用(2)和(3)编码。

除第三个情况外,所有情况至少都有两种可能的编码。
如果有多种编码可用,汇编程序通常会选择最短的一种,但并非总是如此。

对于GAS:
movabs[q]始终对应于(2)。
mov[q]对应于(a)和(d)情况下的(3),其他情况对应于(2)。
它不会为移动到64位寄存器而生成(1)。

要使其变为(1),我们必须使用等效的mov[l] $0xffffffff, %edi(我相信GAS不会将将64位寄存器的移动转换为低32位寄存器的移动)这是等效的。)


在16/32位时代,区分(1)和(3)并不是很重要(还in GAS it's possible to pick one specific form),因为它不是符号扩展操作,而是原始编码的伪像。 8086。

mov指令从未拆分成两种形式来说明(1)和(3),而是使用了一个mov,汇编程序几乎总是在(3)上选择(1) )。

使用具有64位立即数的新64位寄存器会使代码过于稀疏(并且很容易违反当前的最大16字节指令长度),因此不值得将(1)扩展为总是64位立即数。
取而代之的是(1)仍然具有32位立即数和零扩展(以打破任何错误的数据依赖关系),并且(2)在极少数情况下实际上需要64位立即数操作数的情况下引入。 借此机会,(3)也被更改为 still 立即采用32位,但也对其进行了符号扩展。
(1)和(3)应该足以满足最常见的立即数(例如1或-1)。

但是(1)/(3)和(2)之间的差异比(1)和(3)之间的过去差异还要深,因为(1)和(3)都具有相同大小的操作数, 32位(3)具有64位立即数。

为什么要有人为延长指令?
一个用例可能是填充,因此下一个循环为16/32字节的倍数。
这会牺牲前端的资源(指令缓存中更多的空间),而不是后端的资源(比不填充op指令要少的uOP)。

另一种且更常见的用例是仅需要生成机器代码模板的情况。
例如,在JIT中,可能需要准备要使用的指令序列,并仅在运行时填充立即数值。
在那种情况下,使用(2)将大大简化处理,因为对于所有可能的值总是有足够的空间。

另一种情况是某些修补功能,在软件的调试版本中,可以使用刚加载了(2)的寄存器中的地址间接进行特定调用,以便调试器可以轻松地将调用劫持到任何新目标。