之前介绍了,段描述符是用来填充段寄存器余下位置的,然而段寄存器余下位置有80位,而段描述符仅有64位,那到底是如何填充的呢?这篇就从这个问题开始,逐步探究段描述符的属性。首先,回顾一下段描述符的结构:
P位
P位,位于段描述符高4字节的第15位,是判断描述符是否有效的位置。
P = 1:该描述符有效
P = 0:该描述符无效
G位
在解析G位前,先来回顾下之前的问题,64位的段描述符到底是如何分配给段寄存器余下80位的。 首先回顾一下段寄存器的结构:
1 | struct SegMent { |
段选择子: 由mov/les/lds/lss/lfs/lgs指令直接写入16位。
属性:段描述符中高4字节的第8-23位,刚好16位,作为属性写入段寄存器。
基址:将段描述符高4字节中第24-31位,与第0-7位拼接成作为高16位,低四字节的第16-31位作为低16位,拼接成32位,作为Base,写入到段寄存器里。 (这里没用~号是因为会转义成删除线)
限长:这里就要用到G位了。首先观察段描述符结构,可以发现,在高4字节的第16-19位,与低4字节的第0-15位,都是段限长,将他们拼接起来,也就是20位,那这20位是如何扩展成32位呢?这里就要用到这部分的关键G位了,当G的值为0时,表示以字节为单位,这时,假设Limit的值加起来为FFFFF(20位),则取0x000FFFFF作为Limit写入段寄存器;当G的值为1时,表示以4KB为单位,这样去理解,如果一个段的大小为1KB,也就是1024B或0x400B,这时,实际上能取的范围是0-1023或0-0x3FF,所以此时的Limit应该为3FF。这样当以单位为4KB来计算一个段的Limit时,若Limit的值为1,说明可以取0和1两个值,也真正的大小实际上是2,所以用2*4KB=8192B=0x2000B,但是真是可以取到的值为0-1FFF,所以此时写入段寄存器Limit的值为1FFF。同理,若段描述符Limit的值为FFFFF,真正写入段寄存器的值为FFFFFFFF(32位)。
具体的公式如下:
Code1
2
3
4
5G = 0: = Limit
G = 1: (Limit + 1)*4KB - 1
= Limit*4KB + 4KB - 1
= (Limit<<12) + 0xFFF
= (Limit<<12)|0xFFF
以上也就真正完成了段描述符拆分写入段寄存器的过程,值得注意的是,FS对应的段描述符较为特殊,拆分后的值与段寄存器中的值不符合,后续会再次说明。
S位
位于段描述符高4字节的第12位
- S = 1:描述代码段或数据段
- S = 0: 描述系统段
这里教大家一个小g巧,S位在第12位,P位在第15位,在Windows操作系统中,DPL只有可能是11或者00这两种情况,所以当第12~16位是值为1001(9)或1111(F)时,该段描述符一定是数据段或者代码段。此外系统段大多数情况下值为1000(8),因为代码段或者数据段很少拥有DPL值为0的权限。
Type域
Type域位于段描述符的第8~11位,不同段,每个位所含意义也不同
数据段
1 | S位 == 1 && 第11位 == 0 |
由图,数据段具有三个属性位,E,W,A
A:Accessed,表示该段描述符是否有过被加载到段寄存器。
W:Write,表示该描述符所描述的数据段,是否可写。
E:Expand,扩展方向。值为0时,正常向上扩展;值为1时,向下扩展。
由图,正常情况下,E位为0时,扩展方向是正常向上的,假设已知fs.Base和Limit,右图绿色部分即为有效部分(这样理解,Windows中堆栈都是由高到低,所以向上扩展的情况如右图绿色部分) 当E位为1时,扩展方向是向下的,此时,相同条件下,左图的绿色部分为有效范围。
代码段
1 | S位 == 1 && 第11位 == 1 |
由图,数据段具有三个属性位,E,W,A
A: Accessed,同数据段
R:Read,表示该描述符所描述的代码段,是否可读。
C:一致位:
- C = 1:一致代码段
- C = 0: 非一致代码段
具体如何区分,后面会详细说明
由于代码段和数据段的主要区别是第11位,所以只需要判断8~11位这个16进制数是否大于8就可以确定该段为代码段还是数据段。
系统段
当S=0时,该段描述符为系统描述符,具体分类如下:
DB位
- 对CS段的影响:
- D = 0:采用32位寻址方式
- D = 1:采用16位寻址方式
- 对SS段的影响:
- D = 1:隐式堆栈访问指令(如:PUSH POP CALL) 使用32位堆栈指针寄存器ESP
- D = 0:隐式堆栈访问指令(如:PUSH POP CALL) 使用16位堆栈指针寄存器SP
- 隐式堆栈访问指令:例如push ebp,这句指令没有出现esp却修改了esp的值,就叫做隐式堆栈访问指令
- 向下扩展的数据段:
- D = 1:段上线为4GB
- D = 0:段上线为64KB
- 实际上是限制扩展有效范围,大致如下
由于DB,对于64位的系统来说,理论上影响不大,所以这个位暂不深究,以后若是用到再返回讨论