全局描述符表(GDT)
这里要先说明下,保护模式下对内存段的访问是有限制的,简单来说就是你不能再随意的访问了,只能访问授权给你的,然后段的访问限制等等信息就记载在一个叫做全局描述表里
段描述符
段描述符存储了某个段的具体信息,就像我们每个人的档案一样,记录着我们的信息
然后段描述符占用 8个字节,以下是段描述符的结构,上面是高32位,下面是低32位
可以看到分割比较奇怪,这可能是因为要兼容16位保护模式的缘故吧
总之,一个段描述符就是由32位的段基址、20位的段界限和12位的属性组成的
段描述符结构具体解析
- 段基地址 : 32位的段起始地址,可以是 0~4GB 的任意地址,不过还是建议选取16字节对齐的地址
- 段界限 : 20位的段界限是用来限制段的扩展范围的,感觉就像是段的长度、访问边界。访问内存的方法就是用段基地址加上偏移量
- 对于向上扩展的段(代码段或数据段),偏移量从0开始递增,段界限决定了偏移量的最大值
- 对于向下扩展的段(栈段),段界限决定了偏移量的最小值
- 属性:
-
G位 :1位,粒度(Granularity)位,用于解释段界限的含义,其实就是控制单位的大小,段界限是20位,也就是说段的扩展范围是 1~1M 个单位大小
- G=0,段界限以 字节 为单位,所以段的扩展范围是 1B~1MB
- G=1,段界限以 4KB 为单位,所以段的扩展范围是 4KB~4GB
-
S位 :1位,用于指定描述符的类型
- S=0,表示是一个 系统段
- S=1,表示是一个 代码段或者数据段(栈段是特殊的数据段)
-
DPL :2位,表示描述符的特权级,这里描述符的特权级用于指定 要访问该段所不许具有的最低特权级别
- 4种特权级别,分别是0、1、2、3,0最高,3最低
-
P位 :1位,段存在位(Segment Present),用于指示描述符所对应的段是否存在与内存中
-
D/B位 :1位,用于指示默认的操作数大小 或者 默认的栈指针大小,该标志位对于不同的段有不同的效果
- D位 : 对于代码段而言是D位,用于指示 指令中默认的偏移地址和操作数尺寸
- D=0,16位
- D=1,32位
- B位 :对于栈段而言是B位,用于在进行隐式的栈操作时(比如push、pop、call),是使用 SP 寄存器还是 ESP 寄存器
- B=0,使用 SP,且栈段的上部边界(SP的最大值)位 0xFFFF
- B=1,使用 ESP,且栈段的上部边界(ESP的最大值)位 0xFFFFFFFF
- D位 : 对于代码段而言是D位,用于指示 指令中默认的偏移地址和操作数尺寸
-
L位 :1位,64位代码段标志
-
TYPE字段 :4位,用于指示描述符的子类型或者类别
- 对于数据段来说,这4位是X、E、W、A
- X表示是否可执行,X=0不可执行,X=1可执行
- E指示段的扩展方向,E=0向上扩展,E=1向下扩展
- W表示段是否可写,W=0不可写,W=1可写
- A是已访问位,用于指示段最近是否被访问过
- 对于数据段来说,这4位是X、C、R、A
- C指示段是否为特权级依从的,这个不太明白
- R表示段是否可读,R=0不可读,R=1可读
- 其他一样
- 对于数据段来说,这4位是X、E、W、A
-
AVL位 :是软件可使用的位,不太明白
-
描述符表
简单来说就是 段描述符的集合
描述符表分为 全局描述符表(GDT) 和 局部描述符表(LDT)
全局描述符表寄存器GDTR
GDTR是一个48位的寄存器,分为两部分,32位的线性地址和16位的边界
线性地址保存的是全局描述符表在内存中的起始线性地址
边界保存的是全局描述符表的边界(界限),在数值上等于表的大小(总字节数)减1
GDT的界限是16位,则,表最大是2^16字节,每个描述符占用8个字节,所以最多定义8192个描述符
GDT可以位于内存中的任何地方,但它的安装是在实模式下进行的,所以通常定义在1MB以下,允许在进入保护模式后换个位置重新定义GDT
以下是GDTR和GDT的关系示意图
进入保护模式前的内存映像
进入保护模式
安装存储器的段描述符
说人话,就是将我们定义的段的信息,也就是段描述符加载到内存中(分配给GDT的范围)
在进行下面的步骤前,还要设置下堆栈段
; 设置堆栈段和栈指针
mov ax, cs
mov ss, ax
mov sp, 0x7c00 ; 分界线,代码段向上增长,堆栈段向下增长
- 将GDT的线性地址(物理地址)转换成逻辑地址和偏移地址
; 计算GDT所在的逻辑段地址
mov ax, [cs:gdt_base + 0x7c00] ; 低16位
mov dx, [cs:gdt_base + 0x7c00 + 0x02] ; 高16位
mov bx, 16
div bx
mov ds, ax ; 令ds指向该段进行操作
mov bx, dx ; 段内起始偏移地址
简单来说就是将GDT的线性地址(32位)存进DX:AX,然后除以16,得到的商(AX)就是逻辑地址,余数(DX)就是偏移地址
- 安装段描述符
; 创建#0描述符,空描述符,这是处理器的要求
mov dword [bx + 0x00], 0x00
mov dword [bx + 0x04], 0x00
; 创建#1描述符,保护模式下的代码段描述符
mov dword [bx + 0x08], 0x7c0001ff
mov dword [bx + 0x0c], 0x00409800
; 创建#2描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区)
mov dword [bx + 0x10], 0x8000ffff
mov dword [bx + 0x14], 0x0040920b
; 创建#3描述符,保护模式下的堆栈段描述符
mov dword [bx + 0x18], 0x00007a00
mov dword [bx + 0x1c], 0x00408600
需要注意的是 处理器要求第一个描述符定义成空描述符,然后每个段描述符占用8个字节,写入的内容对照段描述符的结构图看吧
加载GDTR
- 加载GDTR寄存器
; 初始化描述符寄存器GDTR
mov word [cs:gdt_size + 0x7c00], 31 ; 描述符表的界限(总字节数减1)
lgdt [cs:gdt_size + 0x7c00]
打开A20地址线
; 打开A20地址线
in al, 0x92
or al, 0000_0010B
out 0x92, al
关中断
; 关中断
cli ; 保护模式下中断机制尚未建立,应该禁止中断
设置cr0寄存器的PE位
; 设置cr0寄存器的PE位
mov eax, cr0
or eax, 1
mov cr0, eax ; 设置PE位
CR0是32位的寄存器,包含了一系列用于控制处理器操作模式和运行状态的标志位。它的第一位是保护模式允许位---PE位,置1表示处理器进入保护模式
到这里就已经进入保护模式了
保护模式下的内存访问
保护模式下段寄存器存储的不在是简单的段地址了,而是稍微复杂的 段选择子
32位处理器的段寄存器
在32位下,传统的段寄存器存储的是段选择子,所以更应该叫 段选择器,然后配上一个 描述符高速缓存器 才是32位下的段寄存器
段选择子
其实就是看从GDT中选择哪个段
- 描述符索引:就是段描述符的索引号,第0个段、第1个段……
- TI : 描述符表指示器,TI=0表示描述符在GDT中,TI=1表示描述符在LDT中
- RPl :请求特权级
描述符加载到不可见的描述符高速缓存器中
首先知道描述符在表内的偏移地址是 索引号乘以8(每个段描述符占8个字节)
解析过程:
- 将段选择子加载到寄存器 DS 中
- 处理器将 DS 中的段选择子的索引部分乘以 8 然后加上寄存器 GDTR 中的 GDT的基地址,从而得到对应段描述符的起始地址
- 把得到的段描述符加载到描述符高速缓存器部分
保护模式下的内存访问
以下是一个简单的访问数据段的例子:
解析步骤:这个例子就是要访问 DS:0x00 这个地址(没有加段超越前缀默认是DS)
- [0x00] 给出偏移地址是 0x00,且没有段超越前缀,则默认是使用 DS 段寄存器
- 将 DS 寄存器中的描述符高速缓冲器提供的 段的基地址 加上偏移地址就得到了物理地址
访问其他段也是一样的,我们不用再访问GDT中的描述符,直接用段寄存器描述符高速缓存器提供的线性基地址加上偏移地址就能得到物理地址了
清空流水线并串行化处理器
- 清空流水线就是将流水线中指令清空,因为这些指令是在实模式下编译执行的,是16位的,在32位保护模式下,由于对操作数和默认地址大小的解释不同,可能会出错,所以要清空流水线
- 串行化处理器其实就是清理掉乱序执行得到的中间结果,然后重新按指令的自然顺序执行
说了挺多,我也没太明白这些是啥意思,总之,解决方案就是 使用远转移指令jmp或者元过程调用指令call,跳到保护模式下的代码段执行就好了
保护模式mbr的源代码
; 设置堆栈段和栈指针
mov ax, cs
mov ss, ax
mov sp, 0x7c00 ; 分界线,代码段向上增长,堆栈段向下增长
; 清屏
push ax
mov ah,0x06
mov al,0
mov ch,0 ;(0,0)
mov cl,0
mov dh,24 ;(24,79)
mov dl,79
mov bh,0x07 ;黑底白字
int 0x10
pop ax
; 计算GDT所在的逻辑段地址
mov ax, [cs:gdt_base + 0x7c00] ; 低16位
mov dx, [cs:gdt_base + 0x7c00 + 0x02] ; 高16位
mov bx, 16
div bx
mov ds, ax ; 令ds指向该段进行操作
mov bx, dx ; 段内起始偏移地址
; 创建#0描述符,空描述符,这是处理器的要求
mov dword [bx + 0x00], 0x00
mov dword [bx + 0x04], 0x00
; 创建#1描述符,保护模式下的代码段描述符
mov dword [bx + 0x08], 0x7c0001ff
mov dword [bx + 0x0c], 0x00409800
; 创建#2描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区)
mov dword [bx + 0x10], 0x8000ffff
mov dword [bx + 0x14], 0x0040920b
; 创建#3描述符,保护模式下的堆栈段描述符
mov dword [bx + 0x18], 0x00007a00
mov dword [bx + 0x1c], 0x00408600
; 初始化描述符寄存器GDTR
mov word [cs:gdt_size + 0x7c00], 31 ; 描述符表的界限(总字节数减1)
lgdt [cs:gdt_size + 0x7c00]
; 进入保护模式的前置条件
; 打开A20地址线
in al, 0x92
or al, 0000_0010B
out 0x92, al
; 关中断
cli ; 保护模式下中断机制尚未建立,应该禁止中断
; 设置cr0寄存器的PE位
mov eax, cr0
or eax, 1
mov cr0, eax ; 设置PE位
; 进入保护模式
jmp dword 0x0008:flush ; 16位的描述符选择子:32位偏移
; 清流水线并串行化处理器
[bits 32]
flush:
mov cx, 00000000000_10_000B ; 加载数据段选择子(0x10, 索引为2)
mov ds, cx
;以下在屏幕上显示"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'
ghalt:
hlt ; 已禁止中断,将不会被唤醒
;---------------------------------------------------------------
gdt_size dw 0
gdt_base dd 0x00007e00 ; GDT的物理地址
times 510-($-$$) db 0
db 0x55, 0xaa ; 结束标志
标签:保护模式,mov,地址,描述符,chapter11,寄存器,------,bx
From: https://www.cnblogs.com/winter-z/p/18355738