===================== 第一部分 ====================
4.3 全局描述符表
全局描述符表(GDT)。
4.3.1 段描述符
问:段描述符是什么,做什么用?
实模式下访问内存使用的是 段基址:偏移地址。
在保护模式下为了安全性,给其中的段基址添加了很多的安全属性同时也增长了段基址长度,让总长度增加到了8字节,使用段选择子替代段基址。
段选择子中就有者段描述符的"下标"。
name | 长度 | 功能 | 备注 |
---|---|---|---|
段界限 | 20bit | 描述段的拓展范围,界限粒度由G来决定 。CPU硬件会负责检测偏移地址是否越界 | 段界限边界值 =(描述符中段界限+1)*(段界限的粒度大小:4KB或是1)-1,因为从零算,所以多一行,因为从零算,所以少一个。大小分为1MB/32GB |
G | 1bit | 为0,界限粒度为1Byte;为1,界限粒度为4KB=2^12Byte | |
S | 1bit | 0,系统段;1,数据段 | 各种门的结构即属于"系统段"。例如:调用门,系统门等。 |
type | 4bit | 标记段类型,配合S使用,如表4-10 | |
段描述符type类型【非系统段】 | |||
A | 设置为0 | cpu访问后会置为1 | |
C | 0,非一致性代码段,1,一致性代码段 | 一致不一致,是和和转移后的段比较,和不和转移后的段特权级一致。1 则用转移前的特权级 CPL <= CPL,0 则用目标的特权级 CPL <= DPL。 | 一致代码段和非一致代码段 |
R/W | 0,不可读;1,可读;0,不可写;1,可写 | ||
X | 0,不可执行;1,可执行 | ||
E | 0,向上拓展;1,向下拓展 | 和前面的拓展范围有关联,这里E决定了拓展方向。 | 代码段,数据段向上拓展(地址越来越高);栈段像下拓展。(地址越来越低) |
name | 长度 | 功能 | 备注 |
DPL | 2bit | 共4种特权级,0,1,2,3 | 操作系统0,用户等级3权限最小 |
P | 1bit | 0,不存在;1,存在 | 检查是否存在于内存中 |
AVL | 1bit | 没有专门用途 | |
L | 1bit | 1,为64位代码段;0,32位代码段 | |
D/B | 1bit | 对于代码段D:0/1,有效地址和操作数为16bit/32bit,指令有效地址用IP/EIP寄存器; | 对于栈段B:涉及到栈指针的选择和栈地址上限;0/1使用的是SP/esp寄存器,也就是栈的起始位置是16/32位寄存器的最大寻址范围。0xFFFF/0xFFFFFFFF |
全局描述符表GDT、局部描述符LDT及选择子
问:这么多描述符放在哪里?
答:放在全局描述符表——GDT中,GDT由GDTR寄存器指向它,方便CPU找它。lgdt指令。
使用格式:lgdt 48位内存数据
4.3.1.1 段的选择子
使用段选择子替代段基址。
段选择子也是16bit和段寄存器一样。
RPL | 2bit | 0,1,2,3特权级 | 此处为当前的特权级 |
IT | 1bit | 0,在GDT中;1,在LDT中 | |
index | 描述符的索引值位 | 相当于GDT数组中的下标 | |
此时访问内存,直接用选择子对应的”段描述符中的段基址“加上”段内偏移地址“就是要访问的内存地址。另外,GDT中的第0个段描述符不可用。 |
4.3.1.2 LDT
只不过lgdt不怎么被用到。寄存器为LDTR,使用 lldt 指令加载LDT。
使用格式为:lldt 16位寄存器/16位内存。
问:为什么这里lldt 的是16位的,而不是像lgdt中的那样?
答:因为LDT所在的位置被划分成段,只能用段选择子直接访问了,而段选择子正是16bit。
注意:与GDT不同的是LDT的第0个段描述符是可用的。
4.3.3 打开A20地址线
早期实模式最大地址为 0xFFFF0+0xFFFF=>0x10FFEF,而实模式下地址线是20,即为只能访问到0xFFFFF。因此cpu采取将超过1MB的部分自动回绕到0地址这种方式来解决"地址溢出"。也称为“地址回绕”。
所以控制这第21根线A20(第一根线是A0)。
A20Gate 打开 | 访问0x10 0000~0x10 FFFF时候访问的是真正的该地址 | |
A20Gate 关闭 | 访问0x10 0000~0x10 FFFF时候采用的是8086/8088的地址回绕。 | |
如今是要进入安全模式,突破第20条地址线到更多的地址上去。所以,开启A20Gate必不可少。 |
4.3.3.1 打开A20Gate的方式
将 端口0x92 的第一个位置置1即可。只需三部:
in al, 0x92
or al, 0000_0010B
out 0x92, al
4.3.4 保护模式的开关,CR0寄存器的PE位
我们要用到CR0寄存器的第0位,即PE位,此位用于启动保护模式。
PE置1:
mov eax, cr0
or eax, 0000_0001B
mov cr0, eax
4.3.5 让我们进入保护模式
保护模式是在loader.bin中进入的。
- 打开A20
- 加载GDT
- 将cr0的PE位置置为1
========== 第二部分 ==========
4.5 使用远跳转指令清空流水线,更新段描述符缓冲寄存器
jmp dword SELECTOR_CODE:p_mode_start ;刷新流水线
进入保护模式后,32位模式下存放在缓冲区中的16位代码就不能继续使用了,就要清空。使用jmp清空缓冲区。该处直接使用SELECTOR_CODE选择子
4.6 保护模式之内存段的保护
4.6.1段寄存器加载选择子时候的保护
- 判断选择子指向的描述符是否越界/存在。
- 描述符表基地址+选择子中的索引值8+7 <= 描述符表基地址+描述符表界限值;因为描述符一个8字节所以 *8,第0个描述符是空的所以+7。不符合<=则抛出异常。
- 检查段描述符中的type是否符合相应的段寄存器。
- 只有具备 可执行(代码段) 才能加载到CS寄存器
- 只具备 执行(代码段)不能加载到除了CS寄存器之外的寄存器
- 只有具备 可写 (数据段)才能加载到SS栈寄存器
- 至少具备 可读的段才能加载到DS,ES,FS,GS段寄存器中.
- 检查描述符P位判断该内存是否是存在的。P由操作系统设置CPU检查,A是CPU设置的
4.6.2 代码段和数据段的保护
EIP偏移地址+指令/数据长度-1<=实际边界长度大小
指令/数据不能"骑在"边界上。
举例:段边界为0x12345,基地址0x00000000
G=0:实际到达0x12345
G=1:实际到达0x12345*0x1000+0xFFF=0x12345FFF
当G=1时候,访问0x12345FFF,获取的是1字节则可以。但mov ax,word[0x12345FFF]像这样2字节的,因为是数据所以向上延伸,操作的是0x12345FFF和0x12346000的数据。就会出现“骑墙”的情况而报错。