[windows内核]段描述符和段选择子

上面我们已经提到根据段引申出来的一个概念就是GDT(Global Descriptor Table 全局描述符表)

GDT表与LDT表
通常情况下,在设计程序时,我们认为段寄存器为16-bit(虽然每个段寄存器事实上有一个64-bit长的不可见部分,但对于程序员来说,段寄存器就是16-bit),但是为了描述一个段,还需要【Base Address, Limit, Attr】三方面因素,它们加在一起被放在一个64-bit长的数据结构中,被称为段描述符(上面所说的缺少了80位数据,但是这里只存储64位数据是因为80位中只有部分位是有效的)。也就是说,本应该需要64-bit的段寄存器来存储段描述符,但Inter为了向下兼容而规定段寄存器为16-bit,这就需要程序在运行时能通过这16bit数据来找到64bit的段描述符。

怎么解决呢? 解决方法就是将这个64-bit的段描述符放入一个数组中,而将段寄存器中的值作为下标索引来间接引用(事实上,是将段寄存器中的高13 -bit的内容作为索引)。这个全局的数组就是GDT。 但是,GDT这个数组中,存放的不仅仅是段描述符,还有其他的一些64-bit长的描述符。

GDT是Protected Mode所必须的数据结构,也是唯一的。且正如它的名字(GGDTlobal Descriptor Table)所示,它是全局可见的,对任何一个任务而言都是这样。
而段描述符在GDT表里的结构如下一共是8个字节

[windows内核]段描述符和段选择子_第1张图片
用结构体描述
[windows内核]段描述符和段选择子_第2张图片
GTD也称为段描述符表,在windows没有采用Intel的LDT,只采用GDT
也就是我们计算机在寻址的时候会去查找这个gdt表中的段描述符,来检查其操作是否符合规范,那么GDT存在哪?怎么找?

GDT可以被放在内存的任何位置,那么当程序员通过段寄存器来引用一个段描述符时,CPU必须知道GDT的入口,也就是基地址放在哪里。这就引入了 GDTR 概念。 为了解决这个问题,Intel的设计者们提供了一个名为GDTR的寄存器用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。

LDT:

除了GDT之外,IA-32还允许程序员构建与GDT类似的数据结构,它们被称作LDT(Local Descriptor Table,局部描述符表),但与GDT不同的是,LDT在系统中可以存在 多个,并且从LDT的名字可以得知,LDT不是全局可见的,它们只对引用它们的任务可见,每个任务最多可以拥有一个LDT。另外,每一个LDT自身作为一个段存在,它们的段描述符被放在GDT中。
LDT只是一个可选的数据结构,你完全可以不用它。使用它或许可以带来一些方便性,但同时也带来复杂性,如果你想让你的OS内核保持简洁性,以及可移植性,则最好不要使用它。

同样的,GDT有GDTR,LDT也有LDTR

IA-32为LDT的入口地址也提供了一个寄存器LDTR,因为在任何时刻只能有一个任务在运行,所以LDT寄存器全局也只需要有一个。如果一个任务拥有自身的LDT,那么当它需要引用自身的LDT时,它需要通过lldt指令将其LDT的段描述符装入此寄存器。lldt指令与lgdt指令不同的时,lgdt指令的操作数是一个32-bit的内存地址,这个内存地址处存放的是一个32-bit GDT的入口地址,以及16-bit的GDT Limit。而lldt指令的操作数是一个16-bit的选择子,这个选择子主要内容是:被装入的LDT的段描述符在GDT中的索引值。

在windbg中查看GDT表的位置
在这里插入图片描述
查看gdtr表的长度
在这里插入图片描述
大概查看一下 使用dq命令
[windows内核]段描述符和段选择子_第3张图片

段选择子
前面我们说了,我们通过使用段选择子作为索引去GDT/LDT取得保存该段信息的80位数据。那么问题来了,系统该如何区分数据保存在GDT还是LDT,请求的权限是多少,索引值是选择子的一部分还是全部? 这就需要了解段选择子的结构。
什么是段选择子?
其实我们之前也见过了,就是OD中显示可见的16位值
[windows内核]段描述符和段选择子_第4张图片
其结构如下
[windows内核]段描述符和段选择子_第5张图片
RPL:请求特权级别 (若该段为CS段,则为CPL)
TI:TI=0 查GDT表;TI=1 查LDT表
Index:描述符索引值 处理器将索引值乘以8在加上GDT或者LDT的基地址,就是要加载的段描述符
例如:我们拆分0023=‭00 10 00 11‬
3=11=RPL 特权级别为 3
0=0=TI 查GDT表
4=00 10 0=Index 也就是GDT表中的第四个
[windows内核]段描述符和段选择子_第6张图片

加载段描述符至段寄存器
除了mov指令,我们还可以使用LES,LSS,LDS,LFS,LGS指令修改寄存器
CS不能通过上述指令进行修改,CS为代码段,CS的改变会导致Eip的改变,要想改CS必须保证CS和EIP一致
可以做如下实验

char buff[6]
__asm
{
les ecx,fword ptr ds:[buff]//高两个字节给es,低四个字节给ecx
}

这里要注意一点的是RPL<=DPL
因为这里有两个概念

请求特权级(RPL)
RPL作为段选择子的一部分,是针对段选择子而言的,每个段的选择子都有自己的RPL,其表示用什么权限去访问一个段,也意味着它的改变不会对你所要访问哪个段产生影响(影响你访问哪个段的仅仅由Index决定)。RPL可以实现一些权限的控制,比如当前进程的CPL是0,但我想以一个只读的形式访问一个数据段,就可以用RPL来降低权限(如RPL置3)

段特权级(DPL)
DPL保存在段描述符的段属性中,其规定了访问所在段描述符所需要的特权级别是多少。它与ring环一样,都是数值越大,权限越小。
(注意:在Windows中,DPL只会出现两种情况,要么全为0,要么全为1,即0或3)

如果你3的请求特权级别往0的段特权级别写入,那肯定是不会成功的

这里还有一个问题,段描述符只有64位,但我们需要填充的是80位,还剩下16位怎么填?留给下次解决

你可能感兴趣的:(Windows内核编程)