SmlOS五-GDT和IDT初始化

  • 内容
  • 评论
  • 相关

什么是GDT

GDT (Global Descriptor Table) 全局描述表,

简要来说,在保护模式下,可以通过它来设定各个内存段,并能设定内存段的相关权限等。

首先呢,我们用windbg看下自身本机的GDT长啥样

OS:win7x86

// gdtr寄存器值
kd> r gdtr
gdtr=80b95000
// gdt长度
kd> r gdtl
gdtl=000003ff
// gdt表
kd> dq gdtr
80b95000  00000000`00000000 00cf9b00`0000ffff
80b95010  00cf9300`0000ffff 00cffb00`0000ffff

我们可以看到这里gdtr的地址是0x80b95000

gdt表的第一项是00000000`00000000

后面就是gdt的所有的段描述符

比如00cf9b00`0000ffff,就是段描述符,大小是64位


段描述符是什么意思,我整理了以下的表:

段描述符

高32位

31-24 23 22 21 20 19-16 15 14-13 12 11-8 7-0 位
Base 31-24 G D/B 0 AVL Seg Limit 19:16 P DPL S Type Base 23:16
Base 31-24位 段界限粒度

G=0:粒度字节

G=1:粒度4k
可执行段描述

D=1:32位代码段

D=0:16位代码段
- 软件可利用位,目前被忽略 段界限高位 Present存在位

P=1表示地址转换有效

p=0表示段不存在,对该段访问会引起异常
描述符特权级 S=1系统段

S=0门描述符
S=1:可读,可写,可执行,一致码段

S=0:TSS,中断门,陷阱门
Base 23-16位

低32位

31-16 15-0 位
Base 15:0 Seg Limit 15:0
Base 15-0位 Seg Limit 15-0位

在c语言可以这样使用结构体表示段描述符:

struct SEGMENT_DESCRIPTOR
{
 short limit_low, base_low;
 char base_mid, access_right;
 char limit_high, base_high;
};

这个用于段描述符,释义如下:

limit_low:段限长的0~15bit

base_low: 段基址的0~15bit

base_mid: 段基址的16~23bit

access_right:

 (0~3bit TYPE字段, 4bit S字段 5~6 DPL字段 7bit P字段)

limit_high:

段限长的16~19bit + AVL + D/B + G域

base_high: 段基址的24~31bit

里面的含义在表格中有列出。

对于gdt的初始化,首先找个地址用于存储段表位置。

在这里我们设定gdt的初始位置是0x00270000,idt的初始位置是0x0026f800


GDT设置的限定

  • 然后GDT默认初始化8192个位置,这是gdtr最多支持的数量。

  • 在GDT的初始化中,gdt的第0号位置需要留空,这是intel的设置要求。

在SmlOS中:

  • gdt的1号位置设置为内核的数据段,段限长4G-1,段基址为0,可读可写。
  • gdt的2号位置设置为内核的代码段,段限长512K,段基址0x280000。
  • 其他清空

设置好gdt表后,采用汇编LGDT指令将gdt表地址加载到gdtr寄存器。

因为gdtr是特殊寄存器,48位寄存器,由32位基地址(gdt表首地址),和16位表界限组成。

需要读取6个字节内容,所以汇编写成了这样:

_load_gdtr: ; void load_gdtr(int limit, int addr);
 MOV AX,[ESP+4] ; limit
 MOV [ESP+6],AX
 LGDT [ESP+6]
 RET

段描述符的设置

而对段描述符设置中,需要对段限长的大小进行判断,判断是否大于1M,则对段描述符ar进行设置,在段描述符中有一个G位,当G=1的时候,则段限长便以4kb为单位。

在这里提供段描述符的设置函数

// 设置段描述符,sd 段描述符结构指针,limit 段限长,base 段基址,ar 段描述符属性
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
  if (limit > 0xfffff)
  {
     //设置G_bit = 1
     ar |= 0x8000; 

     // 调整段限长的值即除以4K                  
     limit /= 0x1000;           
  }

  // 将各值填入相应的域中 
  sd->limit_low    = limit & 0xffff;
  sd->base_low     = base & 0xffff;
  sd->base_mid     = (base >> 16) & 0xff;
  sd->access_right = ar & 0xff;
  sd->limit_high   = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);
  sd->base_high    = (base >> 24) & 0xff;
  return;
}

什么是IDT

IDT (Interrupt Descriptor Table)中断描述符表

简要来说就是用来设置发生中断后,定义该调用什么处理函数的表。

还是来看下本机的IDT设置

// idtr首地址
kd> r idtr
idtr=80b95400
kd> dq idtr
80b95400  83e48e00`0008bfc0 83e48e00`0008c150
80b95410  00008500`00580000 83e4ee00`0008c5c0

// 查看所有中断处理函数的地址
kd> !idt -a

Dumping IDT: 80b95400

00: 83e4bfc0 nt!KiTrap00
01: 83e4c150 nt!KiTrap01
02: Task Selector = 0x0058
03: 83e4c5c0 nt!KiTrap03
...

至于门描述符的结构呢,我从网上找了张图:

image

对应C语言结构体:

struct GATE_DESCRIPTOR
{
 short offset_low, selector;
 char dw_count, access_right;
 short offset_high;
};

门描述符与段描述符类似,只是有些字段有些许差别

offset_low :偏移地址的低16bit

selector:段选择子16bit

dw_count: 参数个数, 只是调用门中有效

access_right:

0~3bit:TYPE字段, 4bit:S字段, 5~6bit:DPL字段, 7bit:P字段

offset_high: 偏移地址的高16bit

不同的中断对应的可能是不同的类型:

主要是中断和异常,不同的中断号对应的时中断还是异常可以参考intel手册:

image


中断和异常区别

类型 区别
异常 异常是来自于CPU本身产生的,比如int3,缺页异常
中断 中断来自于CPU外部,由中断源发起的,CPU是被动的,比如鼠标,键盘中断

异常分类

分类 保存的cs和Eip指针 可恢复性
错误(fault) 导致异常的那条指令 可恢复
陷阱(trap) 导致异常的那条指令的下一条指令 可恢复
终止(abort) 不确定 不可以

idt 的设置和gdt类似,但最多支持256个中断。

寄存器的设置和设置gdtr设置类似。

这些中断函数如何编写,将在后面进行介绍。

这里提供下门描述符的设定函数:

// 设置门描述符 ,gd 门描述符结构指针,offset 过程入口偏移,selector 段选择子,ar 门描述符属性
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
{
  gd->offset_low   = offset & 0xffff;
  gd->selector     = selector;
  gd->dw_count     = (ar >> 8) & 0xff;
  gd->access_right = ar & 0xff;
  gd->offset_high  = (offset >> 16) & 0xffff;
  return;
}