1.?保护模式的相关数据结构
保护模式必要的数据结构定义
? ??GDT:即为 Global?Descriptor?Table(全局描述符表),又称段描述符表,?为保护模式下的一个数据结构。其中包含多个 descriptor,定义了段的起始地址,界限属性等。
? ??Descriptor:段描述符,包含段基址,段界限,段属性。
? ??Selector:选择子,其作用是表示段描述符符相对于 GDT 基址的偏移。
? ??GDTR:GDT?寄存器。其结构与?GDTPTR?类似,有?6 字节,前两字节为?GDT 界限,后?4?字节为?GDT?基地址。
宏Descriptor的定义
如下图所示,Descriptor?分别存储了段基址、段界限和相关属性。由于部分原因,?段基址与段界限被分别存储:
2.?从实模式到保护模式
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 g?s, 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.??准备 GDT。
2. ?用?lgdt?加载?gdtr。
3.??打开?A20。
4. ?置?cr0?的?PE?位。
5. ?跳转,进入保护模式。
jmp跳转:dword前缀
在?pmtest1.asm?的第?71?行:jmp dword SelectorCode32:0?删除?dword?标识会导?致编译后偏移地址被截断,可能无法正确地跳转到指定的位置。?原因:此时操作?系统的运行环境已由实模式(16?位)转换为保护模式(32?位),但由于编译器的?限制,此段代码只能放置于?16?位汇编代码部分,因此需要?dword?标识避免?32?位?长度的偏移地址被截断,而发生未知错误。 测试代码的反编译源码如下所示:
GDT表的构造
Descriptor?的构造
段描述符的构造借助?pm.inc?文件来进行,分别对段基址、段界限与相关属性进行?定义,宏会根据定义将数据重组以满足规范的要求
Selector的构造
Selector?的值为段描述符位置与?GDT?表初始位置之差
GDT表的切换
由于代码在实模式将段的偏移地址存储在的?GDT?表的段基址中。?因此,在保护模?式下,程序使用事先定义好的?Selector?选择子即可实现?GDT?表的切换。
3.2.?由保护模式返回实模式
与从实模式进入保护模式的方法相反,但是由于进入实模式的相关操作需要在?16?位代码段文成,且由于高速缓存的原因,因此,由保护模式返回实模式共一下几 步:
1. ?加载相关段寄存器。
2.??清?cr0?的 PE?位。
3. ?跳转,返回?16?位代码段。
4. ?设置实模式的段寄存器。
5.??关闭?A20。
6.??开中断
7. ?返回实模式
执行效果展示
下图为?pmtest2.asm?代码的执行效果,可以看到,在引导入保护模式后,程序打?印了字符并回到了实模式下的?dos?系统:
4.?LDT?切换
LDT?(Local?Descriptor?Table)
LDT?与?GDT?相同,均为描述符表,但?LDT?表仅有部分作用范围,而?GDT?表则作用?于全局
pmtest3.asm代码片段展示
[SECTION?.gdt]
...
LABEL_DESC_LDT: ???Descriptor???????0,????????LDTLen -?1,?DA_LDT ????;
LDT
...
SelectorLDT ????equ LABEL_DESC_LDT ?????- LABEL_GDT
; END?of?[SECTION?.gdt]
...
[SECTION .s32]; 32?位代码段.?由实模式跳入.
[BITS???32]
LABEL_SEG_CODE32:
...
;?Load?LDT
mov ax,?SelectorLDT
lldt????ax
jmp SelectorLDTCodeA:0??; 跳入局部任务
...
; END?of?[SECTION?.s32]
...
;?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]
LDT表的构造
1. ?在?GDT?表中定义?LDT?表所对应的代码段
2. ?在?LDT?表中定义对应的?LDT?表
– ?在?LDT?表的选择子定义上,在代码偏移量的基础上,需要额外增加?一个属性?SA_TIL,该属性将在?pm,inc?宏中存储至选择子的?TL?部分,?用于辨识该选择子是?LDT?表的选择子
LDT表的使用
1. ?使用?lldt?指令加载?LDT?表所在的段
2. ?使用对应?LDT?表所对应的选择子进行跳转
3.??执行代码
执行效果展示
下图为?pmtest3.asm?代码的执行效果,可以看到,程序在?LDT?表段打印输出了字?母L:
5.权限访问与段间切换
权限访问规则
在?IA32?的分段机制中,特权级总共有?4?个特权级别,从高到低分别是?0、1、2、3。数字越小表示的特权级越大。
CPL
CPL?是当前执行的程序或任务的特权级。它被存储在?cs?和?ss?的第?0?位和第?1?位?上。在通常情况下,CPL?等于代码所在的段的特权级。当程序转移到不同特权级?的代码段时,处理器将改变?CPL。一致代码段可以被相同或者更低特权级的代码?访问。当处理器访问一个与?CPL?特权级不同的一致代码段时,CPL?不会被改变。
DPL
DPL?表示段或者门的特权级。它被存储在段描述符或者门描述符的?DPL?字段中。?当当前代码段试图访问一个段或者门时,DPL?将会和?CPL?以及段或门选择子的?RPL?相比较,根据段或者门类型的不同,DPL?将会被区别对待:
? ??数据段:DPL?规定了可以访问此段的最低特权级。
? ??非一致代码段(不使用调用门的情况下):DPL?规定访问此段的特权级。
? ??一致代码段:DPL?规定了访问此段的最高特权级。
RPL
RPL?是通过段选择子的第?0?位和第?1?位表现出来。处理器通过检查?RPL?和?CPL?来?确认一个访问请求是否合法。也就是说,如果?RPL?的数字比 CPL?大(数字越大特 权级越低),那么?RPL?将会起决定性作用,反之亦然。
特权级检验测试
使用?pmtest2.asm,将数据段描述符的?DPL?与其段选择子的?RPL?进行修改:?
[SECTION?.gdt]
;?GDT
;
LABEL_DESC_DATA: ??Descriptor 0, DataLen-1, DA_DRW+DA_DPL1?; Data
;?GDT?结束
;?GDT?选择子
SelectorData ??equ LABEL_DESC_DATA - LABEL_GDT +?SA_RPL3
; END?of?[SECTION?.gdt]
调试结果如下所示:
调试程序报错:load_seg_reg?(DS, 0x0023): RPL & CPL must be <= DPL 证明此?处违反了特权级管理的规则
段间切换
一下几种方法可以实现不同代码段之间的转移:
????使用?jmp?或?call?指令
–??目标操作数包含目标代码段的段选择子
– ?目标操作数指向一个包含目标代码段选择子的调用门描述符。
? ??使用门描述符进行转移
其中,通过?jmp?和?call?所能进行的代码段间转移是非常有限的,对于非一致代码?段,只能在相同特权级代码段之间转移。遇到一 致代码段也最多能从低到高,而?且 CPL?不会改变。 若需要进行不同特权级之间的转移,则需要即运用门描述符?进行转移。
门描述符
门描述符的结构
调用门直接定义了目标代码对应段的选择子、入口偏移地址等一系列属性,可直?接进行代码段的跳转。?门描述符有以下?4?种:
????调用门
????中断门
????陷阱门
????任务门
调用门的使用
pmtest4.asm?代码片段展示
[SECTION?.gdt]
;?GDT
; ???????????????????????????段基址,???????段界限?????, 属性
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_DPL0
...
;?GDT?选择子
SelectorCodeDest ???equ LABEL_DESC_CODE_DEST ???- LABEL_GDT
SelectorCallGateTest ???equ LABEL_CALL_GATE_TEST ???- LABEL_GDT
; END?of?[SECTION?.gdt]
...
[SECTION .s32]; 32?位代码段.?由实模式跳入.
[BITS???32]
...
; 测试调用门(无特权级变换),将打印字母 'C'
call ???SelectorCallGateTest:0
...
jmp SelectorLDTCodeA:0??; 跳入局部任务,将打印字母 'L'。
...
; END?of?[SECTION?.s32]
使用?pm.inc?中的?Gate?宏进行门的调用,在代码片段中,此门所指向的位置为?SelectorCodeDest:0,即?LABEL_DESC_CODE_DEST?处的代码。
执行效果展示
程序进入调用门所指向的代码段,输出了字母?C
6.?使用调用门进行特权级的变换
调用门使用的特权检验
堆栈的特权级转换——TSS
由于在进行?call-ret?跳转时,堆栈会存储跳转前的?cs:eip?地址,并在子程序结?束时返回。但是在有特权级的代码跳转时,所调用的堆栈段也会发生改变。因此,?需要使用?TSS(Tasj-State Stack)来避免这类问题。
TSS?的结构
有?TSS?辅助的转移过程
1. ?根据目标代码段的DPL(新的 CPL) 从?TSS?中选择应该切换至哪个?ss?和?esp
2. ?从?TSS?中读取新的?ss?和?esp。在这过程中如果发现?ss、esp?或者?TSS?界限?错误都会导致无效?TSS?异常(#TS)。
3. ?对?ss?描述符进行检验,如果发生错误,同样产生#TS?异常。 同样产生#TS?开?m。mt?接出媒设
4. ?暂时性地保存当前?ss?和?esp?的值。
5. ?加载新的?ss?和?esp。
6. ?将刚刚保存起来的?ss?和?esp?的值压入新栈。
7. ?从调用者堆栈中将参数复制到被调用者堆栈(新堆栈)中,复制参数的数目?由调用门中 Param Count?一项来决定。如果?Param Count?为?0?的话,将不?会复制参数。
8. ?将当前的?cs?和?eip?压栈。
9. ?加载调用门中指定的新的?cs?和?eip,开始执行被调用者过程。?
?有?TSS?辅助的返回过程
1. ?检查保存的?cs?中的?RPL?以判断返回时是否要变换特权级。
2. ?加载被调用者堆栈上的?cs?和?eip?(此时会进行代码段描述符和选择子类型?和特权级检验)。
3. ?如果?ret?指令含有参数,则增加?esp?的值以跳过参数,然后?esp?将指向被?保存过?的调用者 ss?和?esp?。注意?,ret?的参数必须对应调用?门?中?的?ParamCount?的值。
4. ?加载?ss?和?esp,切换到调用者堆栈,被调用者的?ss?和?esp?被丢弃。在这?里将会进行?ss?描述符、esp?以及?ss?段描述符的检验。
5. ?如果?ret?指令含有参数,增加?esp?的值以跳过参数(此时已经在调用者堆栈?中)。
6. ?检查?ds、es、fs、gs?的值,如果其中哪一个寄存器指向的段的?DPL?小于?CPL(此规则不适用于一致代码段),那么一个空描述符会被加载到该寄存?器。
使用调用门进行特权级转换
pmtest5.asm?实现了一个?ring3?特权级的代码(将在屏幕中打印数字?3);并初始?化?TSS,通过调用门引用?ring0?特权级的代码片段在屏幕上打印字母?C。 实验结?果如下图所示:
测试成功
1. 解决问题与动手改
1.1 GDT、Descriptor、Selector、GDTR?结构,及其含义是什么?他们的关联关?系如何?pm.inc?所定义的宏怎么使用?
? ? ? ?GDT(Global?Descriptor Table?全局描述符表)又叫段描述符表,为保护模?式下的一个数据结构。其中包含多个?descriptor,定义了段的起始地址,界限属?性等,其作用是提供段式存储机制;
Descriptor?为段描述符,包含段基址、段界限、段属性;
? ? ? ?Selector?为选择子,可以对应一个描述符。在?pmtest1.asm?程序中,其对应?的就是描述符相对于?GDT?基址的偏移。
? ? ? ?GDT?是一个结构数组,包含多个?Descriptor,每个?Descriptor?都是?GDT?数组?的一个表项,存储各个段的段基址、段界限和属性。Selector?记录对应?Descriptor?相对于?GDT?基址(LABEL_GDT)的偏移、表类型(GDT/LDT)和段描述符特权。GDTR?则是记录了?GDT?表的基地址和界限。综合以上,就能够得到某个?Descriptor?的地?址。而保护模式下寻址就是先靠?GDTR?找到?GDT,而后根据?Descriptor?找到对应 段的地址,而后再加上段内偏移?offset,就获得某个线性地址。
程序中?Descriptor 由?pm.inc?中的宏?Descriptor?生成。宏的具体使用如下:?
a.?宏名?Descriptor,3?表明有三个参数,分别为段基址、界限、属性;
b.?第一行?dw?为两字节,决定了段界限;
c.?第二三行?dw?和?dd?确定了段基址?1、2;
d.?第四行?dw?两字节构成段属性和段界限?2;
e.?最后一行的?dw?两字节构成段基址?3。
1.2??从实模式到保护模式,关键步骤有哪些?为什么要关中断?为什么要打开?A20?地址线?从保护模式切换回实模式,又需要哪些步骤?
从实模式到保护模式步骤:
a. 准备?GDT,初始化?GDT?属性,将需要的段的描述符和选择子定义好;?
b. 初始化段;
c. 将?GDT?的物理地址填充到?GdtPtr?中,然后将填充的地址加载到寄存器?gdtr;
d.?关中断;
e. 打开?A20?地址线;
f. 置?cr0?的?PE?位;
g. 跳转到描述符对应段首地址,进入保护模式。
? ? ? ?需要关中断的原因是:保护模式下的中断处理机制和实模式下不同,如果开?启中断而对应的中断处理机制尚未完善将会出现错误。
? ? ? ?需要打开?A20?地址线的原因是:在?8086 CPU?中只有?20?位地址总线,它的最大寻?址能力只能达到?1 MB。8086 设计当程序在访问?1 MB?以上的内存地址时,将从?0?地址开始“?回卷?”,也就是说当访问?1 MB?零?1?位时,实际访问的空间是?1?地址。?而在之后的?CPU?中,访问空间早已超过?1 MB,这就导致了不兼容。IBM?设计如过?A20?不被打开,则继续使用回滚机制,第二十个地址为是?0。如果打开?A20,则可?以正常访问?1 MB?以上的内存地址。
从实模式到保护模式步骤:
1.3??解释不同权限代码的切换原理,call,?jmp,retf?使用场景如何,能够互换吗?
? ? ? ?对于?jmp?而言,长跳转和短跳转仅仅存在结果上的不同,短跳转对应段内跳?转,长跳转对应段间跳转。
? ? ? ?对?call?而言,由于?call?指令会影响堆栈,所以长调用和短调用会产生不同?影响,在短调用当中,call?指令会将下一条指令?eip?压栈,当遇到?ret?指令执行?时,该?eip?会被从堆栈中弹出。
? ? ? ?在长调用时,call?指令也会将?eip?与?cs?都压入栈中,遇到?retf?时会弹出?eip?和?cs,大致来说?call?相当于?push+jmp ?ret?相当于?pop+jmp。
1.4??动手改:
①?自定义添加?1?个?GDT?代码段、1?个?LDT?代码段,GDT?段内要对一个内存数据结?构写入一段字符串,然后?LDT?段内代码段功能为读取并打印该?GDT?的内容;
该功能参考?pmtest3.asm?实现,实验结果如下:
第一步:在.Data?段中修改?StrTest 内容
第二步:修改?32?味代码段,跳入?LDT?局部任务中
第三步:修改?LDT,CodeA?中的逻辑,将偏移设为?TestStr?的偏移量,最后输出到?屏幕上
②?自定义?2?个?GDT?代码段?A、B,分属于不同特权级,功能自定义,要求实现?A-->B?的跳转,以及?B-->A?的跳转。