初步学习Protected Mode(1)

根据书本上的知识学习 大牛可以无视 本菜的自我总结


实模式到保护模式的总步骤

1、初始化GDT和选择子

2、16位初始化32位代码段描述符,为进入32位代码段做准备

3、加载gdtr

4、关闭中断

5、打开地址线

6、用jmp正是切换到保护模式


%include	"pm.inc"	; 常量, 宏, 以及一些说明

org	0100h
	jmp	LABEL_BEGIN

[SECTION .gdt]
; GDT
;                                         段基址,      段界限     , 属性
LABEL_GDT:		Descriptor	   0,       0,                0     		; 空描述符
LABEL_DESC_CODE32:	Descriptor	   0,       SegCode32Len - 1, DA_C + DA_32	; 非一致代码段, 32
LABEL_DESC_VIDEO:	Descriptor	   0B8000h, 0ffffh,           DA_DRW		; 显存首地址
; GDT 结束

GdtLen		equ	$ - LABEL_GDT	; GDT长度
GdtPtr		dw	GdtLen - 1	; GDT界限
		dd	0		; GDT基地址

; GDT 选择子
SelectorCode32		equ	LABEL_DESC_CODE32	- LABEL_GDT
SelectorVideo		equ	LABEL_DESC_VIDEO	- LABEL_GDT
; END of [SECTION .gdt]

[SECTION .s16]
[BITS	16]
LABEL_BEGIN:
	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	mov	sp, 0100h

	; 初始化 32 位代码段描述符
	xor	eax, eax
	mov	ax, cs
	shl	eax, 4
	add	eax, LABEL_SEG_CODE32
	mov	word [LABEL_DESC_CODE32 + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_CODE32 + 4], al
	mov	byte [LABEL_DESC_CODE32 + 7], ah

	; 为加载 GDTR 作准备
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_GDT		; eax <- gdt 基地址
	mov	dword [GdtPtr + 2], eax	; [GdtPtr + 2] <- gdt 基地址

	; 加载 GDTR
	lgdt	[GdtPtr]

	; 关中断
	cli

	; 打开地址线A20
	in	al, 92h
	or	al, 00000010b
	out	92h, al

	; 准备切换到保护模式
	mov	eax, cr0
	or	eax, 1
	mov	cr0, eax

	; 真正进入保护模式
	jmp	dword SelectorCode32:0	; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0  处
; END of [SECTION .s16]


[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS	32]

LABEL_SEG_CODE32:
	mov	ax, SelectorVideo
	mov	gs, ax			; 视频段选择子(目的)

	mov	edi, (80 * 10 + 0) * 2	; 屏幕第 10 行, 第 0 列。
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	mov	al, 'P'
	mov	[gs:edi], ax

	; 到此停止
	jmp	$

SegCode32Len	equ	$ - LABEL_SEG_CODE32
; END of [SECTION .s32]

org	0100h
这是一个com程序,起始地址运行在0100h上



Descriptor是一个宏定义:

%macro Descriptor 3
	dw	%2 & 0FFFFh				; 段界限 1				(2 字节)
	dw	%1 & 0FFFFh				; 段基址 1				(2 字节)
	db	(%1 >> 16) & 0FFh			; 段基址 2				(1 节)
	dw	((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)	; 属性 1 + 段界限 2 + 属性 2		(2 字节)
	db	(%1 >> 24) & 0FFh			; 段基址 3				(1 字节)
%endmacro ; 共 8 字节

在保护模式下,虽然段值仍然由原来的16位cs ds等寄存器表示,但此时它仅仅变成了一个索引,这个索引指向一个数据结构的一个表项。这个数据结构就是GDT(LDT)。GDT的作用是用来提供段式存储机制,这种机制通过段寄存器和GDT中的描述符共同提供的。GDT中的表项称为描述符 Descriptor。


本图表示代码段和数据段描述符。描述符的种类还有系统端描述符和门描述符。

;         ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
;         ┃ 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描述符中合适的位置。本例中的GDT共有三个描述符。这里总称为:DESC_DUMMY\DESC_CODE32\DESC_VIDEO
不过这个宏本菜是看不懂啦!233自嘲


我们看这个LABEL_DESC_VIDEO: Descriptor  0B8000h, 0ffffh,   DA_DRW中,9B8000h为段基址,0fffffh位段界限,DA_DRW为段属性。这个描述符指向的正是显存。


关注下面的代码:

[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS	32]

LABEL_SEG_CODE32:
	mov	ax, SelectorVideo
	mov	gs, ax			; 视频段选择子(目的)

	mov	edi, (80 * 10 + 0) * 2	; 屏幕第 10 行, 第 0 列。
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	mov	al, 'P'
	mov	[gs:edi], ax

	; 到此停止
	jmp	$
mov ax, SelectorVideo 

mov gs,ax

此时段寄存器gs变成了SelectorVideo 

SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT

直观上看,DESC_VIDEO这个描述符相对于GDT基址的偏移。实际上,DESC_VIDEO叫做选择子Selector,不是一个偏移,它的结构如下:


; 选择子图示:
;         ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
;         ┃ 15 ┃ 14 ┃ 13 ┃ 12 ┃ 11 ┃ 10 ┃ 9  ┃ 8  ┃ 7  ┃ 6  ┃ 5  ┃ 4  ┃ 3  ┃ 2  ┃ 1  ┃ 0  ┃
;         ┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫
;         ┃                                 描述符索引                                                                        ┃ TI ┃   RPL    ┃
;         ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛

当TI RPL位0 选择子就编程了相对于GDT基址的偏移。

mov [gs:edi],ax这行,gs指示对应显存的描述符DESC_VIDEO,这条指令把ax的值写到显存中偏移位edi中的位置。
*包含描述符的,不仅只有GDT,还有LDT。


关注[SECTION .s16]这段:

[SECTION .s16]
[BITS	16]
LABEL_BEGIN:
	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	mov	sp, 0100h

	; 初始化 32 位代码段描述符
	xor	eax, eax
	mov	ax, cs
	shl	eax, 4
	add	eax, LABEL_SEG_CODE32
	mov	word [LABEL_DESC_CODE32 + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_CODE32 + 4], al
	mov	byte [LABEL_DESC_CODE32 + 7], ah

	; 为加载 GDTR 作准备
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_GDT		; eax <- gdt 基地址
	mov	dword [GdtPtr + 2], eax	; [GdtPtr + 2] <- gdt 基地址

	; 加载 GDTR
	lgdt	[GdtPtr]

	; 关中断
	cli

	; 打开地址线A20
	in	al, 92h
	or	al, 00000010b
	out	92h, al

	; 准备切换到保护模式
	mov	eax, cr0
	or	eax, 1
	mov	cr0, eax

	; 真正进入保护模式
	jmp	dword SelectorCode32:0	; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0  处
; END of [SECTION .s16]

	mov	ax, cs
	shl	eax, 4
	add	eax, LABEL_SEG_CODE32

LABEL_SEG_CODE32的物理地址即位[SECTION .s32]这个段的物理地址赋值给eax

也就是有效地址=段地址*16+偏移地址

	mov	word [LABEL_DESC_CODE32 + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_CODE32 + 4], al
	mov	byte [LABEL_DESC_CODE32 + 7], ah
这三个mov分别赋给DESC_CODE32中的相应位置。由于DESC_CODE32的段界限和属性已经指定,所以DESC_CODE32的初始化全部完成。


接着执行lgdt [GdtPtr]指令

这个指令的作用是将GdtPtr指示的6字节加载到寄存器gdtr。


紧接着cli关中断,是因为保护模式下中断处理的机制是不同的,不关掉中断会出现错误。

接着是打开A20地址线,这事一个历史遗留问题。8086有20位地址总线,只能寻址到1MB。为了能访问1MB以上的内存,我们必须打开A20地址线。

in	al, 92h
	or	al, 00000010b
	out	92h, al
打开的方法并不唯一。


mov	eax, cr0
	or	eax, 1
	mov	cr0, eax

cr0寄存器的第0位是PE位,此位为0时,CPU运行于实模式。为1时,CPU运行于保护模式。但是此时cs仍然是实模式下的值。我们需要吧代码段的选择子装入cs。

历史性的

jmp	dword SelectorCode32:0

终于到来了。我们要跳转到DESC_CODE32对应的段的首地址,即LABEL_SEG_CODE32处。

为什么要加入DWORD呢?因为偏移地址是32位的。


描述符属性是啥?非常庞大。网上可以搜得到图片。

以DA_C+DA_32为例,DA_C=10011000b P位为1这个段在内存中存在,S位为1表明这个段是代码段或者是数据段,TYPE=8表明这个段是只执行的段。

以上本菜只是随着书本上的内容做了总结,琢磨了一个半个晚上,到现在还有点晕乎乎的,是该再研究几遍的节奏了。

 

欢迎大牛打脸!!!欢迎喷子!!




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值