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

Mat*_*usz 8 assembly x86-64 att

看完这个堆栈溢出的答案,而这个文件,我还是不明白之间的差别movqmovabsq.

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

将立即数据移动到64位寄存器可以通过movq指令进行,该指令将签署扩展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
Run Code Online (Sandbox Code Playgroud)

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

打印0xffffffff.(这适用于较大的值,例如,如果你输入一些额外的"F").movabsq生成相同的输出.

Clang是在推断我想要的吗?如果是,是否有仍然是受益movabsq过度movq

我错过了什么?

Mar*_*oom 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位寄存器.

请注意在装配级别如何存在歧义,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]对应于(3)对于情况(a)和(d),对于(2)对应于其他情况.
它永远不会生成(1)移动到64位寄存器.

为了使它接收(1)我们必须使用mov[l] $0xffffffff, %edi相同的(我相信GAS不会将移动转换为64位寄存器,将其转换为较低的32位寄存器,即使这相当).


在这两者之间的16位/ 32位时代区别(1)和(3)不被认为非常重要的(但在GAS有可能选择一个特定的形式),因为它不是一个符号扩展操作,但原始编码的伪影在8086年.

mov指令从未分成两种形式来解释(1)和(3),而是一个单独mov使用,汇编程序几乎总是在(3)上选择(1).

使用具有64位立即数的新64位寄存器会使代码太稀疏(并且很容易违反当前最大16字节的指令长度),因此扩展(1)总是占用64位是不值得的即时.
相反,(1)仍然具有32位立即数和零扩展(以打破任何错误的数据依赖性),并且(2)是在实际需要64位立即数操作数的罕见情况下引入的.
抓住机会,(3)也被改为仍然采取32位立即,但也签署扩展它.
(1)和(3)应足以满足最常见的中间体(如1或-1).

然而,(1)/(3)和(2)之间的差异比(1)和(3)之间的过去差异更深,因为虽然(1)和(3)都具有相同大小的操作数,32位,(3)具有64位立即数操作数.

为什么人们会想要人为加长的教学?
一个用例可以是填充,以便下一个循环是16/32字节的倍数.
这牺牲了前端的资源(指令缓存中的更多空间)用于后端的资源(比没有op指令填充更少的uOP).

另一个更常见的用例是当只需要生成机器代码模板时.
例如,在JIT中,可能希望准备要使用的指令序列并仅在运行时填充immediates值.
在这种情况下,使用(2)将极大地简化处理,因为总有足够的空间容纳所有可能的值.

另一种情况是一些打补丁的功能,在软件的具体调用可能间接地与一个地址在刚刚装载寄存器进行的调试版本(2),以便调试器可以轻松劫持调用任何新的目标.