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),以及一些调试数据。

处理器在异常(或中断)发生时的栈,如图:

Windows内核原理与实现读书笔记之异常分发

 

函数从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,那么就会分发第二轮。

异常分发流程,如图:

Windows内核原理与实现读书笔记之异常分发