微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

当操作数具有相同大小时,为什么 MOVZX 不起作用?

如何解决当操作数具有相同大小时,为什么 MOVZX 不起作用?

使用 Z2 dword ?mov eax,Z2 工作正常,但 movzx eax,Z2 给出“无效指令操作数”错误

在这里有点困惑:尽管 Z2eax 的大小相同, 为什么程序集不能为此接受 movzxmovzx 似乎特别希望操作数的大小不同。

设计这样的指令的原因是什么?

如果它被设计为简单地允许相同大小的操作数,那么编码不是更容易吗?

解决方法

它确实有效(在机器代码中),但效率低下。
这就是为什么大多数装配工阻止你用脚射击自己。


设计这样的指令的原因是什么?

从窄源数据执行零扩展。
这就是助记符中的 ZX 的意思。

如果您有相同大小的操作数,您应该使用 mov,
不要尝试使用零扩展或符号扩展复制指令。


就像 MOVSXD 一样,即使可以使用 MOVZX 操作码来编码等效于 mov r,r/m16 的指令,但出于效率原因,也不建议这样做。

喜欢 Intel says for MOVSXD不鼓励使用没有 REX.W 的 MOVSXD(它会编码 movsxd r32,r/m32。应该使用常规 MOV 而不是使用没有 REX.W 的 MOVSXD。(我从引用中去掉了“在 64 位模式下”,因为那是多余的;movsxd 只存在于 64 位模式中;操作码在其他模式下意味着别的东西。)


无论如何,是的,在 x86 机器代码中 movzx ax,bx 是可能的,但是汇编程序可以使您免于自己的麻烦并且拒绝汇编该低效指令。(2 字节操作码而不是 1 mov; movzx 是 386 中的新内容,所有 1 字节操作码在此之前已经用完。)

将源操作数(寄存器或内存位置)的内容复制到目标操作数(寄存器),并用零扩展该值。 转换后的值的大小取决于操作数大小属性。
https://www.felixcloutier.com/x86/movzx

我使用以下 NASM 源在我的 Skylake CPU 上对其进行了测试,编写的可能也与 MASM 一起组装。 (例如,db 66h 而不是在 o16 行上使用 movzx NASM 前缀。)

mov  edx,-1
xor  eax,eax
db   66h             ; operand-size prefix that we're not telling the assembler about
movzx  eax,dx

mov  ax,dx          ; for comparison

(超级简单,利用工具链的默认设置来解决这个从未打算成为合适程序的一次性使用。)

$ nasm -felf64 movzx.asm && ld -o movzx  movzx.o 
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
$ objdump -drwC -Mintel  ./movzx
...
  401000:       ba ff ff ff ff          mov    edx,0xffffffff
  401005:       48 b8 cc cc cc cc 44 33 22 11   movabs rax,0x11223344cccccccc
  40100f:       66 0f b7 c2             movzx  ax,dx
  401013:       66 89 d0                mov    ax,dx       # note it's shorter.  
          # Fun fact: we can see NASM picked the mov r/m16,r form,since the ModRM byte is different.

有趣的是,GNU Binutils 中的反汇编器(objdump -d 和 GDB)将其解码为 movzx ax,dx 或 AT&T 语法中的 movzww %dx,%ax

在静态可执行文件上使用 gdb ./movzx,我使用 layout regstarti / stepi 单步执行并查看寄存器更改:

66 0f b7 c2 movzx ax,dx 正常执行,并且
将 RAX 从 0x11223344cccccccc 更改为 0x11223344ccccffff,证明它的行为与 16 位 mov 完全一样,没有触及 RAX 的任何高位字节。 (包括不隐式零扩展 RAX 的高 32 位,就像写入 EAX 那样。)

(然后退出 GDB,因为我没有包含退出的代码,只有我真正想要单步执行的代码。)


这对于 movzx al,dl 是不可能的 - 66 或 REX 前缀选择 16 位与 32 位与 64 位操作数大小以覆盖模式的默认值,但 8 位操作数-大小仅通过操作码设置。没有前缀可以将指令覆盖为 8 位操作数大小。当然,没有带有 8 位目标操作数的 movzx 形式。 (如果您想将半字节零扩展为一个字节,请复制并and reg,0x0f。)


允许它的汇编器:在 .intel_syntax 模式下只是 GAS?

NASM 和 YASM 拒绝 movzx ax,dx
clang 也是如此(使用 .intel_syntax noprefix)。
但是 llvm-objdump -d 会像 GNU Binutils 一样反汇编它。

但是 GNU Binutils 不仅反汇编了它(Intel movzx ax,dx,AT&T movzww %dx,%ax),它(GNU as接受 Intel 语法版本。气体:

.intel_syntax noprefix
    movzx  ax,dx             # works,producing the above machine code.

.att_syntax
    movzw   %dx,%ax         # Error: operand size mismatch for `movzw'
    movzww  %dx,%ax         # Error: invalid instruction suffix for `movzw'

相关:

,

似乎 movzx 特别希望操作数的大小不同

movzx 特别希望目标大于源。

为什么汇编不能为此接受 movzx

理论上;没有理由汇编器不能接受“具有相同大小的操作数的movzx(助记符)”并以静默方式生成mov(操作码)。

设计这样的指令的原因是什么?

人类会犯错。对于所有编程语言,最好尽快检测并报告错误(理想情况下,在 IDE 中,因此您甚至不必在发现错误之前进行编译或汇编;并且从未被普通用户检测到并通过 .软件发布后的错误报告)。

对于“具有相同大小的操作数”的“movzx”,它更可能是一个错误(例如,程序员希望将较小的东西用零扩展成更大的东西,但他们错误地输入了一个操作数)而不是故意的(鉴于 mov 会更容易输入);所以汇编程序最好将其视为错误,以便(如果是错误)程序员早点知道。

请注意,在某些情况下,某些“由汇编程序完成的方便的无声替换”将是有益的。一个例子是movzx rax,eax,很明显,程序员希望用零扩展更小的东西以变成更大的东西;但鉴于 CPU 在默认情况下为零扩展,最好让汇编程序生成 mov eax,eax

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。