鱼C论坛

 找回密码
 立即注册
查看: 2730|回复: 0

[学习笔记] X86汇编语言-从实模式到保护模式—笔记(38)-第14章 任务和特权级保护(4)

[复制链接]
发表于 2017-12-9 21:05:17 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 兰陵月 于 2017-12-15 12:21 编辑

代码段的特权级检查是很严格的。一般来说,控制转移只允许发生在两个特权级相同的代码段之间。如果当前特权级为2,那么,它可以转移到另一个DPL为2的代码段接着执行,但不允许转移到DPL为0、1和3的代码段执行。

为了让特权级低的应用程序可以调用特权级高的操作系统例程,处理器也提供了相应的解决办法。
第一种方法将高特权级的代码段定义为依从的先复习一下描述符的结构,如下图图14-005,位11~8为描述符的TYPE字段:

14-005.png

再回忆一下TYPE字段的各个位代表的意义。下图图14-006为代码段和数据段描述符的TYPE字段:

14-006.png

如上图,当代码段描述符的TYPE字段有C位(也就是当X=1时),如果C=0,这样的代码段只能供同特权级的的程序使用如果C=1,则这样的代码段称为依从的代码段,可以从特权级比它低的程序调用并进入

但是,即使是将控制转移到依从的代码段,也是有条件的,要求当前特权级CPL必须低于或者等于目标代码段描述符的DPL。即,在数值上,CPL≥目标代码段描述符的DPL。举例来说,如果一个依从的代码段,其描述符的DPL为1,则只有特权级别为1、2、3的程序可以调用,而特权级为0的程序则不能。在任何时候,都不允许将控制从较高的特权级转移到较低的特权级。

依从的代码段不是在它的DPL特权级上运行,而是在调用程序的特权级上运行。就是说,当控制转移到依从的代码段上执行时,不改变当前特权级CPL,段寄存器CS的CPL字段不发生变化,被调用过程的特权级依从于调用者的特权级,这就是为什么它被称为“依从的”代码段。

第二种方法使用门。门(Gate)是另一种形式的描述符,称为门描述符,简称门。和段描述符不同,段描述符用于描述内存段,门描述符则用于描述可执行的代码,比如一段程序、一个过程(例程)或者一个任务。
门的类型,根据用途不同,分为调用门、中断门/陷阱门、任务门

调用门:用于不同特权级之间的过程调用;

中断门/陷阱门:用于中断处理过程使用;

任务门:对应单个任务,用来执行任务切换。

所有描述符都是64位的,调用门描述符也不例外。在调用门描述符中,定义了目标过程(例程)所在代码段的选择子,以及段内偏移。要想通过调用门进行控制转移,可以使用jmp far或者call far指令,并把调用门描述符的选择子作为操作数。

jmp far指令,可以将控制通过门转移到比当前特权级高的代码段,但不改变当前特权级别。

call far指令,也可以将控制通过门转移到比当前特权级高的代码段,但当前特权级会提升到目标代码段的特权级别。也就是说,处理器是在目标代码的特权级上运行的。但是,除了从高特权级别的例程(通常是操作系统例程)返回外,不允许从特权级高的代码段将控制转移到特权级低的代码段(是代码段转移到代码段,高级别代码段访问低级别数据段是允许的),因为操作系统不会引用可靠性比自己低的代码。


描述符属性字段:0x00CF9200
其二进制表示为:0000 0000 1100 1111 1001 0010 0000 0000;
其位14、13位为DPL位,值为“00”。

描述符属性字段:0x00409800
其二进制表示为:0000 0000 0100 0000 1001 1000 0000 0000;
其位14、13位为DPL位,值为“00”。

描述符属性字段:0x00CF9600
其二进制表示为:0000 0000 1100 1111 1001 0110 0000 0000;
其位14、13位为DPL位,值为“00”。

描述符属性字段:0x0040920B
其二进制表示为:0000 0000 0100 0000 1001 0010 0000 1011;
其位14、13位为DPL位,值为“00

上面这个4个段的特权级都是最高级别,0特权级。

特权级保护机制只在保护模式下才能启用,而进入保护模式的方法是设置CR0寄存器的PE位。而且,处理器建议,在进入保护模式后,执行的第一条指令应当是跳转或者过程调用指令,以清空流水线和乱序执行的结果,并串行化处理器,就像下面的指令:

jmp dword 0x0010:flush

转移到的目标代码是刚刚定义的,描述符特权级DPL为0。要将控制转移到这样的代码段,当前特权级必须为0。不过,这并不是问题。进入保护模式之后,处理器自动当前特权级CPL设定为0,以0特权级的身份开始执行保护模式的初始指令

段选择子实际上由三部分组成,分别是描述符的索引号、表指示器TI和RPL字段。在以上指令中,段选择子0x0010的TI位是0,意味着目标代码段的描述符在GDT中。该选择子的索引字段的值是2,指向(GDT中的)2号描述符。

GDT中的1号该程序的1#描述符应该为一个数据段,而不是初始代码段,所以这里的“1号”应该改为“2号”)描述符是保护模式下的初始代码段描述符,特权级DPL为0,而当前特权级CPL也是0,从初始的0特权级转移到另一个0特权级的代码段,这是允许的。转移之后,jmp指令中的选择子0x0010被加载到段寄存器CS,其低两位采用目标代码段描述符DPL的值。也就是说,控制转移之后,当前特权级仍为0。

选择子中的RPL字段,RPL的意思是请求特权级(Requested Privilege Level)。我们知道,要将控制从一个代码段转移到另一个代码段,通常是使用jmp和call指令,并在指令中提供目标代码段的选择子,以及段内偏移量(入口点)。而为了访问内存中的数据,也必须先将段选择子加载到段寄存器DS、ES、FS或者GS中。不管是实施控制转移,还是访问数据,这都可以看成是一个请求,请求者提供一个段选择子,请求访问指定的段。从这个意义上来说,RPL也是请求者的特权级别

在绝大多数时候,请求者都是当前程序自己,因此,CPL=RPL。要判断请求者是谁,最简单的方法就是看谁提供了选择子。以下是两个典型的例子:

代码清单13-1中的第55行:

jmp dword 0x0010:flush

在这里,提供选择子0x0010书中此处为“0x0008”,应该为笔误)的是当前程序自己。

再比如同一代码清单中的第59、60行:

mov eax,0x0008

mov ds,eax

非常清楚的是,这同样是当前程序自己拿着段选择子0x0008来“请求”代入段寄存器DS,以便在随后的指令中访问该段中的数据。

但是,在一些并不多见的情况下,RPL和CPL并不相同。如下图图14-007所示,特权级为3的应用程序希望从硬盘读一个扇区,并传送到自己的数据段,因此,数据段描述符的DPL同样会是3。

14-007.png

由于I/O特权级的限制,应用程序无法自己访问硬盘。好在位于0特权级的操作系统提供了相应的例程,但必须通过调用门才能使用,因为特权级间的控制转移必须通过门。假设,通过调用门使用操作系统例程时,必须传入3个参数,分别是CX寄存器中的数据段选择子、EBX寄存器中的段内偏移,以及EAX中的逻辑扇区号。

高特权级别的程序可以访问低特权级别的数据段,这是没有问题的。因此,操作系统例程会用传入的数据段选择子代入段寄存器,以便代替应用程序访问那个段。比如mov ds,cx,在执行这条指令时,CX寄存器中的段选择子,其RPL字段的值是3,当前特权级CPL已经变成0,因为通过调用门实施控制转移可以改变当前特权级。显然,请求者并非当前程序,而是特权级为3的应用程序,RPL和CPL并不相同。

不过,上面的例子只是表明RPL有可能和CPL并不相同,但并没有说明引入RPL到底有什么必要性,它似乎是多余的,没有它,程序也能正常工作,但实际上情况并不是这样的,它是很有用的。

14-008.png

如上图图14-008所示,应用程序的编写者通过钻研,知道了操作系统数据段的选择子,而且希望用这个选择子访问操作系统的数据段。当然,他不可能在应用程序里访问操作系统数据段,因为那个数据段的DPL为0,而应用程序工作时的当前特权级为3,处理器会很机警地把来访者拒之门外。

但是,他可以借助于调用门。调用门工作在目标代码段的特权级上。一旦处理器的执行流离开应用程序,通过调用门进入操作系统例程时,当前特权级从3变为0。当那个不怀好意的程序将一个指向操作系统数据段的选择子通过CX寄存器作为参数传入调用门时,因为当前特权级已经从变为0,可以从硬盘读出数据,并且允许向操作系统数据段写入扇区数据。

处理器的智商很低,它不可能知道谁是真正的请求者。作为最聪明的灵长类动物,我们当然可以通过分析程序的行为来区分它们,但处理器不能。因此,当指令mov ds,ax或者mov ds,cx执行时,AX或CX寄存器中的选择子可能是操作系统自己提供的,也可能来自于恶意的用户程序,这两种情况要区别对待,但已经超出了处理器的能力和职权范围。

引入请求特权级(RPL)的原因是处理器在遇到一条将选择子传送到段寄存器的指令时,无法区分真正的请求者是谁。但是,引入RPL本身并不能完全解决这个问题,这只是处理器和操作系统之间的一种协议,处理器负责检查请求特权级RPL,判断它是否有权访问,但前提是提供了正确的RPL;内核或者操作系统负责鉴别请求者的身份,并有义务保证RPL的值和它的请求者身份相符,因为这是处理器无能为力的。

操作系统的编写者很清楚段选择子的来源,即,真正的请求者是谁。当它自己读写一个段时,这没有什么好说的;当它提供一个服务例程时,3特权级别的用户程序给出的选择子在哪里,也是由它定的,它也知道。在这种情况下,它所要做的,就是将该选择子的RPL字段设置为请求者的特权级(可以使用arpl指令,将在本章的后面介绍)。剩下的工作就看处理器了。每当处理器执行一个将段选择子传送到段寄存器(DS、ES、FS、GS)的指令,比如mov ds,cx时,会检查以下两个条件是否都能满足:

一、当前特权级CPL高于或者和数据段描述符的DPL相同。即,在数值上,CPL≤数据段描述符的DPL;

二、请求特权级RPL高于或者和数据段描述符的DPL相同。即,在数值上,RPL≤数据段描述符的DPL。

如果以上两个条件不能同时成立,处理器就会阻止这种操作,并引发异常中断。

按照Intel公司的说法,引入RPL的意图是“确保特权代码不会代替应用程序访问一个段,除非应用程序自己拥有访问那个段的权限”。多数读者都只在字面上理解这句话的意思,而没有意识到,这句话只是如实地描述了处理器自己的工作,并没有保证它可以鉴别RPL的有效性。


总结一下基本的特权级检查规则:

首先将控制直接转移到非依从的代码段,要求当前特权级CPL和请求特权级RPL都等于目标代码段描述符的DPL。即,在数值上,

CPL=目标代码段描述符的DPL

RPL=目标代码段描述符的DPL

一个典型的例子就是使用jmp指令进行控制转移,比如:

jmp 0x0012:0x00002000

因为两个代码段的特权级相同,故,转移后当前特权级不变。

其次要将控制直接转移到依从的代码段,要求当前特权级CPL和请求特权级RPL都低于或者和目标代码段描述符的DPL相同。即,在数值上,

CPL≥目标代码段描述符的DPL
RPL≥目标代码段描述符的DPL

控制转移后,当前特权级保持不变。

第三高特权级别的程序可以访问低特权级别的数据段,但低特权级别的程序不能访问高特权级别的数据段。访问数据段之前,肯定要对段寄存器DS、ES、FS和GS进行修改,比如mov fs,ax,在这个时候,要求当前特权级别CPL和请求特权级别RPL都必须高于或者和目标数据段描述符的DPL相同。即,在数值上,

CPL≤目标数据段描述符的DPL;

RPL≤目标数据段描述符的DPL。

最后处理器要求,在任何时候,栈段的特权级别必须和当前特权级CPL相同。因此,随着程序的执行,要对段寄存器SS的内容进行修改时,必须进行特权级检查。比如mov ss,ax,在对段寄存器SS进行修改时,要求当前特权级CPL和请求特权级RPL必须等于目标栈段描述符的DPL。即,在数值上,

CPL=目标栈段描述符的DPL;

RPL=目标栈段描述符的DPL。

0特权级是最高的特权级别,当一个系统的各个部分都位于0特权级时,各种特权级检查总能够获得通过,就像这种检查和检验并不存在一样。所以,处理器的设计者建议,如果不需要使用特权机制的话,可以将所有程序的特权级别都设置为0,就像我们一直所做的那样。


小结:


一、程序员在写程序时,不需要指定特权级别。当程序运行时,操作系统将程序创建为任务局部空间的内容,并赋予较低特权级别,比如3,操作系统对应着任务全局空间的内容。如果有多个任务,则操作系统属于所有任务的公共部分。

二、当任务运行在局部空间时,可以在各个段之间转移控制,并访问私有数据,因为它们具有相同的特权级别,但不允许直接将控制转移到高特权级别的全局空间的段,除非通过调用门,或者目标端是依从的代码段。

三、当通过调用门进入全局空间执行时,操作系统可以在全局空间内的各个段之间转移控制并访问数据,因为它们也具有相同的特权级别。同时,操作系统还可以访问任务局部空间的数据,即低特权级别的数据段。但除了调用门返回外,不允许将控制转移到低特权级别的局部空间内的代码段。

四、任何时候,当前栈的特权级别必须和CPL是一样的。进入不同特权级别的段执行时,要切换栈。



检测点14.1

1、B、A;
2、非依从的:CPL=RPL=目标代码段描述符的DPL
   依从的:CPL≥目标代码段描述符的DPL
           RPL≥目标代码段描述符的DPL

本帖被以下淘专辑推荐:

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-4-25 19:41

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表