5. ModRM 寻址模式


上一页 返回目录 下一页

 

 

在 x86/x64 指令集的世界里:

Opcode 对指令提供操作码,ModRM 最主要作用是对指令的 operands 提供寻址,另一个作用是对 Opcode 进行补充,而 SIB 则是对 ModRM 进行补充寻址

 

有两种情况下是无需用 ModRM 提供寻址的:

(1)一部分操作数是寄存器的,它直接嵌入 Opcode 中。

(2)一部分操作数是立即数的,它直接嵌入指令编码中。

 


回到前面举的这个例子:
前面举的这个例子

REX prefix
Opcode
ModRM
SIB
displacement
immediate
4
W
R
X
B
mod
reg
r/m
scale
index
base
0100
1
0
0
0
C7
10
000
100
11
001
000
44 33 22 11
78 56 34 12

(1)C7 这个 Opcode 是 Group 属性,需要 ModRM.reg 来配合定位。

(2)ModRM.mod = 10 结合 ModRM.r/m = 100 提供 [SIB + disp32] 的寻址

(3)ModRM.reg = 000 表明 Opcode 是个 mov Ev, Iz

(4)SIB 由 ModRM.r/m = 100 来引导出

(5)REX.W = 1 使用 64 位的 operand


 


1、ModRM 的结构

表1: ModRM 结构表
--
描述
ModRM.mod
[7:6]
 提供寻址模式: 11 = register  00, 01, 10 = memory
ModRM.reg
[5:3]
 提供 register 或者对 Opcode 进行补充
ModRM.r/m
[2:0]
 提供 register 或者 memory 依赖于 ModRM.mod


  上表所示 ModRM 字节的组成部分为:mod-reg-r/m 三个部分,按 2-3-3 的比例组成,在这整篇文档中的写法是:ModRM.mod、ModRM.reg 以及 ModRM.r/m。

★ ModRM.mod 提供寻址的模式,这个模式以 displacement 值作区别的。当 ModRM.mod = 11 时,它提供 register 寻址。

★ ModRM.reg 提供寄存器寻址,reg 表示寄存器 ID 值,或者对 Group 属性的 Opcode 进行补充。

★ ModRM.r/m 提供 register 寻址或 memory 寻址,取决于 ModRM.mod 值:ModRM.mod = 11 时,ModRM.r/m 提供寄存器的 ID 值。


 

1.1、 ModRM.mod 寻址模式。

  2 位组成 4 种寻址模式,总的来说,只有两种寻址模式,就是:内存寻址模式和寄存器寻址模式。 如下表所示:


表2:mod 寻址模式表
  ModRM.mod
 寻址模式
 描述
00
 [base]
 提供 [base] 形式的 memory 寻址
01
 [base + disp8]
 提供 [base + disp8] 形式的 memory 寻址
10
 [base + disp32]
 提供 [base + disp32] 形式的 memory 寻址
11
 register
 提供 register 寻址。





1.2、 ModRM.reg 寻址 register

  3 位组成 8 个寄存器 ID 值,从 000 ~ 111,对应于 EAX、ECX、EDX、EBX、ESP、EBP、ESI 以及 EDI 寄存器。

  这个 ID 值可以被 REX.R 扩展为 4 位: 0000 ~ 1111(对应于 RAX 到 R15 寄存器)。

详见:寄存器编码表

 


1.3、ModRM.r/m 寻址 register 或 memory

r/m 意即:registers/memory,提供对 register 或 memory 的寻址,它与 ModRM.mod 是密切相关的。如下表:

表2: ModRM.r/m 寻址

ModRM.mod
ModRM.r/m
ModRM.r/m 寻址
REX.B = 0
REX.B = 1
00
000
[rax]
[r8]
001
[rcx]
[r9]
010
[rdx]
[r10]
011
[rbx]
[r11]
100
[SIB]
[SIB]
101
[disp32] 或 [rip + disp32]  *
[disp32] 或 [rip + disp32]
110
[rsi]
[r14]
111
[rdi]
[r15]
01
000
[rax + disp8]
[r8 + disp8]
001
[rcx + disp8]
[r9 + disp8]
010
[rdx + disp8]
[r10 + disp8]
011
[rbx + disp8]
[r11 + disp8]
100
[SIB + disp8]
[SIB + disp8]
101
[rbp + disp8]
[r13 + disp8]
110
[rsi + disp8]
[r14 + disp8]
111
[rdi + disp8]
[r15 + disp8]
10
000
[rax + disp32]
[r8 + disp32]
001
[rcx + disp32]
[r9 + disp32]
010
[rdx + disp32]
[r10 + disp32]
011
[rbx + disp32]
[r11 + disp32]
100
[SIB + disp32]
[SIB + disp32]
101
[rbp + disp32]
[r13 + disp32]
110
[rsi + disp32]
[r14 + disp32]
111
[rdi + disp32]
[r15 + disp32]
11
000
rax
r8
001
rcx
r9
010
rdx
r10
011
rbx
r11
100
rsp
r12
101
rbp
r13
110
rsi
r14
111
rdi
r15


上表所示:

★ ModRM.r/m = 0100 或 1100 时(扩展后的值),它寻址的是 SIB 字节,引导出 SIB 字节,交由 SIB 进行补充寻址。

★ ModRM.mod = 00 并且 ModRM.r/m = 0101 或 1101 时,在 32 位寻址模式下它是 32 位的 displacment 值,在 64 位寻址模式下它是 [rip + disp32] 寻址。

★ REX.B 可以扩展 ModRM.r/m 为 4 位: 0000 ~ 1111

 

 


2、 [rsp] 寻址的疑惑(ModRM.r/m = 100) 

  按照 ModRM 编码规则,当 ModRM.r/m = 100,ModRM.r/m 应该是对应于 [rsp] 寻址。

  事实上并非如此,ModRM.r/m 对应的是 SIB 寻址,由此而引导出 SIB 字节。指令系统设计者选择在 [rsp] 位置上用 SIB 进行替代。

这是因为:

★ 必须要有编码对应于 SIB 字节。

★ 用 SIB 代替 [rsp] 或许因为 [rsp] 相比其它的寻址并不那么常用。

 

2.1、那么怎样实现 [rsp] 寻址的编码?

  既然在 ModRM 上无法满足 [rsp] 的寻址,那么只好在 SIB 里实现 [rsp] 的寻址。

  在 SIB 里实现 [rsp] 寻址,是根据一个重要的原则: rsp 寄存器不能做为 index 寄存器,只能做 base 寄存器。

  具体 SIB 的实现细节,详见:[rsp] 寻址的实现

 


3、 [rbp] 寻址的疑惑(ModRM.r/m = 101)

  同样情况下,按照 ModRM 编码规则,ModRM.r/m = 101 时,应该对应于 [rbp] 寻址。

  事实上并非如此:当 ModRM.mod = 00 时,ModRM.r/m = 101 它对应的是 [disp32] 的寻址,在 64 位下,它是 [rip + disp32]。

  指令系统的设计者们在 ModRM.r/m = 101 这个地方又进行了变通。

 

3.1、 那么怎样实现 [rbp] 寻址的编码呢?

  在 [rbp] 寻址的实现上,又遵循另一个重要原则:rbp 作为 base 寄存器时,它必须以 [base + disp] 这种形式存在。

  这个原则是建立在:rbp 意为 stack base pointer(基址指针),从语义上讲 base pointer 应该要加一个偏移量(displacement)

  因此:[rbp] 这种形式在 encode 上是不存在的。

  但是在 assembly(汇编语句)层面上是正确的,即:mov qword ptr [rbp], rax 是正确的语句。

  具体 [rbp] 的实现,详见:[rbp] 寻址的实现

 

 




4、 16 位下的 ModRM 寻址模式


   16 位寻址与 32 位寻址有较大的差异,16 位下是不支持 [base + index * scale + disp] 这种寻址模式的,这样在编码序列里就无需提供 SIB 字节了。

  即:在 16 位下是无 SIB 字节的。

注:
  16 位的 ModRM 寻址支持的是基址和变址寄存器间接寻址和 基址+变址寻址模式。基址寄存器 2 个:就是 bx 和 bp。变址寄存器也是 2 个:就是 si 和 di。

  基于上述设计,基址+变址寻址的组合只有 4 个,也就是:[bx+si]、[bx+di]、[bp+si] 以及 [bp+di]。

 

表3: 16 位的 ModRM 寻址

ModRM.mod
ModRM.r/m
ModRM.r/m 寻址
00
000
[bx+si]
001
[bx+di]
010
[bp+si]
011
[bp+di]
100
[si]
101
[di]
110
[disp16]
111
[bx]
01
000
[bx+si+disp8]
001
[bx+di+disp8]
010
[bp+si+disp8]
011
[bp+di+disp8]
100
[si+disp8]
101
[di+disp8]
110
[bp+disp8]
111
[bx+disp8]
10
000
[bx+si+disp16]
001
[bx+di+disp16]
010
[bp+si+disp16]
011
[bp+di+disp16]
100
[si+disp16]
101
[di+disp16]
110
[bp+disp16]
111
[bx+disp16]
11
000
ax
001
cx
010
dx
011
bx
100
sp
101
bp
110
si
111
di






 

5、 64 位寻址下的 ModRM

  在 64 位下,ModRM 的含义与 32 位一致,改进的只是在原来基础上增加了 8 个 GPRs,通过 REX prefix 进行对新增的寄存器进行访问。

  寄存器的 ID 取值为:0000 ~ 1111。由 REX.R 以及 REX.B 位进行扩展访问。

详见:ModRM.r/m 寻址表





6、结合 Opcode 来看寻址模式及 Opcode 的定位

  Opcode 定义指令的执行码,用于执行什么操作,对于操作数寻址上,Opcode 结合 ModRM 来定义操作数,这是一个经过反复琢磨推敲的过程,而最终又影响到 Opcode 的定位。



6.1、 一个操作数的 Opcode 定位

操作数要么就是 registers,要么就是 memory,要么就是 Immediate 值。如果指令只有一个操作数。

(1)operand 是 register 的情况下

   在只有 1 个寄存器操作数的情况下,非 Group 属性的 Opcode,则 ModRM 无用武之处。所以,在这种情冲下,寄存器操作数绝大部分是嵌在 Opcode 里面,它由 Opcode 的寄存器域指出。如常见的 inc ecx、dec ecx、push eax 等。

除了上述嵌入 Opcode 情况外,如果 ModRM 用于寻址 1 个操作数,这条指令的 Opcode 必定是:Group 属性


   若是 Group 属性,则 ModRM 有用武之地了。


(2)如它是 Immediate 的话,它绝对是无 ModRM。直接将 Immediate 值嵌入指令编码里

(3)如它是 memory 的话,它绝对是 Group 属性,需要 ModRM.reg 来配合定位。那为什么不能是直接 offset 呢,直接 offset 寻址留给最常用的,最有用的 Opcode,以免 Opcode 占位,浪费资源。




8.5.2、 两个操作数的 Opcode 定位

   两个操作数大部分都需 ModRM 配合定位寻址。ModRM 提供的 2 个操作数寻址大有用武之地。

(1)2 个操作数中,其中 1 个是寄存器,另1个不是立即数的这种情况最直接简单,由 ModRM 的 reg 及 r/m 提供寻址。
   若其中 1 个是 GPRs 另 1 个是 immediate 或 displacement 的情形下更简单,GPRs 则直接由 Opcode 提供寻址。


(2)2 个操作数中,没有寄存器的情形下,也就是要么是 memory,要么是 Immediate,它必然是个 Group 属性,reg 域提供 Opcode 的定位,r/m 提供内存寻址。Immediate 直接嵌入指令编码中。



8.5.3、 三个操作数的 Opcode 定位

   三个操作数中有一个必定是 Immediate,在 AMD 的 SSE5 指令集推出之前,x86 平台是无法提供第 3 个非 Immediate 操作数的定位。
   直至 AMD 的 SSE5 通过增加另一个描述操作数的字节来寻址第 3 个操作数。


   如上所述,Opcode 的设计要考虑到与 ModRM 的配合。主要表现在什么时候是 Group 属性。


前面已提过:
   Opcode 的分组是按 Operands 属性进行的。即:这一组 Opcode 拥有相同类型的 Operands。
   这个类型的 Operands 主要是单个内存操作数或单个寄存器操作数而非嵌入 Opcode 中。

 




 

上一页 返回目录 下一页


mik 写于 2008-11-26 15:40