-
-
[原创]5.滴水中级班(内核驱动)——段描述符的属性之P位、G位
-
发表于: 2025-9-19 22:53 500
-
上一节课我们知道了段寄存器的值是通过段描述符填充的,就好比MOV DS,AX,那么AX就是一个段选择子,通过这个段选择子,可以在GDT表中找到对应的段描述符,但是有一个疑问就是一个段描述符只有八个字节也就是64位,但是我们需要80位,一个段寄存器是96位的,段选择子的16位我们已经知道了,那么剩下的80位是怎么来的呢?也就是说,我们如何用64位的值去填充一个80位的段寄存器呢,如果想要了解这些,就必须要了解段描述符的相关属性。

P位是高四字节的第15位,G位是高四字节的第23位。
先介绍一下P位,如下图所示。

当我们通过指令将一个段描述符加载到段寄存器的时候,CPU第一件事就是检查P位看是否是有效的,如果P位等于0,那么后续的检查就不做了,只有当P位为1的时候才会做后续其他的检查。这就是P位,一个说明当前段描述符是有效还是无效的位。
在讲G位之前,我们先看一下下面的结构体。这里根据Gemini来汇总了一下段选择子、段描述符、段寄存器这三个之间的关系:段选择子 是用来 找到段描述符 的钥匙。 段描述符 是对一个内存段的 完整定义。 段寄存器持有 段选择子,并 缓存 了由段描述符提供的关键信息,以便 快速、安全地访问 对应的内存段。

稍后会解释着96位的内容,先把上面的图和下面的图看完。

先补充一下,段寄存器就是CS、DS、SS、ES、FS、GS等。
| 角色 | 用途 | 位宽 | 内容 |
|---|---|---|---|
| 段寄存器 | 保存段选择子,指向段描述符,用于逻辑地址到线性地址的转换 | 16位可见 + 80位隐藏(共96位) | 可见部分:段选择子;隐藏部分:段基址+段限长+段属性(由CPU自动填充) |
| GDTR | 保存全局描述符表(GDT)在内存中的起始地址和表长度 | 48位(32位基地址+16位限长) | 无选择子、无段基址,仅用于定位GDT表位置 |
这个结构体是段寄存器的结构,段寄存器一共有96位,第一个Selector是16位,这个就是段选择子。Atrribute的16位对应的是段描述符中高四字节中第8位开始、第23位结束的这16个字节的内容。也就是从Type位到G位。
这里海哥提了一下为什么intel把这些东西弄得这么碎,因为cpu是一步一步发展过来的,为了考虑向下兼容,所以就不能破坏以前的结构,所以在以前结构的基础上拓展过来的,这也就是为什么我们看段描述符总是分成一块一块的。
接着再看看Base这个32位,这个Base是由三部分组成,第一部分也即最高位是高四字节的24到31位,第二部分是高四字节的第0位到7位,第三部分是低四字节的第16位到31位。这三部分构成了一个32位的地址,也就是我们说的这个Base。
最后是Limit,Limit是界限,限制整个段长是多少,Limit由两部分组成,第一部分是高四字节的第16到19位;第二部分是第四字节的第0到15位。这两部分加起来是20位,20位如果转成十六进制,那么最大值是FFFFF(1111 1111 1111 1111 1111)。那么这个时候就存在问题了,Limit正常是32位的,对应的是8个十六进制,那么这个就没法填充了,所以如果我们想要正确填充的话,就必须要看G位。
如果G位为0,那就意味着Limit的单位是字节,也就是意味着补三个0就可以了即000FFFFF,换而言之,当G位为0的时候,Limit最大界限就是FFFFF;当G位为1的时候,Limit的单位是4KB而不是原来的字节了,4KB用10进制表示的话就是4096,4096代表的是多少个,而由于地址从0开始计算所以我们要算的是“上限”,那么这里还需要减1,那么就是4095,接着转换为16进制刚好是3个F就是FFF。那么我们可以这么理解,当G位为1的时候,如果你的Limit界限是1的话,那么就是相当于1后面跟了三个F也就是1FFF,如果你的界限也就是前面的全部填满的话,那么就是五个F后面再跟三个F也就是最大值8个F即FFFFF FFF,那么这样我们就弄清楚了这个64位是怎么转成80位的,也就是Limit这个是20位还是34位的问题。如果G位为0的话,那么就在前面添三个0,如果G位为1的话,那么Limit的上限就是在后面添三个F也就是最多能达到八个F。
补充一下:
G位在x86段描述符中被称为“Granularity bit”,直译为粒度位,作用是控制段限长(Segment Limit)的解析单位。
这种设计让段描述符能更灵活地描述不同大小的段,而“Granularity bit”就是这个功能的控制位。
FS除外,大家以后做练习的时候不要用FS来做练习。

通过上图我们可以看到两个点:1.FS除外。2.所有的其他段寄存器(不包含LDTR和TR),其他的都是FFFFF FFF,也就意味着G位都是1。

我们来做一下这个练习:
段选择子 0x1B、0x23 对应段描述符分析及段寄存器结构填写
段寄存器结构分析
x86架构下,每个段寄存器(如CS、DS、ES等)实际是一个96位的结构体,分为四个部分:
Selector(段选择子,16位可见):指向GDT/LDT中的段描述符,记录索引号、TI(GDT/LDT表)、RPL(请求特权级)。
Attribute(属性,16位隐藏):记录段类型、属性等信息,从段描述符提取。
Base(基址,32位隐藏):段的起始线性地址,由段描述符拼接而成。
Limit(限长,32位隐藏):段的长度(单位为字节或4KB),受G位(粒度位)影响。
段选择子拆解与查找方法
段选择子结构:高13位(位3~15)为索引号,位2为TI(表指示,0=GDT,1=LDT),低位2位(0~1)为RPL。
查找过程:根据索引号,在GDT(TI=0)/LDT(TI=1)表中找到对应8字节(64位)段描述符,由CPU自动填充到段寄存器不可见部分。
具体案例分析
假设GDT在内存中的内容如下:
| 地址 | 段描述符高32位 | 段描述符低32位 |
|---|---|---|
| 8003f000 | 00000000 | 00000000 |
| 8003f008 | 00cf9b00 | 0000ffff |
| 8003f010 | 00cf9300 | 0000ffff |
| 8003f018 | 00cffb00 | 0000ffff |
| 8003f020 | 00cff300 | 0000ffff |
| 8003f028 | 80008b04 | 200020ab |
| 8003f030 | ffc093df | f0000001 |
| 8003f038 | 0040f300 | 00000fff |
1. 段选择子 0x1B
拆解:0x1B = 0000 0000 0001 1011(二进制)
索引号:0000 0000 0001 1 = 3(十进制)
TI:0(查找GDT表)
RPL:11b = 3
填写到段寄存器结构体:
| 分段 | 值(16进制) | 说明 |
|---|---|---|
| Selector | 0x001B | CS 的段选择子 |
| Attribute | 0xCFB | G=1 D/B=1 P=1 DPL=3 S=1 TYPE=1011(32位非一致代码段) |
| Base | 0x00000000 | 段基址,全0 |
| Limit | 0xFFFFFFFF | G=1,LIMIT=0xFFFFF,扩展为32位 0xFFFFFFFF |
2. 段选择子 0x23
拆解:0x23 = 0000 0000 0010 0011(二进制)
索引号:0000 0000 0010 0 = 4(十进制)
TI:0(查找GDT表)
RPL:11b = 3
填写到段寄存器结构体:
| 分段 | 值(16进制) | 说明 |
|---|---|---|
| Selector | 0x23 | ES/DS/SS 的段选择子 |
| Attribute | 0xCF3 | G=1 D/B=1 P=1 DPL=3 S=1 TYPE=0011(32位可读写数据段) |
| Base | 0x00000000 | 段基址,全0 |
| Limit | 0xFFFFFFFF | G=1,LIMIT=0xFFFFF,扩展为32位 0xFFFFFFFF |
详细解析与注意事项
段描述符填充到段寄存器
Selector(16位):直接来自段选择子。
Attribute(16位):从段描述符高32位的位8~23提取(即高四字节的低16位与高8位拼接),组合为段属性。
Base(32位):由段描述符高32位的位24~31(Base31-24)、位8~7(Base23-16)、低32位的位16~31(Base15-0)三部分拼接而成。
Limit(32位):由段描述符高32位的位16~19(Limit19-16)、低32位的位0~15(Limit15-0)拼接为20位LIMIT。G位=1时,20位LIMIT扩展为32位时低12位补1(即乘以0x1000,最大4GB,即0xFFFFFFFF);G=0时,20位LIMIT扩展为32位时高12位补0(即最大1MB,0x000FFFFF)。
FS段描述符的特殊性
FS对应的段描述符(如0x003B)查分后的值与段寄存器中的值通常不符合。这是因为在Windows系统中,FS段常用于存放TEB(线程环境块)等线程特定信息,其基址和长度由操作系统动态设置,通常并不直接对应GDT表项描述的内容(如Base可能指向TEB,Limit可能更小),这是Windows线程模型、用户态和内核态切换时的特殊处理,涉及到更复杂的加载逻辑,后续操作系统(线程)章节会详细讲解。
补充:段寄存器结构体(C语言示例)
cstruct Segment_Register { WORD Selector; // 段选择子,16位 WORD Attribute; // 段属性,16位 DWORD Base; // 段基址,32位 DWORD Limit; // 段限长,32位};总结表
| 段选择子 | GDT索引 | 段描述符高32位 | 段描述符低32位 | Selector | Attribute | Base | Limit | 备注 |
|---|---|---|---|---|---|---|---|---|
| 0x1B | 3 | 00cffb00 | 0000ffff | 0x001B | 0xCFB | 0x00000000 | 0xFFFFFFFF | 32位代码段(CS) |
| 0x23 | 4 | 00cff300 | 0000ffff | 0x23 | 0xCF3 | 0x00000000 | 0xFFFFFFFF | 32位数据段(DS/ES/SS) |
关键要点:
段寄存器由Selector索引段描述符,由CPU自动填充隐藏属性、基址和限长。
G位决定Limit的计算方式,G=1时LIMIT单位是4KB,最大4GB(0xFFFFFFFF)。
FS的特殊性(异常值)是操作系统线程模型导致,后续会详细讲解。
如果老师或课本提供了不同的GDT表内容,请根据实际内容计算。
如需FS(如0x003B)的具体填充分析,建议等学到操作系统线程相关章节再展开。