【问题标题】:data movement error clarification数据移动错误说明
【发布时间】:2018-04-24 22:43:22
【问题描述】:

我目前正在解决计算机系统第 3 版中的问题 3.3:从程序员的角度来看,我很难理解这些错误的含义...

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

movl %rax, (%rsp)movb %si, 8(%rbp) 给出错误,指出指令后缀和寄存器 ID 不匹配。

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 的

【问题讨论】:

  • 是的,如果您使用的是 64 位架构,地址长度必须为 64 位。
  • movb $0xf, (%ebx) 是正确的。您只需要一个地址大小覆盖即可使用它。你的汇编器可能不支持这个吗?
  • @fuz 不确定,但是如果地址大小被覆盖,movb $0xf, (%bx) 是否也正确?
  • @calebeja9 不可以。地址大小在长模式下只能是 32 位或 64 位。
  • 这是来自 CS:APP 第 3 版,“全球”版本吗?在练习题中充满了关于 x86-64 的错误声明。CS:APP example uses idivq with two operands? 有一些关于那本书的情况的信息,并且出版商雇用的无知的人,在不了解 x86-64 的情况下编造新问题。

标签: c assembly x86-64


【解决方案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 不是其中之一。

【讨论】:

【解决方案2】:

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 为什么 gcc 在将指针视为 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 字节 @ 987654326@.)

例外情况包括 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 创建了一个传统的堆栈帧,则存储到8(%rbp) 将覆盖低16位您在堆栈上的返回地址。

但在 Windows 或 Linux 的 x86-64 代码中,您不需要有 %rbp 指向那里,或者根本不需要持有有效的指针。它只是一个像%rbx 这样的调用保留寄存器,只要在返回之前恢复调用者的值,就可以随意使用它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-26
    • 1970-01-01
    • 1970-01-01
    • 2013-08-08
    • 2011-12-26
    • 2012-09-12
    • 1970-01-01
    • 2012-06-17
    相关资源
    最近更新 更多