AMD monitorx 指令的正确语法是什么?

What is the correct syntax for AMD monitorx instruction?

Ryzen 支持 monitorx 指令,如 cpuid 标志所示。不幸的是,visual studio masm 汇编程序似乎不喜欢这些指令,并且关于如何使用它们的在线文档非常少。

以下代码(非常基于AMD自己的文档)报错A2070“invalid instruction operands:

push rbx
mov eax, 5844h
mov ecx, 0
mov edx, 0
monitorx eax, ecx, edx
pop rbx
ret

我知道这段代码不是很有用,但它不应该引发构建时错误,这有什么关系?

问题是 eax、ecx 和 edx 是 32 位寄存器,但它是在 64 位模式下组装的。因为第一个操作数是指针大小,所以它必须是 64 位。以下代码适用于 64 位程序:

push rbx
mov eax, 5844h
mov ecx, 0
mov edx, 0
monitorx rax, rcx, rdx
pop rbx
ret

在机器代码中,操作数是隐式的。在汇编语法中,普通 monitorx 适用于大多数汇编程序。可以指定操作数来记录指令,或者在某些汇编程序中指定对 address-size.

的覆盖

AMD's manual 明确表示 ECX 和 EDX 是 32 位操作数。 (他们说没有定义任何提示或扩展,所以 ECX 必须为 0 否则#GP,而 EDX 被当前的 CPU 忽略。)

对于地址操作数,他们将其记录为 rAX,我认为这意味着它可以是 EAX 或 RAX。在伪代码示例中,他们使用 MONITORX EAX, ECX, EDX,但 32 位 EAX 意味着这是一个 32-bit-mode 示例。

这是他们的第 3 卷手册,2021 年 11 月修订版 3.33,最新链接来自 https://developer.amd.com/resources/developer-guides-manuals/


NASM 2.15.05(在 version 2.12.01 中添加了对 monitorx 的支持)

操作数列表可以指定也可以省略。如果指定,第一个操作数必须是 EAX 或 RAX,接下来的两个 必须 是 ECX 和 EDX。 (指定RCX或RDX是错误的)。

但是 NASM 不会通过使用 EAX 在 64 位模式下推断出 32 位 address-size。
可以使用 NASM 前缀指定段覆盖。
来自 nasm -l/dev/stdout -felf64 foo.asm 的示例列表:

     1 00000000 0F01FA             monitorx
     2 00000003 0F01FA             monitorx rax, ecx, edx
     3 00000006 0F01FA             monitorx eax, ecx, edx       ; address-size override not inferred from EAX

     4 00000009 670F01FA           a32 monitorx                 ; address-size prefix, NASM style
     5 0000000D 640F01FA           fs monitorx rax, ecx, edx    ; segment override works, too

ndisasm -b64 将其反汇编为 monitorxfs monitorxa32 monitorx,而不列出隐式操作数。 GNU Binutils objdump-Mintel 语法模式下相同,但在 AT&T 模式下列出操作数。 (向后,以 %rax%eax 作为第一个操作数,而不是像 AT&T 语法应该的那样反转!)


YASM 太旧了,很多年没更新了。我还没有尝试过 FASM。


GNU 汇编程序 (GNU Binutils) 2.36.1

与 NASM 不同,GAS 确实从 RAX 与 EAX 中推断出 address-size。 (或者在 32 位模式下,从 monitorx ax, ecx, edx 推断 16 位地址大小)

GAS 检查最后两个操作数的大小,只是它们是 ECX、EDX 的某个版本(dl/dh 除外)。例如,monitorx rax, rcx, dx 毫无怨言地组装。对最后一个操作数使用 ebxdl 给出了“错误:'monitorx' 的操作数类型不匹配”,这毫无用处,因为问题不在于类型,而在于寄存器。 (这是典型的 GAS 错误消息,对人类的帮助通常不如 NASM。)

英特尔语法模式,.intel_syntax noprefix
objdump -d -Mintel disassembly   |  source
                                 |    .intel_syntax noprefix
0f 01 fa      monitorx           |    monitorx
0f 01 fa      monitorx           |    monitorx rax, ecx, edx
67 0f 01 fa   addr32 monitorx    |    monitorx eax, ecx, edx       # address-size inferred from EAX
67 0f 01 fa   addr32 monitorx    |    addr32 monitorx              # address-size prefix
64 0f 01 fa   fs monitorx        |    fs monitorx rax, ecx, edx    # segment override works, too
AT&T 语法模式

GAS 令人惊讶地使用与 Intel 语法相同的 operand-order,而不是像您对 AT&T 语法所期望的那样颠倒过来。所以 %rax / %eax 仍然是第一个。

GNU Binutils objdump 在反汇编为 AT&T 语法时选择包含操作数,这与 -Mintel 不同。

 objdump -d -Matt  disassembly (trimmed)   |   source
-------------------------------------------+---------------------------
0f 01 fa      monitorx %rax,%ecx,%edx      | monitorx
0f 01 fa      monitorx %rax,%ecx,%edx      | monitorx %rax, %ecx, %edx
67 0f 01 fa   monitorx %eax,%ecx,%edx      | monitorx %eax, %ecx, %edx       # address-size inferred from EAX
67 0f 01 fa   monitorx %eax,%ecx,%edx      | addr32 monitorx              # address-size prefix
64 0f 01 fa   fs monitorx %rax,%ecx,%edx   | fs monitorx %rax, %ecx, %edx    # segment override works, too

clang 13.0.0 与 LLVM 的 built-in 汇编器 / llvm-objdump

llvm-objdump 给出无用的输出,不表示存在 address-size 或段覆盖:

       0: 0f 01 fa                      monitorx
       3: 0f 01 fa                      monitorx
       6: 67 0f 01 fa                   monitorx
       a: 67 0f 01 fa                   monitorx
       e: 64 0f 01 fa                   monitorx

同样,LLVM 的 built-in 汇编程序似乎根本不接受操作数,只是说“指令操作数无效”。

             |  .intel_syntax noprefix
0f 01 fa     |  monitorx
             |  #monitorx rax, ecx, edx     # error
             |  #monitorx eax, ecx, edx     # error
67 0f 01 fa  |  addr32 monitorx             # address-size prefix works
64 0f 01 fa  |  fs monitorx                 # segment override works

在 AT&T 语法模式下同样处理,无论是否反转 operand-list。它不接受 monitorx %edx, %ecx, %raxmonitorx %rax, %ecx, %edx.

无论哪种方式,LLVM 都接受 monitorx 并正确组装前缀。
只有 LLVM-objdump 反汇编程序无法以任何方式显示前缀。