是否可以访问 32 位和 64 位寄存器的较高部分?如果有,是哪些?

逆向工程 部件 x86 登记 x86-64
2021-06-29 02:14:20

我不知道这是否是一个愚蠢的问题,但我找不到任何答案。

随着 CPU 架构的演进,寄存器的大小从 8 位扩展到 16 位、32 位,最后是 64 位。我想知道是否有任何方法可以访问寄存器的较高部分。

下面是一个关于rax64 位寄存器及其后续划分的示例

6                              3               1 
4                              2               6       8       1
+------------------------------+---------------+-------+-------+
|                             rax                              |
+------------------------------+---------------+-------+-------+
            (???)              |              eax              |
                               +---------------+-------+-------+
                                     (???)     |       ax      |
                                               +-------+-------+
                                               |  ah   |   al  |
                                               +-------+-------+

ax通过分别引用ah和 ,可以轻松访问 的较高部分和较低部分al但是我找不到任何关于更高部分raxeax更高部分的参考(用 表示(???)

  1. 是否可以访问 32 位和 64 位寄存器的较高部分?如果有,是哪些?
  2. 假设不可能,这背后的原因是什么?

注意:我说的是直接访问这些字节,我不是要求可以检索它们的指令序列。

3个回答

这是错误的问题,真的。AH是例外。

现在真正的问题是,为什么会有AH这样的例外?这是一个旧的寄存器,来自 8086 时代。它的存在是为了方便从 8080 转移代码。

8080 与 8086 有不同的寄存器,所以你不能直接移动代码。特别是,它没有 AL、AH 或 AX 寄存器。它确实有一个 8 位 A 累加器和一个 8 位 F 标志寄存器,它们组合形成了一个 16 位 AF 寄存器。

8086 保留了一个 16 位累加器,但将标志移到了自己的寄存器中。然而,仍然有一个残留的遗迹。从 的低 8 位LAHF加载,保留 AL 中的低 8 位不变。该指令简化了 8080 代码到 8086 的移植。是的,2018 年的 Core i9 中仍然支持 8080。AHF

是否可以访问 32 位和 64 位寄存器的较高部分?如果有,是哪些?

无法直接访问EAXRAX寄存器或任何其他 32 位和 64 位寄存器的较高部分如果您有兴趣这样做,您将不得不使用间接指令序列。这是因为在任何指令中都没有访问这些部分的编码。

假设不可能,这背后的原因是什么?

正如@nordwald 在下面的评论中所指出的,它在手册中只是这么定义的。要获得更详细的官方答案,我们需要询问规范定义的成员。我们可以安全地假设核心原因是提供对所有可用寄存器分数的访问成本超过了收益

我将尝试列出为什么现在成本超过收益但过去没有的几个可能原因

  1. 更多可用寄存器空间的好处

    在 16 位寄存器的时代,寄存器稀缺(由于芯片制造成本),而芯片吹嘘提供给用户的寄存器数量。需要更多的寄存器。公开半寄存器允许更好地利用可用的(宝贵的和昂贵的)寄存器不动产。随着时间的推移,更多的寄存器可用,更好地使用精确寄存器大小的需求减少了。如今,您可以仅将 64 位寄存器用于其低字节或字,而不必“担心”没有足够的寄存器。

  2. 增加指令集的成本

    由于可用的指令集相对较小,并且只有几个寄存器,因此有足够的位可用于对不同的寄存器进行编码,而不会增加指令集的大小。在最初的 16 位寄存器时代,大部分可用指令集空间都被使用了。移至 32(然后再次移至 64 位),而不是重新实现具有不同寄存器大小前缀的相同指令(0x66例如,对于 32 位寄存器),保持指令的其余部分完整。这使得在 16 位原始指令集之上支持 32 位寄存器变得微不足道,但访问这些寄存器的较高部分需要更复杂的设计。您会注意到只有 8 个完全大小的寄存器(AX, CX, DX, BX,SPBPSIDI),以及只有8个半尺寸寄存器(ALCLDLBLAHCHDH),因此,只有3个比特被要求指定所需的寄存器。

    如今,对寄存器的所有部分进行编码会增加指令集和整体 CPU 的复杂性。

  3. 支持遗留代码

    当英特尔制造第一批 16 位处理器(8086 处理器系列)时,他们希望保持他们所谓的源代码兼容性这意味着 8 位处理器 (Z80/8080) 代码可以组装到 16 位处理器而无需更改代码/源代码,尽管允许更改指令的底层二进制表示。因此,即使指令二进制表示被完全重新设计并且新 CPU 不支持 8 位模式(与二进制的 32 和 64 位模式不同),8 位寄存器也必然会保留在早期的 8 位处理器中。向后兼容)。Z80 处理器系列有六个 8 位寄存器,它们可以一起访问以形成一个 16 位字。

    此外,在从 16 位处理器过渡到 32 位处理器,然后从 32 位处理器过渡到 64 位处理器时,需要保持向后兼容性并支持传统执行模式。出于这个原因,保持相同的二进制编码是通过为多个指令保持相同的指令集和相同的二进制编码来实现的,“强制”包含半寄存器作为可用指令集的一部分。

  4. 寄存器访问同步成本

    随着流水线优化变得越来越流行,同步寄存器访问(除其他外)的负担增加,使 CPU 更难跟踪寄存器访问。

    在现代 CPU 中,半寄存器实际上并不与完整寄存器重叠,相反,当一个寄存器发生变化时,CPU 会在所有其他寄存器上设置一个特殊的无效标志,因此当其他寄存器被访问时,它会知道读取更新的寄存器。这允许高级 CPU 级别优化(例如寄存器重命名)但当半大小寄存器交替使用(不再那么频繁)时,执行实际上会变慢

  5. 访问一半大小的寄存器仅部分可用开始

    尽管您可能会假设,访问或使用半大小的寄存器并不总是可行的。例如,您不能推送半个寄存器(没有“推送 AL”)。这样做只是为了支持使半寄存器有用所需的最小值,但它们不被视为全寄存器。

显然,有些原因可能比其他原因更重要,有些可能与最初的决定无关,有些原始原因可能在这里遗漏,等等。这些只是有根据的猜测和 YMMV

更新: 感谢 Nirlzr,我发现我错过了帖子底部的强调点。这个答案虽然不是 OP 正在寻找的,但可能对正在寻找访问这些位的方法的任何人都有用。为遗漏的 OP 全心全意道歉!

几年前我实际上写了一篇关于这个主题的深入文章:访问和修改 x86 和 x64 寄存器中的高位位

因此,虽然没有任何直接指令来专门访问 32 位或 64 位寄存器的上半部分,但您可以通过使用移位旋转指令来获取该数据(您的使用选择主要取决于您是否关心关于保持寄存器下半部分位的完整性)。

我会在此处逐条分解帖子以获得更充实的答案,但最好按原样阅读帖子,其中包含大量示例,非常有意地流程。