我目前正在解决计算机系统第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
答案 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, %edx
与movl
相同。
没有寄存器源或目标的存储器+立即指令需要明确的大小: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
,只要在返回之前恢复调用者的值,就可以用于任何你想要的。