IDT,Interrupt Descriptor Table,中断描述符表是CPU用来处理中断和程序异常的。
CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。那么什么是中断向量表呢?中断向量表就是中断向量的列表。那么什么又是中断向量呢?所谓中断向量,就是中断处理程序的入口地址。展开来讲,中断向量表,就是中断处理程序入口地址的列表。
中断向量在内存中保存,其中存放着256个中断源所对应的中断处理程序的入口。
CPU只要中断了中断类型码,就可以将中断类型码作为中断向量表的表项号,定位相应的表项,从而得到中断处理程序的入口地址。
IDT是一个有256个入口的线形表,每个IDT的入口是个8字节的描述符,所以整个IDT表的大小为256*8=2048 bytes,每个中断向量关联了一个中断处理过程。所谓的中断向量就是把每个中断或者异常用一个0-255的数字识别。Intel称这个数字为向量(vector).
对于中断描述表,操作系统使用IDTR寄存器来记录idt位置和大小。
kd> r idtr
idtr=80b95400
IDT有三种不同的描述符或者说是入口,分别是:
1.任务门描述符
2.中断门描述符
3.陷阱门描述符
也就是说,在保护模式下,80386只有通过中断门、陷阱门或任务门才能转移到对应的中断或异常处理程序。
Intel参考手册上指出“同步中断”(在一个指令执行完成后,由CPU控制单元产生的)作为“异常”。异步中断(可能会在任意时刻由其他硬件产生的)才称为“中断”。中断被外部的I/O设备产生。但是异常是由编程错误或者是由反常情况(必须由内核来处理)触发的。在该文档中,术语“中断信号”既指异常又指中断。
中断分为两种类型:可屏蔽中断–它在短时间片段里可被忽略;不可屏蔽中断–它必须被立即处理。例如:硬件失败为不可屏蔽中断,IRQS(中断请求)失败为可屏蔽中断。
异常被分为不同的两类:处理器产生的异常(Faults, Traps, Aborts)和编程安排的异常(用汇编指令int or int3 触发)。后一种就是我们经常说到的软中断。
其中:后两种描述符,非常的相似,只有1个bit位的差别。在处理上,采用相同的处理方式。
如图所示,在这后两类的描述符里面记录了一个中断服务程序(ISR )的地址offset. 在IDT的256个向量中,除3个任务门入口外,其他都是这两种门的入口。并且所有的trap/interrupt gate的入口,他们的segment selector都是一样的,即:08h. 我们察看GDT中Selector = 8的描述符,描述的是00000000h ~ 0ffffffffh的4G地址空间。 因此,在描述符中的中断服务程序(ISR )的地址offset就代表了函数的入口地址。windows在处理的时候,按照下图方式,来处理这两类的描述符入口。即:根据segment selector在GDT中找出段基地址等信息,然后跟描述符中的中断服务程序(ISR )的地址offset相加得到代码段中的函数入口地址。然后调用该函数。在操作系统执行这过程时,还有很多的出错判断和异常保护,这里我们略过。
接下来,我们看看任务门描述符的情况。
首先,根据IDT中任务门描述符的TSS Segment Selector ,我们在GDT中找出这个选择子。在这个选择子中,对应一个tss描述符,即:任务状态段描述符。这个描述符大小为068h, 即104字节。
下面是这个任务状态段描述符的格式。
我们看到在这个描述符中记录了任务状态段的位置和大小。
我们根据任务状态段描述符中的base Address, 找到TSS的内存位置。然后我们就可以进行任务切换。所谓任务切换是指,挂起当前正在执行的任务,恢复或启动另一任务的执行。在任务切换过程中,首先,处理器中各寄存器的当前值被自动保存到TR所指定的TSS中;然后,下一任务的TSS的选择子被装入TR;最后,从TR所指定的TSS中取出各寄存器的值送到处理器的各寄存器中。由此可见,通过在TSS中保存任务现场各寄存器状态的完整映象,实现任务的切换。 TR寄存器可见部分保存了tss selector, 不可见部分,保存了任务状态段的位置和大小.如下图所示。
任务状态段TSS的基本格式如下图所示。
从图中可见,TSS的基本格式由104字节组成。这104字节的基本格式是不可改变的,但在此之外系统软件还可定义若干附加信息。
透过一个简单的代码,看看256个中断向量对应的ISR., 其中任务门的ISR没有,由于整个IDT中只有3个任务门,我们忽略这部分。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | #include <ntddk.h> #include <stdio.h> #define MAX_IDT_ENTRIES 0xff #define MAKELONG(a, b) ((unsigned long) (((unsigned short) (a)) | ((unsigned long) ((unsigned short) (b))) << 16)) #pragma pack(1) typedef struct { unsigned short IDTLimit; //IDT范围 占8位 unsigned short LowIDTbase; //基地址低8位 unsigned short HiIDTbase; //基地址高8位 } IDTINFO; typedef struct { unsigned short LowOffset; //中断处理函数地址低16位 unsigned short selector; unsigned char unused_lo; unsigned char segment_type:4; //0x0E is an interrupt gate unsigned char system_segment_flag:1; unsigned char DPL:2; // descriptor privilege level unsigned char P:1; /* present */ unsigned short HiOffset; //中断处理函数地址高16位 } IDTENTRY; #pragma pack() void HookInterrupt() { unsigned long count; IDTINFO idt_info; IDTENTRY* idt_entries; IDTENTRY* i; unsigned long addr; char _t[255]; //加载idt_info __asm sidt idt_info; idt_entries = (IDTENTRY*)MAKELONG(idt_info.LowIDTbase,idt_info.HiIDTbase); //保存旧的IDT指针 for(count = 0; count < MAX_IDT_ENTRIES; count++) { i = &idt_entries[count]; addr = MAKELONG(i->LowOffset,i->HiOffset); _snprintf(_t,253,"interrupt id: %d ISR:0x%08x \n",count,addr); DbgPrint(_t); } } VOID OnUnload( IN PDRIVER_OBJECT DriverObject ) { DbgPrint("OnUnload\n"); } NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath ) { theDriverObject->DriverUnload = OnUnload; HookInterrupt(); return STATUS_SUCCESS; } |