保护模式下的RPL(一)

《x86汇编语言:从实模式到保护模式》读书笔记
源码面前,了无秘密
—-jjhou

<这是链接—保护模式下的CPL,RPL,DPL(二)(两篇文章一起看最好,由于先写的二,后写的一,所以有一些重复的,懒得合并了(●’◡’●)—->

CPL,RPL,DPL我觉得最难理解的就是RPL,看了书上的代码后
有种豁然开朗的感觉,子曰:独乐乐不如众乐乐,相信看完这篇
文章,各位看官应该能理解RPL了。
名词解释:
PL (Privilege Level) :特权级别
CPL(Current Privilege Level) :当前执行的代码段的特权级别,其
值就是CS段寄存器中的选择子的PL字段
RPL(Request Privilege Level) :选择子中的PL字段,它代表的是
调用者的特权级别
DPL(Description Privilege Level):段描述符中的DPL字段
RPL:请求特权级,最重要的是要明白RPL代表的是调用者的特权级(先记住,先往下看)

一》:要明白RPL,就有必要讲一下什么是用户程序头部:
操作系统要从磁盘加载一个用户程序到内存中就需要用到一个叫做加载器的程序。一般来说,加载器和用户程序是在不同的时间、不同的地方,由不同的人或公司开发的。这就意味着,它们彼此并不了解对方的结构和功能。事实上,也不需要了解。它们彼此看对方都是一个黑盒子,并不了解对方是怎么编写的,是做什么的。但是,也不能完全是黑的,加载器必须了解一些必要的信息,虽然不是很多,但足以知道如何加载。这就涉及加载器的编写者,以及用户程序的编写者,他们之间是怎么协商的。他们之间必须有一个协议,或者说协定,比如说,在用户程序内部的某个固定位置,包含一些基本的结构信息,每个用户程序都必须把自己的情况放在这里,而加载器也固定在这个位置读取。经验表明,把这个约定的地点放在用户程序的开头,对双方,特别是对加载器来说比较方便,这就是用户程序头部。

二》要明白RPL还有必要讲一下什么叫重定位
因为你编写的程序是分段的,而每次你的程序加载到内存中的地址是不
确定的,故为了能让程序正确运行,故在加载程序的时候必须修正段的位置,因为用户程序是通过段来访问内存的,所以加载器就需要并且一定要告知/用户程序/段的位置。还有啊,用户程序要调用系统调用,而系统调用的位置可能随着OS的升级而改变位置,那么用户又是如何知道系统调用在哪个地方呢?怎么办呢?这些问题的解决都取决于两个字:那就是变量,是通过变量,是通过变量。什么意思呢?先来看一个用户程序头部(注:书上写的一个能加载用户程序的操作系统,此用户程序便是与该OS配套的)
来人,上图!
图一:
这里写图片描述

看第13行用一个双字变量来存储用户程序堆栈段的选择子
看第18行用一个双字变量来存储用户程序代码段1的选择子
看第21行用一个双字变量来存储用户程序代码段2的选择子
看第24行用一个双字变量来存储用户程序数据段的选择子
看第32行用一个双字变量来存储用户程序调用的系统调用
PrintString的选择子
看第35行用一个双字变量来存储用户程序调用的系统调用
TerminalProgtam的选择子
看第38行用一个双字变量来存储用户程序调用的系统调用ReadDiskDate
的选择子
再看一张图:
图二:
这里写图片描述
再仔细看下上面的图。你有没有发现什么?对,你没看错:所有的段,
所有的系统调用都是通过变量来访问的也就是说用户程序用到的所有的
段的段选择子和系统调用的选择子都是放在变量中的。什么意思呢?
意思就是user说:由于我不知道运行时我的各个段和你的函数放在哪
里,所以我就通过变量来引用我的数据段,代码段,堆栈段,和调用你
提供的系统调用,你(指加载器)就应该把重定位后我的各个段的段地址还有你的函数的地址(此处已经进入了保护模式,所以应该是段选择子)回填到我提供的变量里。

罗里巴嗦说了一大堆,可是这和我们的RPL又有什么关系呢
(●’◡’●)往下看:

再次强调:RPL代表的是用户的特权级别。
看下图:
这里写图片描述

保护模式下:用户程序通过调用门调用系统调用,而系统调用(如PrintString)的选择子就存放在我们上面讲过的双字变量(PringString)中.调用调用门需要检查CPL,选择子的RPL和调用门的DPL,而我们前面说过RPL代表的是用户程序的特权级,即选择子中的PL字段就是我们的RPL,那么问题就来了:我们怎么就知道选择子中的RPL字段代表的就是我们当前程序的特权级呢?换句话说就是我们是如何将用户程序的特权级别放到选择子中的RPL字段。其实将user的特权级和RPL联系起来这个过程发生在加载器对用户程序进行重定位的过程中
我们来看一下代码

;代码一:
;功能:加载并重定位用户程序
;参数:push逻辑扇区号
;      push任务控制块基地址
;返回:无
load_relocate_program:........
;下面为用户程序安装段描述符(亦叫重定位)mov edi,[es:esi+0x06]mov eax,edimov ebx,[edi+0x04];段长度dec ebx;段界限mov ecx,0x0040f200    ;核心指令0:;字节粒度的数据段描述符,特权级3由os为user创建段描述符.   ;并且由os决定用户程序特权级call sys_routine_seg_sel:make_seg_descriptormov ebx,esi;TCB的基地址call fill_descriptor_in_ldt   ;核心指令1;返回的选择子放在cx中or cx,0x0003 ;核心指令2:设置选择子的RPL为3(用户程序)

我们先留意一下第12行和最后两条指令
mov ecx,0x0040f200
call fill_descriptor_in_ldt
or cx,0x0003;
我们再来看一下函数fill_descriptor_in_ldt:

代码二:
;功能:在LDT内安装一个新的描述符
;输入:EDX:EAX=描述符
;      EBX=TCB基地址 
;返回:CX=描述符的选择子
fill_descriptor_in_ldt:........or  cx,0x0004 ;此行VeryImportant:选择子返回之前将RPL设为00;?????????为什么要将RPL设为00,不是这是用户程序的段描述符吗........ret;
代码三:
;功能:加载并重定位用户程序
;参数:push逻辑扇区号
;      push任务控制块基地址
;返回:无
load_relocate_program:................call fill_descriptor_in_ldt;核心指令1or cx,0x0003    ;核心指令2:设置选择子的RPL为3(用户程序)mov [edi+0x04],cx    ;核心指令3:;将修改后的选择子写回到用户程序头部

我们整理一下:上述代码对用户程序各个段中的一个段进行重定位,代码一中将用户程序的特权级设为3,然后将特权级3当作参数传给函数fill_descriptor_in_ldt,然后fill函数(代码二)就在LDT中创建一个DPL字段为3的段描述符,这个段描述符就是用户程序的段的描述符,同时此DPL代表的就是用户程序的特权级,然后fill函数在cx中返回刚创建的那个描述符的选择子(区分段描述符中的DPL和返回的段描述符的选择子中的RPL字段),在cx中返回的选择子的PL字段通通都是00,而我们说过选择子中的RPL字段代表的是用户程序的特权级,显然00不是用户程序的特权级,所以在fill函数返回后,我们需要将选择子中的RPL字段改为用户程序的特权级(3)(代码三),然后再把这个选择子回填到用户程序头部对应的变量中(核心指令3)。然后重复n次,直到将用户程序声明的所有段和用户程序用到的系统调用的选择子回填到用户程序,至此我们就用/用户程序的特权级/替换了/所有用到的选择子的RPL字段/也就是说所有选择子中的RPL字段代表的是我们的用户程序的特权级别,也就是说RPL代表的是用户程序的特权级。
来,让我们回到图二。我们看一下调用系统调用pringstring的那条语句:
call far [fs:PrintString]
因为是调用门,所以偏移地址被忽略,只用了那个存放在变量PrintString开始的一个字中的调用门选择子,而前面说了PrintString选择子中的RPL字段已经替换成了我们用户程序的特权级,这样调用调用门时便知道是特权级为3的用户程序在调用了。
再举个例子:从代码段1转到代码段2,因为代码段2的选择子中的RPL字段是代码段1的特权级(3),故在转移的过程中 CPL=代码段2的DPL,RPL=代码段2的DPL,故可以成功实现转移。
至此,你是否已经将RPL和用户程序的特权级别联系起来了。
(●’◡’●)

Published by

风君子

独自遨游何稽首 揭天掀地慰生平