段描述符和段选择子
我想 C 语言你应该是学了很久了,那么我来定义一个数组。
// 一个 QWORD 是一个 8 字节的整数
QWORD gdt[1024];
很明显,这是一个能容纳 1024 个元素的数组。现在我来定义:
gdt 数组中的每个元素都是一个段描述符
数组的索引号是段选择子
这个 gdt 数组被称为 gdt 表
只不过……,只不过这个段选择子,可能不会直接就表示成你想要的索引号,0就是0,5就是5,它稍微有些区别。
另外,段描述符,就是一个 8 字节的整数,可是这个整数,包含的信息量有点大。后面我们要做的,就是破译这个整数。
既然如此,后文自然是重点解析段选择子和段描述符,打通通往操作系统之路。
段描述符与段选择子的结构
段选择子结构
段选择子就是一个数字,一共有16位,结构如下:
| 1 | 0 | 字节
|7654321076543 2 10| 比特
|-------------|-|--| 占位
| INDEX |T|R | 含义
| |I|P |
| | |L |
INDEX:在GDT数组或LDT数组的索引号
TI:Table Indicator,这个值为0表示查找GDT,1则查找LDT
RPL:请求特权级。以什么样的权限去访问段。
段描述符结构
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 字节
|76543210|7 6 5 4 3210 |7 65 4 3210|76543210|76543210|76543210|76543210|76543210| 比特
|--------|-|-|-|-|---- |-|--|-|----|--------|--------|--------|--------|--------| 占位
| BASE |G|D|0|A|LIMIT|P|D |S|TYPE|<------- BASE 23-0 ------>|<-- LIMIT 15-0 ->| 含义
| 31-24 | |/| |V|19-16| |P |
|B| |L| | |L |
BASE: 段基址,由上图中的两部分(BASE 31-24 和 BSE23-0)组成
G:LIMIT的单位,该位 0 表示单位是字节,1表示单位是 4KB
D/B: 该位为 0 表示这是一个 16 位的段,1 表示这是一个 32 位段
AVL: 该位是用户位,可以被用户自由使用
LIMIT: 段的界限,单位由 G 位决定。数值上(经过单位换算后的值)等于段的长度(字节)- 1。
P: 段存在位,该位为 0 表示该段不存在,为 1 表示存在。
DPL:段权限
S: 该位为 1 表示这是一个数据段或者代码段。为 0 表示这是一个系统段(比如调用门,中断门等)
TYPE: 根据 S 位的结果,再次对段类型进行细分。
段描述符安装在 GDT 或者 LDT 数组中,可以在 WinDbg 中使用命令 r gdtr来查看 GDT 基址在哪里。
有关段描述符属性的具体细节,后文陆续给出。
如何把段描述符填充到段选择子
段寄存器一共有 96 位,其中 16 可见部分来源于段选择子的索引部分。剩下 80 位来源于 GDT 表。
下面来分析一下,如何把 0x1B 、0x23 这两个选择子对应的描述符填充到段寄存器。
做这个练习的时候,先不要问这些字段是什么含义,只要把这些字段的值查出来就行了。
原始数据:
|--地址--|-------------16进制值---------------|
8003f000 00000000`00000000 00cf9b00`0000ffff
8003f010 00cf9300`0000ffff 00cffb00`0000ffff
8003f020 00cff300`0000ffff 80008b04`200020ab
8003f030 ffc093df`f0000001 0040f300`00000fff
0x1B
0x1B = 0000 0000 0001 1011b
索引号:0000 0000 0001 1= 3 (查找gdt[3])
RPL: 11b = 3
TI: 0 (查找 GDT 表)
查找到的 GDT 描述符为:gdt[3] = 00cffb00`0000ffff
段寄存器结构:
selector = 0x001B
attribute = 0xcffb (G = 1 DB = 1 P = 1 DPL = 3 S = 1 TYPE = 1011(非一致代码段,可读已访问过))
base = 0x00000000
limit = 0xffffffff
10
0x23
0x23 = 0000 0000 0010 0011b
索引号:0000 0000 0010 0 = 4
TI: 0 (查找 GDT 表)
RPL: 11b = 3
查找到的 GDT 描述符为:gdt[4] = 00cff300`0000ffff
段寄存器结构:
selector = 0x23
attribute = 0xcff3 (G = 1 DB = 1 P = 1 DPL = 3 S = 1 TYPE = 0011(可读可写向上扩展的数据段))
base = 0x00000000
limit = 0xffffffff
这里最麻烦的应该是分析 limit 了。
如何分析 limit
limit 的含义是这个段的大小。实际上这么说的点不准确。limit 应该描述为,段大小再减去1字节。(这里的 limit 是换算后的 limit)。后面我用大写的 LIMIT 表示段描述符中的 20bit LIMIT。
如果粒度 G=0,LIMIT= 0x3ff,这意味着该段的大小是 0x3ff+1=0x400 字节。如果 G=1,那意味着该段的大小是(0x3ff+1)*4KB=0x400000字节,所以换算后的 limit = 0x400000-1=0x003fffff.
再举个例子。LIMIT=0xfffff, G=1,则该段的大小是 (0xfffff+1)*4KB=0x100000*0x1000=0x100000000字节,所以换算后的 limit=0x100000000-1=0xffffffff
limit 简算法
如果 G = 0,把段描述符中的 20 bit LIMIT取出来,比如 0x003ff,然后在前面补 0 至32bit,即 limit = 0x000003ff.
如果 G=1,把段描述符中的 20 bit LIMIT取出来,比如 0x003ff,然后在后面补 f 至 32bit, 即 LIMIT = 0x003fffff
总结
本篇主要讲解了段寄存器 中的数据来源。上篇实验中,给出了几个实验,当时我们只是把另一个段寄存器中的数据读入到寄存器 ax(16bit),然后把 ax 代入到了 ds,可是 ax 明明只有 16 位啊,而 ds 有 96 位。
CPU必然在背后帮我们做了一些事情,它从 GDT 表中取出对应的段描述符,经过分析后自动的填写的了段寄存器中。这个过程,是需要大家深刻理解和掌握的
8086寄存器中,只有bx,bp,si,di这四个寄存器可以用在[……]中表示偏移地址。
---------------------
作者:--Allen--
来源:CSDN
原文:https://blog.csdn.net/q1007729991/article/details/52538080