gpt4 book ai didi

assembly - x86-64 (AMD64) 架构中是否有默认操作数大小?

转载 作者:行者123 更新时间:2023-12-04 07:24:31 29 4
gpt4 key购买 nike

这是一个关于 x86-64 (AMD64) 架构中操作数大小覆盖前缀的问题。
这是一堆汇编指令(nasm)及其编码;新我的意思是 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 |
通过研究这些以及与其他寄存器相同的指令,我推断出以下内容。
“旧”和"new"寄存器之间存在配对。不完全:
   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)。
我可能没有完全正确地理解它,但我想我明白了它的要点。那么看起来操作数大小覆盖前缀覆盖的事实是没有它们指令将使用 32 位“旧”操作数。
但可以肯定的是,有些东西让我无法理解,否则:谈论“x86-64 版本的默认操作数大小为 64 位”(如 here )有什么意义?
或者有没有办法在 64 位机器上运行,将默认操作数大小设置为 32 或 64,如果是这样,如果我的程序适本地设置了机器,我会看到不同的编码?
另外:什么时候会使用 66H 操作数大小覆盖前缀?

最佳答案

是的,在 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 位未在 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/ re: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 隐式使用的默认堆栈地址大小是由 SS 选择器 IIRC 独立选择的。所以我认为你可以有正常的 32 位模式,其中 add eax, [edx] 是 2 个字节,除了 push/pop/call/ret 使用 ss:sp 而不是 ss:esp 。我从来没有尝试过设置。)

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

    在汇编源代码中,没有默认的 ,它必须由寄存器隐含或明确指定。
    除了一些默认为 push/pop 的汇编器,或者一些对其他情况有默认值的坏汇编器,包括 GNU 汇编器,用于诸如 add $1, (%rdi) 默认为 dword 之类的东西,只有在最近的版本中才会有警告。奇怪的是,GAS 在模棱两可的 mov 上确实出错了。 clang 的内置汇编器更好,在任何不明确的操作数大小时出错。

    关于assembly - x86-64 (AMD64) 架构中是否有默认操作数大小?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68289333/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com