第二十二讲 指令格式

程序员小x大约 13 分钟汇编语言

第二十二讲 指令格式

MIPS32指令格式

我们先前研究指令流水线是提到过MIPS32MIPS32使用固定长度的指令,即每个指令32位。MIPS32存在三种不同的指令格式,但这三种指令格式都使用高6位作为操作码。

R(Register)指令格式

R指令格式具有三个寄存器的字段(通常为两个源寄存器和一个目标寄存器),以及移位量(5 位)和函数(6 位)。R指令格式用于没有立即数的算术运算或者位运算。所有的R指令的Opcode都是全0。

Opcode = 000000RSRTRDSh.AmountFunction
6比特5比特5比特5比特5比特6比特

函数字段指定了要应用于 RS、RT(源)和 RD(目标)的实际算术函数。例如,函数字段 32 (100000b) 是加法。左/右移位指令使用移位量字段来指定要移位的量。下表给出了更多的例子:

指令名格式oprsrtrdsh amtfuncexample
addR0231032add $1, $2, $3
adduR0231033addu $1, $2, $3
subR0231034sub $1, $2, $3
subuR0231035subu $1, $2, $3
andR0231036and $1, $2, $3
orR0231037or $1, $2, $3
norR0231039nor $1, $2, $3
sltR0231042slt $1, $2, $3
sltuR0231043sltu $1, $2, $3

I(Imediate)指令格式

I指令格式包含两个寄存器字段(通常是源寄存器和目标寄存器)和 16位立即数字段。 I格式用于带有立即数的算术运算,也用于使用基址+位移寻址方案的内存加载/存储指令(位移存储在立即数字段中,并且仅限于 16 位,有符号)

操作码RSRD立即数
6比特5比特5比特16比特

MIPS有32个寄存器(25{2}^{5}),因此使用5个比特位就可以进行编码表示, 所以RS/RT各为5个比特。立即数字段使用任何16位立即值进行填充。

J(jump)指令格式

J指令格式用于跳转指令,跳转到绝对地址。其中目标地址为26位,由于MIPS的地址是32位对齐的,因此其地址的低2位固定为零。这样已经构成了28位地址。缺失剩下的4个位,由PC指针的高4位提供。

实际的跳转地址=PC指针的高4+26位伪地址+20 实际的跳转地址 = PC指针的高4位 + 26位伪地址 + 2个0位

操作码伪地址
6比特26比特

J格式合法的操作码是0x20x3

例子

这里我们看一下MIPS指令的一些示例并对其进行解码。

例1

00000000001000100001100000100000 = 0x00221820

因为高6位是 0,这是一个算术运算,是R指令格式,所以我们将指令分解为:

00000000001000100001100000100000
OpcodeRsRtRdsh.amtOperation

两个源寄存器是r1和r2;目标寄存器是r3 该操作是32,对应于add指令,因此解码为add r3, r1, r2

例2

00001000110110101100010000100110

操作码(高6位)为2,表示跳转指令,因此上述指令可以解码为:

00001000110110101100010000100110
操作码地址

这对应于地址为 0xDAC426 的跳转(该地址将使用指令指针的当前内容扩展为 32 位)。

例3

001000 00001 00010 0000000000100101

操作码是8,它既不是R指令也不是J指令,所以它就是I格式:

00100000001000100000000000100101
操作码RsRd立即数
addi r2, r1, 37

x86-64操作码的格式

x86架构的指令格式在intel的手册中可以查到:intel手册open in new window

基本指令格式如下:

FieldPrefixesOpcodeModR/MSIBDisplacementImmediate
size(bytes)0-41,2,30,10,10,1,2,4,80,1,2,4,8

X86-64指令最大长度限制为15个字节。

前缀

x86-64指令最多可以有4组前缀。每组前缀都会调整操作码的解释。每组前缀的大小为1个字节。因此前缀字段的大小范围是0~4字节。

这四组前缀分别是:

  • 1.锁定和重复前缀

    • 锁定前缀(LOCK)可以使得指令可以以原子的方式运行,编码为F0H。

    • 字符串操作指令前缀

      F2h = REPNE, REPNZ
      F3h = REP,REPE/REPZ
      

      REP 重复指令的次数由迭代计数 ECX 指定

      REPEREPNE 前缀允许按照 ZF 标志值终止循环。

  • 段覆盖前缀,用于使用指定的段来替换默认的段

    2Eh = CS
    36h = SS
    3Eh = DS
    26h = ES
    64h = FS
    65h = GS
    
  • 操作数覆盖,66h。更改指令默认模式所需的数据大小,例如16 位到 32 位,反之亦然。

  • 地址覆盖,67h。更改指令期望的地址大小。 32 位地址可以切换为 16 位地址,反之亦然。

ModR/M 和 SIB 字段

ModR/M和SIB字段如果存在,可以用来指定内存操作的工作方式。内存操作数可以由常量位移、两个寄存器(基址和偏移量)和一个标度(0、1、2、4 或 8)组成。配置必须以 ModR/M 和 SIB 字节进行编码。

ModR/M可以被分解为:

字段ModRegR/M
比特7,65,4,32,1,0
  • Mod 和 R/M 字段一起指定八个寄存器之一和 24 种不同的寻址模式(位移、基址、偏移量、标度的组合)。对于寄存器-寄存器操作数,Mod 字段的值为 3 (= 11b)。
  • Reg/Opcode 字段对寄存器(基址/位移或其他用途)进行编码,或者它是主操作码的扩展。
  • R/M 可以是另一个寄存器,也可以是 Mod 字段的扩展。

Reg 和 R/M 字段的三位,当解释为寄存器时,具有以下解码:

比特十进制寄存器
0000rax
0011rcx
0102rdx
0113rbx
1004rsp
1106rsi
1117rdi

ModR/M 的编码相当复杂,因为它用于指定指令中使用哪些寄存器,因此对于普通指令、XMM 指令等有不同的解释。

SIB 细分为:

字段scaleindexBase
比特7,65,4,32,1,0
  • scale给出了比例因子,0,1,2或者4。
  • Index 使用上面给出的编码给出索引寄存器的寄存器编号(0-8)。
  • Base 给出基址寄存器的寄存器号(0-8)。

在某些内存寻址模式中,它们的解释不同。

REX 前缀

REX前缀不属于上述四个前缀组。对于使用扩展64位寄存器(r8-r15xmm 寄存器)之一或使用64位大小的立即数或地址的任何指令,都需要REX前缀。如果REX前缀存在,则其位置位于操作码之前,在普通前缀的后面。

字段0100WRXB
比特7-43210
  • W(如果设置)表示 64 位操作数大小。
  • R 用作 ModR/M 字节的 Reg 字段中的额外位。由于Reg字段只有3位,因此通常只能访问前8个寄存器。需要一个额外的位来访问寄存器 r8-r15。
  • X 用作SIB字节索引字段中的额外位。由于 Index 字段命名了一个寄存器,因此它与上面的 R 字段具有相同的理由。
  • B 用作SIB字节的Base字段中的额外比特,有时也用作ModR/M字节的R/M字段中的额外比特。这两个字段都命名一个寄存器,因此访问 r8-r15 时需要一个额外的位。

操作码

操作码的长度可以在1到3个字节之间。

  • 如果操作码长度只有一个字节,则操作码的值不会等于0x0f
  • 如果第一个字节是0x0f,则其后必须存在第二个操作码字节。
  • 如果第一个字节是0x660xF20xF3的强制前缀,则其后必须存在第二个操作码字节。
  • 如果第一个字节是转义字节或强制前缀,则对于三字节操作码,可以跟随两个操作码字节。操作码的第一个字节确定其后面是否有另一个操作码字节。

https://stackoverflow.com/questions/52346724/what-does-escape-opcode-meanopen in new window

例子

例1

add rax, rbx

该指令使用 64 位寄存器,因此需要 REX.W 前缀字节:

01001000
    WRXB

add指令的操作码根据所使用的寄存器以及是否存在立即数操作数而变化。对于两个64位寄存器,它们都不在r8-r15,操作码是0x01

00000001

对于两个64位寄存器操作数,操作数在ModR/M字节中编码,第一个(目标)操作数在 Reg 字段中,第二个操作数在 R/M 字段中。 rax作为寄存器号的编码是000b,rbx是011。Mod字段是11,因为两个操作数都是寄存器。因此,ModR/M 字节是

11 011 000

将它们组装在一起给出一个三字节指令

0100 1000  00000001  11011000
REX  W     Opcode    ModR/M

或十六进制的 0x48 01 D8

值得一提的是,根据Intel的add指令集open in new window, 将2个64位寄存器相加有两种操作码0x10x3支持

操作码指令含义
REX.W+01/rADD r/m64 r64Add r64 to r/m64.
REX.W+03/rADD r64 r/m64Add r/m64 to r64.

操作码0x10x3都可以用于64位register的add,区别在于源寄存器和目标寄存器在ModR/M中存放的顺序不一样。

使用0x1,则源寄存器存储在ModR/M的register字段中,目标寄存器存储在ModR/M的R/M字段中。

使用0x3,则源寄存器存储在ModR/M的R/M字段中, 目标寄存器存储在ModR/M的register字段中。

因此add rax, rbx也可以按照下面的格式进行解析:

0100 1000  00000011  11000011
REX  W     Opcode    ModR/M

或十六进制的 0x48 03 c3

例2

add r10, r11

此示例使用两个仅在 64 位模式下可用的寄存器。这将需要使用 REX 前缀的位来扩展 ModR/M 字节中的寄存器索引:

0100 1101
REX  WRXB

R = 1 将用作Reg 字段的额外高位,而B = 1 将用作R/M 字段的额外高位。

如上所述,目标寄存器是 r10(= 寄存器号 10),应将其放置在 R/M 字段中。为了将 10 编码为二进制,我们需要 4 位(十进制 10 = 1010 二进制),因此高位设置为 REX.B

前缀,其余三位010放在R/M中。同样,十进制 11 是二进制 1011,因此我们将 1 放入 REX.R,然后将剩余的三位放入 Reg 字段。这给了我们一个 ModR/M 字节

11 011 010
M  Reg R/M

最后,对于两个 64 位寄存器操作数(其中目标是扩展 64 位寄存器),使用操作码 1 (= 00000001b)。这给我们留下了最终的编码

0100 1101  00000001  11 011 010
REX  WRXB  Opcode    M  Reg R/M

例3

add r10, 17

这是以扩展 64 位寄存器作为目标的立即数的加法。立即数足够小,可以用单个字节进行编码,17 = 00010001b

目标寄存器 r10 需要设置 REX.B 中的高位,以及 R/M 中的 010。立即寄存器操作也使用 Mod = 11b,因此我们的 ModR/M 字节是

11 000 010
M Reg R/M

REX前缀是:

0100 1001
REX  WRXB

最后,add-with-immediate 的操作码是 0x83(= 十进制 131,10000011b),解码为

0100 1001  10000011  11 000 010  00010001
REX  WRXB  Opcode    Mo Reg R/M  Imm.

例4

add eax, 10

在这里,我们没有使用任何 64 位寄存器,因此不需要 REX 前缀。立即数加法使用操作码 0x83。目标寄存器使用 ModR/M 字节(rax = 寄存器 0)指定,给出解码

10000011  11 000 000  00001010
Opcode    Mo Reg R/M  Imm.

例5

scasb

该指令不引用任何操作数(64 位或其他),因此可以在没有前缀或 ModR/M 字节的情况下表示。 scasb 的操作码是 0xAE = 10101110,这又是整个解码:

10101110
Opcode

例6

repe scasq

在这里,我们做了两个改变:将指令大小升级到64位(需要REX前缀)并添加repe前缀。 REX 前缀必须紧接在操作码之前(0xAF = 10101111;0xAF 是大于字节的 scas 的操作码)。 repe 前缀为 0xF3 = 11110011b。因此,我们有

11110011  0100 1000  10101111
repe      REX  W     Opcode

例7

syscall

Syscall 是一个两字节指令,以 0x0F 开头。它的主要操作码是 0x05,编码为

00001111 00000101

例8

lea rax, [rbx + 2*rcx]

像往常一样,我们需要 REX.W 前缀 = 01001000。 lea 的操作码是 10001101b = 0x8D。因为我们使用带有基址寄存器和偏移(索引)寄存器的内存操作数,所以我们需要 ModR/M 和 SIB 字节。

  • 为了对基址 + 索引 * 标度寻址方案进行编码,我们设置 Mod = 0 和 R/M = 100b。
  • 目标寄存器 rax 是寄存器编号 0,位于 Reg 字段中。
  • 可能的比例值 1、2、4、8 映射到二进制值 00、01、10、11。我们的比例 2 在比例字段中放置为 01。
  • 索引(偏移)寄存器是rcx,它是寄存器号1。它被放置在索引字段中。
  • 基址寄存器是 rbx,它是寄存器编号 3,放置在 Base 字段中。

ModR/M字段为:

00 000 100
Mo Reg R/M

SIB

01    001   011
Scale Index Base

最终编码为:

0100 1000  10001101 00 000 100  01 001 011
REX  W     Opcode   Mo Reg R/M  Sc Ind Bas

例9

add rax, qword [10]

这是最复杂的示例,因为它使用 64 位寄存器和 qword 大小的内存操作数。

操作码仍然是 3,用于从寄存器或内存源添加到 64 位寄存器中。目标寄存器将通过 Reg 字段指定(rax = 寄存器号 0)。 Mod 和 R/M 字段组合起来指定寻址模式,这里只是位移(不存在基数、偏移量或标度)。其编码为 Mod = 0,R/M = 100b。

末尾额外的 0 字节是填充字节,添加该字节是为了使指令的大小为 8。

0100 1000  00000011  00 000 100  00    100   101   00001010  00000000  00000000  00000000
REX  W     Opcode    Mo Reg R/M  Scale Index Base  Address   
                                 rsp   rbp

附录

MIPS指令格式:

http://www.cs.kzoo.edu/cs230/Resources/MIPS/MachineXL/InstructionFormats.htmlopen in new window

https://en.wikibooks.org/wiki/MIPS_Assembly/Instruction_Formats#J_Formatopen in new window

课程原文:

https://staffwww.fullcoll.edu/aclifton/cs241/lecture-instruction-format.htmlopen in new window

x86-64 指令格式

http://www.c-jump.com/CIS77/CPU/x86/open in new window

Loading...