windows中 GDTR和GDT关于快速调用的实现1

windows中 GDTR和GDT关于快速调用的实现1

windows中 GDTR和GDT关于快速调用的实现1


一、全局表述表

为进一步理解上面我们讲的两条指令(sysenter,sysexit).,我在解释一个”全局段描述符表(Global Descriptor Table)“即GDT及其段寄存器之间的关系,CPU中的系统寄存器GDTR指向内存中的GDT,这是一个结构数组。数组中的每一个表象都是一个64位的KGDTENTRY数据结构,里面描述了段的起点和大小,保护模式,访问权限等信息。合在一起就是一个64位的段描述块。数组的大小位NUM_GDT,定义为28
GDT的定义

//
// GDT Entry Definition
//
typedef struct _KGDTENTRY
{
   
   
    USHORT LimitLow;
    USHORT BaseLow;
    union
    {
   
   
        struct
        {
   
   
            UCHAR BaseMid;
            UCHAR Flags1;
            UCHAR Flags2;
            UCHAR BaseHi;
        } Bytes;
        struct
        {
   
   
            ULONG BaseMid:8;
            ULONG Type:5;
            ULONG Dpl:2;
            ULONG Pres:1
<think>我们正在讨论全局描述符表(GDT)的实现方式。根据之前的讨论引用资料,我们可以从以下几个方面来详细说明GDT实现方式: 1. **GDT的存储结构**:GDT是一个数组,每个元素是一个8字节的描述符。描述符的结构需要符合x86架构的规范。 2. **描述符的字段定义**:每个描述符包含基地址、段界限、访问权限、特权级等字段。这些字段在描述符中的位置含义需要明确。 3. **GDT的加载**:使用`LGDT`指令将GDT的基地址界限加载到GDTR寄存器中。 4. **具体实现步骤**:包括定义描述符结构、创建GDT数组、加载GDTR以及后续的段寄存器加载。 下面将详细展开这些内容。 ### 1. GDT描述符结构 根据引用[3],一个GDT描述符由64位(8字节)组成,其结构如下: ``` 63 54 53 52 51 50 48 47 46 44 43 40 39 16 15 0 ┌──────────────┬──┬──┬──┬──┬────────┬──┬───┬───┬──┬──────┬────────────────┬────────────────┐ │ Base[31:24] │G │D │0 │A│Limit[19:16]│P │DPL│ S │ Type │ Base[23:16] │ Base[15:0] │ └──────────────┴──┴──┴──┴──┴────────┴──┴───┴───┴──┴──────┴────────────────┴────────────────┘ 63 54 53 52 51 50 48 47 46 45 44 43 40 39 16 15 0 31 0 ┌───────────────────────────────────┬───────────────────────────────────────────────────────┐ │ Base[15:0] │ Limit[15:0] │ ├───────────────────────────────────┼───────────────────────────────────────────────────────┤ │ Base[23:16] │ Flags (G,D,0,A) and Limit[19:16] | Access Byte (P,DPL,S,Type) │ └───────────────────────────────────┴───────────────────────────────────────────────────────┘ ``` #### 具体字段说明: - **基地址(Base)**:32位,表示段的起始物理地址。在描述符中分为三部分:Base[15:0]、Base[23:16]Base[31:24]。 - **段界限(Limit)**:20位,表示段的大小。在描述符中分为两部分:Limit[15:0]Limit[19:16]。段界限的实际大小取决于G位: - $G=0$:段界限单位为字节,因此段大小最多为$1$ MB(因为$20$位最大值为$2^{20}-1$,即$1,048,575$字节)。 - $G=1$:段界限单位为$4$ KB,因此段大小最多为$4$ GB($2^{20} \times 4$ KB = $2^{32}$字节)。 - **G位(Granularity)**:粒度标志,位于描述符的第55位(从0开始计数)。 - **D/B位**:默认操作数大小/默认栈指针大小。对于代码段,称为D位,表示默认的操作数大小($0$表示$16$位,$1$表示$32$位)。对于数据段,称为B位,影响栈指针($0$用SP,$1$用ESP)栈操作的范围($0$最大64KB,$1$最大4GB)。位于第54位。 - **L位**:$64$位代码段标志(在$32$位模式下应置$0$)。位于第53位。 - **AVL位**:保留位,通常置$0$。位于第52位。 - **P位(Present)**:存在标志,$1$表示段在内存中有效。位于第47位。 - **DPL(Descriptor Privilege Level)**:$2$位,表示描述符特权级($0$最高,$3$最低)。位于第45-46位。 - **S位(Descriptor Type)**:$1$表示代码段或数据段,$0$表示系统段。位于第44位。 - **Type字段**:$4$位,用于指定段的类型(代码段、数据段、系统段等)。位于第40-43位。具体含义: - 当$S=1$(代码/数据段)时: - 代码段:Type的$4$位为$1XXX$($X$表示其他属性),其中最低位(第40位)为$1$表示可执行,第41位为$1$表示可读(对于代码段,可读表示允许读取段内数据,如常数),第42位为$C$(一致性),第43位为$A$(已访问)。 - 数据段:Type的$4$位为$0XXX$,第40位为$0$(不可执行),第41位为$1$表示可写,第42位为$E$(扩展方向),第43位为$A$(已访问)。 - 当$S=0$(系统段)时:Type表示系统描述符类型(如TSS、LDT等)。 ### 2. GDT实现步骤 在操作系统中(如Linux 0.11),实现GDT通常包括以下步骤: #### 步骤1:定义描述符结构 在代码中,我们需要定义一个数据结构来表示描述符,或者直接使用数组(每个描述符8字节)。在汇编语言中,可以直接定义字节序列。 #### 步骤2:创建GDT数组 GDT是一个描述符数组,第一个描述符(索引0)必须为空描述符(全0)。后续描述符包括: - 内核代码段 - 内核数据段 - 用户代码段 - 用户数据段 - 其他系统段(如TSS) 以Linux 0.11为例(引用[1]),其GDT包含: 1. 空描述符 2. 内核代码段(DPL=0) 3. 内核数据段(DPL=0) 4. 用户代码段(DPL=3) 5. 用户数据段(DPL=3) 6. TSS描述符(每个任务一个,但Linux 0.11只有一个TSS) #### 步骤3:加载GDTR 使用`LGDT`指令加载GDT的基地址界限(界限 = 表大小 - 1)。GDTR是一个48位的寄存器,低16位是表界限(以字节为单位),高32位是GDT的物理基地址。 在汇编中: ```assembly lgdt [gdt_descriptor] ; gdt_descriptor是一个6字节的内存区域,前2字节是界限,后4字节是基地址 ``` #### 步骤4:加载段寄存器 加载GDTR后,需要重新加载段寄存器(如CS、DS等)以使用新的描述符。对于CS,通常通过一个远跳转(far jump)来实现: ```assembly jmp 0x08:flush ; 0x08是内核代码段的选择子,flush是跳转目标 flush: mov ax, 0x10 ; 加载内核数据段选择子到AX mov ds, ax ; 加载其他段寄存器 ``` ### 3. 具体代码示例(汇编) 下面是一个简化的GDT实现示例(基于x86汇编): ```assembly ; 定义GDT gdt_start: ; 第一个描述符必须是空描述符 dd 0x00000000 dd 0x00000000 ; 内核代码段描述符(选择子0x08) dw 0xFFFF ; 段界限低16位 (0-15) dw 0x0000 ; 基地址低16位 (0-15) db 0x00 ; 基地址中8位 (16-23) db 0b10011010 ; 访问字节:P=1, DPL=00, S=1, Type=1010(代码段,可执行、可读、非一致性) db 0b11001111 ; 标志位:G=1, D=1, L=0, AVL=0, 段界限高4位 (16-19) = 0xF db 0x00 ; 基地址高8位 (24-31) ; 内核数据段描述符(选择子0x10) dw 0xFFFF ; 段界限低16位 dw 0x0000 ; 基地址低16位 db 0x00 ; 基地址中8位 db 0b10010010 ; 访问字节:P=1, DPL=00, S=1, Type=0010(数据段,可读写) db 0b11001111 ; 标志位:G=1, D=1, L=0, AVL=0, 段界限高4位 = 0xF db 0x00 ; 基地址高8位 gdt_end: ; 定义GDTR gdt_descriptor: dw gdt_end - gdt_start - 1 ; 界限(表大小-1) dd gdt_start ; 基地址 ; 加载GDT lgdt [gdt_descriptor] ; 刷新段寄存器 jmp 0x08:flush ; 远跳转,0x08是内核代码段选择子(索引1,TI=0, RPL=0) flush: ; 更新数据段寄存器 mov ax, 0x10 ; 内核数据段选择子(索引2,TI=0, RPL=0) mov ds, ax ; 更新其他段寄存器(ES, FS, GS, SS) ``` ### 4. 现代操作系统的实现变化 现代操作系统(如Linux)虽然仍然使用GDT,但通常采用平坦内存模型(flat memory model)。在这种模型中,代码段数据段的基地址均为$0$,界限为$4$ GB($0xFFFFFFFF$),通过分页机制实现内存管理[^2]。此时,分段的作用被弱化,但GDT仍然是进入保护模式所必需的。 ### 总结 GDT实现主要包括定义描述符结构、构建GDT数组、加载GDTR寄存器以及刷新段寄存器。每个描述符需要按照x86架构规范设置基地址、界限访问属性。操作系统的设计决定了描述符的具体参数,如平坦内存模型中代码段数据段的基址为$0$,界限为$4$ GB[^1][^2][^3]。 §§相关问题§§ 1. 在x86架构中,如何创建加载局部描述符表(LDT)? 2. 保护模式下的内存地址转换过程是怎样的? 3. 现代操作系统(如Linux)如何使用平坦内存模型简化分段机制? 4. 任务状态段(TSS)在GDT中的作用是什么? </think>### 全局描述符表(GDT)的实现方式详解 #### 一、GDT的基础结构 GDT 是一个由**8字节描述符**组成的数组,存储在内存中。每个描述符对应一个内存段,其结构通过位字段精确控制: ```mermaid graph LR A[描述符结构] --> B[基地址 32位] A --> C[段界限 20位] A --> D[访问控制字节] D --> E[P/DPL/S/Type] A --> F[标志位] F --> G[G/D/B/L/AVL] ``` 数学表示(假设基地址为$base$,界限为$limit$): $$描述符 = (base_{31:24} \ll 56) \ |\ (flags \ll 52) \ |\ (limit_{19:16} \ll 48) \ |\ (access \ll 40) \ |\ (base_{23:16} \ll 16) \ |\ limit_{15:0}$$ #### 二、关键实现步骤 1. **描述符定义**(以Linux内核代码为例): ```c struct gdt_entry { uint16_t limit_low; // 段界限低16位 uint16_t base_low; // 基地址低16位 uint8_t base_mid; // 基地址中8位 uint8_t access; // 访问控制字节 uint8_t granularity; // 界限高4位+标志位 uint8_t base_high; // 基地址高8位 } __attribute__((packed)); ``` 2. **典型描述符初始化**(内核代码段): ```c void set_gdt_entry(int idx, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran) { gdt[idx].limit_low = limit & 0xFFFF; gdt[idx].base_low = base & 0xFFFF; gdt[idx].base_mid = (base >> 16) & 0xFF; gdt[idx].access = access; gdt[idx].granularity = (limit >> 16) & 0x0F; gdt[idx].granularity |= gran & 0xF0; // 包含G/D/B标志 gdt[idx].base_high = (base >> 24) & 0xFF; } // 设置内核代码段:基址0x00000000,界限0xFFFFFFFF set_gdt_entry(1, 0, 0xFFFFF, 0x9A, 0xCF); ``` 3. **访问控制字节解析**: - `access = 0x9A`(二进制 `1001 1010`)表示: - `P=1`(段在内存中) - `DPL=00`(最高特权级) - `S=1`(代码/数据段) - `Type=1010`(代码段/可读/非依从) 4. **GDTR寄存器加载**(汇编实现): ```asm gdt_descriptor: .word gdt_end - gdt - 1 ; 界限值 = 表大小-1 .long gdt ; GDT物理地址 load_gdt: lgdt [gdt_descriptor] ; 加载GDTR jmp 0x08:reload_cs ; 远跳转刷新CS reload_cs: mov ax, 0x10 ; 加载数据段选择子 mov ds, ax ; ... 其他段寄存器 ``` #### 三、现代系统的平坦模型实现 当前Linux采用**平坦内存模型**,核心配置: - 代码段:基址$0x00000000$,界限$0xFFFFFFFF$(4GB) - 数据段:基址$0x00000000$,界限$0xFFFFFFFF$ - 关键参数: - `Type=2`(数据段) / `Type=10`(代码段) - `G=1`(界限以4KB为单位) - `D/B=1`(32位模式) ```mermaid graph TD A[GDT] --> B[空描述符 index0] A --> C[内核代码段 index1] A --> D[内核数据段 index2] A --> E[用户代码段 index3] A --> F[用户数据段 index4] A --> G[TSS描述符 index5] ``` #### 四、技术细节分析 1. **段界限计算**: - 当$G=0$时:段大小 = $limit + 1$(字节) - 当$G=1$时:段大小 = $(limit + 1) \times 4096$ - 例如 $limit=0xFFFFF$,$G=1$ → $4\text{GB}$空间 2. **权限检查机制**: - CPU比较段选择子的`RPL`描述符的`DPL` - 用户进程(RPL=3)访问内核段(DPL=0)触发异常 3. **特殊描述符实现**: - **TSS描述符**:`S=0`,`Type=0x9` 或 `0xB` - **调用门**:`Type=0xC`,包含目标代码段选择子 #### 五、实现注意事项 1. **对齐要求**: - GDT首地址需8字节对齐(x86架构要求) - 使用`__attribute__((aligned(8)))`确保对齐 2. **实时更新**: - 修改GDT后需立即执行`lgdt` - 远跳转强制刷新段缓存(尤其CS寄存器) 3. **多处理器扩展**: 现代系统为每个CPU核心维护独立GDT: ```c struct gdt_table { struct gdt_entry gdt[GDT_ENTRIES]; struct desc_ptr gdt_ptr; // GDTR值 } __per_cpu[NR_CPUS]; ``` > ⚠️ **关键点**:GDT是保护模式的基础设施,即使现代系统使用分页机制,GDT仍是启动阶段必需品[^1][^2][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值