Windows内核原理与实现读书笔记之异常分发
异常分发
在Intel X86 体系结构中,异常也是通过IDI(中断描述符表)分发的。
异常记录EXCEPTION_RECORD 定义:
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;//异常产生的原因
DWORD ExceptionFlags;//异常标志
struct _EXCEPTION_RECORD *ExceptionRecord;//相关联的异常记录
PVOID ExceptionAddress;//用来记录异常地址,错误类异常与陷阱类异常会有区别
DWORD NumberParameters; // 附加参数个数,即ExceptionInformation数组的有效个数。
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;
异常处理例程_KiTapXX 如何向KiDispatchException 提供参数信息。
首先,IDT的每个异常处理例程都回在入口处,通过ENTER_TRAP 宏为当前所发生的异常建立起一个陷阱帧。ENTER_TRAP宏会将异常发生时的主要寄存器存放到栈中,包括非易失的寄存器(ebp,ebx,esi,edi)、段寄存器fs、当前处理器的异常链表(位于KPCR的开头)、发生异常时的处理器模式(内核或用户模式)、易失寄存器(eax、ecx、edx)、段寄存器(ds、es、gs),以及一些调试数据。
处理器在异常(或中断)发生时的栈,如图:
函数从3环到0环时的保存现场
操作系统维护了一个结构 _KTRAP_FRAME ,在该结构里保存了一个线程3环的寄存器的值。每一个线程有一个该结构体
Trap Frame是指中断、自陷、异常进入内核后,在堆栈上形成的一种数据结构。用来存储三环的寄存器
_KTRAP_FRAME陷阱帧定义:
typedef struct _KTRAP_FRAME //Trap现场帧
{
// ------------------这些是KiSystemService保存的---------------------------
ULONG DbgEbp;
ULONG DbgEip;
ULONG DbgArgMark;
ULONG DbgArgPointer;
ULONG TempSegCs;
ULONG TempEsp;
ULONG Dr0; //调试寄存器
ULONG Dr1;
ULONG Dr2;
ULONG Dr3;
ULONG Dr6;
ULONG Dr7;
ULONG SegGs;
ULONG SegEs;
ULONG SegDs;
ULONG Edx;//xy 这个位置不是用来保存edx的,而是用来保存上个Trap帧,因为Trap帧是可以嵌套的
ULONG Ecx; //中断和异常引起的自陷要保存eax,系统调用则不需保存ecx
ULONG Eax;//中断和异常引起的自陷要保存eax,系统调用则不需保存eax
ULONG PreviousPreviousMode;
struct _EXCEPTION_REGISTRATION_RECORD FAR *ExceptionList;//上次seh链表的开头地址
ULONG SegFs;
ULONG Edi;
ULONG Esi;
ULONG Ebx;
ULONG Ebp;
//----------------------------------------------------------------------------------------
ULONG ErrCode;//发生的不是中断,而是异常时,cpu还会自动在栈中压入对应的具体异常码在这儿
//-----------下面5个寄存器是由int 2e内部本身保存的或KiFastCallEntry模拟保存的现场---------
ULONG Eip; //硬件填充
ULONG SegCs; //硬件填充
ULONG EFlags; //硬件填充
ULONG HardwareEsp; //硬件填充,仅当有模式切换时才使用
ULONG HardwareSegSs; //硬件填充,仅当有模式切换时才使用
//---------------以下用于用于保存V86模式的4个寄存器也是cpu自动压入的-------------------
ULONG V86Es; //硬件填充,仅当从V86模式切换过来时才使用
ULONG V86Ds; //硬件填充,仅当从V86模式切换过来时才使用
ULONG V86Fs; //硬件填充,仅当从V86模式切换过来时才使用
ULONG V86Gs; //硬件填充,仅当从V86模式切换过来时才使用
} KTRAP_FRAME, *PKTRAP_FRAME;
Typedef KTRAP_FRAME *PKTRAP_FRAME;
Typedef KTRAP_FRAME *PKEXCEPTION_FRAME;
Windows 利用陷阱帧中的SegCS ,即发生异常时的指令所在的段,来确定处理器模式。SegCS的0~1位代表了段选择符的特权级,即异常发生时的当前特权级(CPL)。
KiDisPatchException函数原型
void KiDispatchException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN KPROCESSOR_MODE PreviousMode,
IN BOOLEAN FirstChance
)
ExceptionRecord:用来描述要分发的异常。
ExceptionFrame:指向的KTRAP_FRAME结构,用来描述异常发生时的处理器状态,包括各种通用寄存器、调试寄存器、段寄存器等。
PreviousMode:枚举,用来表示前一种状态是内核模式还是用户模式。
FirstChance:表示第几轮分发。
KiDispatchException会先调用KeContextFromKframes函数,目的是根据TrapFrame参数指向的KTRAP_FRAME结构产生一个CONTEXT结构,以供向调试器和异常处理器函数报告异常时使用。
内核太异常的分发过程,对于第一轮异常KiDispatchException会试图先通知内核调试器来处理异常,如果没有处理异常,那么会调用RtlDispatchException,试图寻找已经注册的结构化异常处理器(SEH)。如果也没有找到,那么就会给内核调试器第二次处理的机会。仍返回FALSE的话,就会调用KeBugCheckEx触发蓝屏。
用户态异常的分发过程,首先,KiDispatchException 会判断是否发送给内核调试器,但内核调试器通常不会处理用户态异常,所以KiDispatchException 会试图发送给用户态调试器,方法是调用DbgkForwardException。如果不成功,KiDispatchException下一步动作是试图寻找异常处理块来处理该异常,因为用户异常发送在用户态代码中,异常处理块也是在用户态代码中。所以需要转到用户态去执行。如果最终也返回FALSE,那么就会分发第二轮。
异常分发流程,如图: