OS_lab——保护模式之GDT、 Descriptor、Selector、GDTR 及其之间关系

发布时间:2024年01月07日

1.?保护模式的相关数据结构

保护模式必要的数据结构定义

? ??GDT:即为 Global?Descriptor?Table(全局描述符表),又称段描述符表,?为保护模式下的一个数据结构。其中包含多个 descriptor,定义了段的起始地址,界限属性等。

? ??Descriptor:段描述符,包含段基址,段界限,段属性。

? ??Selector:选择子,其作用是表示段描述符符相对于 GDT 基址的偏移。

? ??GDTRGDT?寄存器。其结构与?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. ?检查?dsesfsgs?的值,如果其中哪一个寄存器指向的段的?DPL?小于?CPL(此规则不适用于一致代码段),那么一个空描述符会被加载到该寄存?器。

使用调用门进行特权级转换

pmtest5.asm?实现了一个?ring3?特权级的代码(将在屏幕中打印数字?3);并初始??TSS,通过调用门引用?ring0?特权级的代码片段在屏幕上打印字母?C。 实验结?果如下图所示:

测试成功

1. 解决问题与动手改

1.1 GDTDescriptorSelectorGDTR?结构,及其含义是什么?他们的关联关?系如何?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. 加载一个合适的描述符选择子到有关段寄存器;
  2. 跳转至?16?位代码段;
  3. ?cr0??PE?位;
  4. 跳转回到实模式;
  5. 关闭?A20;
  6. 打开中断。

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?的跳转。

文章来源:https://blog.csdn.net/weixin_49816179/article/details/135433928
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。