第二十二讲 指令格式
第二十二讲 指令格式
MIPS32指令格式
我们先前研究指令流水线是提到过MIPS32。MIPS32使用固定长度的指令,即每个指令32位。MIPS32存在三种不同的指令格式,但这三种指令格式都使用高6位作为操作码。
R(Register)指令格式
R指令格式具有三个寄存器的字段(通常为两个源寄存器和一个目标寄存器),以及移位量(5 位)和函数(6 位)。R指令格式用于没有立即数的算术运算或者位运算。所有的R指令的Opcode都是全0。
Opcode = 000000 | RS | RT | RD | Sh.Amount | Function |
---|---|---|---|---|---|
6比特 | 5比特 | 5比特 | 5比特 | 5比特 | 6比特 |
函数字段指定了要应用于 RS、RT(源)和 RD(目标)的实际算术函数。例如,函数字段 32 (100000b
) 是加法。左/右移位指令使用移位量字段来指定要移位的量。下表给出了更多的例子:
指令名 | 格式 | op | rs | rt | rd | sh amt | func | example |
---|---|---|---|---|---|---|---|---|
add | R | 0 | 2 | 3 | 1 | 0 | 32 | add $1, $2, $3 |
addu | R | 0 | 2 | 3 | 1 | 0 | 33 | addu $1, $2, $3 |
sub | R | 0 | 2 | 3 | 1 | 0 | 34 | sub $1, $2, $3 |
subu | R | 0 | 2 | 3 | 1 | 0 | 35 | subu $1, $2, $3 |
and | R | 0 | 2 | 3 | 1 | 0 | 36 | and $1, $2, $3 |
or | R | 0 | 2 | 3 | 1 | 0 | 37 | or $1, $2, $3 |
nor | R | 0 | 2 | 3 | 1 | 0 | 39 | nor $1, $2, $3 |
slt | R | 0 | 2 | 3 | 1 | 0 | 42 | slt $1, $2, $3 |
sltu | R | 0 | 2 | 3 | 1 | 0 | 43 | sltu $1, $2, $3 |
I(Imediate)指令格式
I指令格式包含两个寄存器字段(通常是源寄存器和目标寄存器)和 16位立即数字段。 I格式用于带有立即数的算术运算,也用于使用基址+位移寻址方案的内存加载/存储指令(位移存储在立即数字段中,并且仅限于 16 位,有符号)
操作码 | RS | RD | 立即数 |
---|---|---|---|
6比特 | 5比特 | 5比特 | 16比特 |
MIPS有32个寄存器(),因此使用5个比特位就可以进行编码表示, 所以RS/RT各为5个比特。立即数字段使用任何16位立即值进行填充。
J(jump)指令格式
J指令格式用于跳转指令,跳转到绝对地址。其中目标地址为26位,由于MIPS的地址是32位对齐的,因此其地址的低2位固定为零。这样已经构成了28位地址。缺失剩下的4个位,由PC指针的高4位提供。
操作码 | 伪地址 |
---|---|
6比特 | 26比特 |
J格式合法的操作码是0x2
和0x3
。
例子
这里我们看一下MIPS指令的一些示例并对其进行解码。
例1:
00000000001000100001100000100000 = 0x00221820
因为高6位是 0,这是一个算术运算,是R指令格式,所以我们将指令分解为:
000000 | 00001 | 00010 | 00011 | 00000 | 100000 |
---|---|---|---|---|---|
Opcode | Rs | Rt | Rd | sh.amt | Operation |
两个源寄存器是r1和r2;目标寄存器是r3 该操作是32,对应于add指令,因此解码为add r3, r1, r2
例2:
00001000110110101100010000100110
操作码(高6位)为2,表示跳转指令,因此上述指令可以解码为:
000010 | 00110110101100010000100110 |
---|---|
操作码 | 地址 |
这对应于地址为 0xDAC426
的跳转(该地址将使用指令指针的当前内容扩展为 32 位)。
例3:
001000 00001 00010 0000000000100101
操作码是8,它既不是R指令也不是J指令,所以它就是I格式:
001000 | 00001 | 00010 | 0000000000100101 |
---|---|---|---|
操作码 | Rs | Rd | 立即数 |
addi r2, r1, 37
x86-64操作码的格式
x86架构的指令格式在intel的手册中可以查到:intel手册
基本指令格式如下:
Field | Prefixes | Opcode | ModR/M | SIB | Displacement | Immediate |
---|---|---|---|---|---|---|
size(bytes) | 0-4 | 1,2,3 | 0,1 | 0,1 | 0,1,2,4,8 | 0,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 指定REPE
和REPNE
前缀允许按照 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可以被分解为:
字段 | Mod | Reg | R/M |
---|---|---|---|
比特 | 7,6 | 5,4,3 | 2,1,0 |
- Mod 和 R/M 字段一起指定八个寄存器之一和 24 种不同的寻址模式(位移、基址、偏移量、标度的组合)。对于寄存器-寄存器操作数,Mod 字段的值为 3 (= 11b)。
- Reg/Opcode 字段对寄存器(基址/位移或其他用途)进行编码,或者它是主操作码的扩展。
- R/M 可以是另一个寄存器,也可以是 Mod 字段的扩展。
Reg 和 R/M 字段的三位,当解释为寄存器时,具有以下解码:
比特 | 十进制 | 寄存器 |
---|---|---|
000 | 0 | rax |
001 | 1 | rcx |
010 | 2 | rdx |
011 | 3 | rbx |
100 | 4 | rsp |
110 | 6 | rsi |
111 | 7 | rdi |
ModR/M 的编码相当复杂,因为它用于指定指令中使用哪些寄存器,因此对于普通指令、XMM 指令等有不同的解释。
SIB 细分为:
字段 | scale | index | Base |
---|---|---|---|
比特 | 7,6 | 5,4,3 | 2,1,0 |
- scale给出了比例因子,0,1,2或者4。
- Index 使用上面给出的编码给出索引寄存器的寄存器编号(0-8)。
- Base 给出基址寄存器的寄存器号(0-8)。
在某些内存寻址模式中,它们的解释不同。
REX 前缀
REX前缀不属于上述四个前缀组。对于使用扩展64位寄存器(r8
-r15
或 xmm
寄存器)之一或使用64位大小的立即数或地址的任何指令,都需要REX前缀。如果REX前缀存在,则其位置位于操作码之前,在普通前缀的后面。
字段 | 0100 | W | R | X | B |
---|---|---|---|---|---|
比特 | 7-4 | 3 | 2 | 1 | 0 |
- 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
,则其后必须存在第二个操作码字节。 - 如果第一个字节是
0x66
、0xF2
或0xF3
的强制前缀,则其后必须存在第二个操作码字节。 - 如果第一个字节是转义字节或强制前缀,则对于三字节操作码,可以跟随两个操作码字节。操作码的第一个字节确定其后面是否有另一个操作码字节。
https://stackoverflow.com/questions/52346724/what-does-escape-opcode-mean
例子
例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指令集, 将2个64位寄存器相加有两种操作码0x1
和0x3
支持
操作码 | 指令 | 含义 |
---|---|---|
REX.W+01/r | ADD r/m64 r64 | Add r64 to r/m64. |
REX.W+03/r | ADD r64 r/m64 | Add r/m64 to r64. |
操作码0x1
和0x3
都可以用于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.html
https://en.wikibooks.org/wiki/MIPS_Assembly/Instruction_Formats#J_Format
课程原文:
https://staffwww.fullcoll.edu/aclifton/cs241/lecture-instruction-format.html
x86-64 指令格式