【问题标题】:Is there a default operand size in the x86-64 (AMD64) architecture?x86-64 (AMD64) 架构中是否有默认操作数大小?
【发布时间】:2021-09-18 04:40:06
【问题描述】:

这是一个关于 x86-64 (AMD64) 架构中操作数大小覆盖前缀的问题。

这是一堆汇编指令(nasm)及其编码; new 我的意思是 r8, ..., r15 寄存器:

                                                                   67: address-size override prefix
                                                                   |
                                                                   |  4x: operand-size override prefix
                                                                   |  |
   ;   Assembler                   ; | Dst operand | Src operand | -- --
       mov      eax,ecx            ; | 32-bit      | 32-bit      |       89 C8     |
       mov      r8d,ecx            ; | 32-bit new  | 32-bit      |    41 89 C8     |
       mov      eax,r9d            ; | 32-bit      | 32-bit new  |    44 89 C8     |
       mov      r8d,r9d            ; | 32-bit new  | 32-bit new  |    45 89 C8     |
       mov      rax,rcx            ; | 64-bit      | 64-bit      |    48 89 C8     |
       mov      r8,rcx             ; | 64-bit new  | 64-bit      |    49 89 C8     |
       mov      rax,r9             ; | 64-bit      | 64-bit new  |    4C 89 C8     |
       mov      r8,r9              ; | 64-bit new  | 64-bit new  |    4D 89 C8     |

       lea      eax,[ecx]          ; | 32-bit      | 32-bit      | 67    8D 01     |
       lea      r8d,[ecx]          ; | 32-bit new  | 32-bit      | 67 44 8D 01     |
       lea      eax,[r9d]          ; | 32-bit      | 32-bit new  | 67 41 8D 01     |
       lea      r8d,[r9d]          ; | 32-bit new  | 32-bit new  | 67 45 8D 01     |
       lea      rax,[rcx]          ; | 64-bit      | 64-bit      |    48 8D 01     |
       lea      r8,[rcx]           ; | 64-bit new  | 64-bit      |    4C 8D 01     |
       lea      rax,[r9]           ; | 64-bit      | 64-bit new  |    49 8D 01     |
       lea      r8,[r9]            ; | 64-bit new  | 64-bit new  |    4D 8D 01     |

       push     rax                ; |             | 64-bit      |       50        |
       push     r8                 ; |             | 64-bit new  |    41 50        |

通过研究这些以及与其他寄存器相同的指令,我推断出以下内容。 “旧”和“新”寄存器之间存在配对。并非详尽无遗:

   AX <--> R8
   CX <--> R9
   DX <--> R10
   BX <--> R11
   BP <--> R13 

忽略大小前缀,指令字节不是指特定的寄存器,而是指寄存器对。例如:字节 89 C8 表示一条 mov 指令从源(ecx、rcx、r9d 或 r9)到目标(eax、rax、r8d 或 r8)。鉴于操作数必须同时为 32 位或 64 位宽,因此有八种合法的可能组合。操作数大小覆盖前缀(或不存在)指示这些组合中的哪一个是预期的组合。例如,如果前缀存在且为 44,则源操作数必须是 32 位新寄存器(在此示例中,然后折叠为 r9d),目标必须是 32 位旧寄存器(此处为 eax 信号)。

我可能没有完全正确,但我想我明白了它的要点。这样看来,操作数大小覆盖前缀所覆盖的事实是,如果没有它们,指令将使用 32 位“旧”操作数。

但可以肯定的是,有一些事情让我无法理解,否则:谈论“具有 64 位默认操作数大小的 x86-64 版本”(如 here)有什么意义?

或者有没有办法在 64 位机器上运行,将默认操作数大小设置为 32 或 64,如果是这样,并且如果我的程序适当地设置了机器,我会看到不同的编码?

另外:什么时候会使用 66H 操作数大小的覆盖前缀?

【问题讨论】:

  • 查看英特尔软件开发手册。然后解释了前缀的编码和含义。我以后可能会写一个真正的答案。
  • 40h4Fh 前缀称为 REX 前缀。它们可以指示 64 位操作数大小。它们还可以指示将高 8 个寄存器之一用于源或目标。我相信这些选项的任何组合都是可能的。
  • 66 前缀将操作数大小更改为 16 位。
  • 在机器码中是,大多数指令默认为 32 位,堆栈和跳转/调用指令默认为 64 位。在汇编源代码中,没有默认值,它必须由寄存器隐含或明确指定。 (除了一些默认为 push/pop 的汇编程序。)请注意,16 位 AX 对应于 16 位 R8W,而 RAX 和 R8 是由 REX 前缀区分的对。
  • @ecm 具体来说,REX 前缀编码四位状态(一位用于操作数大小,三位用于高位寄存器)。仅存在 REX 前缀就额外编码了您想要的 sildilsplbpl,而不是 ahchdhbh

标签: assembly x86-64 machine-code prefixes


【解决方案1】:

是的,在 64 位机器代码中,大多数指令64-bit for stack and jump/call instructions 的默认操作数大小为 32 位,loopjrcxz 也是 64 位。 (并且默认地址大小是 64 位,所以 add eax, [rdi] 是 2 字节指令,没有前缀。)不,默认值不可更改,你不能有 2 字节 add rax, rdx

64位模式下的操作数大小编码

  • 64 位操作数大小由 REX.W 发出信号(0x4? 在低半字节中设置高位,48..4f)。对于默认为其他值的操作码,清除 W 位的 REX 前缀永远不会将操作数大小覆盖为 32 位。 (喜欢push
  • 16 位操作数大小由 0x66 前缀表示,例如 imul ax, [r8], 123
  • 8 位操作数大小使用不同的操作码。 (8086 有 8 位和 16 位操作数大小;此后 8 位操作数大小的操作码保持不变。8086 的 16 位操作数大小操作码默认为模式和前缀相关。)

(在其他模式下,没有 REX,66 将其设置为非默认值。)

有趣的事实:loop and jrcxz are overridden 通过地址大小前缀而不是操作数大小隐式使用 ECX 而不是 RCX。 IIRC,这是有道理的,因为分支的操作​​数大小属性会影响它是否将 EIP 截断为 IP。

例如,上面那些 NASM 语法示例的 GNU .intel_syntax 反汇编。

objdump -drwC -Mintel foo
  401000:       6a 7b                   push   0x7b
  401002:       66 6a 7b                pushw  0x7b
  401005:       03 07                   add    eax,DWORD PTR [rdi]
  401007:       66 03 07                add    ax,WORD PTR [rdi]
  40100a:       48 03 07                add    rax,QWORD PTR [rdi]
  40100d:       66 41 6b 00 7b          imul   ax,WORD PTR [r8],0x7b

请注意,imul 示例使用了一个“高”寄存器,因此它需要一个 REX 前缀来表示 R8,而需要一个 66 前缀来表示 16 位操作数大小。 .W 位 not 设置在 rex 前缀中,它是 0x41 而不是 0x49

同时拥有 REX.W 和 0x66 前缀没有意义。在这种情况下,似乎 REX.W 前缀“获胜”。在 i7-6700k (Skylake) 上的 Linux GDB 中单步执行 66 48 05 40 e2 01 00 data16 add rax,0x1e240,单步使 RIP 指向整个指令的末尾(并将完整的立即数添加到 RAX),而不是将其解码为 add ax, 0xe240 和让 RIP 指向 4 字节立即数的中间。 (66 前缀是该操作码的长度变化,就像大多数具有 32 位立即数变成 16 位的一样。请参阅 https://agner.org/optimize/ 回复:LCP 停止。)

我让 NASM 从o16 add rax, 123456 发出它。 REX 前缀通常是正常的,并且带有 66 前缀,例如编码add r8w, [r15 + r12*4],需要在 REX 的低半字节中设置所有其他 3 个位。


  • 32 位 地址 大小由 0x67 前缀表示,例如 add eax, [edx]

它当然可以与操作数大小的东西结合,完全正交。

通常 32 位地址大小仅对 Linux x32 ABI (ILP32 in long mode to save cache footprint on pointer-heavy data structures) 有用,您可能希望从指针中截断高垃圾以确保地址数学正确包装以保持在低 4GiB 中,即使使用 32 位负数也是如此。

  401012:       67 03 04 ba             add    eax,DWORD PTR [edx+edi*4]

在其他模式下,67 将地址大小设置为非默认值。 16 位地址大小也意味着 ModRM 字节的 16 位解释,因此只允许 [bx|bp + si|di],没有 SIB 字节以实现 32 / 64 位寻址的灵活性。


模式和默认设置

不,在 64 位模式下无法更改默认值。 CS(或任何其他方法)选择的 GDT 条目中的不同位无关紧要。 AFAIK,https://en.wikipedia.org/wiki/X86-64#Operating_modes 中的表格是模式和默认操作数/地址大小的可能组合的完整列表。

只有一组设置完全允许 ​​64 位操作数大小。即使在任何传统模式下也不可能拥有像 16 位操作数、32 位地址大小这样的组合。

从硬件复杂性的角度来看,这是有道理的。它需要支持的事物组合越多,CPU 中已经很复杂且耗电的部分可能涉及的晶体管就越多。

(尽管 push/pop 隐式使用的默认 stack 地址大小是由 SS 选择器 IIRC 独立选择的。所以我认为您可以使用正常的 32 位模式,其中 add eax, [edx] 为 2字节,除了 push/pop/call/ret 使用 ss:sp 而不是 ss:esp。我从未尝试过设置。)


请注意,16 位 AX 对应 16 位 R8W,而 RAX 和 R8 是由 REX 前缀区分的对。


在汇编源代码中,没有默认值,它必须由寄存器隐含或明确指定。

除了一些默认为 push/pop 的汇编器,或一些在其他情况下默认为默认值的不良汇编器,包括用于 add $1, (%rdi) 默认为 dword 之类的 GNU 汇编器,仅在最近的版本中出现警告。奇怪的是,GAS 在模棱两可的mov 上出错。 clang 的内置汇编器更好,在任何不明确的操作数大小上都会出错。

【讨论】:

  • 请注意,6648 可以与 SSE 指令同时出现,其中 66 选择不同的数据组织。 IIRC 也有一些最近的特殊指令具有这种编码,将不得不查找。
  • @fuz:哦,是的,没错,当66 只是一个强制前缀,它本质上是操作码的一部分。在这一点上,它并没有真正充当操作数大小的前缀,即使它仍然是一个前缀并且可以根据需要以不同的顺序出现(尽管英特尔建议使用特定的顺序)。
  • 虽然它并不是操作码的一部分;它只是具有不同的功能(选择数据组织)。
  • @fuz:你的意思是pspd?是的,但它也用于整数 SSE2 / SSSE3 xmm 与 MMX 寄存器版本,如 paddb xmmpaddb mm。即使在 ps/pd 中,SSE2 cvtps2pd xmm 是(无前缀)NP 0F 5ASSE2 cvtpd2ps xmm66 0F 5A。对于像rep这样的其他强制性前缀,用它重载的指令包括F2 0F 38 F1crc32 r, r/m320F 38 F0 movbe等不同的东西。
  • 非常感谢您提供如此详细的答案。另一个问题:当使用 32 位值时,到目前为止,我更喜欢 mov eax,r8d 而不是 mov rax,r8,因为我错误地认为后者带前缀而不是前者。但现在在我看来,无论是性能还是其他方面,都应该没有区别?
猜你喜欢
  • 2018-09-25
  • 2016-10-23
  • 2011-10-03
  • 1970-01-01
  • 2019-09-21
  • 1970-01-01
  • 2015-06-06
  • 1970-01-01
相关资源
最近更新 更多