【问题标题】:Using 8-bit registers in x86-64 indexed addressing modes在 x86-64 索引寻址模式下使用 8 位寄存器
【发布时间】:2017-01-03 13:06:39
【问题描述】:

是否可以在 x86-64 的索引寻址模式中使用 8 位寄存器 (al, ah, bl, bh, r8b)?例如:

mov ecx, [rsi + bl]
mov edx, [rdx + dh * 2]

特别是,这将允许您使用寄存器的低 8 位作为 0-255 偏移量,这对某些内核可能很有用。

我翻阅了英特尔手册,但它们并未明确说明这一点,但它们提供的所有示例都只有 32 位或 64 位基址和索引寄存器。在 32 位代码中,我只看到 16 或 32 位寄存器。查看 mod-r/m 和 SIB 字节编码的细节似乎也指向“否”,但这已经足够复杂,有足够多的极端情况,我不确定我是否做对了。

我最感兴趣的是 x86-64 行为,但当然,如果它在 32 位模式下可行,我只想知道。

作为一个附加问题太小并且与值得另一篇文章相关 - 16 位寄存器可以用于基址或索引吗?例如,mov rax, [rbx + cx]。我的调查指向与上述基本相同的答案:可能不是

【问题讨论】:

  • 不可能,两个寄存器的大小都必须与地址大小相同,因此也排除了[reg16 + disp32]。你需要一个movzx。显示编码的表格确实列举了所有种可能的编码。
  • 是的,表格显示了诸如 rax/eax/ax/al 之类的内容。所以它让我的希望保持在高位。您可以计算位并看到只有三个可用于选择寄存器以猜测只有一种大小可用,但是您必须检查所有不同字节的大小变化位,并读取所有 REX 前缀的详细信息,等等。但是,是的,我已经很怀疑了。
  • 寻址模式表不会显示al,因为没有 8 位寻址模式。不过,我理解这个问题的动机:当我刚接触 x86 时,我一直想知道是否有我不知道的寻址模式。但事实证明只有[base + idx*scale + disp8/disp32],或者省略这三个组件中的一个或两个的任何子集。 (加上 64 位模式下的相对于 RIP)。这就是 I wrote this answer 的原因,这可能是一个很好的文档主题。
  • 对。不过,我从来没有在英特尔文档中找到适合 64 位寻址模式的表格。当前英特尔开发手册第 2A 卷第 2.1.5 节中有表 2-3,其中涵盖了 32 位情况,但我从未找到 64 位情况的对应表。我在搜索的早期被this page 进一步抛弃,这清楚地表明[disp + reg8 + reg32*scale] 寻址模式,这正是我想要的(在32 位模式下)。看起来这是一个错字,他们的意思是disp8 + reg32 ...
  • 嗯, xlat(或xlatb)的奇怪案例。它确实(隐式地)使用8 位寄存器al 作为[r/e]bx 隐式指向的表的索引。不幸的是,它非常浪费编码空间,并且只能加载到al 寄存器中。

标签: assembly x86 x86-64 addressing-mode


【解决方案1】:

不,您不能在 64 位模式下使用 8 位或 16 位寄存器进行寻址计算,也不能在 32 位模式下使用 8 位寄存器。您可以在 32 位模式下使用 16 位寄存器,在 64 位模式下使用 32 位寄存器,通过使用0x67 地址大小前缀字节。

(但使用较窄的寄存器会使整个地址变窄,而不是相对于 32 位数组地址的 16 位索引。任何寄存器都需要与地址的宽度相同,您通常希望与模式匹配你进去了,除非你在地址空间的低 16 位或低 32 位有东西。)

This table 很好地总结了操作数和地址大小的各种选项。一般模式是默认地址大小与当前模式相同(即32位模式下32位,64位模式下64位)1,然后如果包含0x67 前缀,地址大小更改为通常大小的一半(即,32 位模式下为 16 位,64 位模式下为 32 位)。

这是上面链接的完整表格的摘录,仅显示了 64 位长模式行为,对于 REX.W0x66 操作数和 0x67 地址大小前缀的各种值:

REX.W 0x66 prefix (operand) 0x67 prefix (address) Operand size (footnote 2) Address size
0 No No 32-bit 64-bit
0 No Yes 32-bit 32-bit
0 Yes No 16-bit 64-bit
0 Yes Yes 16-bit 32-bit
1 Ignored No 64-bit 64-bit
1 ignored Yes 64-bit 32-bit

1 这似乎很明显,但这与 operand 大小在 64 位模式下的工作方式相反:大多数默认为 32 位,即使在 64-位模式,并且需要一个 REX 前缀 来将它们提升为 64 位。

2Some instructions 默认为 64 位操作数大小,没有任何 REX 前缀,特别是 pushpopcall 和条件跳转,正如彼得在下面指出的那样,这会导致奇怪的情况,其中至少有一些指令(包括pushpop)不能被编码为使用 32 位操作数,但可以使用 16 位操作数(带有 0x66 前缀)。

【讨论】:

  • 请注意,在长模式下,32 位推送/弹出是不可编码的。英特尔手册中的措辞暗示 REX.W=0 可以修改操作数大小,但它实际上因非法指令而出错。不过,PUSHW 仍然可以使用操作数大小的前缀进行编码。无论如何,PUSH/POP 不只是默认为 64 位操作数大小,它们很特别。
  • 更新了脚注。太糟糕了,因为我认为pushpop 是比剩下的(call/jcc)更好的例子,因为这很明显,因为 call/jcc 的“操作数”在概念上是一个地址(即使它表现为操作数用于编码)。
  • 你走得太远了。操作数大小前缀仍然有效。 push axpush rax 是可编码的,只有 push eax 不是。不过,我从未尝试过修改 CALL 指令。 Intel's table 表示 CALL r/m32 在长模式下不可编码。然而,远调用仍然可以使用不同的操作数大小。但是CALL m16:64需要一个REX前缀,默认是CALL m16:32。所以这不是一个可以覆盖到 32 的 64 位默认值的示例。
  • Bleh,我把它改回来了(原来的措辞仍然是正确的,即使它实际上没有提到 32 位不能用 REX.W 编码),并附上关于 no 32 的注释- 位版本的推送/弹出。脚注实际上并没有说 REX.W 位具有与通常相反的功能:它允许您将 64 位默认值更改为 32 位,但可能是暗示的?我懒得检查 osdev wiki 链接中的其他说明,看看是否有类似的行为。看起来是正确的。
  • @Vlad 您的编辑已完全损坏。 SE支持tables有一段时间了,你可以用它
猜你喜欢
  • 2011-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-11
  • 1970-01-01
  • 2016-03-07
相关资源
最近更新 更多