从实模式到保护模式笔记8储存器的保护

从实模式到保护模式笔记8储存器的保护

0x00 代码

mov eax,cs
mov ss,eax;实际上和mov ss,ax的汇编指令一样
mov sp,0x7c00
;计算GDT的逻辑段地址
mov eax,[cs:pgdt+0x7c00+0x02]
xor edx,edx
mov ebx,16
div ebx;64/32位除法 商在eax 余数edx

mov ds,eax;32位处理器的段寄存器操作即使在实模式下也是将段地址写入低16位段选择子(原值) 和 段描述符高速缓存部分的段基地址部分(* 16后的值)
mov ebx,edx;起始偏移地址

;0#描述符,空描述符,处理器要求
mov dword [ebx+0x00],0x00000000
mov dword [ebx+0x04],0x00000000

;1#描述符,一个数据段 0-4GB
mov dword [ebx+0x08],0x0000ffff;段基址为0,界限0xfffff
mov dword [ebx+0x0c],0x00cf9200;G位为1粒度4KB 因此实际界限0-0xffffffff

;2#描述符 代码段
mov dword [ebx+0x10],0x7c0001ff
mov dword [ebx+0x14],0x00409800
;00000000_01000000_1001_1000_00000000  type :1000 代码段不可读 只能同级或者门调用,没有访问过

;3#描述符 上述代码段的别名段,是一个数据段
mov dword [ebx+0x18],0x7c0001ff
mov dword [ebx+0x1c],0x00409200
;00000000_01000000_1001_0010 _00000000 数据段可读可写

;4#描述符 栈段
mov dword [ebx+0x20],0x7c00fffe
mov dword [ebx+0x24],0x00cf9600
;00000000_01000000_1001_0110 _00000000 栈段可读可写,且向下扩展 粒度为4KB

mov word [cs:pgdt+0x7c00],39
lgdt [cs:pgdt+0x7c00]

in al,0x82;开启a20
or al,0000_0010b
out 0x92,al

cli;保护模式下的中断未配置好,关了

mov eax,cr0
or eax,1
mov cr0,eax;进入保护模式

jmp dword 0x0010:flush

[bits 32]
flush:
mov eax,0x0018
mov ds,eax

mov eax,0x0008;都指向那个4GB的数据段,段基地址0x00000000
mov es,eax
mov fs,eax
mov gs,eax

mov eax,0x0020
mov ss,eax
xor esp,esp 

mov dword [es:0x0b8000],0x072e0750;字符'P' '.'及其颜色
mov dword [es:0x0b8004],0x072e074d
mov dword [es:0x0b8008],0x07200720
mov dword [es:0x0b800c],0x076b076f

mov ecx,pgdt-string-1;遍历次数=串长度-1;比如2个元素,只交换一次就够了
@@1:
push ecx
xor bx,bx
@@2:
mov ax,[string+bx];读两个字节
cmp ah,al
jge @@3 ;jge jmp while greater or equal
xchg al,ah ;交换
mov [string+bx],ax;重新写入
@@3:
inc bx
loop @@2
pop ecx
loop @@1

mov ecx,pgdt-string
xor ebx,ebx
@@4:
mov ah,0x07
mov al,[string+ebx]
mov [es:0xb80a0+ebx*2],ax;32位寻址方式允许比例
inc ebx
loop @@4
hlt;休眠
;----------------------------
string db 'ahsduiash234oiah123.'
pgdt dw 0;size
dd 0x00007e00 ;GDT的物理地址
;----------------------------
times 510-($-$$) db 0
db 0x55,0xaa

SharedScreenshot1

上述代码运行正确

0x01 代码讲解

​ 引入保护模式的目的是提供内存保护,通过限制访问段的界限,添加访问属性实现。另一方面保护模式也可以实现虚拟内存管理(利用P位和统计TYPE里的A位),进行内存和硬盘的换入换出。

​ 在32位处理器中 不论是16位模式还是32位模式下 mov ds,ax和mov ds,eax的指令是一样的(实际上32位中mov ds,ax本质是做mov ds,eax) 不论是在实模式还是保护模式(都是将低16位对于ax来说就是本身 传入ds的段选择子【本身】和段描述符高速缓存部分的段基址部分【*16后】)

mov eax,0xffff7c00
mov ss,eax;实际上和mov ss,ax的汇编指令一样
mov sp,0x7c00

SharedScreenshot2

​ 可以看到即使eax里是0xffff7c00 mov ss,eax执行后也只是将低16位放到ss的段选择子和将低16位 * 16后放到描述符高速缓存部分的段基址部分

在32位处理器上,即使是实模式,也可以使用32位的寄存器

​ 为什么每次GDT的0#都是全0,因为如果有那个段寄存器忘了设置,那么他的默认段选择子就是0#,所以为了安全起见我们给0#一个空位置

​ 对于段界限,一个段的空间实际上是 [段基地址-段界限],包含两端,当然这是G位未0,粒度为1字节时,如果G位为1,粒度为4kb,则为[段基地址-段界限 * 0x1000+0xfff],同样是包含两端

别名段(alias) 在保护模式下,代码段不可以写入,不可写入不是指改变了物理内存的性质,而是说,通过该段的描述符访问这个区域时,处理器不允许向里面写入数据或者改变数据

​ 但是很多时候,有需要对代码段做一些修改,比如在调试程序时,需要加入断点指令int3,这种时候只能为该段安装一个新的描述符,并将其定义为可读可写的数据段,这样需要修改代码段内的数据时,可以通过这个新的描述符来进行。像这样,两个以上的描述符都描述和指向同意给段时,把另外的描述符称为别名(alias),也可以给数据段栈段添加别名段。

修改段寄存器时的保护

​ 保护模式下对段寄存器进行修改时,处理器在变更段选择子以及隐藏的描述符高速缓存部分的内容时,要先检查其代入值得合法性。

​ ①检查段选择子中的索引号*8-1<=gdt的边界

​ ②检查在gdt中依据段选择子找到的段描述符的类别(TYPE) 是否和待修改的段寄存器匹配。例如,如果类型时只执行的代码段,是不允许加载到除cs以外的段寄存器的。

SharedScreenshot3

​ 上图给出了type和段寄存器之间的匹配,可以看到 ss要求段的type要可读可写。而且 对于cs和ss来说不允许传入索引为0的段选择子。

代码段执行时的保护:

​ 32位保护模式下,尽管段的信息都在描述符表里,但是一旦相应的描述符被加载到段寄存器的描述符高速缓存部分,则处理器取指令和执行指令时,将不再访问描述符表,而是直接使用段寄存器的描述符高速缓存部分,从中取出线性段基地址,同指针寄存器EIP相加,共同形成32位的线性地址(如果没有分页的话就是物理地址了),再去取出指令。不过在指令执行之前,处理器会检验其存放地址是否超过代码段的可用空间(实际界限)

0<=EIP+指令长度-1<=实际使用的段界限(G位为0则就是描述符里的,如果为1则是描述符里的段界限*0x1000+0xfff)

​ 上述的范围,是对于当前EIP指向的一条指令,举个极端例子,假设当前EIP指向了 0xffffffff(这刚好是代码段的实际使用的段界限),这里有一条指令,长度为1,那么这条指令还没有越界,如果长度为2,那么后面一个字节越界了,所以可以验证上面的公式是对的。

栈操作的保护

​ 对于向下扩展的栈段,每当要往栈中压入数据时,ESP的值要减去操作数的长度,而且需要注意的是,减去后的ESP不能等于实际使用的段界限,也就是说对于栈段边界是不被包含的,假如实际使用的段界限是0x7c00,那么当前ESP指向0x7c01,即使再往里push一个字节也不行(假设处理器允许我们push一个字节)。也就是

实际使用的段界限+1<=ESP的值-操作数的长度<=0xffffffff

*而栈的实际使用的段界限=段基地址(也就是16后那个值)+段界限 * 粒度(实际上其他类型的实际段界限都是这么算的)上一章中,我们设置栈段的描述符时,G位设置为0,以字节为单位,这导致一个结果,我们想设置一个大小为512字节的栈区,然而由于栈是向下扩展的,所以我们设置界限为0x01ff时,0x7c00+0x1ff只是栈区的下界,这就导致了从0xffffffff-0x7dff都可以用,和我们设想的512字节大小不同,因为我们的下界是0x7dff比上界 0x7c00大*,所以我们让粒度为4KB 然后把段界限设置成0xffffe这种,这样 0x7c00+0xffffe0x1000+0xfff=0x6bff ,又因为如上面所说,实际段界限不的能到达,所以实际的下界0x6bff 还要+1 =0x6c00 ,下界比上界小,再把esp设置成0x7c00,这样栈的界限就能正常工作了 段的大小就是 0x7c00-0x6c00=0x1000 (因为4KB为粒度,所以栈的最小空间也是4KB了)

一般数据段访问的保护

​ 对于一个数据段,指令中给出了数据的有效地址(偏移地址)EA,也给出了要访问的数据长度,那么我们直到从[ds/es/fs/gs:EA]本身开始算第一个字节,而且对于边界是可以包含在可用空间的,那么有:

0<=EA+数据长度-1<=使用的实际界限

​ 对于本章的冒泡排序,不再解析,过于简单,本章主要介绍内存保护机制