x86-64 (AMD64) 架构中是否有默认操作数大小? [英] Is there a default operand size in the x86-64 (AMD64) architecture?

查看:34
本文介绍了x86-64 (AMD64) 架构中是否有默认操作数大小?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

This is a question about the operand-size override prefixes in the x86-64 (AMD64) architecture.

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

Here is a bunch of assembler instructions (nasm) and their encodings; by new I mean the r8, ..., r15 registers:

                                                                   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        |

通过研究这些以及与其他寄存器相同的指令,我推断出以下内容.旧"和新"寄存器之间存在配对.不完全:

From studying these and the same instructions with other registers, I deduce the following. There is a pairing between ‘old’ and ‘new’ registers. Non-exhaustively:

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

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

Ignoring the size prefix, the instruction bytes do not refer to particular registers, but to pairs of registers. As an example: the bytes 89 C8 indicate a mov instruction from a source which is either ecx, rcx, r9d, or r9, to a destination which is either eax, rax, r8d, or r8. Given that the operands must be both 32- or 64-bits wide, there are eight legal possible combinations. The operand-size override prefix (or absence thereof) indicates which of those combinations is the intended one. For instance if the prefix is present and is 44, then the source operand must be a 32-bit new register (in this example then collapsing to r9d) and the destination must be a 32-bit old register (here then signalling eax).

我可能没有完全正确地理解它,但我想我明白了它的要点.那么看来,操作数大小覆盖前缀覆盖的事实是,没有它们,指令将使用 32 位旧"操作数.

I may not have got it totally right, but I think I get the gist of it. It would appear then that what the operand-size override prefixes do override is the fact that without them the instruction would use 32-bit ‘old’ operands.

但可以肯定的是,有些东西让我无法理解,否则:谈论默认操作数大小为 64 位的 x86-64 版本"(例如 这里)?

But for sure, there is something that escapes me, otherwise: what sense then does it make to talk about "a version of x86-64 with a default operand-size of 64-bit" (like here)?

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

Or is there a way, running on a 64-bit machine, to set the default operand size to either 32 or 64, and if so, and if my program set the machine appropriately, I would see different encodings?

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

Also: when would the 66H operand-size override prefix be used?

推荐答案

在 64 位机器码中是的,大多数指令的默认操作数大小为 32 位,64 位堆栈和跳转/调用指令,以及 loopjrcxz 的 64 位.(并且默认地址大小是 64 位,所以 add eax, [rdi] 是一个 2 字节指令,没有前缀.)不,默认值是不可更改的,你不能有 2 字节 add rax, rdx.

Yes in 64-bit machine code, the default operand-size is 32-bit for most instructions, 64-bit for stack and jump/call instructions, and also 64-bit for loop and jrcxz. (And the default address-size is 64-bit, so add eax, [rdi] is a 2-byte instruction, no prefixes.) And no, the defaults are not changeable, you can't have 2-byte add rax, rdx.

  • 64 位操作数大小由 REX.W 发出信号(0x4?,高位设置在低半字节中,48..4f).对于默认为其他值的操作码,清除 W 位的 REX 前缀永远无法将操作数大小覆盖为 32 位.(比如push)
  • 16 位操作数大小由 0x66 前缀表示,例如 imul ax, [r8], 123
  • 8 位操作数大小使用不同的操作码.(8086 有 8 位和 16 位操作数大小;8 位操作数大小的操作码从那时起没有改变.8086 的 16 位操作数大小操作码的默认值取决于模式和前缀.)
  • 64-bit operand-size is signalled by REX.W (0x4? with the high bit set in the low nibble, 48..4f). A REX prefix with the W bit cleared can never override the operand-size to 32-bit for opcodes where it defaults to something else. (Like push)
  • 16-bit operand-size is signalled by a 0x66 prefix, like imul ax, [r8], 123
  • 8-bit operand-size uses different opcodes. (8086 had 8 and 16-bit operand-sizes; the opcodes for 8-bit operand size are unchanged since then. 8086's opcodes for 16-bit operand-size have their default being mode and prefix dependent.)

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

(In other modes, there is no REX, and 66 sets it to whatever the non-default is.)

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

Fun fact: loop and jrcxz are overridden to use ECX instead of RCX implicitly by an address-size prefix, not operand-size. IIRC, this makes some sense because the operand-size attribute of a branch affects whether it truncates EIP to IP or not.

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

For example, GNU .intel_syntax disassembly of those NASM-syntax examples from above.

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.

Note the imul example used a "high" register so it needed a REX prefix to signal R8, separate from needing a 66 prefix to signal 16-bit operand-size. The .W bit is not set in the rex prefix, it's 0x41 not 0x49.

同时使用 REX.W 和 0x66 前缀是没有意义的.似乎 REX.W 前缀wins"在这种情况下.在 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 停顿.)

It doesn't make sense to have both REX.W and a 0x66 prefix. It seems that the REX.W prefix "wins" in that case. Single-stepping 66 48 05 40 e2 01 00 data16 add rax,0x1e240 in Linux GDB on an i7-6700k (Skylake), the single-step leaves RIP pointing to the end of that whole instruction (and adding the full immediate to RAX), not decoding it as add ax, 0xe240 and leaving RIP pointing into the middle of the 4-byte immediate. (A 66 prefix is length-changing for that opcode, like most that have a 32-bit immediate which becomes 16-bit. See https://agner.org/optimize/ re: LCP stalls.)

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

I got NASM to emit that from o16 add rax, 123456. REX prefixes in general are normal and fine with a 66 prefix, e.g. to encode add r8w, [r15 + r12*4], needing all 3 other bits to be set in the REX's low nibble.

  • 32 位 地址 大小由 0x67 前缀表示,例如 add eax, [edx].
  • 32-bit address size is signalled by a 0x67 prefix, like add eax, [edx].

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

It can of course be combined with operand-size stuff, totally orthogonal.

通常 32 位地址大小仅对 Linux x32 ABI(ILP32 在长模式下保存)有用缓存占用大量指针的数据结构),您可能希望从指针中截断高垃圾以确保地址数学正确包装以保持低 4GiB,即使是 32 位负数.

Normally 32-bit address size is only useful for the Linux x32 ABI (ILP32 in long mode to save cache footprint on pointer-heavy data structures) where you may want to truncate high garbage from a pointer to make sure address math correctly wraps to stay in the low 4GiB, even with 32-bit negative numbers.

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

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

In other modes, 67 sets address size to the non-default. 16-bit address-size also implies 16-bit interpretation of the ModRM byte, so only [bx|bp + si|di] are allowed, no SIB byte to allow the flexibility of 32 / 64-bit addressing.

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

No, the defaults can't be changed in 64-bit mode. Different bits in the GDT entry selected by CS (or any other method) won't matter. AFAIK, the table in https://en.wikipedia.org/wiki/X86-64#Operating_modes is a complete list of the possible combinations of modes and default operand/address sizes.

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

There's only one set of settings that allows 64-bit operand-size at all. It's not possible even in any legacy mode to have a combo like 16-bit operand, 32-bit address size.

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

This makes some sense from a hardware-complexity perspective. The more different combos of things it needs to support, the more transistors might be involved in an already complex and power-intensive part of the CPU.

(虽然 push/pop 隐式使用的默认 stack 地址大小是由 SS 选择器 IIRC 独立选择的.所以我认为你可以拥有普通的 32 位模式,其中 add eax, [edx] 是 2 个字节,除了 push/pop/call/ret 使用 ss:sp 而不是 ss:esp.不是我所拥有的曾经尝试过设置.)

(Although the default stack address size used implicitly by push/pop is selected independently by the SS selector, IIRC. So I think you can have normal 32-bit mode where add eax, [edx] is 2 bytes, except with push/pop/call/ret using ss:sp instead of ss:esp. Not something I've ever tried setting up.)

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

Note that 16-bit AX corresponds to 16-bit R8W, while RAX and R8 are the pair distinguished by a REX prefix.

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

In assembly source, there's no default, it must be implied by a register or specified explicitly.

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

Except for some assemblers having a default for push/pop, or a few bad assemblers that have a default for other cases, including the GNU assembler for things like add $1, (%rdi) defaulting to dword, with a warning only in recent versions. GAS does error on ambiguous mov, strangely. clang's built-in assembler is better, erroring on any ambiguous operand-size.

这篇关于x86-64 (AMD64) 架构中是否有默认操作数大小?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆