如何确定一条汇编指令的长度
最近在做实验的时候遇到了一些奇怪的问题,为了排除一些选项,不得不确定先一条汇编指令的长度,现记录如下。
当然最简单的方法是用ida反汇编之后直接对照。
如果是一段机器码片段,也有在线反编译器可使用12。
指令和操作数是如何编码的
一条指令由可选的指令前缀、操作码、寻址说明符、偏移量和一个立即数字段组成1。 如下图所示:

Opcode
用于指定要执行的操作。操作码的长度大部分为1个字节,也有2或3个字节的。在Intel手册的附录中提供了可供查询的操作码表,但是查起来还是很繁琐,有一些在线文档可以提供操作码到指令和指令到操作码的快速双向查询101113。

以32位版本 inc 为例,它的primary opcode为 $40+r$,这个 $r$ 指的就是寄存器编号

而32位版本寄存器编号如下

64位如下(REX是一个指令前缀):

所以在32位下,inc eax 的机器码为 40H。但是出现了一个问题,ax 的编号同样为0,难道inc eax 和 inc ax 没有任何的区别吗?
这就涉及到指令前缀了。
Instruction Prefixes
指令前缀被分成四组,每组中有若干前缀编码。对于每条指令,最多包含四条不同组的前缀。组1到组4可以按任意顺序排列。1
第1组包含 lock 和 repeat。第2组包含段前缀,不同的指令前缀代表了不同的段寄存器。第3组用于修改指令寄存器的默认长度,第4组用于修改默认寻址方式,允许程序在16和32位寻址之间切换。
32位和16位寄存器的使用就涉及到第3组前缀 66H,inc eax 的机器码为 40H ,inc ax 的机器码则为 6640H 。
ModR/M
但是前面还没有涉及到内存,使用内存的指令在主操作码后面都有一个寻址说明,也就是 ModR/M 字段。
ModR/M 字段共占1字节。Mod字段为00、01和10时指示内存,为11时指示寄存器。

在下表中,寄存器编号在最上面,由Reg/Opcode字段确定;下面的四大块中,由ModR/M 字段合在一起可以确定。

以 MOV 相关指令为例,假设现在有机器码 8901H。那么opcode为 89H, 由下表可知,这是 MOV r/m16/32 r16/32,其中r表示寄存器,m表示内存。
但是由于没有 66H 前缀,所以使用32位寄存器,则为 MOV r/m/32 r/32。
将 01 转换为二进制为 00000001b,其中 ModR/M 字段为 000001b,所以查表可得为 [ECX],Reg/Opcode 字段为 000b, 所以是32位寄存器 EAX。
综上,机器码 8901H 表示的指令为 Mov [ECX], EAX。但是这还不完整,需要添加上默认的段前缀和存储大小指示,完整版本为 Mov dword ptr ds:[ECX],EAX。
此外,如果有偏移会跟在 ModR/M 字段的后面(或者SIB后面)。

但是还留有一个问题,如果需要带寄存器偏移怎么办,比如有指令 Mov dword ptr ds:[ECX+EBX+48H],EAX。
这就涉及到了 SIB 字段。
SIB
SIB字段主要用于基址加偏移量寻址等情况。格式如下:

计算公式为 $Base + Index*2^{Scale}$,假设现在SIB字段为91H,转换为二进制即为 10010001b,所以scale为10b,index为010b,base为001b。index对应的寄存器为EDX,base对应的寄存器为ECX。

来看一条机器码,89 9C 91 22 22 22 22 22H。
由上面论述可知,89H 为 MOV r/m/32 r/32;9CH 二进制为 10011100b,所以格式为 Mov [–][–][disp32],EBX;跟在后面的是SIB字段,在上面分析过了,之后的5个16进制,由于偏移量是disp32,所以只取4个,最后一个22属于下一条指令了。
所以这条机器码对应的完整汇编指令是 Mov [ECX+EDX*4+2222222H], EBX 。
有了上面的论述,应该可以从汇编指令写出对应的机器码,也可以将机器码翻译为汇编指令。但是,CPU是怎么完成解码这一过程的呢?
CPU是如何解码指令的
从宏观上来看,fetch window每次从L1 cache中取16B大小的指令。在预解码缓冲区,主要进行指令边界的检测和标记,并对其他前缀进行解码,预解码完成后将发往Instruction queue中等待后续的解码。15 需要注意的是参考中的wikichip在预解码阶段对 IPC(Instruction per cycle) 的论述是错误的。
事实上,预解码阶段已经完成了我们上面论述的部分,而后面的解码阶段主要是将宏指令 (macro-op) 翻译为微指令 (micro-op) ,这就不在本文的论述范围内了。

参考
- 1.“Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2A: Instruction Set Reference, A-L,” n.d., 652. ↩
- 2.[原创]IA-32指令解析之操作码 ↩
- 3.[原创]IA-32指令解析之立即数 ↩
- 4.[原创]IA-32指令解析之ModR ↩
- 5.Intel硬编码(一):Opcode Map、定长指令与指令前缀 ↩
- 6.Intel硬编码(二):不定长指令、ModR/M与SIB详解(基于P6微架构) ↩
- 7.How to tell the length of an x86 instruction? ↩
- 8.Get size of assembly instructions ↩
- 9.Instruction - ModR/M Byte ↩
- 10.Asm-Dude wiki ↩
- 11.X64 Opcode and Instruction Reference ↩
- 12.online disassembler ↩
- 13.X86 Opcode and Instruction Reference ↩
- 14.Lecture 2 The CPU, Instruction Fetch & Execute ↩
- 15.Skylake (client) - Microarchitectures - Intel ↩
如何确定一条汇编指令的长度