chap3
3.1 认识保护模式
- pmtest1.asm——实现由实模式到保护模式的转换
; ==========================================
; pmtest1.asm
; 编译方法:nasm pmtest1.asm -o pmtest1.bin
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些说明
org 07c00h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_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 * 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]
- 编译:
nasm pmtest1.asm -o pmtest1.bin
- 写入软盘映像:
dd if=pmtest1.asm of=a.omg bs=512 count=1 conv=notrunc

3.1.1 保护模式的运行环境
前面的操作是将pmtest1.bin直接写入引导扇区,从而实现从实模式到保护模式的切换。这样操作方便,但由于引导扇区空间有限(仅512bytes),如果pmtest1.bin尺寸变得更大,甚至至超过引导扇区的大小,那前述操作就行不通了。
书中给出了两个解决方案:1.写一个引导扇区以读取我们的程序并执行。2.借用DOS来执行我们的程序。书中最终采用方案2。
-
在bochs官网下载一个FreeDos。解压后将其中的a.img复制到我们的工作目录中,并改名为freedos.img
-
用bximage生成一个软盘映像,命名为pm.img
-
修改bochsrc
floppya: 1_44=../mine/freedos.img, status=inserted
floppyb: 1_44=../mine/pm.img, status=inserted
boot:a
- 启动bochs,待FreeDos启动完毕后格式化B:盘
format b:

5. 将pmtest1.asm的第8行改为org 0100h,并重新编译。
nasm pmtest1.asm -o pmtest1.com
- 将pmtest1.com复制到虚拟软盘pm.img上。
sudo mkdir /mnt/floppy
sudo mount -o loop pm.img /mnt/floppy
sudo cp pmtest1.com /mnt/floppy
sudo umount /mnt/floppy
- 启动bochs,执行下面的命令。(右侧红色"P"出现说明执行pmtest1成功。)
b:\pmtest1.com

3.1.2 保护模式&实模式切换
切换基本流程
- 准备GDT(准备GDT基地址)
- 用lgdt加载GDTR。
- 打开A20。
- 置CR0的PE位。
- 跳转,进入保护模式。
调试代码pmtest1.asm
-
将执行至mov eax, cr0,CR0的PE位为0

-
将执行至jmp dword SelectorCode32:0,CR0的PE位置1,进入保护模式

3.1.3 段描述符分析

在pmtest1.asm中,段描述符的定义使用了来自于pm.inc的宏。在代码中定义段描述符时,只需确定段基址、段界限和段属性的值。pm.inc中的Descriptor宏,会自动将各值重新组织,以符合段描述符的结构。

%1、%2和%3分别对应着于pmtest1.asm中定义段描述符时需要确定的3个值(段基址、段界限和段属性)。通过逻辑运算(&、|)和位移操作(>>、<<),将原始数据按段描述符的格式重新组织。
3.1.4 LDT的添加
书上给出了一个添加LDT的步骤,还算清晰。可以按部就班地完成LDT的添加。
- 增加一个32位的代码段(内容不妨简单)。
- 增加一个段,内容是一个描述符表(LDT),可以只有一个代码段描述符,也可以添加更多的段描述符以描述更多的段。注意,涉及的选择子的TI位是1。
- 在GDT中增加一个描述符,用以描述这个LDT,同时要定义其选择子。
- 增加为新添的描述符进行初始化的代码,主要针对段基址。
- 用新加的LDT描述的局部任务准备完毕。
- 使用
lldt指令加载ldtr,用jmp指令跳转等方式进行跳转。
3.2 特权级
3.2.1 CPL DPL RPL
- CPL:描述当前执行程序或者任务的特权级。存储在CS和SS的bit 0,bit 1。当程序在不同特权级代码间转移,CPL会发生改变。
- DPL:描述段或者门的特权级,存储在描述符的DPL字段中,是这个段的特权级别,用来表明访问这个段时候,所需要的特权。
- RPL:描述选择子的特权级,段选择子(Selector)的bit0和bit1,是可以重载的。
3.2.2 直接跳转
“有关代码的特权检查都发生在能够改变段寄存器CS和指令指针寄存器 EIP 的指令中(即这些指令要么改变 EIP,要么改变CS和 EIP,例如 call、jmp、int、ret、sysexit 等能改变程序执行流的指令。)jmp或call后跟着48位全指针(16位段选择子+32位地址偏移),且其中的段选择子指向代码段描述符,权限访问规则及CPL变化如下:”

3.2.3 代码段访问数据段
在段选择子被载入段寄存器前,会进行特权检查:CPL <= 目标数据段DPL && RPL <= 目标数据段 DPL。如果符合该条件,则允许访问。
3.2.4 通过调用门跳转
“当段间转移指令jmp和段间转移指令call后跟着的目标段选择子指向一个调用门描述符时,该跳转就是利用调用门的跳转。这时如果选择子后跟着32位的地址偏移,这个偏移不会被cpu使用。因为调用门描述符已经记录了目标代码段的偏移。使用调用门进行的跳转比普通跳转多一个步骤,即在访问调用门描述符时要将描述符当作一个数据段来检查访问权限,要求指示调用门的选择子的 RPL <= 门描述符DPL,同时当前代码段CPL <= 门描述符DPL。就如同访问数据段一样,要求访问数据段的程序的CPL <= 待访问的数据段的DPL,同时选择子的RPL <= 待访问的数据段或堆栈段的DPL。只有满足了以上条件,CPU才会进一步从调用门描述符中读取目标代码段的选择子和地址偏移,进行下一步的操作。
从调用门中读取到目标代码的段选择子和地址偏移后,我们当前掌握的信息又回到了先前,和普通跳转站在了同一条起跑线上(普通跳转一开始就得到了目标代码的段选择子和地址偏移)。有所不同的是,此时,CPU会将读到的目标代码段选择子中的RPL清0,即忽略了调用门中代码段选择子的RPL的作用 。完成这一步后,CPU开始对当前程序的CPL,目标代码段选择子的RPL(事实上它被清0后总能满足要求)以及由目标代码选择子指示的目标代码段描述符中的DPL 进行特权级检查,并根据情况进行跳转。”

其中,CPL为当前代码段的特权级。RPL为指向调用门的段选择子的请求特权级。DPL_G为门描述符的特权级。DPL_B是目标段的特权级。
3.2.5 call retf jmp
直接转移只需要进行特权级检查,符合条件,即可通过选择子找到对应的段描述符。最后根据段描述符中的段基址并结合偏移,找到目标段,但CPL不会发生变化。(2)通过调用门的转移,也需要进行特权级检查。如果通过特权级检查,就可以根据门描述符中的段选择子找到目标段的段描述符,然后找到目标段。当使用call指令,通过调用门方式访问非一致代码段时,如果CPL > 目标段DPL,则会发生特权级变化,CPL最终等于目标段DPL。除此情况外,只要通过特权级检查,特权级都不会变化。
call可以通过调用门实现由低特权级向高特权级的切换。retf可以实现由高特权级向低特权级的切换。jmp不能实现特权级(这里指CPL)的变换。
call和retf在进行跳转时,还涉及堆栈操作。在保护模式下,对应每一个特权级,存在一个堆栈空间(共4中特权级,因此共4块堆栈空间。)。在从低特权级向高特权级跳转时(call gate),会从TSS中读出目标特权级堆栈的SS和SP并切换(还有对当前CS、EIP的保存和参数复制。返回时,retf会完成一个非逆操作流,但能实现堆栈切换与返回。此处不过度展开)。
3.2.6 尝试
- 自定义添加1个GDT代码段、1个LDT代码段,GDT段内要对一个内存数据结构写入一段字符串,然后LDT段内代码段功能为读取并打印该GDT的内容。
首先,分别添加GDT代码段、LDT代码段的段选择子与段描述符,并在段描述符初始化中,添加对两者段描述符的初始化。
然后,编写两处代码段:
GDT中,将字符“12345”写入data段中Empty处(Empty是实现开辟好的一段空间);LDT中,将字符“12345”读出,并在屏幕上显示。最终,系统从LDT代码段跳转回实模式。
;基于pmtest3.asm修改
; ==========================================
; pmtest3.asm
; 编译方法:nasm pmtest3.asm -o pmtest3.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些说明
org 0100h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代码段, 32
LABEL_DESC_CODE: Descriptor 0, SegCodeLen - 1, DA_C + DA_32 ; my code segment
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代码段, 16
LABEL_DESC_DATA: Descriptor 0, DataLen - 1, DA_DRW+DA_DPL1 ; Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA + DA_32; Stack, 32 位
LABEL_DESC_LDT: Descriptor 0, LDTLen - 1, DA_LDT ; LDT
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址
; GDT 结束
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; GDT 选择子
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorLDT equ LABEL_DESC_LDT - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
SelectorCode equ LABEL_DESC_CODE - LABEL_GDT
; END of [SECTION .gdt]
[SECTION .data1] ; 数据段
ALIGN 32
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0
; 字符串
PMMessage: db "In Protect Mode now. ^-^", 0 ; 进入保护模式后显示此字符串
OffsetPMMessage equ PMMessage - $$
StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest equ StrTest - $$
Mystr: db "12345", 0
OffsetMystr: db Mystr - $$
Empty: db 0, 0, 0, 0, 0, 0
OffsetEmpty: equ Empty - $$
DataLen equ $ - LABEL_DATA
; END of [SECTION .data1]
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
; END of [SECTION .gs]
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
mov [LABEL_GO_BACK_TO_REAL+3], ax
mov [SPValueInRealMode], sp
; 初始化 16 位代码段描述符
mov ax, cs
movzx eax, ax
shl eax, 4
add eax, LABEL_SEG_CODE16
mov word [LABEL_DESC_CODE16 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE16 + 4], al
mov byte [LABEL_DESC_CODE16 + 7], ah
; 初始化 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
;initialization mycodesegment descriptor
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE
mov word [LABEL_DESC_CODE + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE + 4], al
mov byte [LABEL_DESC_CODE + 7], ah
; 初始化数据段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_DATA
mov word [LABEL_DESC_DATA + 2], ax
shr eax, 16
mov byte [LABEL_DESC_DATA + 4], al
mov byte [LABEL_DESC_DATA + 7], ah
; 初始化堆栈段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_STACK
mov word [LABEL_DESC_STACK + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK + 4], al
mov byte [LABEL_DESC_STACK + 7], ah
; 初始化 LDT 在 GDT 中的描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_LDT
mov word [LABEL_DESC_LDT + 2], ax
shr eax, 16
mov byte [LABEL_DESC_LDT + 4], al
mov byte [LABEL_DESC_LDT + 7], ah
; 初始化 LDT 中的描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_CODE_A
mov word [LABEL_LDT_DESC_CODEA + 2], ax
shr eax, 16
mov byte [LABEL_LDT_DESC_CODEA + 4], al
mov byte [LABEL_LDT_DESC_CODEA + 7], ah
; 初始化 LDT 中的描述符B
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_CODE_B
mov word [LABEL_LDT_DESC_CODEB + 2], ax
shr eax, 16
mov byte [LABEL_LDT_DESC_CODEB + 4], al
mov byte [LABEL_LDT_DESC_CODEB + 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 SelectorCode:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [SPValueInRealMode]
in al, 92h ; ┓
and al, 11111101b ; ┣ 关闭 A20 地址线
out 92h, al ; ┛
sti ; 开中断
mov ax, 4c00h ; ┓
int 21h ; ┛回到 DOS
; END of [SECTION .s16]
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE:
xor eax, eax
mov ax, SelectorData
mov es, ax
mov edi, OffsetEmpty
mov ax, SelectorStack
mov ss, ax
mov esp,TopOfStack
xchg bx, bx
mov al, 49 ; 1
xor ecx, ecx
mov cx, 5
fill:
xchg bx, bx
mov [es:edi], al
inc ax
inc edi
loop fill
xchg bx, bx
mov ax, SelectorLDT
lldt ax
jmp SelectorLDTCodeB:0
SegCodeLen equ $ - LABEL_SEG_CODE
;--------------------------------------------------------
LABEL_SEG_CODE32:
mov ax, SelectorData
mov ds, ax ; 数据段选择子
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子
mov ax, SelectorStack
mov ss, ax ; 堆栈段选择子
mov esp, TopOfStack
; 下面显示一个字符串
mov ah, 0Ch ; 0000: 黑底 1100: 红字
xor esi, esi
xor edi, edi
mov esi, OffsetPMMessage ; 源数据偏移
mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。
cld
.1:
lodsb
test al, al
jz .2
mov [gs:edi], ax
add edi, 2
jmp .1
.2: ; 显示完毕
call DispReturn
;xchg bx, bx
; Load LDT
mov ax, SelectorLDT
lldt ax
;xchg bx, bx
jmp SelectorLDTCodeA:0 ; 跳入局部任务
; ------------------------------------------------------------------------
DispReturn:
push eax
push ebx
mov eax, edi
mov bl, 160
div bl
and eax, 0FFh
inc eax
mov bl, 160
mul bl
mov edi, eax
pop ebx
pop eax
ret
; DispReturn 结束---------------------------------------------------------
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
; 跳回实模式:
mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, 11111110b
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
; LDT
[SECTION .ldt]
ALIGN 32
LABEL_LDT:
; 段基址 段界限 属性
LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1, DA_C + DA_32 ; Code, 32 位
LABEL_LDT_DESC_CODEB: Descriptor 0, CodeBLen - 1, DA_C +DA_32 ; my local code
LDTLen equ $ - LABEL_LDT
; LDT 选择子
SelectorLDTCodeA equ LABEL_LDT_DESC_CODEA - LABEL_LDT + SA_TIL
SelectorLDTCodeB equ LABEL_LDT_DESC_CODEB - LABEL_LDT + SA_TIL
; END of [SECTION .ldt]
; CodeA (LDT, 32 位代码段)
[SECTION .la]
ALIGN 32
[BITS 32]
LABEL_CODE_B:
mov ax, SelectorVideo
mov gs, ax
mov ax, SelectorData
mov ds, ax
mov esi, OffsetEmpty
mov edi, (80 * 12 + 0) * 2
mov ah, 0Ch
xor ecx, ecx
mov cx, 5
output:
mov al, [ds:esi]
mov [gs:edi], ax
inc esi
inc edi
inc edi
loop output
jmp SelectorCode16:0
CodeBLen equ $ - LABEL_CODE_B
LABEL_CODE_A:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 12 + 0) * 2 ; 屏幕第 10 行, 第 0 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'L'
mov [gs:edi], ax
;xchg bx, bx
; 准备经由16位代码段跳回实模式
jmp SelectorCode16:0
CodeALen equ $ - LABEL_CODE_A
; END of [SECTION .la]

- 自定义2个GDT代码段A、B,分属于不同特权级,功能自定义,要求实现A–>B的跳转,以及B–>A的跳转。
在pmtest5.asm的基础上,修改程序,实现从ring0->ring3->ring1->ring3->ring0的特权级转换。
;基于pmtest5.asm修改
; ==========================================
; pmtest5.asm
; 编译方法:nasm pmtest5.asm -o pmtest5.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些说明
org 0100h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ;空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ;Normal描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32 ;非一致,32
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ;非一致,16
LABEL_DESC_CODE_DEST: Descriptor 0, SegCodeDestLen-1, DA_C+DA_32 ;非一致,32
LABEL_DESC_CODE_RING3: Descriptor 0, SegCodeRing3Len-1, DA_C+DA_32+DA_DPL3
LABEL_DESC_CODE_RING1: Descriptor 0, SegCodeRing1Len-1, DA_C+DA_32+DA_DPL1
LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW ;Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32 ;Stack,32
LABEL_DESC_STACK1: Descriptor 0, TopOfStack1, DA_DRWA+DA_32+DA_DPL1
LABEL_DESC_STACK3: Descriptor 0, TopOfStack3, DA_DRWA+DA_32+DA_DPL3
LABEL_DESC_LDT: Descriptor 0, LDTLen-1, DA_LDT ;LDT
LABEL_DESC_TSS: Descriptor 0, TSSLen-1, DA_386TSS ;TSS
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW+DA_DPL3
; 门 目标选择子, 偏移, DCount, 属性
LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0, DA_386CGate + DA_DPL3
LABEL_CALL_GATE_31: Gate SelectorCodeRing1, 0, 0, DA_386CGate + DA_DPL3
; GDT 结束
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; GDT 选择子
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT
SelectorCodeDest equ LABEL_DESC_CODE_DEST - LABEL_GDT
SelectorCodeRing1 equ LABEL_DESC_CODE_RING1 - LABEL_GDT + SA_RPL1
SelectorCodeRing3 equ LABEL_DESC_CODE_RING3 - LABEL_GDT + SA_RPL3
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorStack1 equ LABEL_DESC_STACK1 - LABEL_GDT + SA_RPL1
SelectorStack3 equ LABEL_DESC_STACK3 - LABEL_GDT + SA_RPL3
SelectorLDT equ LABEL_DESC_LDT - LABEL_GDT
SelectorTSS equ LABEL_DESC_TSS - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
SelectorCallGateTest equ LABEL_CALL_GATE_TEST - LABEL_GDT + SA_RPL3
SelectorCallGate31 equ LABEL_CALL_GATE_31 - LABEL_GDT + SA_RPL3
; END of [SECTION .gdt]
[SECTION .data1] ; 数据段
ALIGN 32
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0
; 字符串
PMMessage: db "In Protect Mode now. ^-^", 0 ; 进入保护模式后显示此字符串
OffsetPMMessage equ PMMessage - $$
StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest equ StrTest - $$
DataLen equ $ - LABEL_DATA
; END of [SECTION .data1]
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
; END of [SECTION .gs]
; 堆栈段ring1
[SECTION .s1]
ALIGN 32
[BITS 32]
LABEL_STACK1:
times 512 db 0
TopOfStack1 equ $ - LABEL_STACK1 - 1
; END of [SECTION .s1]
; 堆栈段ring3
[SECTION .s3]
ALIGN 32
[BITS 32]
LABEL_STACK3:
times 512 db 0
TopOfStack3 equ $ - LABEL_STACK3 - 1
; END of [SECTION .s3]
; TSS ---------------------------------------------------------------------------------------------
[SECTION .tss]
ALIGN 32
[BITS 32]
LABEL_TSS:
DD 0 ; Back
DD TopOfStack ; 0 级堆栈
DD SelectorStack ;
DD TopOfStack1 ; 1 级堆栈
DD SelectorStack1 ;
DD 0 ; 2 级堆栈
DD 0 ;
DD 0 ; CR3
DD 0 ; EIP
DD 0 ; EFLAGS
DD 0 ; EAX
DD 0 ; ECX
DD 0 ; EDX
DD 0 ; EBX
DD 0 ; ESP
DD 0 ; EBP
DD 0 ; ESI
DD 0 ; EDI
DD 0 ; ES
DD 0 ; CS
DD 0 ; SS
DD 0 ; DS
DD 0 ; FS
DD 0 ; GS
DD 0 ; LDT
DW 0 ; 调试陷阱标志
DW $ - LABEL_TSS + 2 ; I/O位图基址
DB 0ffh ; I/O位图结束标志
TSSLen equ $ - LABEL_TSS
; TSS ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
mov [LABEL_GO_BACK_TO_REAL+3], ax
mov [SPValueInRealMode], sp
; 初始化 16 位代码段描述符
mov ax, cs
movzx eax, ax
shl eax, 4
add eax, LABEL_SEG_CODE16
mov word [LABEL_DESC_CODE16 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE16 + 4], al
mov byte [LABEL_DESC_CODE16 + 7], ah
; 初始化 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
; 初始化测试调用门的代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE_DEST
mov word [LABEL_DESC_CODE_DEST + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE_DEST + 4], al
mov byte [LABEL_DESC_CODE_DEST + 7], ah
; 初始化数据段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_DATA
mov word [LABEL_DESC_DATA + 2], ax
shr eax, 16
mov byte [LABEL_DESC_DATA + 4], al
mov byte [LABEL_DESC_DATA + 7], ah
; 初始化堆栈段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_STACK
mov word [LABEL_DESC_STACK + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK + 4], al
mov byte [LABEL_DESC_STACK + 7], ah
;stack desc r1
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_STACK1
mov word [LABEL_DESC_STACK1 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK1 + 4], al
mov byte [LABEL_DESC_STACK1 + 7], ah
; 初始化堆栈段描述符(ring3)
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_STACK3
mov word [LABEL_DESC_STACK3 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK3 + 4], al
mov byte [LABEL_DESC_STACK3 + 7], ah
; 初始化 LDT 在 GDT 中的描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_LDT
mov word [LABEL_DESC_LDT + 2], ax
shr eax, 16
mov byte [LABEL_DESC_LDT + 4], al
mov byte [LABEL_DESC_LDT + 7], ah
; 初始化 LDT 中的描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_CODE_A
mov word [LABEL_LDT_DESC_CODEA + 2], ax
shr eax, 16
mov byte [LABEL_LDT_DESC_CODEA + 4], al
mov byte [LABEL_LDT_DESC_CODEA + 7], ah
; 初始化Ring3描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_CODE_RING1
mov word [LABEL_DESC_CODE_RING1 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE_RING1 + 4], al
mov byte [LABEL_DESC_CODE_RING1 + 7], ah
; 初始化Ring3描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_CODE_RING3
mov word [LABEL_DESC_CODE_RING3 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE_RING3 + 4], al
mov byte [LABEL_DESC_CODE_RING3 + 7], ah
; 初始化 TSS 描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_TSS
mov word [LABEL_DESC_TSS + 2], ax
shr eax, 16
mov byte [LABEL_DESC_TSS + 4], al
mov byte [LABEL_DESC_TSS + 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 处
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [SPValueInRealMode]
in al, 92h ; ┓
and al, 11111101b ; ┣ 关闭 A20 地址线
out 92h, al ; ┛
sti ; 开中断
mov ax, 4c00h ; ┓
int 21h ; ┛回到 DOS
; END of [SECTION .s16]
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorData
mov ds, ax ; 数据段选择子
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子
mov ax, SelectorStack
mov ss, ax ; 堆栈段选择子
mov esp, TopOfStack
; 下面显示一个字符串
mov ah, 0Ch ; 0000: 黑底 1100: 红字
xor esi, esi
xor edi, edi
mov esi, OffsetPMMessage ; 源数据偏移
mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。
cld
.1:
lodsb
test al, al
jz .2
mov [gs:edi], ax
add edi, 2
jmp .1
.2: ; 显示完毕
call DispReturn
xchg bx, bx
; Load TSS
mov ax, SelectorTSS
ltr ax ; 在任务内发生特权级变换时要切换堆栈,而内层堆栈的指针存放在当前任务的TSS中,所以要设置任务状态段寄存器 TR。
xchg bx, bx
push SelectorStack3
push TopOfStack3
push SelectorCodeRing3
push 0
xchg bx, bx
retf ; Ring0 -> Ring3,历史性转移!将打印数字 '3'。
; ------------------------------------------------------------------------
DispReturn:
push eax
push ebx
mov eax, edi
mov bl, 160
div bl
and eax, 0FFh
inc eax
mov bl, 160
mul bl
mov edi, eax
pop ebx
pop eax
ret
; DispReturn 结束---------------------------------------------------------
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
[SECTION .sdest]; 调用门目标段
[BITS 32]
LABEL_SEG_CODE_DEST:
xchg bx, bx
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 12 + 0) * 2 ; 屏幕第 12 行, 第 0 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'C'
mov [gs:edi], ax
; Load LDT
mov ax, SelectorLDT
lldt ax
xchg bx, bx
jmp SelectorLDTCodeA:0 ; 跳入局部任务,将打印字母 'L'。
;retf
SegCodeDestLen equ $ - LABEL_SEG_CODE_DEST
; END of [SECTION .sdest]
; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
; 跳回实模式:
mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, 11111110b
mov cr0, eax
xchg bx, bx
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
; LDT
[SECTION .ldt]
ALIGN 32
LABEL_LDT:
; 段基址 段界限 , 属性
LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1, DA_C + DA_32 ; Code, 32 位
LDTLen equ $ - LABEL_LDT
; LDT 选择子
SelectorLDTCodeA equ LABEL_LDT_DESC_CODEA - LABEL_LDT + SA_TIL
; END of [SECTION .ldt]
; CodeA (LDT, 32 位代码段)
[SECTION .la]
ALIGN 32
[BITS 32]
LABEL_CODE_A:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 13 + 0) * 2 ; 屏幕第 13 行, 第 0 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'L'
mov [gs:edi], ax
; 准备经由16位代码段跳回实模式
xchg bx, bx
jmp SelectorCode16:0
CodeALen equ $ - LABEL_CODE_A
; END of [SECTION .la]
; CodeRing1
[SECTION .ring1]
ALIGN 32
[BITS 32]
LABEL_CODE_RING1:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 14 + 1) * 2 ; 屏幕第 14 行, 第 1 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, '1'
mov [gs:edi], ax
xchg bx, bx
;push SelectorStack3
;push TopOfStack3
;push SelectorCodeRing3
;push 0
retf
SegCodeRing1Len equ $ - LABEL_CODE_RING1
; END of [SECTION .ring1]
; CodeRing3
[SECTION .ring3]
ALIGN 32
[BITS 32]
LABEL_CODE_RING3:
xchg bx, bx
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 14 + 0) * 2 ; 屏幕第 14 行, 第 0 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, '3'
mov [gs:edi], ax
xchg bx, bx
call SelectorCallGate31:0
call SelectorCallGateTest:0 ; 测试调用门(有特权级变换),将打印字母 'C'。
jmp $
SegCodeRing3Len equ $ - LABEL_CODE_RING3
; END of [SECTION .ring3]

3.2.7 对保护模式的“保护”的理解
一方面,段描述符中的段基址和段界限定义了一个段的范围,对超越段界限的地址的访问是禁止的,这无疑是对段的一种保护。段属性作为对段各个方面的定义,规定和限制了段的行为和性质。另一方面,在段间切换的过程中,会进行特权级检查,动态地对访问权限进行限制。


被折叠的 条评论
为什么被折叠?



