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
...
至于门描述符的结构呢,我从网上找了张图:
对应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手册:
中断和异常区别
类型 | 区别 |
---|---|
异常 | 异常是来自于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;
}
发表评论
要发表评论,您必须先登录。