天天看点

X86架构指令模拟

前言:

要进行指令模拟,我们先需要了解X86架构下的指令是长什么样子的。根据intel的编程手册我们找到了如下信息。

Intel CPU的机器指令格式如下图所示:

X86架构指令模拟

e.g.:图片位于intel开发手册第二卷第二章的2.1

根据开发手册,一条指令由 指令前缀(Instruction Prefixes) + 操作码(Opcode) + ModR/M + SIB + 偏移(displacement) + 立即数(Immediate data)几部分组成,一条指令至少需要有Opcode,其它几部分,在不同指令中可能存在可能不存在。今天我们主要来看Opcode、Instruction Prefixes、Immediate data三部分在不同指令中的使用。

1.  指令前缀

指令前缀分为四组,每组都有一组允许的前缀代码。对于每条指令,它仅在包含来自四个组(组 1、2、3、4)中的每一组的最多一个前缀代码时有用。第 1 组至第 4 组可以以任何相对于彼此的顺序放置。
第一组:封锁和重复执行前缀
      

    F0H: LOCK前缀,封锁总线。在有数的指令(如ADD,ADC)前方时,使指令变为原子操作,并与被修饰的指令一起提供内存屏障效果。

    F2H:REPNE/REPNZ前缀(只位于字符串指令前)

    F3H:REP前缀(只位于字符串指令前)

    F3H:(与REP前缀同码)

第二组:段前缀      

    在32位汇编中,有8个段寄存器:ES、CS、SS、DS、FS、GS、LDTR、TR(顺序固定),不再用段寄存器寻址而只做权限控制。前缀和寄存器对应如下:

2E - CS
36 - SS
3E - DS
26 - ES
64 - FS
65 - GS      

    使用前缀修饰后,指令(opcode)的默认段寄存器会被修改,如默认的MOV操作从DS段拿数据,加上36前缀后会基于SS段地址取数据。

第三组:修改操作数默认长度      

     66H,用来“反转”默认的16位或32位操作数宽度。例如,当默认的操作数宽度是32位时,可以用这个前缀选择16位宽度的操作数,或者反之。如下指令码和汇编对照:

50:  PUSH EAX
6650: PUSH AX      
第四组:修改默认地址长度      

    67H,用来“反转”默认的16位或32位地址宽度。例如,当默认的地址宽度是32位时,可以用这个前缀选择16位宽度的地址,或者反之。如下指令码和汇编对照:

8801:   MOV DS:[ECX],AL
678801: MOV DS:[BX+DI], AL      

2.  指令码(opcode)

    主操作码的长度可以是 1、2 或 3 个字节。如果主操作码不能确定指令则会有额外的字段编辑倒ModR/M中。如果主操作码是0x0f开头则需要取第二字节,如果主操作码开头是0x0f38,0x0f3a开头则再取第三字节。

    从intel编程手册中截取一字节指令码格式如下图:

X86架构指令模拟