《Orange's 一个操作系统的实现》学习笔记--特权级代码段之间的转移(二)

<>门描述符 

除存储段描述符和系统段描述符外,还有一类门描述符。门描述符并不描述某种内存段,而是描述控制转移的入口点。这种描述符

好比一个同向另一代码段的门。通过这种门,可实现任务内特权级的变换和任务间的切换。所以,这种门描述符也称为控制门。                                                  
                                                                                                      					      

1.门描述符的一般格式

门描述符的一般格式如下图所示。门描述符只有位于描述符内偏移 5 的类型字节与系统段保持一致,也由该字节标示门描述符和系统段

描述符。该字节内的 P 和 DPL 的意义与其它描述符种中的意义相同。其它字节主要用于存放一个 48 位的全指针(16 位的选择子和32 位的偏移量)。 


根据上图给出的门描述符结构,可定义如下的门描述符结构类型

;windows 平台下汇编格式
GATE STRUC          ;门结构类型定义
    OFFSETL DW      ;32 位偏移的低16 位
    SELECTOR DW     ;选择子
    DCOUNT DB       ;双字计数字段
    GTYPE DB 0      ;类型
    OFFSETH DW 0    ;32 位偏移的高16 位
GATE ENDS

利用门描述符结构类型 GATE 能方便地在程序中说明门描述符。 

例如,下面的门描述符 SUBRG 描述一个 386 调用门,门内的选择子是 10H,入口偏移是 123456H,门描述符特权级是 3,双字计数是 0。

SUBRG GATE <3456,10H,,8CH+60H,12H>

从上述描述符类型的列表中可见,门描述符又可分为:任务门、调用门、中断门和陷阱门,并且除任务门外,其它描述符还各分成286 和 386 两种。 

2.调用门

调用门描述某个子程序的入口。调用门内的选择子必须实现代码段描述符,调用门内的偏移是对应代码段内的偏移。利用段间调用指令

CALL,通过调用门可实现任务内从外层特权级变换到内层特权级。

在上图所示的门描述符内偏移 4 字节的位 0 至位 4 是双字计数字段,该字段只在调用门描述符中有效,在其它门描述符中无效。主程序通

过堆栈把入口参数传递给子程序,如果在利用调用门调用子程序时引起特权级的转换和堆栈的改变,那么就需要将外层堆栈中的参数复制

到内层堆栈。该双字计数字段就是用于说明这种情况发生时,要复制的双字参数的数量。

(1)调用门的使用方式:


假设我们想由代码A转移到代码B,运用一个调用门G,即调用门G中的目标选择子指向代码B的段。实际上,这个问题主要涉及这几个

元素:CPL、RPL、代码B的DPL(记做DPL_B),调用门G的DPL(记做DPL_G)。

调用门使用时特权级检验的规则如下:


也就是说,通过调用门和call指令,可以实现从低特权级到高特权级的转移,无论目标代码段时一致的还是非一致的。

对于使用调用门的段间转移指令 JMP,检测条件与段间直接转移相同。由于已置 RPL=0,所以可认为 RPL<=DPL 的条件总能满足。所以,对于普通的非一致代码段,当 CPL=DPL 时,发生无特权级变换的转移;对于一致代码段,在满足 CPL>=DPL 时也发生无特权级变换的转移;其它情形就引起异常。 

也就是说通过调用门和jmp指令,如果目标代码段是一致的,则可以实现从低特权级到高特权级的转移。如果目标代码段是非一致的,则只能实

现相同特权级的转移。

3.任务门
任务门指示任务。任务门内的选择子必须指示 GDT 中的任务状态段 TSS 描述符,门中的偏移无意义。任务的入口点保存在 TSS 中。

利用段间转移指令 JMP 和段间调用指令 CALL,通过任务门可实现任务切换。

4.中断门和陷阱门

中断门和陷阱门描述中断/异常处理程序的人口点。中断门和陷阱门内的选择子必须指向代码段描述符,门内的偏移就是对应代码段的人

口点的偏移。中断门和陷阱门只有在中断描述符表 IDT 中才有效。关于中断门和陷阱门的区别将在以后的文章中论述。 

<>任务状态段

任务状态段(Task State Segment)是保存一个任务重要信息的特殊段。任务状态段描述符用于描述这样的系统段。任务状态段寄存器TR

的可见部分含有当前任务的任务状态段描述符的选择子,TR 的不可见的高速缓冲寄存器部分含有当前任务状态段的段基地址和段界限等

信息。

TSS 在任务切换过程中起着重要作用,通过它实现任务的挂起和恢复。所谓任务切换是指,挂起当前正在执行的任务,恢复或启动另一任务

的执行。在任务切换过程中,首先,处理器中各寄存器的当前值被自动保存到 TR 所指定的 TSS 中;然后,下一任务的 TSS 的选择子被装入

TR;最后,从 TR 所指定的 TSS 中取出各寄存器的值送到处理器的各寄存器中。由此可见,通过在 TSS 中保存任务现场各寄存器状态的完

整映象,实现任务的切换。

任务状态段 TSS 的基本格式如下图所示。 


从图中可见,TSS 的基本格式由 104 字节组成。这 104 字节的基本格式是不可改变的,但在此之外系统软件还可定义若干附加信息。基本

的 104 字节可分为链接字段区域、内层堆栈指针区域、地址映射寄存器区域、寄存器保存区域和其它字段等五个区域。 

1.寄存器保存区域

寄存器保存区域位于 TSS 内偏移 20H 至 5FH 处,用于保存通用寄存器、段寄存器、指令指针和标志寄存器。当 TSS 对应的任务正在执

行时,保存区域是未定义的;在当前任务被切换出时,这些寄存器的当前值就保存在该区域。当下次切换回原任务时,再从保存区域恢复出

这些寄存器的值,从而,使处理器恢复成该任务换出前的状态,最终使任务能够恢复执行。

从上图可见,各通用寄存器对应一个 32 位的双字,指令指针和标志寄存器各对应一个 32 位的双字;各段寄存器也对应一个 32 位的双字,段

寄存器中的选择子只有 16 位,安排再双字的低 16 位,高 16 位未用,一般应填为 0。

2.内层堆栈指针区域

为了有效地实现保护,同一个任务在不同的特权级下使用不同的堆栈。例如,当从外层特权级 3 变换到内层特权级 0 时,任务使用的堆栈也

同时从 3 级变换到 0 级堆栈;当从内层特权级 0 变换到外层特权级 3 时,任务使用的堆栈也同时从 0 级堆栈变换到 3 级堆栈。所以,一个任

务可能具有四个堆栈,对应四个特权级。四个堆栈需要四个堆栈指针。

TSS 的内层堆栈指针区域中有三个堆栈指针,它们都是 48 位的全指针(16 位的选择子和 32 位的偏移),分别指向 0 级、1 级和 2 级堆栈的

栈顶,依次存放在 TSS 中偏移为 4、12 及 20 开始的位置。当发生向内层转移时,把适当的堆栈指针装入 SS 及 ESP 寄存器以变换到内层

堆栈,外层堆栈的指针保存在内层堆栈中。没有指向 3 级堆栈的指针,因为 3 级是最外层,所以任何一个向内层的转移都不可能转移到 3 级。

但是,当特权级由内层向外层变换时,并不把内层堆栈的指针保存到 TSS 的内层堆栈指针区域。实际上,处理器从不向该区域进行写入,除

非程序设计者认为改变该区域的值。这表明向内层转移时,总是把内层堆栈认为是一个空栈。因此,不允许发生同级内层转移的递归,一旦

发生向某级内层的转移,那么返回到外层的正常途径是相匹配的向外层返回。

3.地址映射寄存器区域

从虚拟地址空间到线性地址空间的映射由 GDT 和 LDT 确定,与特定任务相关的部分由 LDT 确定,而 LDT 又由 LDTR 确定。如果采用分

页机制,那么由线性地址空间到物理地址空间的映射由包含页目录表起始物理地址的控制寄存器 CR3 确定。所以,与特定任务相关的虚拟

地址空间到物理地址空间的映射由 LDTR 和 CR3 确定。显然,随着任务的切换,地址映射关系也要切换。

TSS 的地址映射寄存器区域由位于偏移 1CH 处的双字字段(CR3)和位于偏移 60H 处的字字段(LDTR)组成。在任务切换时,处理器自动从

要执行任务的 TSS 中取出这两个字段,分别装入到寄存器 CR3 和 LDTR。这样就改变了虚拟地址空间到物理地址空间的映射。

但是,在任务切换时,处理器并不把换出任务但是的寄存器 CR3 和 LDTR 的内容保存到 TSS 中的地址映射寄存器区域。事实上,处理器也

从来不向该区域自动写入。因此,如果程序改变了 LDTR 或 CR3,那么必须把新值人为地保存到 TSS 中的地址映射寄存器区域相应字段

中。可以通过别名技术实现此功能。

4.链接字段
链接字段安排在 TSS 内偏移 0 开始的双字中,其高 16 位未用。在起链接作用时,地 16 位保存前一任务的 TSS 描述符的选择子。

如果当前的任务由段间调用指令 CALL 或中断/异常而激活,那么链接字段保存被挂起任务的 TSS 的选择子,并且标志寄存器EFLAGS 中

的 NT 位被置 1,使链接字段有效。在返回时,由于 NT 标志位为 1,返回指令 RET 或中断返回指令 IRET 将使得控制沿链接字段所指恢复

到链上的前一个任务。

5.其它字段

为了实现输入/输出保护,要使用 I/O 许可位图。任务使用的 I/O 许可位图也存放在 TSS 中,作为 TSS 的扩展部分。在 TSS 内偏移66H 处

的字用于存放 I/O 许可位图在 TSS 内的偏移(从 TSS 开头开始计算)。关于 I/O 许可位图的作用,以后的文章中将会详细介绍。

在 TSS 内偏移 64H 处的字是为任务提供的特别属性。在 80386 中,只定义了一种属性,即调试陷阱。该属性是字的最低位,用 T表示。该

字的其它位置被保留,必须被置为 0。在发生任务切换时,如果进入任务的 T 位为 1,那么在任务切换完成之后,新任务的第一条指令执行之前产生调试陷阱。

6.用结构类型定义TSS

根据上图给出的任务状态段 TSS 的结构,可定义如下的 TSS 结构类型: 

;windows平台
;----------------------------------------
;任务状态段结构类型定义
;----------------------------------------
TSS STRUC
TRLink  DW 0    ;链接字段
        DW 0    ;不使用,置为0
TRESP0  DD 0    ;0 级堆栈指针
TRSS0   DW 0    ;0 级堆栈段寄存器
        DW 0    ;不使用,置为 0
TRESP1  DD 0    ;1 级堆栈指针
TRSS1   DW 0    ;1 级堆栈段寄存器
        DW 0    ;不使用,置为 0
TRESP2  DD 0    ;2 级堆栈指针
TRSS2   DW 0    ;2 级堆栈段寄存器
        DW 0    ;不使用,置为 0
TRCR3   DD 0    ;CR3
TREIP   DD 0    ;EIP
TREFlag DD 0    ;EFLAGS
TREAX   DD 0    ;EAX
TRECX   DD 0    ;ECX
TREDX   DD 0    ;EDX
TREBX   DD 0    ;EBX
TRESP   DD 0    ;ESP
TREBP   DD 0    ;EBP
TRESI   DD 0    ;ESI
TREDI   DD 0    ;EDI
TRES    DW 0    ;ES
        DW 0    ;不使用,置为 0
TRCS    DW 0    ;CS
        DW 0    ;不使用,置为 0
TRSS    DW 0    ;SS
        DW 0    ;不使用,置为 0
TRDS    DW 0    ;DS
        DW 0    ;不使用,置为 0
TRFS    DW 0    ;FS
        DW 0    ;不使用,置为 0
TRGS    DW 0    ;GS
        DW 0    ;不使用,置为 0
TRLDTR  DW 0    ;LDTR
        DW 0    ;不使用,置为 0
TRTrip  DW 0    ;调试陷阱标志(只用位 0)
TRIOMap DW $+2  ;指向 I/O 许可位图区的段内偏移
TSS     ENS


<>代码段之间的转移对堆栈的影响

1、“长”跳转/调用 和 “短”跳转/调用

如果一个调用或跳转指令时段间而不是段内进行的,那么我们称之为“长”的(Far jmp/call),反之,如果在段内则是“短”的(Near jmp/call)。

那么长的和短的jmp或call有什么分别呢?

对于jmp而言,仅仅是结果不同罢了,短跳转对应段内,长跳转对应段间。

对于call来说,就比较复杂一些,因为call指令是会影响堆栈的,长调用和短调用对堆栈的影响是不同的。

下面我们讨论短调用对堆栈的影响,call指令执行时下一条指令的eip压栈,到ret指令执行时,这个eip会被从堆栈中弹出,

如下图所示:


这是短调用的情况。

下面我们讨论长调用对堆栈的影响,call指令执行时会将调用者的cs和eip压栈,到ret指令执行时,这个eip和cs会被从堆栈中弹出,如下

图所示:


2、有特权级变换的转移对堆栈的影响

在不同特权级下的堆栈段不同,所以每一个任务最多可能在4个特权级间转移,所以,每个任务实际上需要4个堆栈。可是我们只有一

个ss和一个esp,那么当发生堆栈切换,我们该从哪里获得其余堆栈的ss和esp呢?解决这个问题,需要我们上面介绍的一个数据结构

TSS(Task-State Stack)当堆栈发生切换时,内层的ss和esp就是从这里取得的,比如,我们当前所在的是ring3,当转移至ring1时,堆栈

将被自动切换到由ss1和esp1指定的位置。由于只是在由外层转移到内层(低特权级到高特权级)切换时新堆栈才会从TSS中取得,所以

TSS中没有位于最外层的ring3的堆栈信息。下面让我们来看看整个的转移过程是怎么样的?

执行call前后堆栈段的变化:


(1)根据目标代码段的DPL(新的CPL)从TSS中选择应该切换至哪个ss和esp

(2)从TSS中读取新的ss和esp。在这过程中如果发现ss、esp或者TSS界限错误都会导致无效TSS异常

(3)对ss描述符进行检验,如果发生错误,同样产生#TS异常

(4)暂时性地保存当前ss和esp的值

(5)加载新的ss和esp

(6)将刚刚保存起来的ss和esp的值压入新栈

(7)从调用者堆栈中将参数复制到被调用者堆栈(新堆栈)中,复制参数的数目由调用门中Param Count一项来决定。

(8)如果Param Count是零的话,将不会复制参数。

(9)将当前的cs和eip压栈

(10)加载调用门中指定的新的cs和eip,开始执行被调用者过程。

执行ret前后堆栈段的变化:


(1)检查保存的cs中的RPL以判断返回时是否要变换特权级

(2)加载被调用者堆栈上的cs和eip(此时会进行代码段描述符和选择子类型和特权级检验)

(3)如果ret指令含有参数,则增加esp的值以跳过参数,然后esp将指向被保存过的调用者ss和esp。注意,ret的参数必须对应调用门中的

Param Count的值

(4)加载ss和esp,切换到调用者堆栈,被调用者的ss和esp被丢弃。在这里将会进行ss描述符、esp、以及ss段描述符的检验

(5)如果ret指令含有参数,增加esp的值以跳过参数(此时已经在调用者堆栈中)

(6)检查ds、es、fs、gs的值,如果其中哪一个寄存器指向的段的DPL小于CPL(此规则不适合于一致代码段),那么一个空描述符会被

加载到该寄存器中。

综上所述,使用调用门的过程实际上分为两部分,一部分是从低特权级到高特权级,通过调用门和call指令来实现;另一部分则是从高

特权级到低特权级,通过ret指令来实现。需要注意的是,无论是否通过调用门,只要不发生特权级变换,就不会切换堆栈。


<>向外层返回

与使用CALL 指令通过调用门向内层变换相反,使用RET 指令实现向外层返回。段间返回指令RET 从堆栈中弹出返回地址,并且可以

采用调整 ESP 的方法,跳过相应的在调用之前压入堆栈的参数。返回地址的选择子指示要返回的代码段的描述符,从而确定返回的代码

段。选择子的 RPL 确定返回后的特权级,而不是对应描述符的 DPL,这是因为,段间返回指令 RET 可能使控制返回到一致代码段,而一

致代码段可以在 DPL 规定的特权级以外的特权级执行。需要注意的是,RET 指令所使用的返回地址的选择子只能使用代码段描述符,而

不能使用任何系统段描述符或门描述符,当然,更不能使用数据段描述符,否则会引起异常。与 CALL 指令相对应,RET 指令也不能向内

层返回。

段间返回指令完成返回的步骤如下:


(1)RET 指令先从堆栈中弹出返回地址。如果弹出地址的选择子的 RPL 规定相对于 CPL 更外层的特权级,那么就引起向外层返回。

(2)为向外层返回,跳过内层堆栈中的参数,再从内层栈中弹出指向外层堆栈的指针,并装入 SS 及 ESP,以恢复外层堆栈。

(3)调整 ESP,跳过在相应的调用之前压入到外层堆栈的参数。即返回指令不但弹出内层栈的参数,而且也弹出外层栈的参数。

(4)然后,检查数据段寄存器 DS、ES、FS 及 GS,以保证寻址的段在外层是可访问的,如果段寄存器寻址的段在外层是不可访问的,

那么装入一个空选择子,以避免在返回时发生保护空洞。
(5)返回外层继续执行。上述五步是对带立即数的段间返回指令而言的,立即数规定了堆栈中要跳过的参数的字节数。对于无立即数的

段间返回指令缺少第二步和第三步。若RET 指令不需要向外层返回,那么就只有(1)(5)两步。对于有通过堆栈传递参数的子程序,必

须使用带立即数的返回指令返回,否则返回时会装载错误的外层栈指针。

若不使用带立即数的返回指令,可以在返回前把外层栈的栈指针存入内层栈中的用于保存返回地址上方两个双字的区域中,由外层返回

的过程可知,这可正确恢复外层栈的指针,但在外层程序中,必须人为调整外层栈指针,以便废除在外层栈中压入的参数。在使用C调用约

定的程序中可使用此方法。但这会增加代码的长度和处理时间,使代码效率变低。正因为如此,在Windows 9X下,新增加了

一种 STDCALL 的调用约定,它正是为了适应 Intel 系列处理器的体系结构而产生的。 

参考博文:http://blog.csdn.net/begginghard/article/details/7262901


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值