異常篇—— VEH 與 SEH

寂靜的羽夏發表於2022-02-28

寫在前面

  此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看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意為結構化異常處理,它的結構如下圖所示:

異常篇—— VEH 與 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;
};       

  然後我們所謂的結構就成立這樣子:

異常篇—— VEH 與 SEH

  圖中的_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

  看不懂嗎?我們來畫個堆疊圖,如下所示:

異常篇—— VEH 與 SEH

  標註*的表示原來的值,是不是和結構體的成員對應起來了?注意不要以為只有黃色的區域,由於通常的函式採用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裡的程式碼,這個呼叫流程又被成為異常展開。具體詳細的其他細節將會在總結與提升進行介紹。

下一篇

  異常篇——總結與提升

相關文章