ReactOS中的异常处理实现细节  Windows中的稍有不同,但原理相通。都是基于SEH结构化机制。

当发生异常时,根据异常指令所处的地址空间分为用户空间的异常和内核空间的异常。两个地址空间都各自有一条SEH链表。不管处在哪个地址空间,FS:[0]都指向链表中的开头节点。Windows中,每个节点就是一个try块。而ReactOSV0.3.X版本中,每个节点则是一个try块栈,这点要注意。所谓try块栈是指同一函数内部‘形式上’嵌套在一起的try块构成的一个try块栈。因此,在ReactOS中,SEH链表节点即是try块栈,try块栈即是SEH链表节点。

比如函数:

Void f1()

{

_SEH_TRY

{

   _SEH_TRY

{

       。。。  //1

}

_SEH_EXCEPT(过滤函数)

{

     。。。  //2

}

_SEH_END

。。。

   _SEH_TRY

{

       f2(); //3

}

_SEH_EXCEPT(过滤函数)

{

     。。。 //4

}

_SEH_END

。。。

}

_SEH_EXCEPT(过滤函数)

{

  。。。 //5

}

_SEH_END

   。。。

_SEH_TRY

{

   。。。 //6

}

_SEH_EXCEPT(过滤函数)

{

 。。。//7

}

_SEH_END

。。。
}

 

 

 

Void f2()

{

 _SEH_TRY

{

   。。。 //8

}

_SEH_EXCEPT(过滤函数)

{

 。。。//9

}

_SEH_END

}

 

 

假如程序执行流由f1函数内的3处的函数调用进入到f2函数内的8处,那么此时在SEH链表中,最开头的节点便是f2中的那个try块栈(不过这个try块栈只有一个try块),下一个节点便是f1中的try块栈(该try块栈此时由两个try块嵌套而成)

 

SEH链表中,最开头的节点是最后加入链表的节点,也即最底层的节点。当发生异常时,就从SEH链表开头往后(也即从底层向高层)遍历,直至找到一个能识别处理该异常的节点为止。

一个节点中也可能嵌套含有多个try块,节点内部也有一个子链表,子链表中的每个节点代表一个try块,也是先进后出形式,最开头的节点是最深层的try块。

每个try块有一个异常过滤函数(又叫识别函数)和处理函数(即except块)或finally函数(即finally块)。每个try块的Except块与finally块不能同时存在。每个try块的过滤函数可以对收到的异常进行过滤,过滤出哪些异常是本try块可以截获处理的;也可以让异常回到原发生异常的指令(或下一条指令)处继续原地执行;还可以不管该异常,转交给上层的try块去识别、处理。

 

看看seh链表中节点的结构定义:

typedef struct __SEHFrame  //seh中的节点

{

     _SEHPortableFrame_t
SEH_Header;

     void
* volatile SEH_Locals;//本节点的附加信息

}_SEHFrame_t;

 

typedef struct __SEHPortableFrame

{

     _SEHRegistration_t
SPF_Registration;//基本头部

     unsigned
long SPF_Code;//当前发生的异常码

     volatile
_SEHHandler_t SPF_Handler;//实施远跳转(即跳转到except块)的函数

     _SEHPortableTryLevel_t
* volatile SPF_TopTryLevel;//该节点内部的try块链表

     volatile
int SPF_Tracing;

}_SEHPortableFrame_t;

 

typedef struct __SEHRegistration  //这种结构才是seh链表中的节点

{

     struct
__SEHRegistration * SER_Prev;//下一个节点(即上层的节点)

_SEHFrameHandler_t SER_Handler;
//该节点的异常识别、处理函数。

}_SEHRegistration_t;

如上,seh链表中的每个节点实际上是__SEHFrame结构类型,它包含了__SEHRegistration结构,相当于继承了__SEHRegistration类型,因此,也可看做是一种__SEHRegistration

每个节点都代表一个形式嵌套的try块栈而非一个try块。

每个节点的SER_Handler是其异常识别、处理函数。系统会调用这个函数对异常进行识别、处理。Seh链表中的节点又分三种类型

普通节点:即普通try块栈

嵌套保护节点:用来防止except块中发生嵌套异常而临时加入的一个保护节点,这是系统自动加的。

展开保护节点:用来防止每个节点在进行局部展开时发生异常而临时加入的一个保护节点,这也是系统自动加的。

普通节点的SER_Handler函数(即异常识别处理函数),都固定为_SEHFrameHandler,嵌套保护节点的识别处理函数则是_RtlpExceptionProtector,展开保护节点的则是_RtlpUnwindProtector

普通节点的识别处理函数除了用来识别、处理异常外,还可以用来执行局部展开工作。一个函数两种用途。

 

下面是每个try块的结构定义

typedef struct __SEHTryLevel

{

     _SEHPortableTryLevel_t
ST_Header;

     _SEHJmpBuf_t
ST_JmpBuf;//一个跳转缓冲,间接记录了本try块的处理函数地址和寄存器上下文

}_SEHTryLevel_t;

typedef struct __SEHPortableTryLevel

{

     struct
__SEHPortableTryLevel * volatile SPT_Next;//下一个try块(即上层的try块)

     volatile
_SEHHandlers_t SPT_Handlers;//本try块的识别函数、finally函数

}_SEHPortableTryLevel_t;

typedef struct __SEHHandlers

{

     _SEHFilter_t
SH_Filter;//过滤函数(即识别函数)

     _SEHFinally_t
SH_Finally;//finally函数,即finally块,又叫善后函数

}_SEHHandlers_t;

如上,每个try块记录了它配套的异常识别函数、处理函数、finally函数的地址。

需要说明的是,不管是seh链表中的节点,还是节点内部链表中的节点,都是位于栈中的。

 

_SEH_TRY 、_SEH_EXCEPT 等在ReactOS中都是宏,而非编译器关键字。而_SEH2_TRY_SEH2_EXCEPT则定义为__try__except关键字了。

 

我们看下他们的定义:

//三个文件内的静态变量

Static int _SEHScopeKind=1;//非0值表示是节点内部最顶层的try

Static _SEHPortableFrame_t * _SEHPortableFrame=NULL;

Static _SEHPortableTryLevel_t * _SEHPortableTryLevel=NULL;

#define _SEH_TRY \

     for(;;)
\ //这个for循环不是用来循环,而是用来防止下面的{}符号被编译器优化掉

     { \

         //该try块是否为所在节点内部的最顶层try

         _SEH_INIT_CONST
int _SEHTopTryLevel
= (_SEHScopeKind != 0); \

         //该try块的所在节点

          _SEHPortableFrame_t * const
_SEHCurPortableFrame = _SEHPortableFrame; \

         //该try块的上一层try

         _SEHPortableTryLevel_t
* const _SEHPrevPortableTryLevel
= _SEHPortableTryLevel; \

         { \

              _SEH_INIT_CONST int _SEHScopeKind = 0; \//内层的try

              register int _SEHState = 0; \ //是否设置好了try块描述符

              register int _SEHHandle = 0; \ //是否发生了异常

              _SEHFrame_t _SEHFrame; \ //关键。所属的节点(最高层的try块中,这个变量才有意义)

              _SEHTryLevel_t _SEHTryLevel; \
//关键。该try块在栈中的描述符

              _SEHPortableFrame_t * const _SEHPortableFrame = \

              _SEHTopTryLevel ? &_SEHFrame.SEH_Header : _SEHCurPortableFrame;
\

              _SEHPortableTryLevel_t * const _SEHPortableTryLevel = &_SEHTryLevel.ST_Header; \

              for(;;) \

              { \

                   if(_SEHState) \ //后面才进入这儿,执行try块中的代码

                   { \

                       for(;;) \  //这个for循环也不是用来循环的 

                        { \

                            {

                                   //Try块中的代码

#define _SEH_EXCEPT(FILTER_) \

                            } \

                            break; \

                       } \

                       break; \

                   } \

                   else \  //第一次将来到这儿

                   { \

                        //_SEHSetJmp函数用来设置本try块的异常跳转目标地址固定为 call _SEHSetJmp 下一条指令处。返回值_SEHHandle固定为0,并写入eax中。所以首次来到时,下面的if条件必定满足。但是若经异常跳转而来,则eax1,执行流将进入后面的else块去执行except块中的代码

                       if((_SEHHandle = _SEHSetJmp(_SEHTryLevel.ST_JmpBuf)) == 0) \

                       { \

                            //设置本try块的过滤函数、finally函数、下一个try

                            _SEHTryLevel.ST_Header.SPT_Handlers.SH_Filter
= (FILTER_); \

                            _SEHTryLevel.ST_Header.SPT_Handlers.SH_Finally
= 0; \

                            _SEHTryLevel.ST_Header.SPT_Next = _SEHPrevPortableTryLevel;
\

//节点中的各个try块挂入所在节点内部的try块链表中。越深的try块越在链表前面

                           _SEHFrame.SEH_Header.SPF_TopTryLevel = &_SEHTryLevel.ST_Header; \

                            if(_SEHTopTryLevel)
\//若该try块是最顶层的try块,将栈中的节点描述符加入seh链表中

                            { \

                                 if(&_SEHLocals
!= _SEHDummyLocals) \

                                     _SEHFrame.SEH_Locals
= &_SEHLocals; \

                                 _SEH_EnableTracing(_SEH_DO_DEFAULT_TRACING);
\

                        //固定为_SEHCompilerSpecificHandler函数

                       _SEHFrame.SEH_Header.SPF_Handler = _SEHCompilerSpecificHandler;
\

                                 _SEHEnterFrame(&_SEHFrame.SEH_Header); \//挂入seh链表开头

                            } \

                            ++ _SEHState; \ 
//由0变成1

                            continue; \ //下轮循环将执行try块中的代码了

                       } \

                       else \    //异常发生时,将进入到这儿的else分支中

                       { \

                            break; \

                       } \

                   } \

                   break; \

              } \

              //下面的代码不管try块中有没有发生异常,都会到这儿来

              //将当前try块脱出节点内部的try块链表

              _SEHPortableFrame->SPF_TopTryLevel
= _SEHPrevPortableTryLevel; \

              if(_SEHHandle) \  //经异常而来时,_SEHHandle(即eax)的值必定为1

              {

                    //Except块中的代码

#define _SEH_END \

              } \

         } \

         if(_SEHTopTryLevel) \

              _SEHLeaveFrame(); \ //将节点脱出seh链表

         break;
\

     }

如上,上面的这段代码巧妙的利用了三个全局变量与局部变量同名的技巧,划分不同的作用域。

从上,我们可以看出,没进入一个try块,变在栈中构造好一个try块描述符,加入节点内部的try块链表。若进入第一个try块时,还会构造一个节点描述符,将节点加入全局的seh链表中。以后当离开每层try块时,自动脱出链表。当最顶层的那个try块也退出时,相应的节点会从seh链表脱出。

 

另外一个方便的宏_SEH_HANDLE定义如下

#define _SEH_HANDLE
  _SEH_EXCEPT(_SEH_STATIC_FILTER(_SEH_EXECUTE_HANDLER))

#define _SEH_STATIC_FILTER(ACTION_) ((_SEHFilter_t)((ACTION_) + 2))

每个try块的异常过滤函数(即识别函数)可以返回下面三种值

#define _SEH_CONTINUE_EXECUTION
(-1)  //表示截获了异常,继续返回异常指定处原地执行

#define _SEH_CONTINUE_SEARCH (0) //表示没识别出,继续向上搜索

#define _SEH_EXECUTE_HANDLER
(1) //表示识别截获了异常,执行本try块的处理函数对其处理

 

 

上面的_SEH_TRY定义中,我们看到,在进入try块中的代码前,会先在栈中构造好一个try块描述符。

下面的函数用来设置try块截获到异常后的处理函数跳转地址

_SEHSetJmp@4:

Mov eax,[esp+4]  //eax=跳转缓冲的地址

Mov ecx,[esp]  //ecx=返回地址,即call SEHSetJmp的下条指令处

Lea edx,[esp+8]  //调用本函数前的堆栈栈顶位置

Mov [eax+0],ebp

Mov [eax+4],edx //关键。记录当时的栈顶位置,以方便以后展开时将栈顶一步跳回这里来执行except

Mov [eax+8],ecx //返回地址

Mov [eax+12],ebp

Mov [eax+16],esi

Mov [eax+20],edi

//上面用来保存现场,以在以后内层try中发生异常跳过来时恢复现场

Xor ,eax,eax  //看到没,固定返回0,因此总是进入if块中去执行try块描述符构造工作

Ret 4

 

#define _SEHEnterFrame
 _SEHEnterFrame_f

void _SEH_FASTCALL _SEHEnterFrame_f(_SEHPortableFrame_t
* frame)

{

     frame->SPF_Registration.SER_Handler
= _SEHFrameHandler;//固定为这个识别处理函数

     frame->SPF_Code = 0;

     _SEHRegisterFrame(&frame->SPF_Registration);//加入seh链表

}

__SEHRegisterFrame@4:  //这个函数用来将节点挂入seh链表开头

Mov ecx,[esp+4]

Mov eax,fs:[0]

Mov [ecx+0],eax

Mov fs:[0],ecx

Ret

 

#define _SEHLeaveFrame
_SEHLeaveFrame_f

void _SEH_FASTCALL _SEHLeaveFrame_f(void)

{

     _SEHPortableFrame_t
* frame;

     frame
= _SEH_CONTAINING_RECORD(_SEHCurrentRegistration(),  _SEHPortableFrame_t,

                                 SPF_Registration);

     _SEHUnregisterFrame();

}

__SEHUnregisterFrame:  //将当前最底层的节点脱出seh链表

Mov ecx,fs:[0]

Mov ecx,[ecx+0]

Mov fs:[0],ecx

Ret

 

 

当某个try块识别处了异常后,要对其进行处理时,系统就会调用下面的函数,还原寄存器现场,跳到excep块去执行处理函数。

__SEHLongJmp@8:

Mov eax,[esp+8] //eax固定为1

Mov ecx,[esp+4] //ecx=跳转缓冲的地址

Mov ebp,[ecx+0]

Mov esp,[ecx+4] //关键。迈过那些因异常而跳过的函数栈,还原到当时的栈顶位置

Mov edx,[ecx+8] //返回地址

Mov ebx,[ecx+12]

Mov esi,[ecx+16]

Mov edi,[ecx+20]

//恢复寄存器现场后,跳转到except

Jmp edx //跳转到原call _SEHSetJmp指令后面,不过此时eax的值即_SEHHandle的值为1了,表示截获了异常,跳转而来,因此而进入else分支中,进入去执行后面的except块。

 

 

当发生了异常后,系统会从最底层的try块向上搜索,直到找到一个能识别处理那种异常的try块,然后将执行流跳转到那个try块的except块中去,不过,在跳转之前,须得从底往上,逐一展开那个try块下层的所有try块,调用他们的finally函数进行善后处理,如执行释放内存、关闭文件等善后清理工作。

这个展开过程即是逐层调用各try块的finally函数的过程【展开、调用finally

如果下层的try块需要展开到上一层函数内部的except块,那就叫全局展开。若展开到同一函数内部的某个上层except块,那就叫局部展开。

这样,当展开完所有下层的try块后,就将跳转到那个截获识别了异常的那个try块的except块中去执行异常处理工作,不过,在执行前,还要恢复寄存器现场,最重要的便是要恢复esp。这样,执行流就一步跳回高层函数体中去了。

 

 

 

异常的处理流程:

当发生了异常后,cpu第一时间就压入eflagscseip这三个寄存器的值在内核栈中,以记录异常现场。对于发生在用户空间的异常,cpu还会压入ssesp的值。对于有的异常,如页面异常,Cpu还会自动压入一个具体的异常原因码。记录完了现场后,cpu就第一时间在自己的IDT表中找到处理该异常的函数,那个异常处理函数类似isr,我们叫epr,名为:‘异常处理例程’。

我们看看0号异常,即除0异常的epr

_KiTrap0:

Push 0 //除0异常,cpu是不会压入具体异常原因码的,我们模拟压入一个以补齐TrapFrame结构大小

构造好异常TrapFrame; //记录异常发生时的完整寄存器现场,保存在栈中

Xor ecx,ecx  该类异常的附加参数个数=0

Call _CommonDispatchException  //调用通用异常派遣函数进行处理

 

再看看14号异常,即页面异常的epr

_KiTrap14:

构造好异常TrapFrame; //记录异常发生时的完整寄存器现场,保存在栈中

。。。

Mov edi,cr2;//cr2中记录了引起页面异常的内存单元地址(注意不是指令地址)

Test dword ptr[ebp+KTRAP_FRAME_EFLAGS],
EFLAGS_INTERRUPT_MASK

Je HandlePf

Sti //页面异常,cpu会自动关中断(以防止嵌套页面异常覆盖cr2),现在要开中断了

HandlePf:

Push ebp //ebp 始终记录着异常TrapFrame的地址

Move eax,[ebp+KTRAP_FRAME_CS]

And eax,MODE_MASK //cs的最后一位为1就表示用户模式

Push eax;//保存上次模式

Push edi //cr2

Mov eax,[ebp+KTRAP_FRAME_ERROR_CODE]

And eax,1 //eax的最后一位表示具体的页面异常原因,1表示权限异常,0表示缺页异常

Push eax

Call _MmAccessFault //这个函数内部会自动处理缺页异常、写复制异常的,前面篇章看过

Test eax,eax

Jl AccessFail//如果上面函数没能处理异常,也即不是缺页异常、写复制异常

Jmp _Kei386EoiHelper@0  //异常处理完毕,返回原地继续执行

AccessFail:

Mov esi,[ebp+ KTRAP_FRAME_EIP]

Cmp eax,STATUS_ACCESS_VIOLATION

Je AccessViol

Cmp eax,STATUS_GUARD_PAGE_VIOLATION

Je SpecialCode

Cmp eax,STATUS_STACK_OVERFLOW

Je SpecialCode

AccessViol:

Mov eax,KI_EXCEPTION_ACCESS_VIOLATION

SpecialCode:

Mov ebx,esi

Mov edx,ecx

Mov esi,edi

Xor edx,edx

Mov ecx,2  //两个附加参数

Call _CommonDispatchException //通用异常处理函数

如上,系统在处理页面异常时,操作系统自己会尝试处理缺页异常。如果不是缺页异常,就交由通用的异常处理流程去处理

 

下面我们看通用的异常处理流程是什么

_CommonDispatchException:

Sub esp,EXCEPTION_RECORD_LENGTH  //异常记录结构体的长度

Mov [esp+ EXCEPTION_RECORD_EXCEPTION_CODE],eax
//记录异常码

Mov [esp+ EXCEPTION_RECORD_EXCEPTION_Flag],0
//标志全0

Mov [esp+ EXCEPTION_RECORD_EXCEPTION_RECORD],0
//0

Mov [esp+ EXCEPTION_RECORD_EXCEPTION_ADDRESS],ebx
//异常指令的地址(或下条指令地址)

Mov [esp+ EXCEPTION_RECORD_NUMBER_PARAMETERS],ecx
//附加参数个数

Cmp ecx,0

Jz NoParams

Lea ebx,[esp+SIZE_OF_EXCEPTION_RECORD]

Mov [ebx],edx

Mov [ebx+4],esi

Mov [ebx+8],edi

//上面的代码用于在栈中构造一个异常记录结构

NoParams:

Mov ecx,esp

Mov eax,[ebp+KTRAP_FRAME_CS]

And eax,MODE_MASK

Push 1 //第一次机会

Push eax //bUserMode

Push ebp //栈中异常TrapFrame的地址

Push 0

Push ecx 
//异常记录结构地址

//下面这个函数除非异常识别处理结果返回的是SEH_CONTINUE_EXECUTION,否则是不会返回来的

Call _KiDispatchException@20

Mov esp,ebp

Jmp _Kei386EoiHelper@0  //返回原异常指令处继续执行

 

如上,上面的函数在栈中构造好一个异常记录结构,然后调用KiDispatchException而已。下面就是异常记录结构的定义

typedef struct _EXCEPTION_RECORD

{

  NTSTATUS
ExceptionCode;//异常码

  ULONG
ExceptionFlags;

  struct
_EXCEPTION_RECORD *ExceptionRecord;//‘软异常’的上层嵌套异常

  PVOID
ExceptionAddress;//异常指令的地址或下条指令的地址

  ULONG
NumberParameters;//附加参数个数

  ULONG_PTR
ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];//附加参数数组

} EXCEPTION_RECORD,
*PEXCEPTION_RECORD;

 

 

cpu异常分为三种异常:

1、  普通异常,如页面异常、除0异常等,异常发生后,eip停在异常指令本身处

2、  自陷类异常,由自陷指令(如INT 3)引起的异常,异常发生后,eip停在异常指令的下一条指令处

3、  严重异常:机器直接崩掉。

 

VOID

KiDispatchException(IN PEXCEPTION_RECORD ExceptionRecord,

                    IN PKEXCEPTION_FRAME
ExceptionFrame,

                    IN PKTRAP_FRAME TrapFrame,

                    IN KPROCESSOR_MODE PreviousMode,

                    IN BOOLEAN FirstChance)

{

    CONTEXT
Context;

    EXCEPTION_RECORD
LocalExceptRecord;

    KeGetCurrentPrcb()->KeExceptionDispatchCount++;

Context.ContextFlags
= CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;

如果是用户空间发生的异常或者处于调试状态,还需保存浮点寄存器现场

    if
((PreviousMode == UserMode)
|| (KeGetPcr()->KdVersionBlock))

    {

        Context.ContextFlags |= CONTEXT_FLOATING_POINT;

        if
(KeI386FxsrPresent)

            Context.ContextFlags |= CONTEXT_EXTENDED_REGISTERS;

}

//将TrapFrame中的寄存器内容提取到Context中(相当于复制)

    KeTrapFrameToContext(TrapFrame, ExceptionFrame,
&Context);

    switch
(ExceptionRecord->ExceptionCode)

    {

        case
STATUS_BREAKPOINT://INT 3指令触发的异常

            Context.Eip–;//回滚一个字节,回到INT 3指令处,以方便调试器恢复成原来的指令

            break;

        case
KI_EXCEPTION_ACCESS_VIOLATION:

            ExceptionRecord->ExceptionCode = STATUS_ACCESS_VIOLATION;//转换成访问非法异常

            break;

}

if (PreviousMode
== KernelMode) //若是发生在内核空间的异常

 

    {

        if
(FirstChance == TRUE)

        {

            //先通知给内核调试器去处理

           if(KiDebugRoutine(TrapFrame,ExceptionFrame,ExceptionRecord,&Context,PreviousMode,

FALSE))//FALSE表示不是第二次调试器

            {

                goto Handled;

            }

            //若调试器未能处理掉(或者没有调试器),则交给SEH链表去处理

            BOOL bContinue = RtlDispatchException(ExceptionRecord,
&Context);

            //SEH链表的处理结果为处理了异常,则上面的函数不返回来,若处理结果为继续原地执行,//则返回TRUE,若搜完了整个SEH链表,都找不到一个能处理的节点,就返回FALSE

            if
(bContinue) goto Handled;

        }

        //若SEH未能处理,则再次通知调试器处理

        if
(KiDebugRoutine(TrapFrame,ExceptionFrame,ExceptionRecord,&Context,PreviousMode,

                           TRUE))//TRUE表示第二次通知调试器

        {

            goto
Handled;

        }

        //这次若调试器还是未能处理(或者没有调试器),蓝屏!

        KeBugCheckEx(KMODE_EXCEPTION_NOT_HANDLED,

                     ExceptionRecord->ExceptionCode,

                     (ULONG_PTR)ExceptionRecord->ExceptionAddress,

                     (ULONG_PTR)TrapFrame,

                     0);

    }

    Else //若是发生在用户空间的异常

    {

        if
(FirstChance)

        {

            //if 没有一个用户模式的调试器 && 内核调试器可以受理用户空间异常

            //                  或者

            //这是一个调试服务中产生的异常

            if
((!(PsGetCurrentProcess()->DebugPort) && !(KdIgnoreUmExceptions))

||

                (KdIsThisAKdTrap(ExceptionRecord,&Context,PreviousMode)))

            {

                //先通知给内核调试器去处理(实际上内核调试器一般不处理用户态异常的)

                if (KiDebugRoutine(TrapFrame,ExceptionFrame,ExceptionRecord,&Context,

                                   PreviousMode,FALSE))

                {

                    goto Handled;

                }

            }

 

            //发给用户模式的调试器去处理

            if
(DbgkForwardException(ExceptionRecord,
TRUE, FALSE))
return;

            //若用户模式的调试器未能处理或者没有调试器,就走VEHSEH

DispatchToUser:

            _SEH2_TRY  //回写用户空间这种操作可能出现异常,因此也要保护起来

            {

                ULONG Size;

                ULONG_PTR Stack, NewStack;

                Size = (sizeof(CONTEXT) + 3) & ~3; //对齐4B

                Stack = (Context.Esp & ~3) – Size;

                ProbeForWrite((PVOID)Stack, Size, sizeof(ULONG));

//将寄存器现场拷贝到用户空间去存放

                RtlCopyMemory((PVOID)Stack, &Context,
sizeof(CONTEXT));

                Size = (sizeof(EXCEPTION_RECORD) –

                       (EXCEPTION_MAXIMUM_PARAMETERS –

                        ExceptionRecord->NumberParameters)
* sizeof(ULONG)
+ 3) & ~3;

                NewStack = Stack – Size;

                ProbeForWrite((PVOID)(NewStack – 2 * sizeof(ULONG_PTR)),

                              Size +  2 * sizeof(ULONG_PTR), sizeof(ULONG));

                //将异常记录结构拷贝到用户空间去存放

                RtlCopyMemory((PVOID)NewStack, ExceptionRecord, Size);

                //构造好KeUserExceptionDispatcher函数的两个指针参数

                *(PULONG_PTR)(NewStack
– 1 * sizeof(ULONG_PTR))
= Stack;

                *(PULONG_PTR)(NewStack
– 2 * sizeof(ULONG_PTR))
= NewStack;

                //修改TrapFrame中的ssesp

                KiSsToTrapFrame(TrapFrame,
KGDT_R3_DATA);

                KiEspToTrapFrame(TrapFrame,
NewStack – 2 * sizeof(ULONG_PTR));

                /* Force correct segments */

                TrapFrame->SegCs
= Ke386SanitizeSeg(KGDT_R3_CODE,
PreviousMode);

                TrapFrame->SegDs
= Ke386SanitizeSeg(KGDT_R3_DATA,
PreviousMode);

                TrapFrame->SegEs
= Ke386SanitizeSeg(KGDT_R3_DATA,
PreviousMode);

                TrapFrame->SegFs
= Ke386SanitizeSeg(KGDT_R3_TEB,  PreviousMode);

                TrapFrame->SegGs
= 0;

                //修改TrapFrame中的Eip,以从内核退回到用户空间这个函数入口处去

                TrapFrame->Eip =
KeUserExceptionDispatcher;

               _SEH2_YIELD(return);//return后程序就进入用户空间的KiUserExceptionDispatcher

            }

            。。。

        }

        //若用户空间seh链表未能处理(实际上,用户空间每个线程入口都有一个顶层的try块保护着,那个try块的过滤函数即UnhandledExceptionFilter函数在没有调试器的话会处理异常,直接杀掉线程。若有调试器的话,才会返回继续搜索,从而来到下面代码处)          

        if
(DbgkForwardException(ExceptionRecord,
TRUE, TRUE))再第二次发给用户模式调试器

            return;

        else
if (DbgkForwardException(ExceptionRecord, FALSE,
TRUE))

            return;

        //若用户调试器仍未处理,杀死进程

        ZwTerminateProcess(NtCurrentProcess(), ExceptionRecord->ExceptionCode);

    }

 

Handled: //如果seh链表处理了,并且处理结果为“继续返回原地执行”,才会到这儿来

    KeContextToTrapFrame(&Context,ExceptionFrame,TrapFrame,Context.ContextFlags,

                         PreviousMode);

    return;

}

 

如上,KiDispatchException可以说是异常处理的枢纽函数,它根据异常发生的空间,分成两条独立不同的路径去处理。内核空间的异常处理很简单,交由内核调试器->内核中SEH链表->内核调试器
去处理。

用户空间的异常处理则稍显复杂:内核调试器->用户调试器->VEH->SEH->用户调试器

 

 

本章忽略调试器对异常的处理。

先看看内核异常的处理流程,RtlDispatchException是怎么搜索SEH链表的

BOOLEAN  RtlDispatchException(IN PEXCEPTION_RECORD ExceptionRecord, IN PCONTEXT Context)

{

    PEXCEPTION_REGISTRATION_RECORD
RegistrationFrame, NestedFrame
= NULL;

    DISPATCHER_CONTEXT
DispatcherContext;

    EXCEPTION_RECORD
ExceptionRecord2;

    EXCEPTION_DISPOSITION
Disposition;

    ULONG_PTR
StackLow, StackHigh;

    ULONG_PTR
RegistrationFrameEnd;

    if
(RtlpGetMode() != KernelMode)

{

//用户模式的异常先交由VEH向量化异常表去处理(用户可以调用//RtlAddVectoredExceptionHandler函数为进程添加向量化异常处理函数的)

        if
(RtlCallVectoredExceptionHandlers(ExceptionRecord, Context))

            return
TRUE;//VEH处理了

    }

RtlpGetStackLimits(&StackLow,
&StackHigh);

//遍历seh链表中的每个节点,直至找到一个能识别处理该异常的节点

    RegistrationFrame
= RtlpGetExceptionList();//即SEH链表中的第一个节点

    while
(RegistrationFrame != EXCEPTION_CHAIN_END)
//EXCEPTION_CHAIN_END 为 -1

    {

        RegistrationFrameEnd
= RegistrationFrame + sizeof(EXCEPTION_REGISTRATION_RECORD);

        if
((RegistrationFrameEnd > StackHigh) || ((ULONG_PTR)RegistrationFrame < StackLow)
||

            ((ULONG_PTR)RegistrationFrame
& 0x3))

        {

            if
(RtlpHandleDpcStackException(RegistrationFrame,RegistrationFrameEnd,

                                           
&StackLow,&StackHigh))

            {

                continue;

            }

            ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;

            return
FALSE;

        }

/* Check if logging is
enabled */  RtlpCheckLogException(ExceptionRecord,Context,RegistrationFrame,

sizeof(*RegistrationFrame));

 

        //加入一个临时的嵌套异常保护节点,然后调用该节点的异常识别处理函数(因为在except块中//可能会触发嵌套异常)

        Disposition
= RtlpExecuteHandlerForException(ExceptionRecord,

                                                    
RegistrationFrame,

                                       
             Context,

                                                    
&DispatcherContext,

                                                    
RegistrationFrame->Handler);

        if
(RegistrationFrame == NestedFrame)

        {

            ExceptionRecord->ExceptionFlags &= ~EXCEPTION_NESTED_CALL;

            NestedFrame
= NULL;

        }

        switch
(Disposition) //检查该节点的处理结果

        {

            case
ExceptionContinueExecution:

                if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)

                {

                    ExceptionRecord2.ExceptionRecord
= ExceptionRecord;

                    ExceptionRecord2.ExceptionCode
= STATUS_NONCONTINUABLE_EXCEPTION;

                    ExceptionRecord2.ExceptionFlags
= EXCEPTION_NONCONTINUABLE;

                    ExceptionRecord2.NumberParameters
= 0;

                    RtlRaiseException(&ExceptionRecord2);//软件模拟抛出一个‘软异常’

                }

                else

                {

                     return TRUE;//看到没,返回这个值,表示返回到原异常指令继续执行

                }

            case
ExceptionContinueSearch://未能识别,继续搜素下一个节点(即上层的节点)

                break;

            case
ExceptionNestedException://如识别处理过程中发生了一个嵌套异常

                ExceptionRecord->ExceptionFlags
|= EXCEPTION_NESTED_CALL;

 

                if (DispatcherContext.RegistrationPointer
> NestedFrame)

                    NestedFrame = DispatcherContext.RegistrationPointer;

                break;

            default:

                ExceptionRecord2.ExceptionRecord
= ExceptionRecord;

                ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION;

                ExceptionRecord2.ExceptionFlags
= EXCEPTION_NONCONTINUABLE;

                ExceptionRecord2.NumberParameters
= 0;

                RtlRaiseException(&ExceptionRecord2);

                break;

        }

        RegistrationFrame
= RegistrationFrame->Next;//下一个节点

    }

    return
FALSE;

}

 

继续看:

//这个函数用来加入一个临时保护节点,然后请求指定节点识别、处理指定异常

EXCEPTION_DISPOSITION

RtlpExecuteHandlerForException(PEXCEPTION_RECORD
ExceptionRecord,

                               PEXCEPTION_REGISTRATION_RECORD RegistrationFrame, //节点

                               PCONTEXT Context,

                               PVOID DispatcherContext,//用于嵌套异常、展开异常

                               PEXCEPTION_ROUTINE ExceptionHandler);//识别处理函数

_RtlpExecuteHandlerForException@20:

Mov edx,offset
_RtlpExectionProtector  //嵌套保护节点的识别处理函数

Jmp _RtlpExecuteHandler@20

这个函数会跳转到下面的地方处

_RtlpExecuteHandlerForUnwind@20:

Mov edx,offset
_RtlpUnwindProtecor  //展开保护节点的识别处理函数

_RtlpExecuteHandler@20:

Push ebx

Push esi

Push edi

Xor eax,eax

Xor ebx,ebx

Xor esi,esi

Xor edi,edi

Push [esp+0x20]

Push [esp+0x20]

Push [esp+0x20]

Push [esp+0x20]

Push [esp+0x20]

Call _RtlpExecuteHandler2@20  //原样传入那5个参数

Pop edi

Pop esi

Pop ebx

Ret 0x14

 

 

继续看:

_RtlpExecuteHandler2@20:

Push ebp

Mov ebp,esp

Push [ebp+0xC]  //原来的普通节点,即要保护的那个节点

Push edx //嵌套保护节点或展开保护节点的识别处理函数

Push fs:[0]

Mov fs:[0],esp //看到没,将这个临时保护节点挂到seh链表开头,成为新的最底层的节点

Push [ebp+0x14]

Push [ebp+0x10]

Push [ebp+0xC]

Push [ebp+0x8]

Mov ecx,[ebp+0x18]

Call ecx  //关键。现在才调用那个普通节点的异常识别处理函数

Mov esp,fs:[0]

Pop fs:[0]  //将临时保护节点脱出seh链表

Mov esp,ebp

Pop ebp

Ret 0x14

 

如上,这个函数会加入一个临时保护节点,将普通节点的异常识别、处理过程保护起来,以捕捉嵌套异常

下面我们看看嵌套保护节点的识别处理函数是如何识别处理异常的

_RtlpExceptionProtector:

Mov eax,ExceptionContinueSearch

Mov ecx,[esp+4]   //ecx=异常记录结构的地址

Test dword
ptr[ecx+EXCEPTION_RECORD_EXCEPTION_EFLAGS],

              EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND

Jnz Return  //第二次才满足条件return,表示继续向上展开

//第一次进来时执行下面的语句,返回ExceptionNestedException,表示本保护节点发现了嵌套异常,但//不处理,交给上层的节点去处理

Mov ecx,[esp+8]

Mov edx,[esp+16]

Mov eax,[ecx+8]

Mov [edx],eax   //返回所保护的节点到DispatchContext参数中

Mov eax,ExceptionNestedException

Return :

Ret 16

 

 

如上,这个函数并不处理嵌套异常,只是简单返回一个ExceptionNestedException标记发生了嵌套异常,还得由上层的节点去处理

 

好了:下面看看普通节点的识别处理函数是如何对异常进行识别并处理的

int  _SEHFrameHandler

(

     struct
_EXCEPTION_RECORD * ExceptionRecord,//异常记录

     void
* EstablisherFrame,//目标节点

     struct
_CONTEXT * ContextRecord,//异常发生时的寄存器现场

     void
* DispatcherContext //用于返回所保护的节点

)

{

     _SEHPortableFrame_t
* frame;

     _SEHCleanHandlerEnvironment();

     frame
= EstablisherFrame;

     //如果该异常已经识别了,已处于展开状态,那么本函数就用来展开该节点中的所有try

     //可见,这个函数有两个用途

     if(ExceptionRecord->ExceptionFlags
& (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND))

     {

         _SEHLocalUnwind(frame, NULL);//NULL表示局部展开该节点中的所有try

     }

     else

     {

         int
ret;

         _SEHPortableTryLevel_t
* trylevel;

         if(ExceptionRecord->ExceptionCode)

              frame->SPF_Code = ExceptionRecord->ExceptionCode;

         else

              frame->SPF_Code = 0xC0000001;

          //关键。遍历该节点中的所有try块,以找到一个能识别处理该异常的try

         for(trylevel = frame->SPF_TopTryLevel; trylevel; trylevel = trylevel->SPT_Next)

         {

              _SEHFilter_t pfnFilter = trylevel->SPT_Handlers.SH_Filter;

              _SEH_TRACE_TRYLEVEL(frame, trylevel);

              switch((UINT_PTR)pfnFilter)

              {

                   case (UINT_PTR)_SEH_STATIC_FILTER(_SEH_EXECUTE_HANDLER):

                   case (UINT_PTR)_SEH_STATIC_FILTER(_SEH_CONTINUE_SEARCH):

                   case (UINT_PTR)_SEH_STATIC_FILTER(_SEH_CONTINUE_EXECUTION):

                   {

                       ret = (int)((UINT_PTR)pfnFilter)
– 2;

                       break;

                   }

 

                   default:

                   {

                       if(trylevel->SPT_Handlers.SH_Filter)

                       {

                            EXCEPTION_POINTERS ep;

                            ep.ExceptionRecord
= ExceptionRecord;

                            ep.ContextRecord = ContextRecord;

//关键。调用该try块的过滤函数对异常进行识别

                            ret = pfnFilter(&ep, frame);

                       }

                       else

                            ret = _SEH_CONTINUE_SEARCH;

                       break;

                   }

              }

              if(ret < 0) /* _SEH_CONTINUE_EXECUTION */

                   return ExceptionContinueExecution;//返回原异常指令处继续执行

              else if(ret
> 0) /* _SEH_EXECUTE_HANDLER */

                   _SEHCallHandler(frame,
trylevel); //调用该try块的处理函数(即except块)

              Else  /* _SEH_CONTINUE_SEARCH */

                   continue; //继续查找下一个try块(即上层try块)

         }

     }

     return
ExceptionContinueSearch;

}

 

void  _SEHCallHandler(_SEHPortableFrame_t * frame, _SEHPortableTryLevel_t * trylevel)

{

     _SEHGlobalUnwind(frame);//先全局展开下层的所有节点

     _SEHLocalUnwind(frame, trylevel);//再局部展开同一函数内部的内层try

     // SPF_Handler固定为_SEHCompilerSpecificHandler函数

     frame->SPF_Handler(trylevel);//间接执行本try块的处理函数(即except块)

}

 

__declspec(noreturn) Void  _SEHCompilerSpecificHandler(_SEHPortableTryLevel_t*
trylevel)

{

   _SEHTryLevel_t*
tl=_SEH_CONTAINING_RECORD(trylevel,_SEHTryLevel_t,ST_Header);

   //这个函数前文看过,它恢复寄存器现场,回到原try块所在函数内部,执行except块代码

   _SEHLongJmp(tl->ST_JmpBuf,1)//1表示返回值改成1,从而进入else分支去执行except块代码

}

 

如上,一旦找到一个能处理该异常的try后,就会先后进行全局展开、局部展开,调用下面各层try块的finally函数。我们看:

__SEHGlobalUnwind:

Push ebx mov ebx,[esp+8] //当前节点*

Push esi

Push edi

Push 0

Push 0

Push RestoreRegisters //表示让SEHRtlUnwind函数返回来RestoreRegisters

Push ebx

Call __SEHRtlUnwind

RestoreRegisters:

Pop edi

Pop esi

Pop ebx

Ret

 

 

下面看看全局展开过程:

VOID  //全局展开TargetFrame节点下层的所有节点

RtlUnwind(IN PVOID
TargetFrame OPTIONAL,//指定节点

          IN
PVOID TargetIp
OPTIONAL,//本函数的返回地址

          IN
PEXCEPTION_RECORD ExceptionRecord
OPTIONAL,

          IN
PVOID ReturnValue)

{

    PEXCEPTION_REGISTRATION_RECORD
RegistrationFrame, OldFrame;

    DISPATCHER_CONTEXT
DispatcherContext;

    EXCEPTION_RECORD
ExceptionRecord2, ExceptionRecord3;

    EXCEPTION_DISPOSITION
Disposition;

    ULONG_PTR
StackLow, StackHigh;

    ULONG_PTR
RegistrationFrameEnd;

    CONTEXT
LocalContext;

    PCONTEXT
Context;

    RtlpGetStackLimits(&StackLow, &StackHigh);

    if
(!ExceptionRecord) //自己构造一个异常记录

    {

        ExceptionRecord
= &ExceptionRecord3;

        ExceptionRecord3.ExceptionFlags = 0;

        ExceptionRecord3.ExceptionCode = STATUS_UNWIND;

        ExceptionRecord3.ExceptionRecord = NULL;

        ExceptionRecord3.ExceptionAddress = _ReturnAddress();

        ExceptionRecord3.NumberParameters = 0;

    }

    if
(TargetFrame)

        ExceptionRecord->ExceptionFlags |= EXCEPTION_UNWINDING;

    else

        ExceptionRecord->ExceptionFlags |= (EXCEPTION_UNWINDING
| EXCEPTION_EXIT_UNWIND);

   
Context = &LocalContext;

    LocalContext.ContextFlags = CONTEXT_INTEGER
|CONTEXT_CONTROL |CONTEXT_SEGMENTS;

    RtlpCaptureContext(Context);//获得当前的寄存器上下文

    Context->Esp += sizeof(TargetFrame) +

                    sizeof(TargetIp) +

                    sizeof(ExceptionRecord) +

                    sizeof(ReturnValue);

 

Context->Eax
= (ULONG)ReturnValue;

//从底往上遍历指定节点下层的所有节点,调用各层节点的局部展开函数

    RegistrationFrame
= RtlpGetExceptionList();

    while
(RegistrationFrame != EXCEPTION_CHAIN_END)

    {

        if
(RegistrationFrame == TargetFrame)

 ZwContinue(Context,
FALSE);//展开到指定节点后就结束了

        if
((TargetFrame) && (TargetFrame < RegistrationFrame))
//其他异常

        {

            ExceptionRecord2.ExceptionCode = STATUS_INVALID_UNWIND_TARGET;

            ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;

            ExceptionRecord2.ExceptionRecord = ExceptionRecord;

            ExceptionRecord2.NumberParameters = 0;

            RtlRaiseException(&ExceptionRecord2);

        }

        RegistrationFrameEnd
= (ULONG_PTR)RegistrationFrame
+

                               sizeof(EXCEPTION_REGISTRATION_RECORD);

        if
((RegistrationFrameEnd > StackHigh) || ((ULONG_PTR)RegistrationFrame < StackLow)
||

            ((ULONG_PTR)RegistrationFrame
& 0x3))

        {

            if
(RtlpHandleDpcStackException(RegistrationFrame,RegistrationFrameEnd,

                                           
&StackLow,&StackHigh))

            {

                continue;

            }

            ExceptionRecord2.ExceptionCode = STATUS_BAD_STACK;

            ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;

            ExceptionRecord2.ExceptionRecord = ExceptionRecord;

            ExceptionRecord2.NumberParameters = 0;

            RtlRaiseException(&ExceptionRecord2);

        }

        else

        {

            //临时加入一个展开保护节点,再调用各层节点的识别处理函数,不过那个函数这次的用途//是用作局部展开了

            Disposition
= RtlpExecuteHandlerForUnwind(ExceptionRecord,

                                                     
RegistrationFrame,

                                                      Context,

                                                     
&DispatcherContext,

                                                     
RegistrationFrame->Handler);

            switch(Disposition)//检查展开结果

            {

                case
ExceptionContinueSearch://展开成功,就继续向上展开

                    break;

                case ExceptionCollidedUnwind
://展开的展开保护节点本身,也继续向上展开

                    RegistrationFrame = DispatcherContext.RegistrationPointer;

                    break;

                default:

                    ExceptionRecord2.ExceptionRecord
= ExceptionRecord;

                    ExceptionRecord2.ExceptionCode
= STATUS_INVALID_DISPOSITION;

                    ExceptionRecord2.ExceptionFlags
= EXCEPTION_NONCONTINUABLE;

                    ExceptionRecord2.NumberParameters
= 0;

                    RtlRaiseException(&ExceptionRecord2);

                    break;

            }

            OldFrame
= RegistrationFrame;

            RegistrationFrame
= RegistrationFrame->Next;

            RtlpSetExceptionList(OldFrame);//局部展开一个节点后,就脱出seh链表

        }

}

//其他异常

    if
(TargetFrame == EXCEPTION_CHAIN_END)

        ZwContinue(Context, FALSE);

    else

        ZwRaiseException(ExceptionRecord, Context,
FALSE);

}

 

如上,所谓的全局展开,即使逐层调用各下层节点的局部展开函数而已,真正的展开工作函数还是靠调用

_SEHLocalUnwind函数实现的

void _SEHLocalUnwind(_SEHPortableFrame_t * frame, _SEHPortableTryLevel_t * dsttrylevel)

{

     _SEHPortableTryLevel_t
* trylevel;

     for(trylevel = frame->SPF_TopTryLevel;trylevel
!= dsttrylevel;

                                                 trylevel = trylevel->SPT_Next)

     {

         _SEHFinally_t
pfnFinally;

         pfnFinally
= trylevel->SPT_Handlers.SH_Finally;

         if(pfnFinally)

              pfnFinally(frame);//若有finally块,就调用finally函数块

     }

}

 

下面看看展开保护节点的识别处理函数是如何处理展开过程中发生的异常的

_RtlpUnwindProtector:

Mov eax,ExceptionContinueSearch

Mov ecx,[esp+4]

Test dword
ptr[ecx+EXCPTION_RECORD_EXCEPTION_FLAGS]

                   
EXCEPTION_UNWINDING|EXCEPTION_EXIT_UNWIND

Jz Return //第一次会return
ExceptionContinueSearch
,表示不处理该异常,交给上层处理

//第二次则用来展开保护节点本身

Mov ecx,[esp+8]

Mov edx,[esp+16]

Mov eax,[ecx+8]

Mov [edx],eax //返回所保护的那个节点到DispatchContext参数中

Move ax,ExceptionCollidedUnwind

Return:

Ret 16

 

在前面的KiDispatchException函数中,我们看到,若异常发生在用户空间,则会返回到用户空间的异常派遣函数总入口来执行。

下面看看用户空间异常的派遣处理流程

VOID  KiUserExceptionDispatcher(PEXCEPTION_RECORD
ExceptionRecord, PCONTEXT
Context)

{

    EXCEPTION_RECORD
NestedExceptionRecord;

NTSTATUS Status;

// RtlDispatchException是用户空间的函数,与内核的那个同名函数实现是一样的

//正常情况下,用户空间的seh是一定能处理这个异常的,因此不会返回来

if (RtlDispatchException(ExceptionRecord, Context))

{

//如果用户seh链表返回‘继续执行’,就回到内核,然后退回原用户空间的异常指令处继续执行

        Status
= NtContinue(Context,
FALSE);

}

Else //如果seh链表最顶层的try块过滤函数发现调试器,RtlDispatchException就会返回FALSE

{

    //注意这次调用NtRaiseException的最后一个参数表示bFirst=FALSE,这将导致再次进入内核中//KiDispatchException函数,将异常第二次发给调试器去处理。

        Status
= NtRaiseException(ExceptionRecord,
Context, FALSE);

}

    不管是NtContinue还是NtRaiseException,正常情况下都是不返回的。若返回来了,一定是其他异常。

    NestedExceptionRecord.ExceptionCode = Status;

    NestedExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE;

    NestedExceptionRecord.ExceptionRecord = ExceptionRecord;

    NestedExceptionRecord.NumberParameters = Status;

    RtlRaiseException(&NestedExceptionRecord);

}

打赏