• 异常篇—— VEH 与 SEH


    写在前面

      此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

    你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

      看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。


    🔒 华丽的分割线 🔒


    概述

      当用户异常产生后,内核函数KiDispatchException并不是像处理内核异常那样在0环直接进行处理 ,而是修正3环EIP为KiUserExceptionDispatcher函数后就结束了。这样,当线程再次回到3环时,将会从KiUserExceptionDispatcher函数开始执行,这个函数就是我们重点关注对象,我们先看一下它的流程:

    1. 调用RtlDispatchException,查找并执行异常处理函数。
    2. 如果RtlDispatchException返回真,调用ZwContinue再次进入0环,但线程再次返回3环时,会从修正后的位置开始执行。
    3. 如果RtlDispatchException返回假,调用ZwRaiseException进行第二轮异常分发。

      看完上面的流程之后,我们看看其反汇编:

    ; void __stdcall __noreturn KiUserExceptionDispatcher(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextFrame)
                    public _KiUserExceptionDispatcher@8
    _KiUserExceptionDispatcher@8 proc near  ; DATA XREF: .text:off_7C923428↑o
    
    var_C           = dword ptr -0Ch
    var_8           = dword ptr -8
    var_4           = dword ptr -4
    ExceptionRecord = dword ptr  4
    ContextFrame    = dword ptr  8
    
                    mov     ecx, [esp+ExceptionRecord]
                    mov     ebx, [esp]
                    push    ecx             ; ContextRecord
                    push    ebx             ; ExceptionRecord
                    call    _RtlDispatchException@8 ; RtlDispatchException(x,x)
                    or      al, al
                    jz      short loc_7C92E47A
                    pop     ebx
                    pop     ecx
                    push    0
                    push    ecx
                    call    _ZwContinue@8   ; ZwContinue(x,x)
                    jmp     short loc_7C92E485
    ; ---------------------------------------------------------------------------
    
    loc_7C92E47A:                           ; CODE XREF: KiUserExceptionDispatcher(x,x)+10↑j
                    pop     ebx
                    pop     ecx
                    push    0               ; FirstChance
                    push    ecx             ; ContextRecord
                    push    ebx             ; ExceptionRecord
                    call    _ZwRaiseException@12 ; ZwRaiseException(x,x,x)
    
    loc_7C92E485:                           ; CODE XREF: KiUserExceptionDispatcher(x,x)+1C↑j
                    add     esp, -14h
                    mov     [esp+EXCEPTION_RECORD.ExceptionCode], eax
                    mov     [esp+EXCEPTION_RECORD.ExceptionFlags], 1
                    mov     [esp+EXCEPTION_RECORD.ExceptionRecord], ebx
                    mov     [esp+EXCEPTION_RECORD.NumberParameters], 0
                    push    esp             ; ExceptionRecord
                    call    _RtlRaiseException@4 ; RtlRaiseException(x)
    _KiUserExceptionDispatcher@8 endp ; sp-analysis failed
    

      可以看出该函数会调用RtlDispatchException,为了节省篇幅用伪代码如下:

    BOOLEAN __stdcall RtlDispatchException(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextRecord)
    {
      // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
    
      result = 0;
      if ( RtlCallVectoredExceptionHandlers(ExceptionRecord, ContextRecord) )
        return 1;
      RtlpGetStackLimits(&LowLimit, &HighLimit);
      ExceptionRecorda = 0;
      exRecord = RtlpGetRegistrationHead();         // ExceptionList
      if ( exRecord != -1 )
      {
        while ( 1 )
        {
          if ( exRecord < LowLimit
            || &exRecord[1] > HighLimit
            || (exRecord & 3) != 0
            || (handler = exRecord->Handler, handler >= LowLimit) && handler < HighLimit
            || !RtlIsValidHandler(exRecord->Handler) )
          {
            ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
            return result;
          }
          if ( byte_7C99B3FA < 0 )
            v11 = RtlpLogExceptionHandler(ExceptionRecord, ContextRecord, 0, exRecord, 0x10u);
          RtlpExecuteHandlerForException(ExceptionRecord, exRecord, ContextRecord, &a4, exRecord->Handler);
          v6 = v5;
          if ( byte_7C99B3FA < 0 )
            RtlpLogLastExceptionDisposition(v11, v5);
          if ( ExceptionRecorda == exRecord )
          {
            ExceptionRecord->ExceptionFlags &= 0xFFFFFFEF;
            ExceptionRecorda = 0;
          }
          if ( !v6 )
            break;
          if ( v6 == 1 )
          {
            if ( (ExceptionRecord->ExceptionFlags & 8) != 0 )
              return result;
          }
          else
          {
            if ( v6 != 2 )
            {
              e.ExceptionCode = EXCEPTION_INVALID_DISPOSITION;
              e.ExceptionFlags = 1;
              e.ExceptionRecord = ExceptionRecord;
              e.NumberParameters = 0;
              RtlRaiseException(&e);
            }
            v8 = a4;
            ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;
            if ( v8 > ExceptionRecorda )
              ExceptionRecorda = v8;
          }
          exRecord = exRecord->Next;
          if ( exRecord == -1 )
            return result;
        }
        if ( (ExceptionRecord->ExceptionFlags & 1) != 0 )
        {
          e.ExceptionCode = EXCEPTION_NONCONTINUABLE_EXCEPTION;
          e.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
          e.ExceptionRecord = ExceptionRecord;
          e.NumberParameters = 0;
          RtlRaiseException(&e);
        }
        result = 1;
      }
      return result;
    }
    

      RtlCallVectoredExceptionHandlers这个函数就是用来执行VEH的。如果返回假,则说明没有,后面的RtlpGetRegistrationHead就会获取SEH,如果有就执行,它是在堆栈中的。
      有了这些铺垫后,我们来介绍VEHSEH

    VEH

      对于VEH,这个是XP及其之后才有的,中文为向量化异常结构处理。我们先看看它的处理流程:

    1. CPU捕获异常信息;
    2. 通过KiDispatchException进行分发;
    3. KiUserExceptionDispatcher调用RtlDispatchException
    4. RtlDispatchException查找VEH处理函数链表 并调用相关处理函数;
    5. 代码返回到KiUserExceptionDispatcher
    6. 调用ZwContinue再次进入0环(ZwContinue调用NtContinue,主要作用就是恢复_TRAP_FRAME然后通过KiServiceExit返回到3环);
    7. 线程再次返回3环后,从修正后的位置开始执行;

      如下是执行VEH的伪代码:

    BOOLEAN __stdcall RtlCallVectoredExceptionHandlers(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextRecord)
    {
      PRTL_VECTORED_HANDLER_ENTRY p; // esi
      int (__stdcall *VectoredHandler)(EXCEPTION_POINTERS *); // eax
      EXCEPTION_POINTERS ExceptionInfo; // [esp+4h] [ebp-8h] BYREF
      BOOLEAN v6; // [esp+17h] [ebp+Bh]
    
      if ( IsListEmpty(&RtlpCalloutEntryList) )
        return 0;
      ExceptionInfo.ExceptionRecord = ExceptionRecord;
      ExceptionInfo.ContextRecord = ContextRecord;
      RtlEnterCriticalSection(&RtlpCalloutEntryLock);
      for ( p = RtlpCalloutEntryList.Flink; ; p = p->ListEntry.Flink )
      {
        if ( p == &RtlpCalloutEntryList )
        {
          v6 = 0;
          goto EndProc;
        }
        VectoredHandler = RtlDecodePointer(p->VectoredHandler);
        if ( VectoredHandler(&ExceptionInfo) == -1 )
          break;
      }
      v6 = 1;
    EndProc:
      RtlLeaveCriticalSection(&RtlpCalloutEntryLock);
      return v6;
    }
    

      剩余的细节将会在总结与提升进行讲解,下面我们来看看如何使用VEH,如下是实验代码:

    #include "stdafx.h"
    #include <windows.h>
    #include <stdlib.h>
    
    typedef PVOID (NTAPI *VectoredExceptionHandler)(ULONG,_EXCEPTION_POINTERS*);
    
    LONG NTAPI MyVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
    {
        puts("进入异常处理函数……");
        if (pExceptionInfo->ExceptionRecord->ExceptionCode==0xC0000094)
        {
            puts("异常函数处理了……");
            pExceptionInfo->ContextRecord->Ecx = 1;
            return EXCEPTION_CONTINUE_EXECUTION;
        }
    
        return EXCEPTION_CONTINUE_SEARCH;
    }
    
    int main(int argc, char* argv[])
    {
        HMODULE lib = LoadLibrary("kernel32.dll");
        VectoredExceptionHandler AddVectoredExceptionHandler = (VectoredExceptionHandler)GetProcAddress(lib,"AddVectoredExceptionHandler");
        AddVectoredExceptionHandler(1,(_EXCEPTION_POINTERS*)&MyVectoredExceptionHandler);
            
        _asm
        {
            xor edx,edx;
            xor ecx,ecx;
            mov eax,0x10;
            idiv ecx;
        }
        puts("继续执行……");
        system("pause");
        return 0;
    }
    

      执行后会正常执行,并显示异常处理信息。

    SEH

      SEH意为结构化异常处理,它的结构如下图所示:

      也就是说包装的异常处理项目是以单向链表的形式管理的。必须具有两个如上图所示的成员,也就是说,这个结构是可以扩展的,有关扩展的将会在后续介绍,下面我们来看实验代码:

    #include "stdafx.h"
    #include <windows.h>
    #include <stdlib.h>
    
    struct MyException 
    {
        MyException* prev;
        DWORD handle;
    };
    
    EXCEPTION_DISPOSITION MyExceptionHandler(_EXCEPTION_RECORD* ExceptionRecord,void* Establisherframe,CONTEXT* context,void* DispatcherContext)
    {
        puts("进入异常处理……");
        if (ExceptionRecord->ExceptionCode==0xC0000094)
        {
            puts("开始处理异常……");
            context->Eip+=2;
            return ExceptionContinueExecution;
        }
        return ExceptionContinueSearch;
    }
    
    int main(int argc, char* argv[])
    {
    
        DWORD tmp;
        //初始化异常结构
        MyException ex={(MyException*)tmp,(DWORD)MyExceptionHandler};
    
        //加入 SEH
        _asm
        {
            mov eax,fs:[0];
            mov tmp,eax;
            lea ecx,ex;
            mov fs:[0],ecx;
        }
    
        //制造异常    
        _asm
        {
            xor edx,edx;
            xor ecx,ecx;
            mov eax,0x10;
            idiv ecx;
        }
            
        //撤掉 SEH
        _asm
        {
            mov eax,tmp;
            mov fs:[0],eax;
        }
            
        puts("正常运行……");
        system("pause");
        return 0;
    }
    

      该程序正常执行,并打印异常处理结果。

    编译器扩展 SEH

    初识

      前面我们用自己的方式实现了SEH的使用。异常处理很重要,但是,这个对于开发者很不友好。每次都要构造SEH,退出函数要撤掉。编译器提供了关键字,并对SEH进行了扩充,使用如下图所示:

    _try    // 挂入 SEH 链表
    {
           
    }
    _except(/*过滤表达式*/) //异常过滤
    {
      //异常处理程序
    }  
    

      对于过滤表达式的结果值,只能是-101,它们表示的含义如下:

    1. EXCEPTION_EXECUTE_HANDLER (1) 执行except里面的代码
    2. EXCEPTION_CONTINUE_SEARCH (0) 寻找下一个异常处理函数
    3. EXCEPTION_CONTINUE_EXECUTION (-1) 返回出错位置重新执行

      我说只能是这三值,并没有说只能写这三个数字,你可以写入表达式或者函数,使其得到的结果或者返回值是这仨值其中之一就可以,如下是我们的实验程序:

    #include "stdafx.h"
    #include <stdlib.h>
    
    int main(int argc, char* argv[])
    {
        _try
        {
            _asm
            {
                xor edx,edx;
                xor ecx,ecx;
                mov eax,0x10;
                idiv ecx;
            }
            puts("继续跑……");
        }_except(1)
        {
            puts("异常处理……");
        }
        system("pause");
        return 0;
    }
    

      运行该程序,只打印了except里面的,得到正确结果。

    初步深入

      我们接下来在汇编层面查看它是如何实现的,首先我们查看一下编译器为我们扩展的结构,否则看代码是看不懂的。

    struct _EXCEPTION_REGISTRATION
    {
      struct _EXCEPTION_REGISTRATION *prev;
      void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
      struct scopetable_entry *scopetable;
      int trylevel;
      int _ebp;
    };       
    

      然后我们所谓的结构就成立这样子:

      图中的_except_handler3是啥我们看它的反汇编是什么就知道了:

    #include "stdafx.h"
    #include <stdlib.h>
    
    int main(int argc, char* argv[])
    {
    00401010   push        ebp
    00401011   mov         ebp,esp
    00401013   push        0FFh
    00401015   push        offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed"+0Ch (00424030)
    0040101A   push        offset __except_handler3 (00401400)
    0040101F   mov         eax,fs:[00000000]
    00401025   push        eax
    00401026   mov         dword ptr fs:[0],esp
    0040102D   add         esp,0B8h
    00401030   push        ebx
    00401031   push        esi
    00401032   push        edi
    00401033   mov         dword ptr [ebp-18h],esp
    00401036   lea         edi,[ebp-58h]
    00401039   mov         ecx,10h
    0040103E   mov         eax,0CCCCCCCCh
    00401043   rep stos    dword ptr [edi]
        _try
    00401045   mov         dword ptr [ebp-4],0
        {
            _asm
            {
                xor edx,edx;
    0040104C   xor         edx,edx
                xor ecx,ecx;
    0040104E   xor         ecx,ecx
                mov eax,0x10;
    00401050   mov         eax,10h
                idiv ecx;
    00401055   idiv        eax,ecx
            }
            puts("继续跑……");
    00401057   push        offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed" (00424024)
    0040105C   call        puts (004011e0)
    00401061   add         esp,4
        }_except(1)
    00401064   mov         dword ptr [ebp-4],0FFFFFFFFh
    0040106B   jmp         $L865+17h (0040108a)
    $L864:
    0040106D   mov         eax,1
    $L866:
    00401072   ret
    $L865:
    00401073   mov         esp,dword ptr [ebp-18h]
        {
            puts("异常处理……");
    00401076   push        offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xa1\xad\xa1\xad" (00425140)
    0040107B   call        puts (004011e0)
    00401080   add         esp,4
        }
    00401083   mov         dword ptr [ebp-4],0FFFFFFFFh
        system("pause");
    0040108A   push        offset string "pause" (0042401c)
    0040108F   call        system (004010d0)
    00401094   add         esp,4
        return 0;
    00401097   xor         eax,eax
    }
    00401099   mov         ecx,dword ptr [ebp-10h]
    0040109C   mov         dword ptr fs:[0],ecx
    004010A3   pop         edi
    004010A4   pop         esi
    004010A5   pop         ebx
    004010A6   add         esp,58h
    004010A9   cmp         ebp,esp
    004010AB   call        __chkesp (004012d0)
    004010B0   mov         esp,ebp
    004010B2   pop         ebp
    004010B3   ret
    

      看不懂吗?我们来画个堆栈图,如下所示:

      标注*的表示原来的值,是不是和结构体的成员对应起来了?注意不要以为只有黄色的区域,由于通常的函数采用ebp寻址,所以我没有把ebp*打上黄色底色。
      下面我们来看看scopetable成员,它的结构如下:

    struct scopetable_entry
    {
      DWORD previousTryLevel; //上一个try{}结构编号 
      PDWRD lpfnFilter; //过滤函数的起始地址
      PDWRD lpfnHandler;  //异常处理程序的地址     
    }
    

      我们来看看这个结构的内容是啥,最终它的成员如下:

    scopetable.previousTryLevel = -1;
    scopetable.lpfnFilter = 0x40106D;
    scopetable.lpfnHandler = 0x401073;
    

      正好把代码指令和地址逐个对应起来了。

    继续深入

      如果异常处理有嵌套调用的情况会是怎么样呢?如下是测试代码:

    #include "stdafx.h"
    #include <stdlib.h>
    
    int main(int argc, char* argv[])
    {
      _try
      {
        _try
        {
          _asm
          {
            xor edx,edx;
            xor ecx,ecx;
            mov eax,0x10;
            idiv ecx;
          } 
        }_except(1)
        {
          puts("测试");
        } 
        puts("继续跑……");
      }_except(1)
      {
        puts("异常处理……");
      }
      system("pause");
      return 0;
    }
    

      然后查看反汇编结果:

    #include "stdafx.h"
    #include <stdlib.h>
    
    int main(int argc, char* argv[])
    {
    00401010   push        ebp
    00401011   mov         ebp,esp
    00401013   push        0FFh
    00401015   push        offset string "\xb2\xe2\xca\xd4"+0Ch (00424050)
    0040101A   push        offset __except_handler3 (00401450)
    0040101F   mov         eax,fs:[00000000]
    00401025   push        eax
    00401026   mov         dword ptr fs:[0],esp
    0040102D   add         esp,0B8h
    00401030   push        ebx
    00401031   push        esi
    00401032   push        edi
    00401033   mov         dword ptr [ebp-18h],esp
    00401036   lea         edi,[ebp-58h]
    00401039   mov         ecx,10h
    0040103E   mov         eax,0CCCCCCCCh
    00401043   rep stos    dword ptr [edi]
        _try
    00401045   mov         dword ptr [ebp-4],0
        {
            _try
    0040104C   mov         dword ptr [ebp-4],1
            {
                _asm
                {
                    xor edx,edx;
    00401053   xor         edx,edx
                    xor ecx,ecx;
    00401055   xor         ecx,ecx
                    mov eax,0x10;
    00401057   mov         eax,10h
                    idiv ecx;
    0040105C   idiv        eax,ecx
                }
            }_except(1)
    0040105E   mov         dword ptr [ebp-4],0
    00401065   jmp         $L872+17h (0040f5d4)
    $L871:
    00401067   mov         eax,1
    $L873:
    0040106C   ret
    $L872:
    0040106D   mov         esp,dword ptr [ebp-18h]
            {
                puts("测试");
    00401070   push        offset string "\xb2\xe2\xca\xd4" (00424044)
    00401075   call        puts (00401230)
    0040107A   add         esp,4
            }
    0040107D   mov         dword ptr [ebp-4],0
        puts("继续跑……");
    00401084   push        offset string "\xbc\xcc\xd0\xf8\xc5\xdc\xa1\xad\xa1\xad" (00424034)
    00401089   call        puts (00401230)
    0040108E   add         esp,4
        }_except(1)
    00401091   mov         dword ptr [ebp-4],0FFFFFFFFh
    00401098   jmp         $L868+17h (004010b7)
    $L867:
    0040109A   mov         eax,1
    $L869:
    0040109F   ret
    $L868:
    004010A0   mov         esp,dword ptr [ebp-18h]
        {
            puts("异常处理……");
    004010A3   push        offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xa1\xad\xa1\xad" (00424024)
    004010A8   call        puts (00401230)
    004010AD   add         esp,4
        }
    004010B0   mov         dword ptr [ebp-4],0FFFFFFFFh
        system("pause");
    004010B7   push        offset string "pause" (0042401c)
    004010BC   call        system (00401120)
    004010C1   add         esp,4
        return 0;
    004010C4   xor         eax,eax
    }
    004010C6   mov         ecx,dword ptr [ebp-10h]
    004010C9   mov         dword ptr fs:[0],ecx
    004010D0   pop         edi
    004010D1   pop         esi
    004010D2   pop         ebx
    004010D3   add         esp,58h
    004010D6   cmp         ebp,esp
    004010D8   call        __chkesp (00401320)
    004010DD   mov         esp,ebp
    004010DF   pop         ebp
    004010E0   ret
    

      看代码发现还是只是挂了一次,我们得看看scopetable的内容是啥了:

    00425168  FFFFFFFF  0040109A  004010A0  
    00425174  00000000  00401067  0040106D  
    00425180  00000000  00000000  00000000  
    0042518C  00000000  00000000  00000000
    

      可以看到,这里有两个成员了。

    finally 关键字

      当然不仅仅有try_except,还可以使用finally,该关键字的作用就是只要退出try就执行里面的函数,无论通过那种方式,如下是我们的实验代码:

    #include "stdafx.h"
    #include <stdlib.h>
    
    int main(int argc, char* argv[])
    {
      _try
      {
        return 0;
      }__finally
      {
        puts("异常处理……");
        system("pause");
      }
      return 0;
    }
    

      执行结果如下:

    异常处理……
    请按任意键继续. . .
    

      然后我们看看它在汇编层面是如何实现的,其反汇编如下:

    #include "stdafx.h"
    #include <stdlib.h>
    
    int main(int argc, char* argv[])
    {
    00401010   push        ebp
    00401011   mov         ebp,esp
    00401013   push        0FFh
    00401015   push        offset string "stream != NULL"+10h (00425168)
    0040101A   push        offset __except_handler3 (00401450)
    0040101F   mov         eax,fs:[00000000]
    00401025   push        eax
    00401026   mov         dword ptr fs:[0],esp
    0040102D   add         esp,0B4h
    00401030   push        ebx
    00401031   push        esi
    00401032   push        edi
    00401033   lea         edi,[ebp-5Ch]
    00401036   mov         ecx,11h
    0040103B   mov         eax,0CCCCCCCCh
    00401040   rep stos    dword ptr [edi]
        _try
    00401042   mov         dword ptr [ebp-4],0
    00401049   push        0FFh
    0040104B   mov         dword ptr [ebp-1Ch],0
        {
    00401052   lea         eax,[ebp-10h]
    00401055   push        eax
    00401056   call        __local_unwind2 (0040139a)
    0040105B   add         esp,8
            return 0;
    0040105E   mov         eax,dword ptr [ebp-1Ch]
    00401061   jmp         $L865+2 (00401080)
        }__finally
        {
            puts("异常处理……");
    00401063   push        offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xa1\xad\xa1\xad" (00424024)
    00401068   call        puts (00401230)
    0040106D   add         esp,4
            system("pause");
    00401070   push        offset string "pause" (0042401c)
    00401075   call        system (00401120)
    0040107A   add         esp,4
    $L863:
    0040107D   ret
        }
    17:       return 0;
    0040107E   xor         eax,eax
    }
    00401080   mov         ecx,dword ptr [ebp-10h]
    00401083   mov         dword ptr fs:[0],ecx
    0040108A   pop         edi
    0040108B   pop         esi
    0040108C   pop         ebx
    0040108D   add         esp,5Ch
    00401090   cmp         ebp,esp
    00401092   call        __chkesp (00401320)
    00401097   mov         esp,ebp
    00401099   pop         ebp
    0040109A   ret
    

      可以看到在调用return 0;之前,被插入了调用__local_unwind2函数,正是这个函数能够调用finally里面的代码的:

    __local_unwind2:
    0040139A   push        ebx
    0040139B   push        esi
    0040139C   push        edi
    0040139D   mov         eax,dword ptr [esp+10h]
    004013A1   push        eax
    004013A2   push        0FEh
    004013A4   push        offset __global_unwind2+20h (00401378)
    004013A9   push        dword ptr fs:[0]
    004013B0   mov         dword ptr fs:[0],esp
    004013B7   mov         eax,dword ptr [esp+20h]
    004013BB   mov         ebx,dword ptr [eax+8]
    004013BE   mov         esi,dword ptr [eax+0Ch]
    004013C1   cmp         esi,0FFh
    004013C4   je          __NLG_Return2+2 (004013f4)
    004013C6   cmp         esi,dword ptr [esp+24h]
    004013CA   je          __NLG_Return2+2 (004013f4)
    004013CC   lea         esi,[esi+esi*2]
    004013CF   mov         ecx,dword ptr [ebx+esi*4]
    004013D2   mov         dword ptr [esp+8],ecx
    004013D6   mov         dword ptr [eax+0Ch],ecx
    004013D9   cmp         dword ptr [ebx+esi*4+4],0
    004013DE   jne         __NLG_Return2 (004013f2)
    004013E0   push        101h
    004013E5   mov         eax,dword ptr [ebx+esi*4+8]
    004013E9   call        __NLG_Notify (0040142e)
    004013EE   call        dword ptr [ebx+esi*4+8]
    __NLG_Return2:
    004013F2   jmp         __local_unwind2+1Dh (004013b7)
    004013F4   pop         dword ptr fs:[0]
    004013FB   add         esp,0Ch
    004013FE   pop         edi
    004013FF   pop         esi
    00401400   pop         ebx
    00401401   ret
    

      关键调用在call dword ptr [ebx+esi*4+8],执行这个就会调用finally里的代码,这个调用流程又被成为异常展开。具体详细的其他细节将会在总结与提升进行介绍。

    下一篇

      异常篇——总结与提升

  • 相关阅读:
    力扣:108. 将有序数组转换为二叉搜索树(Python3)
    前端体验优化(3)——后端
    excel每行按模板导出为一个excel文件,可以指定列文本生成二维码或者条形码
    <C++>vector容器在算法题中应用那么广泛,确定不来深入了解一下吗
    在springboot框架中用Configuration注解的方式写一个java过滤器的详细实例?
    SQL中有关数据查询的练习
    Jlink_V9固件修复教程
    【RabbitMQ】【Docker】基于docker-compose构建rabbitmq容器
    vue课程71 deep选项
    Go语言基础之包
  • 原文地址:https://www.cnblogs.com/wingsummer/p/15946358.html