第四章 进入保护模式,理论的抽象轰炸

本文最后更新于:1 年前

第四章 进入保护模式,理论的抽象轰炸

写在前面

今天已经来到了9日,因为昨日状态不是很好,没有在往下进行,而是把前今天的内容做成博客发表上来,也是给自己一个复习的机会。今天争取拿下第四章!

进入正文

咱们还是先简单翻过前面的理论知识,记住里面的一些名词和简单概念,然后直接来到实战部分。前面说了,不要忘记主线任务,咱们之前已经完成了loader的基本实现,但是这一切都建立在实模式之下,我们需要进入保护模式。

mbr.S

首先是修改了MBR,

1
2
3
4
5
6
7
mov cx, 1; 待读入内存的扇区数
call rd_disk_m_16;

修改后代码
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov cx, 4; 改成待读入4个扇区, loader.bin 超过了 512 字节
call rd_disk_m_16;

boot.inc

然后对boot.inc进行一个补充,配置好loader以便后面在loader中进入保护模式。这里贴上boot.inc的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
;一一一一一一loader和 kernel

LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2

;一一一一一一- gdt 描述符属性 一一一一一一
DESC_G_4K equ 1000_0000_0000_0000_0000_0000b ;G位为第23位,置1代
;表
;段界限为单位4k
DESC_D_32 equ 1_00_0000_0000_0000_0000_0000b ;D/B 宇段,第22位
;对代码段来说是D位,置1表示指令中的有效地址及
;操作数是32位,指令有效地址用EIP寄存器。

DESC_L equ 0_0000_0000_0000_0000_0000_0000b ; 64位代码标记,我们在32位CPU下编程,
;标记为0便可
DESC_AVL equ 0_0000_0000_0000_0000_0000b ;CPU不用此位,暂置为
DESC_LIMIT_CODE2 equ 1111_0000_0000_0000_0000b ;段界限16~19位
;全设为1,它在下面代码中会与段界限的0~15位拼成0xFFFF,
;0xFFFF*4k等于4G,段基址设为0,采用平坦模型

DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 ;
DESC_LIMIT_VIDEO2 equ 0000_0000_0000_0000_0000b ;
DESC_P equ 1_000_0000_0000_0000b ;第15位,表示段存在
DESC_DPL_0 equ 00_0_0000_0000_0000b ;DPL在13~14位 0为最高特权级
DESC_DPL_1 equ 01_0_0000_0000_0000b
DESC_DPL_2 equ 10_0_0000_0000_0000b
DESC_DPL_3 equ 11_0_0000_0000_0000b
DESC_S_CODE equ 1_0000_0000_0000b ; S为0时表示系统段, S为1时表示非系统段。
DESC_S_DATA equ DESC_S_CODE
DESC_S_sys equ 0_0000_0000_0000b
DESC_TYPE_CODE equ 1000_0000_0000b ;x=1,c=0, r=0,a=0 ,即代码段是可执行的,非一致
;性,不可读,己访问位a清0 配合S使用
DESC_TYPE_DATA equ 0010_0000_0000b ;
;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写,己访问位a清0。

DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + \
DESC_P + DESC_DPL_0 + DESC_S_CODE +\
DESC_TYPE_CODE + 0x00

DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 +\
DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + \
DESC_TYPE_DATA + 0x00

DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 +\
DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + \
DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0B ;注意书上这里写的是0x00,写错了
;显存起始地址应该是0xB8000


;一一一一一一 选择子属性一一一一一一一
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b

注释比较详细了,注意这里都是以宏的形式编写的,说白了就是给nasm编译器看的,我觉得这里只需了解其背后隐含的理论就好,不必过于纠结代码形式。

loader.S

然后就要上正菜了,改一下loader。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
;------------------------
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR ;相同内存地址,地址之下便是栈
jmp loader_start

;构建 gdt 及其内部的描述符
GDT_BASE: dd 0x00000000 ;第0个段描述符不可用
dd 0x00000000

CODE_DESC: dd 0x0000FFFF ;代码段描述符
dd DESC_CODE_HIGH4

DATA_STACK_DESC: dd 0x0000FFFF ;栈段描述符 栈段和数据段共用一个描述符 均向上扩展
dd DESC_DATA_HIGH4

VIDEO_DESC: dd 0x80000007 ; limit=(0xbffff-0xb8000)/4k=0x7 故段界限为7
dd DESC_VIDEO_HIGH4 ;此时dpl为0

GDT_SIZE equ $ - GDT_BASE ; 先是通过地址差来获得 GDT的大小,进而用 GDT大小减1得到了段界限
GDT_LIMIT equ GDT_SIZE - 1 ;用于构建GDTR的段界限
times 60 dq 0 ;此处预留 60 个描述符的空位

;以下是构建代码段、数据段、显存段选择子
SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0
;相当于[(CODE_DESC - GDT_BASE) /8 ]<<3+ TI_GDT + RPL0
;内存地址的编号是一个存储单元8比特,这里CODE_DESC - GDT_BASE应该等于8
;书里的备注应该写掉了"<<3"
SELECTOR_DATA equ (0x0002<< 3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0

;以下是 gdt 的指针即GDTR,前2字节是gdt界限,后4字节是gdt起始地址 后面代码使用lgdt指令时会用上
gdt_ptr dw GDT_LIMIT
dd GDT_BASE

loadermsg db '2 loader in real.'

loader_start:

;------------------------------------------------------------
; INT 0x10 功能号: 0x13 功能描述:打印字符串
;------------------------------------------------------------
;输入:
;AH 子功能号=13H
;BH =页码
;BL =属性(若 AL=OOH OlH)
;CX =字符串长度
; (DH DL )=坐标{行、列)
;ES:BP=字符串地址
;AL=显示输出方式
; 一一字符串中只含显示字符,其显示属性在 BL
;显示后,光标位置不变
; 一一字符串中只含显示字符,其显示属性在 BL
;显示后,光标位置改变
; 一一字符事中含显示字符和显示属性。显示后,光标位置不变
; 一一字符串中含显示字符和显示属性。显示后,光标位置改变
;无返回值
mov sp, LOADER_BASE_ADDR
mov bp, loadermsg ; ES:BP =字符串地址
mov cx, 17 ; cx =字符串长度
mov ax, 0x1301 ; AH = 13, AL = 01h
mov bx, 0x001f ;页号为0(BH = 0)蓝底粉红字( BL = 1fh)
mov dx, 0x1800 ; dh=0x18 十进制为24,代表行数;dl=0x00 表示列数。使用显存的文本模式下,一共25行,所以2 loader in real 会出现在屏幕最后一行
int 0x10 ; 10h号中断 由于AH=0x13,所以该BIOS中断后会执行打印字符串的中断处理程序。

; 一一一一一一一一一一 准备进入保护模式 一一一一一一一一一一一一一一-
;1 打开 A20
;2 加载 gdt
;3 将cr0 的 pe 位置1

;一一一一一一一-打开 A20 一一一一一
in al,0x92
or al, 0000_0010B
out 0x92,al

;一一一一一一一一加载 GDT (也就是设置好gdtr,gdtr记录着gdt的起始地址)一一一一一一一-
lgdt [gdt_ptr ]

;一一一一一一一一 cr0位置1 一一一一一一一-
mov eax, cr0
or eax, 0x00000001
mov cr0, eax

jmp dword SELECTOR_CODE:p_mode_start ;刷新流水线

[bits 32]
p_mode_start:
;;;;用选择子初始化段寄存器
mov ax, SELECTOR_DATA
mov ds, ax
mov es, ax
mov ss, ax
mov esp, LOADER_STACK_TOP
mov ax, SELECTOR_VIDEO
mov gs, ax


mov byte [gs:0xA0], 'P'

jmp $

然后编译运行,输入info gdt发现gdt顺利加载成功了。

写在后面

这一章真是遇到了很大的阻力,需要静下心来一行行地啃汇编代码,同时还需要不断学习理论知识,现在已经是9日的21点了,虽然代码过了一遍但还是有许多模棱两可的地方,随着后面章节的进行还是需要回看。