全局描述符表(Global Descriptor Table, GDT)是x86架构计算机系统中的一个重要的数据结构,它在保护模式下发挥作用,用于描述内存段的特性和属性。GDT通过提供一种机制来管理内存访问权限和段的大小,增强了操作系统的内存管理和程序的安全性。
GDT的基本结构
GDT是一个存放在内存中的数据结构,由一系列的描述符(Descriptor)组成,每个描述符占8个字节(64位),用来描述一个内存段的起始地址、大小以及访问权限等信息。GDT至少包含三个基本段描述符:代码段描述符、数据段描述符和一个空描述符,但根据需要可以包含更多描述符。
每个段描述符主要包含以下关键信息:
- 基地址(Base Address):指明段的起始物理地址。
- 限长(Limit):定义了段的最大长度。
- 访问权限(Access Rights):控制段的访问权限,包括是否可读、可写、可执行以及不同级别的特权级(Ring 0到Ring 3)等。
GDT的作用
- 内存保护:通过设置访问权限,GDT可以防止程序访问不属于它的内存区域或以错误的方式(如尝试写入只读段)访问内存,从而提高了系统的稳定性与安全性。
- 特权级别管理:操作系统可以根据不同的任务或程序赋予不同的特权级别,限制低级别程序对某些敏感资源的访问,比如硬件直接访问或修改内核数据。
- 段管理:允许程序使用更大的地址空间或多个独立的地址空间,每个段可以有不同的属性,便于管理和优化内存使用。
实现机制
在x86架构中,CPU通过GDTR(Global Descriptor Table Register)寄存器来访问GDT。GDTR寄存器存储了GDT的基地址和限长。当处理器需要访问某个内存段时,会根据当前段选择子(Segment Selector,一个16位的值,在段寄存器中存放)从GDT中查找对应的段描述符,然后根据描述符的内容设置段的访问权限和地址翻译参数。
设置和使用GDT
在操作系统初始化时,会构建GDT并加载到内存中,然后通过LGDT指令(Load Global Descriptor Table)将GDT的地址和大小载入GDTR寄存器。之后,通过段选择子和段寄存器的组合,程序可以在保护模式下访问内存。
总的来说,全局描述符表是x86架构保护模式下内存管理和权限控制的关键组件,对于现代操作系统的稳定运行和安全性至关重要。
GDT 与 LDT
GDT是全局描述符表。LDT为局部描述符表,但Windows并没有使用它,故不再介绍,感兴趣请查询Intel白皮书。当我们执行类似MOV DS,AX指令时,CPU会查表,根据AX的值来决定查找GDT还是LDT,并找到对应的段描述符。段描述符将会在后面部分进行介绍。
GDT表存在于内存之中。CPU要想找到它,就必须知道它的位置。于是乎CPU有一个寄存器。它被称之为GDTR,存储了GDT表的位置和大小,是一个48位的寄存器,用C语言表示如下:
struct GDTR
{
DWORD GDTBase; //GDT表的地址
SHORT limit; //GDT表的大小
}
我们如何通过WinDbg来获取GDT的地址、大小和GDT表里的成员呢?首先提一句,段描述符大小为64位。有关其操作见下图:
r指令为查看寄存器
r gdtr指令表示读取GDT表的地址;
r gdtl指令表示读取GDT表的大小;
dq 8003f000 l20指令表示从0x8003f000地址(即为GDT表的地址)读取0x20个64位数据,如果没有l20,默认0x10个。
这些都是以后常用的指令,需要熟练掌握。