IDT是处理异常,实现操作系统与CPU的交互的关口。
系统在初始化阶段会去填写这个结构。
IDT的每一个表项都成为门描述符,因为IDT的功能就像大门一样,从一个空间跳到另一个空间去执行。
IDT中包含三种门描述符
- 任务门描述符:用于任务切换
- 中断门描述符:用于描述中断处理例程
- 陷阱们描述符:用于描述异常处理例程
CPU如何使用IDT
cpu首先根据IDTR找到IDT,再利用向量号码找到门描述符。再去判断门描述符的类型,如果是任务描述符,CPU会执行硬件方式的任务切换,切换到描述符所定义的线程。
如果是陷阱或中断描述符,那么会去调用处理例程。X64架构不支持硬件方式的任务切换,也就不再存在任务门了。
在调用处理例程之前,CPU会把EFLAGS、CS、EIP压入栈,如果发生的异常有错误代码则把错误代码也压入堆栈。
windows系统使用EXCEPTION_RECORD结构描述异常。
1 typedef struct _EXCEPTION_RECORD { 2 DWORD ExceptionCode;//异常代码 3 DWORD ExceptionFlags;//异常标志 4 struct _EXCEPTION_RECORD *ExceptionRecord;//相关的另一个异常 5 PVOID ExceptionAddress;//异常发生的地址 6 DWORD NumberParameters;//参数数组中的参数个数 7 ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];//参数数组 8 } EXCEPTION_RECORD, *PEXCEPTION_RECORD;
异常代码,可以认为是异常的别称,有的异常有异常代码有的则没有。
CommonDispatchException函数会在栈中生成一个EXCEPTION_RECORD结构,把当前的数据情况写入到这个结构中。
然后会以这个结构为参数调用KiDispatchException来分发异常。
软件异常,软件异常是通过直接或间接调用内核函数NtRaiseException产生的,用户层也可调用这个函数,用户层导出了一个RaiseException函数在kernel32.dll中,供用户产生一个自定义的异常。
RaiseException函数的实现原理是把相应的参数放入EXCEPTION_RECORD结构中,调用RtlRaiseException函数,这个函数把当前的线程上下文放入CONTEXT结构中,然后调用NtRaiseException。
NtRaiseException内部实现函数是KiRaiseException
1 //来自WRK1.2 2 NTSTATUS 3 KiRaiseException ( 4 IN PEXCEPTION_RECORD ExceptionRecord,//异常记录 5 IN PCONTEXT ContextRecord,//线程上下文结构 6 IN PKEXCEPTION_FRAME ExceptionFrame,//不使用,为空 7 IN PKTRAP_FRAME TrapFrame,//栈帧基地址 8 IN BOOLEAN FirstChance//表示是第一轮还是第二轮处理 9 ) 10 11 { 12 //省略了部分内容 13 //把ContextRecord复制到当前线程的内核栈中 14 KeContextToKframes(TrapFrame, 15 ExceptionFrame, 16 ContextRecord, 17 ContextRecord->ContextFlags, 18 PreviousMode); 19 //把异常记录中的异常代码最高位清零,这样可以分辨出软件异常和CPU异常 20 ExceptionRecord->ExceptionCode &= ~KI_EXCEPTION_INTERNAL; 21 //调用分发异常的函数 22 KiDispatchException(ExceptionRecord, 23 ExceptionFrame, 24 TrapFrame, 25 PreviousMode, 26 FirstChance); 27 28 return STATUS_SUCCESS; 29 }
时间: 2024-10-29 19:11:50