您的位置:首页 > 其它

学习笔记--实践认识保护模式

2016-09-03 11:31 253 查看
有了保护模式的理论基础,今天就来实站一下,没有看的一定要看,在结合下面程序的分析,一定就会懂的,下面程序我又加了一些注释,理论上应该没有什么问题了。

[cpp]
view plain
copy

; ==========================================
; pmtest1.asm
; 编译方法:nasm pmtest1.asm -o pmtest1.bin
; ==========================================

DA_32 EQU 4000h ; 32 位段
DA_C EQU 98h ; 存在的只执行代码段属性值
DA_DRW EQU 92h ; 存在的可读写数据段属性值
ATCE32 EQU 4098h ;存在的只执行32代码段属性值

;下面是存储段描述符的宏定义
; 有三个参数:段界限,段基址,段属性其中宏定义中的%1代表参数1,%2代表参数2,%3代表参数3
%macro Descriptor 3

dw %2 & 0FFFFh ; 段界限1(参数2的低16位)
dw %1 & 0FFFFh ; 段基址1(参数1的低16位)
db (%1 >> 16) & 0FFh ; 段基址2(参数1的16-23位)
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1(高4位) + 段界限2(高4位) + 属性2(低8位)
db (%1 >> 24) & 0FFh ; 段基址3(参数1的24-31位)
%endmacro ; 共 8 字节
;段界限共20位,段基地址30位,段属性共16位(含段界限高4位)

org 07c00h
jmp LABEL_BEGIN

[SECTION .gdt] ;section是告诉编译器划分出一个叫gdt的段
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段(DA_C + DA_32=ATCE32)
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 ;eax*16==段值*16(实模式下不用手工计算,但是这里需要自己计算)
add eax, LABEL_SEG_CODE32;加上LABEL_SEG_CODE32偏移,这样eax->LABEL_SEG_CODE32基地址
mov word [LABEL_DESC_CODE32 + 2], ax ;设置基地址1(0-15位)
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al ;设置基地址2(16-23位)
mov byte [LABEL_DESC_CODE32 + 7], ah ;设置基地址3(24-31位)

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

; 加载 GDTR
lgdt [GdtPtr] ;将GdtPtr所指的内容送到GDT寄存器

; 关中断
cli

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

; 准备切换到保护模式
mov eax, cr0
or eax, 1 ;设置cr0的0位(PE位,PE=1准备进入保护模式)
mov cr0, eax ;更新cr0

; 真正进入保护模式
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 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'P'
mov [gs:edi], ax

; 到此停止
jmp $

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

我觉得注释挺详细了,但是还是来分析一下吧
(1)常量定义怎么来的?

[cpp]
view plain
copy

DA_32 EQU 4000h ; 32 位段
DA_C EQU 98h ; 存在的只执行代码段属性值



还记得这个图吗?常量就是从这个图中产生的。

4000h=0100000000000000b --->属性位D=1表示段的上界是4G,所以是32位段

98h =0000000010011000b --->p=1表示描述符对地址转换是有效的(存在位),DPL特权级=0,DT=1表示系统段,TYPE=8表示只执行

(2)存储段描述符解释(宏定义)

[cpp]
view plain
copy

;下面是存储段描述符的宏定义
; 有三个参数:段界限,段基址,段属性其中宏定义中的%1代表参数1,%2代表参数2,%3代表参数3
%macro Descriptor 3

dw %2 & 0FFFFh ; 段界限1(参数2的低16位)
dw %1 & 0FFFFh ; 段基址1(参数1的低16位)
db (%1 >> 16) & 0FFh ; 段基址2(参数1的16-23位)
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1(高4位) + 段界限2(高4位) + 属性2(低8位)
db (%1 >> 24) & 0FFh ; 段基址3(参数1的24-31位)
%endmacro ; 共 8 字节
;段界限共20位,段基地址30位,段属性共16位(含段界限高4位)



段界限共20位,段界限1取其低16位,所以和0ffffh与。

段基地址1同理

段基地址2:16-23位,将段基地址右移16位,得到段基地址高16位,和0ffh与得到低8位(也就是16-23位)。

段基地址3同理

段属性:属性1(高4位) + 段界限2(高4位) + 属性2(低8位)看第一个图,(%2
>> 8) & 0F00h)为属性1,(%3 & 0F0FFh)为段界限2(高4位) + 属性2(低8位),当然也可以这样写((%2
>> 8) & 0F00h) | (%3 & 0F000h)| (%3 & 000FFh)

这样是不是清楚很多了。

[SECTION .gdt]告诉编译器划分出一个叫gdt的段(数据段)

[SECTION .s16],[SECTION .s32]为两个代码段

[cpp]
view plain
copy

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

LABEL_GDT(空描述符),LABEL_DESC_CODE32(32位代码段描述符),LABEL_SESC_VIDEO(显示内存描述符)。每个描述符都包含了3个属性,段基址、段界限、段属性。将三个描述符组织到一起构成一个全局段描述符表(GDT)。

[cpp]
view plain
copy

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

GDTR示意图:



GdtPtr是不是和GDTR长得一样呀,GdtLen是GDT的长度。GdtPtr为一个数据结构,包含两个元素,第一个元素是2
bytes的GDT界限。第二个元素是4 bytes的GDT的基地址。该数据结构与全局描述符表寄存器(GDTR)的数据结构相同,所以在加载GDTR的时候(源代码55行),就是将该GdtPtr加载到GDTR中。

[cpp]
view plain
copy

; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段(DA_C + DA_32=ATCE32)
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址

由于第一个段LABEL_GDT是空描述符,它仅仅代表该GDT的初始地址,所以该描述符为空描述符,一般情况下,不为它创建选择子。然后该程序建立了两个选择子SelectorCode32和SelectorVideo,分别对应着这两个段LABEL_SESC_CODE32和LABEL_DESC_VIDEO,一致和非一致后面会解释的。
在保护模式下,首先使用段选择子在段描述符表中查找到相对应的段描述符,找到32位段基址,然后在与32位的偏移量相加,得到线性地址。段基址和段偏移量都是32位的,所以寻址范围大小为4GB。在程序中jmp
dword SlectorCode32:0的作用,就是进入保护模式下的寻址方式。其中,在使用某个段时,它的段选择子是存储在段寄存器中的。

剩下的在代码中解释得很清楚了,这样应该清楚了吧



下面总结一下进入保护模式的主要步骤:

1.准备GDT

2.用lgdt加载gdtr

3.打开A20

4.置cr0的PE位

5.跳转,进入保护模式

注:使用在上次建立的软盘镜像,如果新创建,那么必须在结尾的地方加上aa55h标志,我开始就没有意识到,总是失败,后面自己在后面加结束标记但是没有成功,最后还是用的上次创建的镜像。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐