数据移动错误澄清

时间:2018-04-24 22:43:23

标签: c assembly x86-64

我目前正在解决计算机系统第3版中的问题3.3:程序员的观点,我很难理解这些错误的意思......

movb $0xF, (%ebx)给出错误,因为ebx不能用作地址寄存器

movl %rax, (%rsp)和    movb %si, 8(%rbp)给出错误说明指令后缀和寄存器I.D。

之间不匹配

movl %eax, %rdx给出错误,指出目标操作数大小不正确

为什么我们不能使用ebx作为地址寄存器?是因为它的32位寄存器吗?如果是movb $0xF, (%rbx),那么以下行是否有效?因为rbx是64位寄存器吗?

有关指令后缀和寄存器ID不匹配的错误,是否出现此错误是因为它应该是movq %rax, (%rsp)movew %si, 8(%rbp)而不是movl %rax, (%rsp)movb %si, 8(%rbp)

最后,对于“目标操作数不正确的大小”的错误,这是因为目标寄存器是64位而不是32位?所以,如果代码行是movl %eax, %edx,那么错误就不会发生了?

任何启蒙都会受到赞赏。

这适用于x86-64

2 个答案:

答案 0 :(得分:1)

movb $0xF, (%ebx) gives an error because ebx can't be used as address register

ebx确实不能用作地址寄存器(对于x86-64),但rbx可以。 ebx是rbx的低32位。 64位代码的重点是地址可以是64位,因此尝试使用32位寄存器来引用存储器毫无意义。

movl %rax, (%rsp) and movb %si, 8(%rbp) gives error saying that 
theres a mismatch between instruction suffix and register I.D.

是的,因为您正在使用movl,所以'l'表示长,其中(在此上下文中)表示32位。但是,rax是64位寄存器。如果要从rax中写入64位,则应使用movq。如果要写32位,则应使用eax

movl %eax, %rdx gives an error saying that destination operand incorrect size

您正在尝试将32位值移动到64位寄存器中。有指示为您执行此转换(例如,请参阅cdq),但movl不是其中之一。

答案 1 :(得分:0)

movb $0xF, (%ebx)组合得很好(使用0x67地址大小前缀),并且如果ebx中的地址有效,则会正确执行。

它可能是一个错误(例如导致截断指针的段错误)或次优,但是如果你的书比那更强烈(就像它不能组装那样)那么你的书包含一个错误。

你使用它而不是movb $0xF, (%rbx)的唯一原因是%rbx的高位字节可能存在垃圾,例如in the x32 ABI (ILP32 in long mode),或者如果你是always uses address-size prefixes when targeting 32-bit-pointer mode even when addresses are known to be safely zero-extended的愚蠢编译器。

32位地址大小对于x32 ABI实际上是有用的,对于更常见的情况,索引寄存器保持高垃圾,例如, movl $0x12345, (%edi, %esi,4)

gcc -mx32可以在现实生活中轻松发出movb $0xF, (%ebx)指令。 (注意-mx32(长模式下的32位指针)与-m32不同(i386 ABI))

int ext();          // can't inline
void foo(char *p) { 
    ext();          // clobbers arg-passing registers
    *p = 0xf;       // so gcc needs to save the arg for after the call
}

gcc7.3 -mx32 -O3 on the Godbolt compiler explorer编译成

foo(char*):
    pushq   %rbx              # rbx is gcc's first choice of call-preserved reg.
    movq    %rdi, %rbx        # stupid gcc copies the whole 64 bits when only the low 32 are useful
    call    ext()
    movb    $15, (%ebx)       # $15 = $0xF
    popq    %rbx
    ret

mov $edi, %ebx本来会更好; IDK为什么要在将指针视为32位值时复制整个64位寄存器。遗憾的是x32 ABI从未在x86上真正流行起来,所以我想没有人会花时间让gcc为它生成出色的代码。

AArch64还有一个ILP32 ABI来节省指针数据的内存/缓存占用空间,所以gcc一般会在64位模式下的32位指针上变得更好(如果有任何工作,也可以从x86-64中获益) AArch64 ILP32改进了常见的跨架构部分。

  

所以,如果代码行是movl%eax,而不是%edx,则不会发生错误?

对,that would zero-extend EAX into RDX。如果您想签署 - 将EAX扩展到RDX,请使用movslq %eax, %rdx (aka Intel-syntax movsxd)

(差不多)所有x86指令都要求所有操作数大小相同。 (就操作数大小而言;许多指令都有一个8位或32位立即数的形式,其符号扩展为64位或指令的操作数大小。例如add $1, %eax将使用3-字节add imm8, r/m32 form。)

例外情况包括shl %cl, %eax和movzx / movsx。

在AT& T语法中,寄存器的大小必须与操作数大小后缀匹配(如果使用的话)。如果不这样做,则寄存器意味着操作数大小。例如mov %eax, %edxmovl相同。

没有寄存器源或目标的存储器+立即指令需要明确的大小:add $1, (%rdx)不会汇编,因为操作数大小不明确,但add %eax, (%rdx)addl(32位操作数大小。)

  

movew %si, 8(%rbp)

不,movw %si, 8(%rbp)会起作用:P但是请注意,如果你在函数入口上创建了一个带有push %rbp / mov %rsp, %rbp的传统堆栈帧,那么它将存储到{{1}将覆盖堆栈上返回地址的低16位。

但是对于Windows或Linux的x86-64代码,没有要求8(%rbp)指向那里,或者根本没有指向有效指针。它只是一个保留调用的寄存器,如%rbp,只要在返回之前恢复调用者的值,就可以用于任何你想要的。