文章目录
上一节:21、32位x86处理器编程架构
下一节:23、指令的格式及其操作尺寸
01、保护模式要保护的是什么
保护模式下每个程序都有一个特权级(高 0、1、2、3 低),0特权级具有最高的权限,可以读写任意内存位置、读写所有端口、执行特权指令(用来控制处理器的运行模式和工作状态)。
学习目标:
02、全局描述符表GDT和寄存器GDTR
和一个段有关的信息需要8个字节
来描述,称为段描述符
,集中存放在描述符表中。
最重要的是全局描述符表(GDT:Global Descriptor Table
),需要在进入保护模式前定义。处理器内部有一个GDTR
寄存器(48位),这个寄存器保存了GDT
的起始线性地址和界线值。
GDTR
寄存器高32位存放GDT
线性基地址、低1位存放GDT
界限(表内最后一个字节对与表起始处的偏移,即表的大小 减 一),GDT
的界限是16位的,即GDT
最大为2^16 = 65536字节 = 64K字节
,一个描述符8个字节,最多可存放8192
个描述符。
GDT理论上可定义在任何位置,但是在进入保护模式之前,实模式只能访问前1M字节,所以一般定义在1M字节之内,也可以在进入保护模式之后换个位置。
03、创建全局描述符表
指定的GDT
起始地址:
取得32位的GDT
物理地址:
计算GDT所在段的逻辑地址
和GDT在段内的偏移地址
,程序如下:
...
;计算GDT所在的逻辑段地址
mov ax,[cs:gdt_base+0x7c00] ;低16位
mov dx,[cs:gdt_base+0x7c00+0x02] ;高16位
mov bx,16
div bx ;段地址存在ax中
mov ds,ax ;令DS指向该段以进行操作
mov bx,dx ;段内起始偏移地址传送到基值寄存器bx
...
04、描述符分类
描述符的作用就是记录某个段的地址信息:
存储器的段
描述符:一般用来藐视一般的代码段、数据段、栈段;
系统
描述符:用来描述系统运行和控制相关的内容;
门
描述符:用来描述一些与程序执行有关的逻辑结构,如过程调用和中断处理的逻辑结构。
根据描述符的S
位和TYPE
位的不同,不同描述符的其他位也有不同。
05、段描述符–段的类型和基地址
存储器的段
描述符格式:
TYPE
字段:X
位为0表示不可执行
,即为数据段,其他三位为E、W、A
。
- E(Expand):扩展方向,为0向上扩展、为1向下扩展。
- W(Writtable):可写属性,为0表示不可写、为1表示可写。
- A(Accessed):
为0
表示未被访问,为1
表示已被访问,在描述符创建时此位清零(由软件,操作系统负责),每当该段被访问处理器自动将该位置1。
TYPE
字段:X
位为1表示可执行
、既代码段,其他三位为C(表示特权级)、R、A
。
- C:
为0
表示非依从
的代码段,只有特权级相同的程序才能直接转移到这个段内执行;为1
表示依从
的代码段,特权级低的程序可以直接转移到这个段内执行;此位通常为0。 - R:
为0
表示代码段不能读出,为1
表示代码段可以读出。这里的R位
并非用来限制处理器取指令、执行指令,而是限制程序像访问数据段一样来访问代码段的内容。 - A(Accessed):
为0
表示未被访问,为1
表示已被访问,在描述符创建时此位清零(由软件,操作系统负责),每当该段被访问处理器自动将该位置1。
DPL
(Decriptor Privilege Level
)位:特权级位
06、段描述符–段界限及访问控制位
1、描述符的段界限:
2、向上扩展的段:段界限为0,段长度为1。段的实时长度为段界限减1。
3、向下扩展的段:一般用做栈段,也可不用。此时段界限为SP
所不允许的最小值,超过段界限时会使得处理器触发异常中断。此时段实时长度
为0xFFFF(0xFFFFFFFF)
减去段界限。
4、描述符的G
(粒度)位:为0
表示段界限以字节为单位、为1
表示段界限以4K字节为单位。若G位
为1
,则有:
5、描述符的P
(Present
)位,段存在位。段是一段连续的内存空间,在物理上始终时存在的,但是这段空间及其内部数据是否位当前描述符准备的、是否可以立即通过当前描述符来访问,这就是段的存在性。
一般来说,描述符所指示的段都是存在的,但是当内存空间紧张时,有可能只是建立了描述符,对应的段并没有安排,被认为是不存在的,这是就应当将描述符的P位
清零表示段不存在。
另外当内存空间紧张时,会把很少用的段置换到硬盘中,腾出空间给当前急需要内存的程序使用,此时同样要把段描述符的P位
清零。当再次轮到它执行时,再装入内存,将P位置1
。
P位
是由处理器检查的,每当通过描述符访问内存中的段时,如果P位
是0,处理器就会产生一个异常中断,通常该中断处理过程是由操作系统提供的,该处理过程的任务是将该段从硬盘换回到内存,并将P位置1
。在多用户、多任务的系统中是一种常用的任务调度策略。
6、描述符的L位
(64位代码段标志),为0
表示不为64代码段、为1
表示是64位代码段。
7、描述符的D/B位(操作尺寸/栈上部边界),笼统地来说是指示段按照16位还是32位来进行操作。为0
表示按照16位、为1
表示按照32位。
8、描述符的AVL
(Available
)位(可自由使用的保留位)。通常是由操作系统来用
07、安装存储器的段描述符
1、处理器规定第一个描述符为空描述符,或者叫做哑描述符,代码如下:
...
;创建0#描述符,它是空描述符,这是处理器的要求
mov dword [bx+0x00],0x00
mov dword [bx+0x04],0x00
...
2、安装数据段描述符,代码如下:
...
;创建#1描述符,保护模式下的代码段描述符
mov dword [bx+0x08],0x8000ffff
mov dword [bx+0x0c],0x0040920b
...
段基地址
:0x000B8000
段界限
:0x0FFFF
G位
:为0,表明段界限以字节为单位,共64K
字节P位
:为1,表示这个段存在
DPL位
:为00,表示特权级别为0,最高特权级S位
:为1,表示这个段是存储器的段X位
:为0,表示这个段是数据段E位
:(由于X位
为0),E位
为0,表示这个段是向上扩展的W位
:(由于X位
为0),W位
为1,表示这个段是可写的A位
:为0,表示为被访问过
此描述符在内存中的映像:
当这个描述符安装完毕之后,处理器的内存布局:
08、加载全局描述符表寄存器GDTR
GDT
是由处理器使用的,用户只是负责创建这个表,处理器通过GDTR
来使用GDT
,GDTR
用来保存GDT
的位置和大小信息。
使用lgdt m(m48)
指令来加载GDT
,在地址m
处应该存放的是一个48位的操作数。
代码如下:
...
;初始化描述符表寄存器GDTR
mov word [cs: gdt_size+0x7c00], 15 ;描述符表的界限(总字节数减一)
lgdt [cs: gdt_size+0x7c00] ;lgdt指令不影响任何标志位
...
09、开启处理器的第21根地址线
8086
处理器中只有20根
地址线,所以会有地址回绕特性
:
80286
处理器有24根
地址线,通过键盘控制器来控制第20根
地址线,具体看8042
键盘控制器的0x60
号端口的位1
输出为0
还是为1
来控制A20
地址线。
从80486
处理器开始不实用上述方法控制地址线A20
了。使用如下模式:
其中ICH
芯片的0x92
号端口的位0
链接处理器的INIT#
(低电平有效)位,既复位按钮。位1
连接在一个或门
上,位2~位7
保留不用。
在程序中还是做了A20
打开的操作,其实不需要,因为处理器默认是将A20
打开的。
...
in al,0x92 ;南桥芯片内的端口
or al,0000_0010B
out 0x92,al ;打开A20
...
10、设置寄存器CR0的PE位进入保护模式
CR0
寄存器(Control Register:CR
),位0
是PE
(Protect mode Enable
)位,保护模式允许位,位0为1
表示进入保护模式。
在进入保护模式之前要进行关中断操作,因为在保护模式下会有一套别的中断机制,在进入保护模式之前没有设置,所以就需要清中断。
在保护模式下BIOS中断
也不能使用,因为它们还是使用CS
左移4位加上偏移地址的形式访问内存。
代码如下:
...
cli ;保护模式下中断机制尚未建立,应
;禁止中断
mov eax,cr0
or eax,1
mov cr0,eax ;设置PE位
...
程序上电之后的状态,sreg
命令显示GDTR
的内容:
creg
命令显示CR0
内容:小写为0。
接着执行程序到断点0x7C00
处,执行了BIOS
,GDTR
寄存器状态:此时BIOS
创建了描述符表
此时CR0
状态:
执行程序完成CR0
设置:此时CR0
的PE
位为1
,表示进入保护模式。
11、描述符高速缓存器和保护模式下的内存访问
段寄存器
在32位下扩展了描述符高速缓存器
,而且是不可见的,由处理器内部使用。段寄存器
叫做段选择器
。
- 在实模式下,段选择器的内容为逻辑段地址;
- 在保护模式下,段选择器的内容为
段选择子
,简称选择子
。
段选择子(Segment Select:SS
)
位15~3
:描述符索引,2^13 = 8192
,刚好是描述符的最大个数。位2
是TI
位(Table Indicator:TI
):表指示器,为0
表示要选择的描述符在GDT
中、为1
表示在LDT
中。位1~0
:RPL
位,特权级位,此时置为00即可。
程序中要选择的段描述符索引号为1
,即第2个描述符
:
使用段选择子的过程:
- 1、将
段选择子
传送到段选择器
; - 2、一旦改变了
段选择器
,处理器就是用这个段选择子
乘以8它得到描述符表中的偏移量(因为一个描述符占8个字节,索引号就要乘以8); - 3、由于
TI位为0
,即使用GDT
,于是从GDTR
中取出GDT
的线性基地址; - 4、将2和3两步中的地址相加得到目标描述符的线性地址(若没有分页则此时也可为物理地址);
- 5、处理器使用4中的线性地址访问内存取出这个描述符;
- 6、然后将5中的内容传送到1中哪个
段选择器
的描述符高速缓存器
中,之后处理器就使用描述符高速缓存器
中的线性基地址
加上段内偏移
来访问内存。
代码如下:
...
mov cx,00000000000_10_000B ;加载数据段选择子(0x10)
mov ds,cx
...
上述代码执行之后,每当有访问内存的指令时,就不再访问GDT
中的描述符、也不再使用DS
中的段选择子,而是直接使用段选择器
中的描述符高速缓存
中的线性基地址
(不需要左移4位了)加上指令中给出的偏移量
来访问内存。
显示字符的代码如下:
...
;以下在屏幕上显示"Protect mode OK."
mov byte [0x00],'P'
mov byte [0x02],'r'
mov byte [0x04],'o'
mov byte [0x06],'t'
mov byte [0x08],'e'
mov byte [0x0a],'c'
mov byte [0x0c],'t'
mov byte [0x0e],' '
mov byte [0x10],'m'
mov byte [0x12],'o'
mov byte [0x14],'d'
mov byte [0x16],'e'
mov byte [0x18],' '
mov byte [0x1a],'O'
mov byte [0x1c],'K'
hlt ;已经禁止中断,将不会被唤醒
...
12、调试器中观察实模式和保护模式的内存访问
- 1、在32位处理器的实模式下,只能向段选择器传送16位的逻辑段地址;
- 2、处理器将16位的逻辑段地址左移4位(乘以16),左边加0扩展到32位,传送到段描述符高速缓存器中
- 3、以后就使用段描述符高速缓存器中基地址来访问内存
- 4、就是说在实模式下:段寄存器描述符高速缓存器的内容只有低20位是有效的,高12位全部为0,处理器仍然只能访问1M字节内存。
例如:用BX
所指示的偏移地址
加上0x2000左移4位
来访问内存。
例如:若第21根地址线A20
是打开的,则访问的是0x00100000
地址,否则访问地是0x00000
地址。
处理器加电复位之后处理器的状态:
其中第二行的都是段描述符高速缓存器的状态:段地类型、基地址、段界限、读写状态、是否访问过,dh
(描述符的高32位)、dl
(描述符的高低32位)是描述符的信息。
处理器加电复位之后进入实模式,段描述符高速缓存中的段基地址是段选择器左移4位得到,比如CS
寄存器:
CS
段选择器的内容是0xF000
,然而CS
段描述符高速缓存中的段基地址是0xFFFF0000
,并不是0xF000
左移4位得到的。因为在处理器加电复位之后,所有寄存器都会被预制成特定的内容,段选择器
和段描述符高速缓存器
是分别预制的,一开始两者之间不存在依赖关系,但是从现在开始一单改变了段选择器
的内容,将同时改变段描述符高速缓存器
中的基地址部分。
在实模式下,处理器使用CS高速缓存器
里的基地址 加上
指令指针寄存器IP
来取指令,CS高速缓存器
里的基地址是0xFFFF0000
,指令指针寄存器IP
的当前值为:
IP
的当前值为0xFFF0
,则两者想加为0xFFFFFFF0
,这就是第一条即将执行的指令的物理地址。
一旦我们执行第一条指令jmpf 0x0000:e05b
,则处理器会使用指令中给出的逻辑段地址0xF000
来替换CS段选择器的内容,同时用它乘以16用来替换段描述符高速缓存器中的基地址部分:
之后可以一直打断点,单步执行来调试程序,查看相关寄存器的内容。
使用info gdt
命令查看GDT
中的内容:
上一节:21、32位x86处理器编程架构
下一节:23、指令的格式及其操作尺寸