很久以后,等我大量翻阅关于“保护模式的特权级检查(DPL,RPL,CPL, 一致代码段,非一致代码段)”的资料后,我才发现这篇博客理解得太肤浅了,而且有错(但后面的实验步骤和代码还是可以凑合看一下的),因此特意写了另一篇(主要是转载),名为“(第三章 8 )特权级——保护模式的特权级检查(DPL,RPL,CPL, 一致代码段,非一致代码段)”。
本节见书P48~49。在IA32的分段机制中,特权级共有4个特权级别,从高到低分别是0>1>2>3. 下面通过一个例子来说明CPL、DPL、RPL.
1. 先要明确CPL、DPL、RPL的概念,以及她们在哪里
DPL ,Descriptor Privilege Level,表示段或门的特权级。它是一个静态的概念,包含于段描述符的属性字段部分(P32),只要代码写好了,每个段的特权级就定了。
以本章第一个程序chapter3/a/pmtest1.asm(见P25~)为例,
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32 中最后一个字段指定属性,这里采用默认的DPL特权级0(没有显式指定)
RPL ,Requested Privilege Level,是“调用过程”的选择子的第0位和第1位(P32下面)。
以本章第一个程序chapter3/a/pmtest1.asm为例,
...
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
至此,如果SelectorCode32对应的代码段[SECTION .s32]被调用时(如执行jmp dword SelectorCode32:0),RPL就会被设置成SelectorCode32的第0位和第1位。此时执行jmp dword SelectorCode32:0的过程称“调用过程”,而[SECTION .s32]这个代码段称为“被调用过程”。
CPL ,Current Privilege Level,是“被调用过程”(当前执行程序或任务)的特权级,它被存储在cs和ss的第0位和第1位上。
以本章第一个程序chapter3/a/pmtest1.asm为例,在[SECTION .s16]最后,
jmp dword SelectorCode32:0执行之前,CPL是[SECTION .s16]的DPL,此时为0;
jmp dword SelectorCode32:0执行时,会将SelectorCode32装入cs(注:选择子和cs都是两个字节的,见P32),因为CPL是cs的第0位和第1位,所以CPL就变成了SelectorCode32对应段的DPL,此时也是0.
CPL虽然直接由cs中的最低2位决定,但最终是由“jmp到被调用段时指定的选择子”(此处jmp dword SelectorCode32:0,被调用段的选择子SelectorCode32中最低2位为0,则跳转后CPL=0)决定的。
对于数据的访问,特权级检验还是比较简单的,只要CPL和RPL都小于被访问的数据段的DPL就可以了。
2. 例子
执行方式如下:
1. asm生成com文件
[hadoop@sam1 c]$ pwd
/home/hadoop/Desktop/OSImpl/一个操作系统的实现/chapter3/c
[hadoop@sam1 c]$ nasm pmtest3.asm -o pmtest3.com
2. 将编译好的com文件复制到软盘b(镜像为pm.img)
[hadoop@sam1 bochs-2.6]$ pwd
/home/hadoop/Desktop/OSImpl/bochs-2.6
[hadoop@sam1 bochs-2.6]$ sudo mount -o loop pm.img /mnt/floppy/
[hadoop@sam1 bochs-2.6]$ sudo cp /home/hadoop/Desktop/OSImpl/一个操作系统的实现/chapter3/c/pmtest3.com /mnt/floppy/
[hadoop@sam1 bochs-2.6]$ sudo umount /mnt/floppy/
3. 启动freedos(软盘a:镜像freedos.img),加载软盘b(镜像pm.img)中的com文件
[hadoop@sam1 bochs-2.6]$ ./bochs
发现根本不能运行,这就对了
LABEL_DESC_DATA: Descriptor 0, DataLen - 1, DA_DRW+DA_DPL1; Data
...
SelectorDataequLABEL_DESC_DATA- LABEL_GDT + SA_RPL3
...
[SECTION .s32]; 32 位代码段. 由实模式跳入.
LABEL_SEG_CODE32:
movax, SelectorData
movds, ax; 数据段选择子
... 然后尝试访问Data段,就会失败,因为CPL=0 (当前段[SECTION .s32]的特权级),RPL=3(使用的段选择子SelectorData里面末两位),被访问数据段的DPL=1。不满足 CPL<DPL && RPL<DPL
下面是完整代码:
; ==========================================
; pmtest3.asm
; 编译方法:nasm pmtest3.asm -o pmtest3.com
; ==========================================
%include"pm.inc"; 常量, 宏, 以及一些说明
org0100h
jmpLABEL_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_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 结束
GdtLenequ$ - LABEL_GDT; GDT长度
GdtPtrdwGdtLen - 1; GDT界限
dd0; GDT基地址
; GDT 选择子
SelectorNormalequLABEL_DESC_NORMAL- LABEL_GDT
SelectorCode32equLABEL_DESC_CODE32- LABEL_GDT
SelectorCode16equLABEL_DESC_CODE16- LABEL_GDT
SelectorDataequLABEL_DESC_DATA- LABEL_GDT + SA_RPL3
SelectorStackequLABEL_DESC_STACK- LABEL_GDT
SelectorLDTequLABEL_DESC_LDT- LABEL_GDT
SelectorVideoequLABEL_DESC_VIDEO- LABEL_GDT
; END of [SECTION .gdt]
[SECTION .data1] ; 数据段
ALIGN32
[BITS32]
LABEL_DATA:
SPValueInRealModedw0
; 字符串
PMMessage:db"In Protect Mode now. ^-^", 0; 进入保护模式后显示此字符串
OffsetPMMessageequPMMessage - $$
StrTest:db"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTestequStrTest - $$
DataLenequ$ - LABEL_DATA
; END of [SECTION .data1]
; 全局堆栈段
[SECTION .gs]
ALIGN32
[BITS32]
LABEL_STACK:
times 512 db 0
TopOfStackequ$ - LABEL_STACK - 1
; END of [SECTION .gs]
[SECTION .s16]
[BITS16]
LABEL_BEGIN:
movax, cs
movds, ax
moves, ax
movss, ax
movsp, 0100h
mov[LABEL_GO_BACK_TO_REAL+3], ax
mov[SPValueInRealMode], sp
; 初始化 16 位代码段描述符 ==> [LABEL_SEG_CODE16]在“GDT本段对应的全局描述符”中已经设置了“段长度”和“段属性”,这里实际上是把“段基址”混入进去,使得“GDT本段对应的全局描述符”完整了!!!
movax, cs
movzxeax, ax
shleax, 4
addeax, LABEL_SEG_CODE16
movword [LABEL_DESC_CODE16 + 2], ax
shreax, 16
movbyte [LABEL_DESC_CODE16 + 4], al
movbyte [LABEL_DESC_CODE16 + 7], ah
; 初始化 32 位代码段描述符
xoreax, eax
movax, cs
shleax, 4
addeax, LABEL_SEG_CODE32
movword [LABEL_DESC_CODE32 + 2], ax
shreax, 16
movbyte [LABEL_DESC_CODE32 + 4], al
movbyte [LABEL_DESC_CODE32 + 7], ah
; 初始化数据段描述符
xoreax, eax
movax, ds
shleax, 4
addeax, LABEL_DATA
movword [LABEL_DESC_DATA + 2], ax
shreax, 16
movbyte [LABEL_DESC_DATA + 4], al
movbyte [LABEL_DESC_DATA + 7], ah
; 初始化堆栈段描述符
xoreax, eax
movax, ds
shleax, 4
addeax, LABEL_STACK
movword [LABEL_DESC_STACK + 2], ax
shreax, 16
movbyte [LABEL_DESC_STACK + 4], al
movbyte [LABEL_DESC_STACK + 7], ah
; 初始化 LDT 在 GDT 中的描述符
xoreax, eax
movax, ds
shleax, 4
addeax, LABEL_LDT
movword [LABEL_DESC_LDT + 2], ax
shreax, 16
movbyte [LABEL_DESC_LDT + 4], al
movbyte [LABEL_DESC_LDT + 7], ah
; 初始化 LDT 中的描述符
xoreax, eax
movax, ds
shleax, 4
addeax, LABEL_CODE_A
movword [LABEL_LDT_DESC_CODEA + 2], ax
shreax, 16
movbyte [LABEL_LDT_DESC_CODEA + 4], al
movbyte [LABEL_LDT_DESC_CODEA + 7], ah
; 为加载 GDTR 作准备
xoreax, eax
movax, ds
shleax, 4
addeax, LABEL_GDT; eax <- gdt 基地址
movdword [GdtPtr + 2], eax; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt[GdtPtr]
; 关中断
cli
; 打开地址线A20
inal, 92h
oral, 00000010b
out92h, al
; 准备切换到保护模式
moveax, cr0
oreax, 1
movcr0, eax
; 真正进入保护模式
jmpdword SelectorCode32:0; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY:; 从保护模式跳回到实模式就到了这里
movax, cs
movds, ax
moves, ax
movss, ax
movsp, [SPValueInRealMode]
inal, 92h; ┓
andal, 11111101b; ┣ 关闭 A20 地址线
out92h, al; ┛
sti; 开中断
movax, 4c00h; ┓
int21h; ┛回到 DOS
; END of [SECTION .s16]
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS32]
LABEL_SEG_CODE32:
movax, SelectorData
movds, ax; 数据段选择子
movax, SelectorVideo
movgs, ax; 视频段选择子
movax, SelectorStack
movss, ax; 堆栈段选择子
movesp, TopOfStack
; 下面显示一个字符串
movah, 0Ch; 0000: 黑底 1100: 红字
xoresi, esi
xoredi, edi
movesi, OffsetPMMessage; 源数据偏移
movedi, (80 * 10 + 0) * 2; 目的数据偏移。屏幕第 10 行, 第 0 列。
cld
.1:
lodsb ; [esi] --byte--> al
testal, al ; 若已经到达最后一个字符0,则结束循环显示每个字符
jz.2
mov[gs:edi], ax ; ax(ah: 黑底红字 al: ASCII字符) --2 bytes--> [gs:edi]
addedi, 2 ; [gs:edi] 向后跳两个字节
jmp.1 ; 显示下一个字符
.2:; 显示完毕
callDispReturn
; Load LDT
movax, SelectorLDT
lldtax
jmpSelectorLDTCodeA:0; 跳入局部任务
; ------------------------------------------------------------------------
DispReturn:
pusheax
pushebx
moveax, edi
movbl, 160
divbl
andeax, 0FFh
inceax
movbl, 160
mulbl
movedi, eax
popebx
popeax
ret
; DispReturn 结束---------------------------------------------------------
SegCode32Lenequ$ - LABEL_SEG_CODE32
; END of [SECTION .s32]
; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN32
[BITS16]
LABEL_SEG_CODE16:
; 跳回实模式:
movax, SelectorNormal
movds, ax
moves, ax
movfs, ax
movgs, ax
movss, ax
moveax, cr0
andal, 11111110b
movcr0, eax
LABEL_GO_BACK_TO_REAL:
jmp0:LABEL_REAL_ENTRY; 段地址会在程序开始处被设置成正确的值
Code16Lenequ$ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
; LDT
[SECTION .ldt]
ALIGN32
LABEL_LDT:
; 段基址 段界限 属性
LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1, DA_C + DA_32 ; Code, 32 位
LDTLenequ$ - LABEL_LDT
; LDT 选择子
SelectorLDTCodeAequLABEL_LDT_DESC_CODEA- LABEL_LDT + SA_TIL
; END of [SECTION .ldt]
; CodeA (LDT, 32 位代码段)
[SECTION .la]
ALIGN32
[BITS32]
LABEL_CODE_A:
movax, SelectorVideo
movgs, ax; 视频段选择子(目的)
movedi, (80 * 12 + 0) * 2; 屏幕第 10 行, 第 0 列。
movah, 0Ch; 0000: 黑底 1100: 红字
moval, 'L'
mov[gs:edi], ax
; 准备经由16位代码段跳回实模式
jmpSelectorCode16:0
CodeALenequ$ - LABEL_CODE_A
; END of [SECTION .la]
小结:
理解“选择子”和DPL, RPL, CPL四者的转化和对应关系。