【问题标题】:Intel REX Encoding of PUSHPUSH 的 Intel REX 编码
【发布时间】:2019-06-28 09:21:04
【问题描述】:

GAS 为以下指令提供以下编码:

push rbp    # 0x55
push rbx    # 0x53
push r12    # 0x41 0x54
push r13    # 0x41 0x55

来自AMD64 spec(第313页):

PUSH reg64 50 +rq  将 64 位寄存器的上下文推入堆栈。

由于 rbprbx 的偏移量分别为 5 和 3,因此前两个编码是有意义的。不过,我不明白最后两种编码是怎么回事。

我知道0x40-0x4f 是一个 REX 前缀,0x41 设置了REX.B 位(根据此external reference,它是MODRM.rmSIB.base 的MSB 的扩展)。规范提到要访问所有 16 个 GPR,您需要使用 REX,但目前还不清楚截止点在哪里。

从查阅 MODRM 和 SIB 的文档来看,我认为没有使用 SIB,因为它的目的是使用基数 + 偏移寄存器进行索引(虽然老实说,我无法真正说出您如何区分 MODRM 和SIB 仅给出编码)。

所以,我怀疑这里使用了 MODRM。目前只考虑push r12 (0x41 0x54)(并注意到r12 有偏移12),我们有:

+----------------+--------------------+
| 0x41           | 0x54               |
+----------------+--------------------+
| REX            | MODRM              |
+--------+-------+-----+--------+-----+
| Prefix | WRXB  | mod | reg    | rm  |
| 0100   | 0001  | 01  | 01   0 | 100 |
+--------+-------+-----+--------+-----+

REX.B + MODRM.rm = 0b1100 = 12 所以这表明那是源寄存器(r12 = 偏移量 12)。如果忽略external (unofficial) reference中的所有表,REX.R + MODRM.mod + MODRM.reg = 0b00101 = 5,这是push指令库0x50的第一个半字节。

所以,我认为我已经倒退了,但我不明白我将如何获得像 0x41 0x54 这样的编码。从AMD reference,图1-10(第54页)有一个脚注,如果MODRM.mod = 01 or 10,那么这个字节“包括一个由指令位移字段指定的偏移量”。这也许会暗示为什么我们有指令偏移REX.R + MODRM.mod + MODRM.reg = 0b00101 = 5。但是,为什么MODRM.mod 是指令偏移量的一部分?如果必须包含它,那么采用这种偏移形式的指令仅限于前缀 0b010x10。这不可能吧?

tl;博士

  • REX 编码对于push 之类的指令实际上是如何工作的?
  • 需要 REX 前缀的指令偏移截止值是多少? (有文件证明我不能像 push rbppush rbx 那样为 push r12 执行 0x50 + 12 吗?)
  • 为什么MODRM.mod包含在指令库的前缀中? (或者这完全正确吗?)
  • 这对于像pop 这样的类似指令是否一致? (我怎么知道哪些指令支持这一点?它是否适用于所有具有XX +xx 形式的操作码的指令?)
  • 这在官方手册中的什么地方有记载?
  • 如何区分 REX 前缀后跟 MODRM 还是 SIB 字节?
  • 是否有更好的文档可以分步列出这些过程,而不是让您在几页之间从一个表跳到另一个表?

【问题讨论】:

  • 你不能做+ 12,因为rm字段只有3位,所以它的最大值是7。REX中的B位是第四位。你可以认为它的意思是“给 rm 加 8”。
  • @RaymondChen 对,我明白为什么对于 MODRM,您需要 REX 中的额外 B 位。我不明白为什么你不能只做0x50 + 12(推+r12没有REX的偏移量),因为第二个半字节足以存储所有的寄存器偏移量。另外,当规范只谈到MODRM.reg“用于扩展操作编码”时,为什么指令偏移REX.R + MODRM.mod + MODRM.reg(第54页,“ModRM.reg(Bits[5:3])。”)?
  • 您的所有问题都可以通过仔细阅读手册(如果 AMD 过于含糊,请尝试英特尔)或阅读 Raymond 的评论来回答。 ModRM 对于 16 个寄存器来说太小了,REX.B 扩展了它。有些问题似乎暗示你浏览手册太快了,我觉得你,在页面之间跳转很尴尬;尝试在文本编辑器中记笔记或打印表格(如果可以的话):)
  • @BaileyParker 50+r 只有一个字节长,5 位操作码和 3 位寄存器号,所以没有 REX 就无法编码 r12。编码是从 16 位和 32 位 x86 时代发明的,它们只有 8 个寄存器,不像在过渡到 x86-64 时它们会给你另一个位
  • @phuclv 啊!我错误地理解为操作码为 4,寄存器号为 4。这解释了为什么 R8 以下的所有寄存器都不需要 REX。谢谢!

标签: assembly x86-64 intel gnu-assembler machine-code


【解决方案1】:

这里显然没有 ModRM 字节,因为 整个 指令是一个字节。没有操作码字节就不可能有 ModRM。

push reg/pop reg 短格式将 3 位寄存器代码嵌入操作码字节。这就是50 + rq 的意思。 (与使用 ModRM 的 FF /6 push r/m64 编码不同;您可以使用它对寄存器操作数进行编码以使指令更长,但通常您只会将其用于 push qword [rdi] 或其他东西)。

它与 16 / 32 位格式相同,这就是为什么 x86-64 需要一个额外位(来自 REX 前缀)来编码具有 4 位代码的“新”/高位寄存器之一设置了前导位。

OSdev 省略了这个案例,只提到了ModRM.rmSIB.base


Intel 的 vol.2 手册 PDF 记录了编码:

3.1.1.1 Opcode Column in the Instruction Summary Table (Instructions without VEX Prefix)

  • ...

  • +rb+rw+rd+ro——表示 opcode 字节的低 3 位用于对寄存器操作数进行编码,不带 modR/M 字节。指令列出对应的十六进制值 操作码字节的低 3 位为 000b。在非 64 位模式下,一个 寄存器代码,从 0 到 7,被添加到十六进制值 操作码字节。 在 64 位模式下,表示 REX.b 的四位字段 opcode[2:0] 字段对指令的寄存器操作数进行编码。 “+ro”仅适用于 64 位模式。代码见表3-1。

表 3-1 使用与 ModRM 和 SIB 中的寄存器编号相同的编码方案,这不足为奇,但英特尔全力以赴,并为所有操作数大小提供了一个完整的整数寄存器表。包括AH/BH/CH/DH,因为mov ah, 1可以使用2字节短格式。

我已从“四字寄存器(仅限 64 位模式)”列中摘录了相关行:

From Intel's Table 3-1. Register Codes Associated With +rb, +rw, +rd, +ro
        reg    REX.B  Reg Field
        RBX    None    3

        RBP    None    5

        R12    Yes     4
        R13    Yes     5

有趣的事实:在英特尔的手册中,他们实际上使用50 + rd 而不是50 + ro 来表示PUSH r64,与32 位模式下的push r32 相同。 https://www.felixcloutier.com/x86/push.


这对于类似 pop 的指令是否一致? (我怎么知道哪些指令支持这一点?它是否适用于所有具有 XX +xx 形式的操作码的指令?)

是的。 push/pop regmov reg,immxchg eax, r32 / xchg rax, r64 都使用相同的编码,使用 3 个操作码位对寄存器进行编码。

如果我们可以将这 8 个 xchg 操作码返回给更有用的东西(比如 64 位模式下更紧凑的 VEX 或 EVEX 前缀),那就太好了,但是当 AMD 保守地使用 AMD64 时,这艘船航行了,主要是保持机器代码尽可能类似于 32 位模式。不过,他们确实回收了 0x4? inc/dec reg 操作码用作 REX 前缀。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多