分类:
2014-02-11 17:41:07
原文地址:一个操作系统的实现笔记三之保护模式进阶 作者:飘之叶
一、从保护模式返回
因为在准备结束保护模式回到实模式之前,需要加载一个合适的描述符选择子到有关的寄存器,以使对应的段描述符高速缓冲寄存器中含有合适的段界限和属性,而且,不能从32的代码返回到实模式,只能从16位代码段中返回。因此呢需新增一Normal描述符,在返回实模式之前把对应的SelectorNormal加载到DS,EC,SS中。
LABEL_DESC_NORMAL:
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
; GDT 选择子
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
保护模式到实模式的前期准备工作
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
跳回实模式:
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]
保护模式跳回到实模式
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,
int 21h ; / 回到 DOS
; END of [SECTION .s16]
关于 jmp 0:LABEL_REAL_ENTRY
jmp 0:LABEL_REAL_ENTRY
上图所示是第四个字节是SEGMENT的值,而开始的时候呢
mov [LABEL_GO_BACK_TO_REAL+3], ax 指令已经将CS设置为16位的代码。因此就保证了程序的正确返回。
二、LDT
LDT的使用
1、在GDT中增加LDT的描述符以及对应的LDT描述符选择子以及对这个LDT描述符的初始化代码。
在GDT中增加LDT的描述符
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
………………………………
LABEL_DESC_LDT: Descriptor 0, LDTLen - 1, DA_LDT ; LDT
………………………………
LDT 在 GDT描述符选择子
; GDT 选择子
…………
SelectorLDT equ LABEL_DESC_LDT - LABEL_GDT
…………
初始化 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
2、增加一个LDT描述符表
; 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]
3、增加一个LDT得代码段
; CodeA (LDT, 32 位代码段)
[SECTION .la]
ALIGN 32
[BITS 32]
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
; 准备经由16位代码段跳回实模式
jmp SelectorCode16:0
CodeALen equ $ - LABEL_CODE_A
; END of [SECTION .la]
4、使用前用LLDT指令加载ldtr,用jmp指令跳转方式运行。
三、特权级
1、通过jmp或者call进行直接转移
(1)、如果目标代码是非一致代码段,要求CPL必须等于目标段的DPL,同时要求RPL小于等于DPL;
(2)、如果目标是一致代码段,则要求CPL大于或者等于目标代码段的DPL,RPL此时不做检查。
2、调用门使用方法(通过调用门可以实现从低特权级到高特权级的转移)
(1)、增加一个代码段作为通过调用门转移的目标代码
[SECTION .sdest]; 调用门目标段
[BITS 32]
LABEL_SEG_CODE_DEST:
;jmp $
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
retf
SegCodeDestLen equ $ - LABEL_SEG_CODE_DEST
; END of [SECTION .sdest]
(2)、加入该段的描述符,选择子以及初始化的代码
描述符:
LABEL_DESC_CODE_DEST: Descriptor 0,SegCodeDestLen-1, DA_C+DA_32; 非一致代码段,32
选择子:
SelectorCodeDest equ LABEL_DESC_CODE_DEST - LABEL_GDT
初始化代码:
; 初始化测试调用门的代码段描述符
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
(3)、增加调用门以及选择子
; 门 目标选择子,偏移,DCount, 属性
LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0, DA_386CGate+DA_DPL0
选择子:
SelectorCallGateTest equ LABEL_CALL_GATE_TEST - LABEL_GDT
(4)、使用调用门:
; 测试调用门(无特权级变换),将打印字母 'C'
call SelectorCallGateTest:0
3、ring0->ring3->ring0
使用ret指令可以实现有高特权级到低特权级的转移。
我不知道出现了什么问题,就是编译的时候一直往.com里面写东西写到G以上都没停!!!用光盘的a,b,c历程也是出现这样的情况,不知道作者是什么编译出来的。没找到原因。只好跳过了,用pmtest5.asm了。
(1)、添加ring3的代码段和ring3的堆栈段以及它们的描述符,选择子,初始化。
描述符
LABEL_DESC_CODE_RING3: Descriptor 0, SegCodeRing3Len-1, DA_C+DA_32+DA_DPL3
LABEL_DESC_STACK3: Descriptor 0, TopOfStack3, DA_DRWA+DA_32+DA_DPL3
选择子
SelectorCodeRing3 equ LABEL_DESC_CODE_RING3 - LABEL_GDT + SA_RPL3
SelectorStack3 equ LABEL_DESC_STACK3 - LABEL_GDT + SA_RPL3
初始化
; 初始化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
; 初始化堆栈段描述符(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
两个ring3目标段
; 堆栈段ring3
[SECTION .s3]
ALIGN 32
[BITS 32]
LABEL_STACK3:
times 512 db 0
TopOfStack3 equ $ - LABEL_STACK3 - 1
; END of [SECTION .s3]
; CodeRing3
[SECTION .ring3]
ALIGN 32
[BITS 32]
LABEL_CODE_RING3:
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
call SelectorCallGateTest:0 ; 测试调用门(有特权级变换),将打印字母 'C'。
jmp $
SegCodeRing3Len equ $ - LABEL_CODE_RING3
; END of [SECTION .ring3]
(2)、ring0->ring3
push SelectorStack3
push TopOfStack3
push SelectorCodeRing3
push 0
retf ; Ring0 -> Ring3,历史性转移!将打印数字 '3'。
(3)以上完成了ring0->ring3的转移,在ring3又完成到ring0的转移,因为从低特权级到高特权级的转移需要TSS.因此呢还要完成TSS代码。
描述符
LABEL_DESC_TSS: Descriptor 0, TSSLen-1, DA_386TSS ;TSS
选择子
SelectorTSS equ LABEL_DESC_TSS - LABEL_GDT
TSS段,这个是根据TSS的格式来填充的,因为在历程中只用到ring0的堆栈,因此只初始化ring0的堆栈。
; TSS ---------------------------------------------------------------------------------------------
[SECTION .tss]
ALIGN 32
[BITS 32]
LABEL_TSS:
DD 0 ; Back
DD TopOfStack ; 0 级堆栈
DD SelectorStack ;
DD 0 ; 1 级堆栈
DD 0 ;
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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
初始化 TSS 描述符:
; 初始化 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
(4)、加载TSS
; Load TSS
mov ax, SelectorTSS
ltr ax ; 在任务内发生特权级变换时要切换堆栈,而内层堆栈的指针存放在当前任务的TSS中,所以要设置任务状态段寄存器 TR。
(5)初始化调用门
在ring3打印出‘3’之后,再使用调用门打印字母 'C',之后再跳入局部任务,将打印字母 'L'。因此还要初始化调用门。
描述符
LABEL_DESC_CODE_DEST: Descriptor 0, SegCodeDestLen-1, DA_C+DA_32;非一致,32
; 门 目标选择子, 偏移, DCount, 属性
LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0, DA_386CGate + DA_DPL3
选择子:
SelectorCodeDest equ LABEL_DESC_CODE_DEST - LABEL_GDT
SelectorCallGateTest equ LABEL_CALL_GATE_TEST - LABEL_GDT + SA_RPL3
初始化略。
同时因为是在ring3中显示所以要将LABEL_DESC_VIDEO改为ring3下。
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW+DA_DPL3
程序的流程是:
先打印”In Protect Mode now”à到加载TSSàRing0 到 Ring3à打印数字 '3'à call SelectorCallGateTest:0 ; 测试调用门(有特权级变换),将打印字母 'C'à jmp SelectorLDTCodeA:0 ; 跳入局部任务,将打印字母 'L'。
这样按道理说输出的顺序是
In Protect Mode now
3
C
L
但是实际屏幕输出顺序是
In Protect Mode now
C
L
3
为什么是这样呢?