在 Intel x86-64 架构上是否以小端 4 字节字获取机器代码指令?

问题描述

尽管词(维基百科上的as stated)的一个常见定义是:

用于指定内存中位置的最大可能地址大小通常是硬件字(此处,“硬件字”是指处理器的全尺寸自然字,与使用的任何其他定义相反)。

x86 系统,根据 some sources,注意它被视为 16 位:

在 x86 PC(Intel、AMD 等)中,虽然架构早已 支持 32 位和 64 位寄存器,它的原生字长又回来了 到它的 16 位起源,一个“单个”字是 16 位。一个“双”字 是 32 位。请参阅 32 位计算机和 64 位计算机。

然而英特尔的 official documentation(sdm vol 2,第 1.3.1 节)指出:

这意味着单词的字节从最低有效字节开始编号。图 1-1 说明了这些约定。

和图 1-1 显示了 x86-64 上下文中的 4 个字节的小端序列,而不是 2 个字节或 8 个字节(如上面链接的来源的不同定义所暗示的那样):

enter image description here

我对这一切的真正困惑在于指令是如何获取和解析的。我正在编写一个模拟器,一旦我解析了一个 PE 格式的可执行文件并进入了文本部分,如果我要遵循 4 字节小端格式,这是否意味着将首先解析第 4 个字节?>

让我们组成一些字节,例如:

.text segment buffer:
< 0x10,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20 > ....

我是否会将第一条指令解析为 1C,1B,1A,10,20,1F,1E,1D ...(依此类推,由于长度可变,显然有更多的单词需要阅读取决于这里的实际字节数)?

解决方法

不,x86 指令被解析为字节序列,而不是更长的字。在您的示例中,第一条指令是解码为 0x10 0x1a 的字节 adc [rdx],bl。不是 0x1c 0x1b 会解码为 sbb al,0x1b 也不是 0x20 0x1f 会是 and [rdi],bl

然而,当一条指令包含一个多字节数(16/32/64 位)作为立即数、位移、地址等时,那么该数将被编码为小端。例如,add ecx,0x12345678 被编码为 0x81 0xc1 0x78 0x56 0x34 0x12

,

不,x86 机器码是一个 byte-stream;除了小端的 32 位位移和立即数外,它没有任何面向字的内容。例如在add qword [rdi + 0x1234],0xaabbccdd。它在现代 CPU 上以 16 字节或 32 字节的块进行物理提取,并在指令边界上并行拆分以并行提供给解码器。

48    81   87     34 12 00 00    dd cc bb aa       
REX.W add ModRM    le32 0x1234    le32 0xaabbccdd le32 (sign-extended to 64-bit)

   add    QWORD PTR [rdi+0x1234],0xffffffffaabbccdd

x86-64 不是面向字的架构;没有单一的自然字长,也不必对齐。在考虑 x86-64 时,这个概念不是很有用。整数寄存器宽度恰好是 8 字节,但这甚至不是机器代码中的默认操作数大小,并且您可以使用从字节到 qword 的任何操作数大小与大多数指令,以及从 8 或 16 字节到 32 的 SIMD或 64 字节。最重要的是,机器代码甚至数据不需要更宽整数的对齐。


有些人喜欢将方钉装入圆孔中,并用机器字来描述 x86,但这种概念仅适用于围绕单个字大小设计的 RISC ISA。 (对于某些 RISC 上的字大小访问,固定指令长度、寄存器大小甚至数据存储器加载/存储都需要字对齐,尽管现代的通常允许未对齐的加载/存储会带来一些性能损失。)

(公平地说,64 位 RISC 通常对于 32 位和 64 位整数也同样有效。但与 x86 不同的是,它们不能执行 add ax,cx 来避免将进位传播到寄存器的较高位。尽管 RISC 可以在对符号扩展或零扩展加载结果进行一些数学运算后进行 16 位存储)。

相关:


根据某些来源,请注意它被视为 16 位:

是的,在 x86 术语/文档中,一个“词”是 16 位,因为现代 x86-64 是从 8086 演变而来的,改变每个人使用多年的文档中术语的含义是愚蠢的当 386 发布时。因此 paddw packed add of 16-bit SIMD elementsmovsw/stosw/etc。字符串指令。

x86 16 位“字”与 CPU 架构中的“机器字”概念绝对零联系

在 8086 到 286 上,16 位是寄存器和总线宽度,并且是大多数 ALU 指令可以使用的除字节之外的唯一整数操作数大小。但是这些 CPU 仍然非常不像 MIPS 那样基于“文字”;机器代码格式仍然相同,具有未对齐的小端 16 位立即数和位移。 (8088 与 8086 相同,除了 8 位总线接口和 4 字节指令预取缓冲区而不是 6 字节。)