文章目录
上一节:22、进入保护模式
下一节:24、存储器的保护
01、80286的16位保护模式
80286
的寄存器:
80286
上存储器的段
描述符格式:
80286
上段寄存器
:分为段选择器
和描述符高速缓存器
。
80286
在保护模式
下访问段的步骤:
可以看出虽然段最大之后64K
字节,但是因为基地址是24位的且可以任意选择,所以不影响80286
访问全部的2^24 = 16777216 = 16MB
字节内存。
80286
在是实模式和保护模式下都只能使用8位或者16位的操作数,而且地址中的偏移量只有16位,所以80286
的保护模式又叫做16位的保护模式。
02、16位处理器的指令操作尺寸
指令的操作尺寸:
8086
的指令操作尺寸:8位、16位
80286
的指令操作尺寸:
其中描述符高速缓存器中的基地址在实模式和保护模式下是不同的:
实模式:是将段地址左移4位得到;
保护模式:来自段描述符,当初将段选择子带入段寄存器中,处理器使用段选择子从描述符表中的对应的描述符中取出的。
03、32位处理器的指令操作尺寸
指令的操作尺寸,操作数为8位、16位、32位;有效地址为16位、32位。
其中描述符高速缓存器
中的基地址在实模式和保护模式下是不同的:
实模式:是将段地址左移4位得到,其中还有段界限为0xFFFF
,保证在实模式下访问正确的内存;
保护模式:来自段描述符,当初将段选择子带入段寄存器中,处理器使用段选择子从描述符表中的对应的描述符中取出的。
04、x86的指令格式–操作码和立即数部分
操作码:
操作码相关资料:指令集参考第二卷。
计算下面3条指令的机器码:
1、计算指令mov al, 3
的机器码B003
:
其中:rb
表示长度为1个字节
的寄存器,在这条指令中只有一个寄存器r8
,即rb
就是r8
,此条指令中r8
就是寄存器AL
。即B0
+ AL的编号
,AL
的编号如下:
那么这条指令的操作码就是B0 + 0 = B0
,立即数是8位的03
,即操作码就是B003
。
2、计算指令mov cx, 3
的机器码B90300
:
其中:rw
表示长度为2个字节
的寄存器,在这条指令中只有一个寄存器r16
,即rb
就是r16
,此条指令中r16
就是寄存器CX
。即B0
+ CX的编号
,CX
的编号如下:
那么这条指令的操作码就是B0 + 1 = B9
,立即数是16位的0003
,即操作码就是B90300
(按照低端字节序存放)。
3、计算指令mov edx, 3
的机器码BA0300000
:
其中:rd
表示长度为4个字节
的寄存器,在这条指令中只有一个寄存器r32
,即rb
就是r32
,此条指令中r32
就是寄存器EDX
。即B0
+ EDX的编号
,EDX
的编号如下:
那么这条指令的操作码就是B0 + 2 = BA
,立即数是32位的00000003
,即操作码就是BA03000000
(按照低端字节序存放)。
05、x86的指令格式–ModR/M和偏移量部分
ModR/M
字段的组成:1个字节。
16位寻址方式的ModR/M
方式,还有32位的。
计算下面3条指令的机器码:
1、计算指令mov al, cl
的机器码88C8
:
其中:操作码是88
。
其中:/r
表示指令中的纯寄存器操作数
,在这里就是右操作数r8
。/
是指操作码的扩展,需要将这个/r
中的寄存器r
的编号添加在ModR/M
的中间部分,就是reg/opcode
。这条指令的右操作数是CL
,编号如下为001
,既reg/opcode
为001
。
从/r
就可以看出只要是操作码的后边有斜杠'/'
,这条指令一定有ModR/M
字段。
其中:Mod
如下为11
,对应寄存器寻址,左操作数是AL
,则R/M
位是000
。
那么这条指令由上述各位组合而成:因此这条指令的机器码就是88C8
。
操作码 | mod | reg | r/m | ModR/M |
---|---|---|---|---|
88 | 11 | 001 | 000 | C8 |
2、计算指令mov ax, [bx]
的机器码8B07
:
其中:操作码是8B
。
其中:/r
表示指令中的纯寄存器操作数
,在这里就是左操作数r16
。/
是指操作码的扩展,需要将这个/r
中的寄存器r
的编号添加在ModR/M
的中间部分,就是reg/opcode
。这条指令的左操作数是AX
,编号如下为000
,既reg/opcode
为000
。
从/r
就可以看出只要是操作码的后边有斜杠'/'
,这条指令一定有ModR/M
字段。
其中:Mod
如下为00
,对应地址寻址,右操作数是[BX]
,则R/M
位是111
。
那么这条指令由上述各位组合而成:因此这条指令的机器码就是8B07
。
操作码 | mod | reg | r/m | ModR/M |
---|---|---|---|---|
8B | 00 | 000 | 111 | 07 |
3、计算指令mov cx, [bx+si]
的机器码8B08
:
其中:操作码是8B
。
其中:/r
表示指令中的纯寄存器操作数
,在这里就是左操作数r16
。/
是指操作码的扩展,需要将这个/r
中的寄存器r
的编号添加在ModR/M
的中间部分,就是reg/opcode
。这条指令的左操作数是CX
,编号如下为001
,既reg/opcode
为001
。
从/r
就可以看出只要是操作码的后边有斜杠'/'
,这条指令一定有ModR/M
字段。
其中:Mod
如下为00
,对应地址寻址,右操作数是[BX+SI]
,则R/M
位是000
。
那么这条指令由上述各位组合而成:因此这条指令的机器码就是8B08
。
操作码 | mod | reg | r/m | ModR/M |
---|---|---|---|---|
8B | 00 | 001 | 000 | 08 |
上述计算过程中可通过快速定位计算出ModR/M
的数值,如第3条指令mov cx, [bx+si]
,左操作数CL
、右操作数[BX+SI]
,定位机器码是08
,即得到机器码8B08
。
在ModR/M
后面还可以有偏移量和立即数部分。
计算下面两条指令的机器码:
4、计算指令mov ax, [bx+3]
的机器码8B4703
:
其中:操作码是8B
。
其中:/r
表示指令中的纯寄存器操作数
,在这里就是左操作数r16
。/
是指操作码的扩展,需要将这个/r
中的寄存器r
的编号添加在ModR/M
的中间部分,就是reg/opcode
。这条指令的左操作数是AX
,编号如下为000
,既reg/opcode
为000
。
从/r
就可以看出只要是操作码的后边有斜杠'/'
,这条指令一定有ModR/M
字段。
其中:Mod
如下为01
,对应地址寻址+偏移量,右操作数是[BX]
,则R/M
位是111
。
那么这条指令由上述各位组合而成:因此这条指令的机器码就是8B4703
。
操作码 | mod | reg | r/m | ModR/M | 偏移量 |
---|---|---|---|---|---|
8B | 01 | 000 | 111 | 47 | 03 |
5、计算指令mov word [bx+si+3], 0x55AA
的机器码C74003AA55
:
其中:操作码是C7
。
其中:/0
表示指令中的纯寄存器操作数
,在这里就是左操作数r16
。/
是指操作码的扩展,就是reg/opcode
。将这个0
扩展成3位
既为000
,既reg/opcode
为000
。
其中:Mod
如下为01
,对应地址寻址+偏移量,则R/M
位是000
。
那么这条指令由上述各位组合而成:因此这条指令的机器码就是C74003AA55
。
操作码 | mod | reg | r/m | ModR/M | 偏移量 | 立即数 |
---|---|---|---|---|---|---|
C7 | 01 | 000 | 000 | 40 | 03 | AA55 |
06、x86指令格式–SIB部分
SIB字段的格式:
1、计算指令add edx, [eax+ebx*2]
的机器码031458
:
其中:操作码是03
。
其中:/r
表示指令中的纯寄存器操作数
,在这里就是左操作数r32
。/
是指操作码的扩展,需要将这个/r
中的寄存器r
的编号添加在ModR/M
的中间部分,就是reg/opcode
。这条指令的左操作数是EDX
,编号如下为010
,既reg/opcode
为010
。
从/r
就可以看出只要是操作码的后边有斜杠'/'
,这条指令一定有ModR/M
字段。
其中:Mod
如下为00
,对应地址寻址,则R/M
位是100
。
上面有一个注释1
:表示有SIB
字段
其中SIB
的基址寄存器(Base
)为EAX
为000
。
其中SIB
的寻址方式是EBX*2
,则SS
为01
、Index
为011
。
那么这条指令由上述各位组合而成:因此这条指令的机器码就是031458
。
操作码 | mod | reg | r/m | ModR/M | ss | Index | Base | SIB |
---|---|---|---|---|---|---|---|---|
03 | 00 | 010 | 100 | 14 | 01 | 011 | 000 | 58 |
若指令比较复杂,如:对于32为寻址方式来说偏移量是1、4个字节,即对应8、32位偏移量。
计算下面这条指令的机器码:
2、计算指令add word [eax+ebx*8+0x3c00], 0x55AA
的机器码8184D8003C0000AA55
:
其中:操作码是81
。
其中:/0
表示是操作码的扩展,这个数字0
要转换成3位
即000
添加在ModR/M
的中间部分,就是reg/opcode
。既reg/opcode
为010
。从/r
就可以看出只要是操作码的后边有斜杠'/'
,这条指令一定有ModR/M
字段。
其中:iw(imm word长度为字的立即数)
,这里就是指令中的右操作数imm16
。
其中:Mod
如下为10
,对应地址寻址,则R/M
位是100
。
其中SIB
的基址寄存器(Base
)为EAX
为000
。
其中SIB
的寻址方式是EBX*8
,则SS
为11
、Index
为011
。
那么这条指令由上述各位组合而成:因此这条指令的机器码就是8184D8003C0000AA55
。
操作码 | mod | reg | r/m | ModR/M | ss | Index | Base | SIB | 偏移量 | 立即数 |
---|---|---|---|---|---|---|---|---|---|---|
81 | 10 | 000 | 100 | 84 | 11 | 011 | 000 | D8 | 003C0000 | AA55 |
07、x86指令格式–指令前缀部分
指令前缀:段超越前缀、总线封锁前缀、数据传送指令的重复前缀、操作尺寸反转前缀、地址尺寸反转前缀等等。指令最多有4个前缀
。
课后练习:
08、处理器默认操作尺寸和相关指令前缀
07
小节课后习题答案:
之前讲过的操作尺寸:
16位、32位操作尺寸:
默认操作尺寸:
所以上节习题中:
1、默认操作尺寸
是16位
时:
2、默认操作尺寸
是32位
时:
其中前缀66
将反转操作数
的尺寸,从默认的是16位
反转成32位
。
其中前缀67
将反转有效地址
的尺寸,从默认的是16位
反转成32位
。
09、使用伪指令bits生成16位和32位模块
相同机器指令
对应不同汇编指令
:
相同机汇编指令
对应不同机器指令
:
代码如下:
bits 16 ;也可写为[bits 16],若为第一条[bits 16]指令也可省略;
;因为默认使用[bits 16]编译程序
mov ax, [bx+si]
bits 32 ;也可写为[bits 32]
mov ax, [bx+si]
编译器编译之后的机器码:
10、描述符和段描述符高速缓存器的D位
描述符的D/B
位,对于代码段,即S=1、X=1
来说,此位为D
,为0
表示16位操作尺寸、为1
表示32位操作尺寸。
如使用CS
的段选择子
选择一个描述符到描述符高速缓存器
中,会有一个D位
,就是描述符中的D位
,处理器就根据这个D位
来判断当前的默认操作尺寸。
当程序加电复位时,处理器默认是16位的操作尺寸:
设置断点b 0x7c00
,c
命令执行到此处,查看CS
的D位
:仍然是0,既16位操作尺寸。
n/s
命令单步执行,进入保护模式,查看CS
的D位
,仍然是0,既16位操作尺寸。
其中CS
的状态:
其中X=0
表示不可执行,即数据段,段类型的检查是在将一个描述符加载到段描述符高速缓存器时进行的,主要是看它的类型是否与段寄存器的类型匹配,因为代码段/数据段寄存器只能加载代码段/数据段描述符。然而在平时读取指令时段的类型是不检查的。
11、进入保护模式并切换到32位模块使用32位默认操作尺寸
安装代码段的描述符:
;创建#1描述符,保护模式下的代码段描述符
mov dword [bx+0x08],0x7c0001ff
mov dword [bx+0x0c],0x00409800
段基地址
:0x00007C00
段界限
:0x001FF
G位
:为0
表示段界限以字节为单位。所以段大小为0x001FF + 1 = 0x02000 = 512字节
S位
:为1
表明是代码段X位
:为1
表明可执行P位
:为1
表明是段是存在的D/B位
:为1
表明是32位
的默认操作尺寸
直接绝对远转移指令:
...
;以下进入保护模式
jmp 0000000000010_0_00B:flush
bits 32
flush:
mov cx,00000000000_10_000B ;加载数据段选择子(0x10)
mov ds,cx
;以下在屏幕上显示"Protect mode OK."
mov byte [0x00],'P'
mov byte [0x02],'r'
mov byte [0x04],'o'
...
这时处理器是在16位操作尺寸,所以使用的是IP
不是EIP
。
在进入jmp
指令之前,有很多指令已经进入了流水线,都是按照之前的16位操作尺寸进行译码的。那么在使用bits 32
改变了操作尺寸之后,那些指令都是错误的,但是执行jmp
指令会清空流水线,这样程序才不会出错。
调试程序,执行到jmpf
指令停下查看寄存器状态:此时操作尺寸都是16位,但是下列汇编中使用了ecx
32位寄存器,显然以16位操作尺寸的角度是不正确的。
执行这条jmpf
指令之后,查看寄存器状态:从CS
的状态可以看出当前默认操作尺寸是32位的。
那么再以32位操作尺寸的角度看之后的指令:就和程序中是一样的了。
上一节:22、进入保护模式
下一节:24、存储器的保护