指令编码
现代 X86 指令由以下内容构建:
- 前缀(0、1、2、3、4)
- VEX (0, 2, 3)
- 操作码 (1)
- ModR/M (1)
- SIB (0,1)
- DISP (0, 1, 2, 4)
- IMM (0, 1, 2, 4)
前缀是零到四个字节:
第 1 组:LOCK 或 REP
第 2 组:段(CS、SS、DS、ES、FS、GS — 并非全部都以 64 位提供)和分支提示(即是否更可能采用分支?)
第 3 组:操作数大小(66H,对于某些指令是强制性的!)
第 4 组:地址大小
VEX
VEX 用于 AVX 扩展(大部分)
操作码
OPCODE 是实际指令,只有 8 位,如果你不计算 VEX 和一些其他前缀/特殊字节,例如著名的0F。 (在过去,这是访问 x86 协处理器的方式。)
ModR/M 定义模式
它告诉我们在这个指令中使用了哪个寄存器和/或内存模式。某些指令不支持所有可用模式。
尺度、指数、基数
SIB 是 ModR/M 的扩展。
位移
DISP 是位移,添加到地址寄存器的立即数(如 [ESP+13])它也可以是内存位置的直接地址。
立即
IMM 一个立即数(MOV EBX, $8 中的 8 是加载在 EBX 中的值,即立即数。)
请注意,IMM 通常限制为 32 位。 REX 可用于获取 64 位,但并非对所有指令都可用(因为任何一条指令的总字节数为 15 字节)。要在寄存器中加载 64 位,您总是从内存中加载它。这样做的一种方法是使用基于 IP 的地址。 (类似这样的东西:MOV R8, [RIP, -42])另外我注意到过去的编译器,如 gcc 并没有使用该指令。但是,对于 64 位处理器,可以使用 32 位位移,因此该值几乎可以在任何地方 (±2Gb)。
加载指令
64 位处理器将指令加载到指令缓存中。它一次加载 16 个字节(可能因处理器而异)。然后处理器解释这些字节。根据处理器的不同,它可能会将这些字节转换为一组 RISC 指令或直接执行指令。
例如,LOOP label 指令实际上几乎相当于至少两条指令:
SUB ECX, 1
JNZ label
过去有些处理器很难处理这样的问题,因此 LOOP 非常慢。一个原因是SUB 改变了许多EFLAGS,而LOOP 没有改变。
解释器不会在寄存器中加载指令。它将它加载到 CPU 中并在相应的单元(ALU、ACU、FPU 等)中处理它。不过,还有指向当前指令的 RIP 寄存器。就您而言,RIP 始终指向当前指令的开头或下一条指令的开头。
它是如何实现的,我不知道。他们可能会很快(瞬间)确定关注哪个单元并将指令推送到那里。确定大小并不复杂,因此他们可以快速获取所有字节并将它们推送到相关单元 FIFO,可能是 15 或 16 字节的值(即 FIFO 中的一项肯定总是 16 字节,一个字节可能被忽略,这使得硬件甚至没有行来读取它!)这些字节每次都将定位在相同的位置。因此,如果输入没有LOCK 或REP,它将在该FIFO 字节中输入00h。
请注意,在单元之间移动 FIFO 中的 16 个字节是没有意义的。多年来,GPU 一直在其 FIFO 中移动大量数据。
您可以说这些 FIFO 是附加寄存器。寄存器文件与 FIFO 是一样的,只是它具有随机访问而不是“PUSH/POP”类型的机制。两者都使用类似的技术,即内存,将数据保存在 FIFO 和寄存器中。
文档
我建议第一个文件,目前标题为:
Intel® 64 and IA-32 architectures software developer’s manual combined volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4
来自英特尔的关于可用说明的好读物(并非绝对所有内容,但足以开始使用!)