在实模式下由于对内存的保护不是特别的完善,一个段可以任意访问访问不是该段范围内的内存。例如以下一个小例子
mov cx,0x8000
push ds
mov ds,cx
mov [0x05],dx
pop ds
这个例子就说明了先保存当前的数据段地址之后在修改其他段的内存,之后再返回当前的数据段。也就说明一个问题在实模式下可以任意对内存进行访问这样做是很危险的例如写一个程序从而达到修改内核代码等等。所以引入了保护模式。
在保护模式下对内存的访问仍然是短地址加上段内偏移,但是在访问前必须是已经注册好的段切偏移量未超过该注册段的段界限。但是如何注册一个段呢,注册在哪里呢,怎么注册。所以本次笔记的重要角色出场了,即全局描述符,有它来对一个要注册的段进行描述,以下图片是全局描述符的结构(4字节大小)
很明显描述符指定了一个4字节的段基址和20位的段界限,在实模式下段基址是逻辑地址,左移4位加上偏移量才是真正的物理地址,而在32位保护模式下段基址是32位的线性地址,在未开启分页模式,可以认为就是物理地址。段基址可以是0-4g中的任意一个,但最好是以16字节对其。段界限是对一个段的扩展,因为访问内存是段基址加上段内偏移,所以对于向上扩展的段例如数据段,则段界限限定了一个段的上限,最小偏移为0,但对于向下扩展的段,例如栈段,则段界限限制了段的最小值,即最小偏移量。
G:代表粒度,如果为0,则段界限以字节为单位,因为段界限是20位所以段界限的大小为0-1mb。如果为1,则段界限以4kb为单位,其大小为0-4gb
D/B:代表默认操作数大小或者默认栈指针大小或者上部边界标志。
该标志位对于不同的段有着不同的含义,对于代码段,此标志位为D位,D=0则表示指令中的偏移地址或者操作数是16位的,D=1则为32位的
对于栈段,此标志位为B位,B=0,则默认栈指针位SP,或者决定栈的上部边界为0xffff,B=1,则默认栈指针为ESP,或者决定栈的上部边界为0xffffffff
AVL:
P:段存在位
DPL:段的特权级别
S:指定描述符的属性,为0时则说明该段为系统段,为1时表示代码段或者数据段。
TYPE:表示该段的属性
介绍完全局描述符的结构后该说明如何让计算机知道我们定义了各个段,这需要一个指令lgdt和一个寄存器GDTR。
首先说明寄存器GDTR 是一个6字节的寄存器称为全局描述符寄存器 其高32位为全局描述符的基址低16位为全局描述符的界限,因为地址是从0开始算起所以全局描述符的界限为实际大小减一。
lgdt 指令 定义为 lgdt m48 该指令的操作数是一个6字节内存区域的 高4字节为全局描述符的基址低2字节为全局描述符的界限。
接下来是记录利用全局描述符来进行内存访问的代码
;设置堆栈段和栈指针
mov ax,cs
mov ss,ax
mov sp,0x7c00
;计算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],0x00409600
;初始化描述符表寄存器GDTR
mov word [cs: gdt_size+0x7c00],31 ;描述符表的界限(总字节数减一)
lgdt [cs: gdt_size+0x7c00]
in al,0x92 ;南桥芯片内的端口
or al,0000_0010B
out 0x92,al ;打开A20
cli ;保护模式下中断机制尚未建立,应
;禁止中断
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)
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'
;以下用简单的示例来帮助阐述32位保护模式下的堆栈操作
mov cx,00000000000_11_000B ;加载堆栈段选择子
mov ss,cx
mov esp,0x7c00
mov ebp,esp ;保存堆栈指针
push byte '.' ;压入立即数(字节)
sub ebp,4
cmp ebp,esp ;判断压入立即数时,ESP是否减4
jnz ghalt
pop eax
mov [0x1e],al ;显示句点
ghalt:
hlt ;已经禁止中断,将不会被唤醒
;-------------------------------------------------------------------------------
gdt_size dw 0
gdt_base dd 0x00007e00 ;GDT的物理地址
times 510-($-$$) db 0
db 0x55,0xaa
对于为什么全局描述符一开始要写在0x00007e00处是因为主引导区代码是在0x00007c00处加上大小512字节(0x200)所以就是0x00007e0000
第一次笔记就先按到这里以后会继续努力的加油了