2.2 x86指令格式
编译系统的汇编器需要把编译器生成的汇编语言程序转化为x86格式的二进制机器指令序列,然后将这些二进制信息存储为ELF格式的目标文件。因此需要先了解二进制机器指令的基本结构。
如图2-10所示,在x86的指令结构中,指令被分为前缀、操作码、ModR/M、SIB、偏移量和立即数六个部分。本书设计的编译器生成的汇编指令中不包含前缀,这里暂时不介绍它的含义。操作码部分决定了指令的含义和功能,ModR/M和SIB字节为扩充操作码或者为指令操作数提供各种不同的寻址模式。如果指令含有偏移量和立即数信息,就需要把它们放在指令后边的对应位置。
图2-10 x86指令格式
这里使用一个简单的例子与表2-2说明x86指令结构的含义,例如汇编指令:
add eax,ebx
表2-2 二进制指令编码
指令格式
操作码
mod字段
reg字段
r/m字段
指令编码
add r/m32,reg
0x01
11
011
000
0000 0011 1100
0011
add reg,r/m32
0x03
11
000
011
0000 0001 1101
1000 查阅Intel的指令手册,当操作数为32位寄存器时,add指令的操作码是0x01或者0x03,它们对应的指令格式是add r/m32,reg和add reg,r/m32。在ModR/M字节的定义中,高两位mod字段为0b11时表示指令的两个操作数都是寄存器,低三位表示r/m操作数寄存器的编号,中间三位表示reg操作数寄存器的编号。Intel定义eax寄存器编号为0b000,ebx寄存器编号为0b011。如果我们采用操作码0x01,reg应该记录ebx的编号0b011,r/m32记录eax编号0b000,mod字段为0b11。因此该指令的ModR/M字节为:
11 011 000 =>
0xd8
同理,若采用操作码0x03的话,ModR/M字节应该是:
11 000 011 =>
0xc3
指令不再含有其他信息,因此不存在SIB和偏移量、立即数字段。这样“add
eax,ebx”指令就有两种二进制表示形式:0x01d8与0x03c3。
通过这个例子可以得出结论:在汇编器语法分析阶段,应该记录生成的二进制指令需要的信息。指令的名称决定操作码,指令的寻址方式决定ModR/M和SIB字段,指令中的常量决定偏移量和立即数部分。
由于本书设计的编译器所生成的汇编指令的种类有限,因此降低了汇编器对指令信息分析的复杂度,但是还有大量的其他类型的指令需要具体分析,这些内容会在以后章节中阐述。