您的位置:首页 > 其它

保护模式的进入

2010-02-05 09:00 162 查看
终于进入保护模式了,从放假开始纠结了4天,虽然是冰山一角,但还是很兴奋的~呵呵。之前没接触过汇编,很多概念都不清楚,所以学到了很多。

计算机在开机执行完BIOS进行最初的初始化之后,会跳转到0x7c00处执行引导程序,此时CPU运行在实地址模式下,在实模式下,只能寻址1MB的空间,而且操作系统的代码和数据并没有得到保护,应用程序可能会访问并修改他们,这是很不安全的。而保护模式,顾名思义,是为操作系统提供了保护措施的模式,在保护模式下,应用程序不能直接访问系统代码和数据,只能通过系统调用访问,而且每个进程独享自己4GB的地址空间。而保护模式还有一个更直观且很重要的作用就是它提高了寻址能力,应用程序可以访问4GB的空间。

进入保护模式的主要步骤有

1.创建GDT表

2.载入GDT表

3.打开A20地址线

4.设置CR0的PE位

5.跳转到保护模式

1.创建GDT表

GDT是全局描述符表,用来保存每个段的信息,包括段的限界、基地址和一些属性。描述符的结构如下

高地址………………………………………………………………………低地址

; | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |

; |7654321076543210765432107654321076543210765432107654321076543210| <- 共 8 字节

; |--------========--------========--------========--------========|

; ┏━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓

; ┃31..24┃ (见下图) ┃ 段基址(23..0) ┃ 段界限(15..0)┃

; ┃ ┃ ┃ ┃ ┃

; ┃ 基址2┃③│②│ ① ┃基址1b│ 基址1a ┃ 段界限1 ┃

; ┣━━━╋━━━┳━━━╋━━━━━━━━━━━╋━━━━━━━┫

; ┃ %6 ┃ %5 ┃ %4 ┃ %3 ┃ %2 ┃ %1 ┃

; ┗━━━┻━━━┻━━━┻━━━┻━━━━━━━┻━━━━━━━┛

; │ /_________

; │ /__________________

; │ /________________________________________________

; │ /

; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓

; ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃

; ┣━━╋━━╋━━╋━━╋━━┻━━┻━━┻━━╋━━╋━━┻━━╋━━╋━━┻━━┻━━┻━━┫

; ┃ G ┃ D ┃ 0 ┃ AVL┃ 段界限 2 (19..16) ┃ P ┃ DPL ┃ S ┃ TYPE ┃

; ┣━━┻━━┻━━┻━━╋━━━━━━━━━━━╋━━┻━━━━━┻━━┻━━━━━━━━━━━┫

; ┃ ③: 属性 2 ┃ ②: 段界限 2 ┃ ①: 属性1 ┃

; ┗━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━┛

; 高地址 低地址

在创建GDT时,一定要注意以下几点:

a.Intel是采用小端来保存数据的,我在此采用的是Nasm,Nasm中没有类似于GASM中.quad的指令,所以在Nasm中声明8字节数据时,必须要 按照从右到左的顺序声明。例如

gdt:

dw 0,0,0,0

;代码段描述符

dw 0x0010;1-2字节

dw 0x0000;3-4字节 可执行/写

dw 0x9a00;5-6字节

dw 0x00cf;7-8字节

;显示段描述符

dw 0x0010

dw 0x8000;可读/写

dw 0x920B

dw 0x00cf

而且也要注意,拿代码段描述符的第1-2字节为例,[gdt+8]处的数据是0x10而不是0x00,因为是小端存放的。但是在寄存器中的数据仍然是 0x0010,即如果用访问内存的方式(加中括号)访问数据时,一定要注意存放的顺序,而在访问寄存器时则不用考虑。

b.在进入保护模式后,表达式seg:offset中的seg和实模式下seg的含义是完全不同的。实模式下seg的含义即为某个段的基地址,而保护模式下 seg只是一个索引,它指明了某个段在GDT中的索引,且叫作选择子,它也有自己的结构,如下

; 选择子图示:

; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓

; ┃ 15 ┃ 14 ┃ 13 ┃ 12 ┃ 11 ┃ 10 ┃ 9 ┃ 8 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃

; ┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫

; ┃ 描述符索引 ┃ TI ┃ RPL ┃

; ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛

其中TI=0表明请求的是在GDT中的段,TI=1表明是一个LDT中的段。RPL是请求特权级。可以看到,一个GDT中最多可以有2^13个段。

2.载入GDT表

载入GDT表,可以用lgdt指令,它将一个结构载入gdtr寄存器,该结构指明了GDT表的地址和GDT的界限。GDTR结构共有六个字节,低两字节 指明GDT的界限,高四字节指明GDT的地址。此处也要注意从右到左的声明顺序。

;GDTR

gdtptr:

dw 0x0010

dw gdt,0;不是0x7c00+gdt

其余三个步骤都较为简单且都差不多,所以不多说了。还要注意的是跳转到保护模式后,所有的代码都应该是在32位模式下,所以要在程序中用[BITS 32]指明。整个程序的代码如下:

;[BITS 16]
org 0x7c00
mov ax,cs
mov ds,ax
mov es,ax
;为GDT中的描述符赋基地址,即该段要执行的指令的地址
xor eax,eax
mov ax,cs;cs中是当前段的基地址
shl eax,4;左移四位加段内偏移
add eax,disppmmsg
;改变GDT中的第一个描述符(codeselector)
mov word [gdt+10],ax
shr eax,16
mov byte [gdt+12],al
mov byte [gdt+15],ah
lgdt [gdtptr]
;关中断
cli
;打开A20 才能访问1M以外的地址
in	al, 92h
or	al, 00000010b
out	92h, al
;准备CR0 0x60000010
mov	eax, cr0
or	eax, 1
mov	cr0, eax

jmp dword codeselector:0
;创建GDT
gdt:
dw 0,0,0,0

dw 0x0010
dw 0x0000;可执行/写
dw 0x9a00;从右往左写数据
dw 0x00cf

dw 0x0010
dw 0x8000;可读/写
dw 0x920B;显存地址为0x0B8000
dw 0x00cf

;选择子
codeselector: equ 0x08
videoselector: equ 0x10

;GDTR
gdtptr:
dw 0x0010
dw gdt,0
[BITS 32]
disppmmsg:
mov ax,videoselector
mov gs,ax
mov edi,(80*0+0)*2;每个字符占两个字节,包括一个属性字节
mov ah,0x0c;高字节为属性
mov al,'S';低字节为字符
mov [gs:edi],ax
mov edi,(80*0+1)*2
mov ah,0x0c
mov al,'u'
mov [gs:edi],ax
mov edi,(80*0+2)*2
mov ah,0x0c
mov al,'n'
mov [gs:edi],ax

jmp $

times 510-($-$) db 0
dw 0xaa55


程序中代码段和显示段是这样联系的:在disppmmsg中将显示段赋给gs,然后从12行开始,把要执行代码的地址赋给代码段的基地址。此处要执行的代码地址为当前段基址cs左移四位加上偏移。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: