Chinaunix首页 | 论坛 | 博客
  • 博客访问: 145899
  • 博文数量: 124
  • 博客积分: 70
  • 博客等级: 民兵
  • 技术积分: 1745
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-24 13:49
文章分类

全部博文(124)

文章存档

2011年(55)

2010年(14)

2009年(30)

2008年(25)

我的朋友

分类: WINDOWS

2008-06-03 14:58:00

环境: 我们的分析环境是 windows XP SP2简体中文, VC++ 6 PS6 , windbg 6.8.
目的: 深入理解windows是如何处理异常的.
 
 
我们不再演示如何使用SEH,我们先来看一个简单的例子来分析os是如何对seh提供支持的.
背景:
windows 使用FS:[0] 寄存器来存储当前线程的异常处理hander 列表的头结点。
因此对于每新增加的一个__try block 就相当于在这个列表的头结点增加了一个结构.
 
异常处理handler的结构如下,这个包含在vc++ 6 的运行库源代码中.
从结构可以看到这个市含有8 BYTES 的一个结构.
_EXCEPTION_REGISTRATION struc
     prev    dd      ?
     handler dd      ?
 _EXCEPTION_REGISTRATION ends
// seh_1
DWORD  scratch;

EXCEPTION_DISPOSITION
__cdecl
_except_handler(
    struct _EXCEPTION_RECORD *ExceptionRecord,
    EXCEPTION_REGISTRATION  * EstablisherFrame,
    struct _CONTEXT *ContextRecord,
    void * DispatcherContext )
{
    unsigned i;

    // Indicate that we made it to our exception handler
    printf( "Hello from an exception handler\n" );

    // Change EAX in the context record so that it points to someplace
    // where we can successfully write
    ContextRecord->Eax = (DWORD)&scratch;

    // Tell the OS to restart the faulting instruction
    return ExceptionContinueExecution;
}
int main()
{
    DWORD handler = (DWORD)_except_handler;

    __asm
    {                           // Build EXCEPTION_REGISTRATION record:
        push    handler         // Address of handler function
        push    FS:[0]          // Address of previous handler
        mov     FS:[0],ESP      // Install new EXECEPTION_REGISTRATION
    }

    __asm
    {
        mov     eax,0           // Zero out EAX
        mov     [eax], 1        // Write to EAX to deliberately cause a fault
    }

    printf( "After writing!\n" );

    __asm
    {                           // Remove our EXECEPTION_REGISTRATION record
        mov     eax,[ESP]       // Get pointer to previous record
        mov     FS:[0], EAX     // Install previous record
        add     esp, 8          // Clean our EXECEPTION_REGISTRATION off stack
    }

    return 0;
}

我们来看看FS:[0]在建立新的frame前后的情况.

 
上图上可以看作是建立新的exception_registration 前以及恢复后的样子。
上图下可以看作是建立了新的exception_registration 后的样子.
 
从c语言的伪代码角度看可以理解如下.

EXCEPTION_DISPOSITION
__cdecl
_except_handler(
    struct _EXCEPTION_RECORD *ExceptionRecord,
    void * EstablisherFrame,
    struct _CONTEXT *ContextRecord,
    void * DispatcherContext )
{
  return 0;
}
 
 
struct EXCEPTION_REGISTRATION
{
EXCEPTION_REGISTRATION* pPrev;
VOID*     pHandler;
};
 
// 假定 指向结构的指针= 结构的第一个成员
int main()
{
    EXCEPTION_REGISTRATION new;
    new.pPrev = FS:[0];
    new.handler = _except_handler;
 
    FS:[0] = &new;
 
    .....
 
    FS:[0] = new.pPrev;
    ....
}
 
通过上面的这个例子的分析,我们就可以清楚地看到os是如何处理exception frame的.
 
下面继续看第二个例子. 这个例子将展示一个exception unwind.
struct A
{
 ~A()
 {
  printf("A destroyed \n");
 }
 A* pPrev;
 EXCEPTION_HANDLER handler;
};

void func()
{
 A newhandler;

 __asm
 {
  lea ecx , DWORD PTR [newhandler]
  mov edx , FS:[0]
  mov DWORD PTR [ecx ] , edx
  mov DWORD PTR [ecx + 4] , offset _except_handler
  mov FS:[0] , ecx
 }

 int i = 0 ;
 i = 10 / i;

 __asm
 {
  mov edx , DWORD PTR [ecx]
  mov FS:[0] , edx
 }

 printf("never here \n");
}

int main(int argc, char* argv[])
{

 __try
 {
  func();
 }
 __except(EXCEPTION_EXECUTE_HANDLER )
 {
  printf(" here \n");
 }
 return 0;
}

这个例子使用除0异常来触发,而异常分为两个层次,按照第一个例子的讨论,应该构建了一个

至少含有两个用户节点的EXCEPTION_REGISTRATION.其中内层的没有处理异常,而外层的处理了异常.

注意到代码的变化,结构A我增加了一个析购函数,因为需要观察在unwind时候c++对象的

unwind时候的表象,程序运行后看到的输入如下

handler: Exception Code: C0000094 Exception Flags 0
handler: Exception Code: C0000027 Exception Flags 2 EH_UNWINDING.

我们可以观察到A 的析购函数并没有被调用.

另外我们再来看这个输出,为什么出现了两次被调用而标记不一致.

细致的原因,比如说os在这儿作了什么?我们先不讨论,先说个结果.

当异常发生时候内层的handler被调用,由于内层handler没有处理,因此异常在linklist中继续前进找到前一个handler,非常凑巧,前一个handler处理了这个异常,在这时候,windows在外层异常的处理代码执行前,给了内层的所有frame一个机会,进行unwind,而unwind就是给比如c++ stack 对象一个解构机会的,当然我的代码没有利用这个机会所以自然无法解构了.

至于如何从os层面发生unwind我们在最后一个例子中进行整体展示.
 
这个例子我们来看看如果没有人handle异常,那么是一种什么样子的情况.
 
这个代码这儿不列出了,可以将上面的例子程序的最外城try/except 拿掉就可以了,看到如下
 
 
我们都知道这个是系统帮助我们pop出来的处理.
从刚才的分析来看,不管是谁,只要它要处理异常都必须有它自己的exception registration ,
那么我们可以假想os给每个进程使用了一个外层的try/except.
 
非常有幸,从网上流传的w2k source中能够看到BaseProcessStart 启动函数干了如下事情,这下
终于知道了,我们经常看到的这个crash box到底是谁安装了这个exception handler.
下面这个函数的核心是 UnhandledExceptionFilter , 这个函数我们留到最后一个例子来分析,
如果大家经常看dump,这个函数应该是比较常见的.
 
 
VOID
BaseProcessStart(
    PPROCESS_START_ROUTINE lpStartAddress
    )
{
    try {
        NtSetInformationThread( NtCurrentThread(),
                                ThreadQuerySetWin32StartAddress,
                                &lpStartAddress,
                                sizeof( lpStartAddress )
                              );
        ExitThread((lpStartAddress)());
        }
    except(UnhandledExceptionFilter( GetExceptionInformation() )) {
        if ( !BaseRunningInServerProcess ) {
            ExitProcess(GetExceptionCode());
            }
        else {
            ExitThread(GetExceptionCode());
            }
        }
}
 
到了目前为止,已经带大家领略了一下os提供的基础异常处理,那么实际上我们在使用过程中是不大可能直接
通过汇编代码来控制寄存器来完成的,而是使用c++ compiler 提供给我们的包装功能来完成的,那么我们
要看看c++ compiler , 这儿仅仅指vc++ 6 compiler 在这个上面提供了哪些支持?
在之前我们先来看关键结构, 这两个结构也是从w2k source中复制来的.
 
typedef struct _EXCEPTION_RECORD {
    /*lint -e18 */  // Don't complain about different definitions
    LONG ExceptionCode;
    /*lint +e18 */  // Resume checking for different definitions
    ULONG ExceptionFlags;   // 这个标记很重要
    struct _EXCEPTION_RECORD *ExceptionRecord;
    PVOID ExceptionAddress;
    ULONG NumberParameters;
    ULONG ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
typedef EXCEPTION_RECORD *PEXCEPTION_RECORD;
typedef struct _CONTEXT {
    //
    // This section is specified/returned if the ContextFlags word contains
    // the flag CONTEXT_FLOATING_POINT.
    //
    double Fpr0;                        // Floating registers 0..31
    double Fpr1;
    double Fpr2;
    double Fpr3;
    double Fpr4;
    double Fpr5;
    double Fpr6;
    double Fpr7;
    double Fpr8;
    double Fpr9;
    double Fpr10;
    double Fpr11;
    double Fpr12;
    double Fpr13;
    double Fpr14;
    double Fpr15;
    double Fpr16;
    double Fpr17;
    double Fpr18;
    double Fpr19;
    double Fpr20;
    double Fpr21;
    double Fpr22;
    double Fpr23;
    double Fpr24;
    double Fpr25;
    double Fpr26;
    double Fpr27;
    double Fpr28;
    double Fpr29;
    double Fpr30;
    double Fpr31;
    double Fpscr;                       // Floating point status/control reg
    //
    // This section is specified/returned if the ContextFlags word contains
    // the flag CONTEXT_INTEGER.
    //
    ULONG Gpr0;                         // General registers 0..31
    ULONG Gpr1;
    ULONG Gpr2;
    ULONG Gpr3;
    ULONG Gpr4;
    ULONG Gpr5;
    ULONG Gpr6;
    ULONG Gpr7;
    ULONG Gpr8;
    ULONG Gpr9;
    ULONG Gpr10;
    ULONG Gpr11;
    ULONG Gpr12;
    ULONG Gpr13;
    ULONG Gpr14;
    ULONG Gpr15;
    ULONG Gpr16;
    ULONG Gpr17;
    ULONG Gpr18;
    ULONG Gpr19;
    ULONG Gpr20;
    ULONG Gpr21;
    ULONG Gpr22;
    ULONG Gpr23;
    ULONG Gpr24;
    ULONG Gpr25;
    ULONG Gpr26;
    ULONG Gpr27;
    ULONG Gpr28;
    ULONG Gpr29;
    ULONG Gpr30;
    ULONG Gpr31;
    ULONG Cr;                           // Condition register
    ULONG Xer;                          // Fixed point exception register
    //
    // This section is specified/returned if the ContextFlags word contains
    // the flag CONTEXT_CONTROL.
    //
    ULONG Msr;                          // Machine status register
    ULONG Iar;                          // Instruction address register
    ULONG Lr;                           // Link register
    ULONG Ctr;                          // Count register
    //
    // The flags values within this flag control the contents of
    // a CONTEXT record.
    //
    // If the context record is used as an input parameter, then
    // for each portion of the context record controlled by a flag
    // whose value is set, it is assumed that that portion of the
    // context record contains valid context. If the context record
    // is being used to modify a thread's context, then only that
    // portion of the threads context will be modified.
    //
    // If the context record is used as an IN OUT parameter to capture
    // the context of a thread, then only those portions of the thread's
    // context corresponding to set flags will be returned.
    //
    // The context record is never used as an OUT only parameter.
    //
    ULONG ContextFlags;
    ULONG Fill[3];                      // Pad out to multiple of 16 bytes
    //
    // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
    // set in ContextFlags.  Note that CONTEXT_DEBUG_REGISTERS is NOT
    // included in CONTEXT_FULL.
    //
    ULONG Dr0;                          // Breakpoint Register 1
    ULONG Dr1;                          // Breakpoint Register 2
    ULONG Dr2;                          // Breakpoint Register 3
    ULONG Dr3;                          // Breakpoint Register 4
    ULONG Dr4;                          // Breakpoint Register 5
    ULONG Dr5;                          // Breakpoint Register 6
    ULONG Dr6;                          // Debug Status Register
    ULONG Dr7;                          // Debug Control Register
} CONTEXT, *PCONTEXT;
#define EXCEPTION_NONCONTINUABLE 0x1
#define EXCEPTION_UNWINDING 0x2
#define EXCEPTION_EXIT_UNWIND 0x4
#define EXCEPTION_STACK_INVALID 0x8
#define EXCEPTION_NESTED_CALL 0x10
#define EXCEPTION_TARGET_UNWIND 0x20
#define EXCEPTION_COLLIDED_UNWIND 0x40
#define EXCEPTION_UNWIND 0x66
#define EXCEPTION_EXECUTE_HANDLER 0x1
#define EXCEPTION_CONTINUE_SEARCH 0x0
#define EXCEPTION_CONTINUE_EXECUTION 0xffffffff
 
struct _EXCEPTION_REGISTRATION
{
     struct _EXCEPTION_REGISTRATION *prev;
     void (*handler)(PEXCEPTION_RECORD,
                     PEXCEPTION_REGISTRATION,
                     PCONTEXT,
                     PEXCEPTION_RECORD);
     struct scopetable_entry *scopetable;
     int trylevel;
     int _ebp;
     PEXCEPTION_POINTERS xpointers;
};

typedef struct _SCOPETABLE
{
     DWORD       previousTryLevel;
     DWORD       lpfnFilter
     DWORD       lpfnHandler
} SCOPETABLE, *PSCOPETABLE;

typedef struct _EXCEPTION_POINTERS {
    PEXCEPTION_RECORD ExceptionRecord;
    PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

我们看一个简单的例子.

 void func()
{
 __try
 {
  
  int i = 0 ;
  i = 10 / i;
 }
 __except(EXCEPTION_EXECUTE_HANDLER )
 {
  printf(" here \n");
 }
}
int main(int argc, char* argv[])
{
 func();
 return 0;
}
 
然后对关键部分看汇编,看看编译器帮助我们干了些沙子.
 
 
 
从早些时候的几个例子,我们看到编译器提供了一个_except_handler3 这个一个handler来帮助我们进行处理. 基本上和之前直接用asm写的代码类似,上图的代码体现了安装一个异常node,以及卸载的代码。
关键代码就是 
 
下面的代码进行安装.
0040114A   push        offset __except_handler3 (004043f0)
0040114F   mov         eax,fs:[00000000]
00401155   push        eax
00401156   mov         dword ptr fs:[0],esp
下面的代码进行卸载
004011B5   mov         ecx,dword ptr [ebp-10h]
004011B8   mov         dword ptr fs:[0],ecx
下面我们借助于windbg 来看看_except_handler3 到底帮助我们干了些砂子?
 
0:000> uf seh_4!_except_handler3
seh_4!_except_handler3:
004043f0 55              push    ebp
004043f1 8bec            mov     ebp,esp
004043f3 83ec08          sub     esp,8
004043f6 53              push    ebx
004043f7 56              push    esi
004043f8 57              push    edi
004043f9 55              push    ebp
004043fa fc              cld
004043fb 8b5d0c          mov     ebx,dword ptr [ebp+0Ch] // 拿出第2个参数
004043fe 8b4508          mov     eax,dword ptr [ebp+8]  // 拿出第1个参数
00404401 f7400406000000  test    dword ptr [eax+4],6     // 第1个参数的第二个成员,也就是ExceptionFlags
// 就是说如果不设置了这两个标记就跳转
// if(! (ExceptionRecord->flag & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)) )
00404408 0f8582000000    jne     seh_4!_except_handler3+0xa0 (00404490)
seh_4!_except_handler3+0x1e:   // 设置了标记位 EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND
0040440e 8945f8          mov     dword ptr [ebp-8],eax // EXCEPTION_RECORD* ptr = ExceptionRecord;
00404411 8b4510          mov     eax,dword ptr [ebp+10h] // 第3个参数
00404414 8945fc          mov     dword ptr [ebp-4],eax   // LPVOID context = DispatcherContext
00404417 8d45f8          lea     eax,[ebp-8]       
0040441a 8943fc          mov     dword ptr [ebx-4],eax   // 这句话将构建一个EXCEPTION_POINTER 供GetExceptionInfomation使用
                            // 实际上将第一个参数替换成了EXCEPTION_POINTER
                            // 且同时设定了调用registerframe filter时候的[ebp-14h]
                            // 这个很不直观,但是通过不断的调试就能够得到验证
0040441d 8b730c          mov     esi,dword ptr [ebx+0Ch] // get trylevel
00404420 8b7b08          mov     edi,dword ptr [ebx+8]  // scopetable
seh_4!_except_handler3+0x33:
00404423 83feff          cmp     esi,0FFFFFFFFh      // 判断trylevel是否 = -1
00404426 7461            je      seh_4!_except_handler3+0x99 (00404489)
seh_4!_except_handler3+0x38:
00404428 8d0c76          lea     ecx,[esi+esi*2]
0040442b 837c8f0400      cmp     dword ptr [edi+ecx*4+4],0   // 定位到scopetable[index]->filter
00404430 7445            je      seh_4!_except_handler3+0x87 (00404477)
seh_4!_except_handler3+0x42:      // 存在filter
00404432 56              push    esi
00404433 55              push    ebp
00404434 8d6b10          lea     ebp,[ebx+10h]   // EXCEPTION_REGISTRATION->ebp
00404437 ff548f04        call    dword ptr [edi+ecx*4+4] // call scopetable[trylevel]->lpfilter
             // 注意上面的call就将进入我们设定的frame的filter / except中.
             // 那么EXCEPTION_REGISTRATION->ebp 是否是触发异常是否德ebp,答案y
0040443b 5d              pop     ebp
0040443c 5e              pop     esi
0040443d 8b5d0c          mov     ebx,dword ptr [ebp+0Ch]
00404440 0bc0            or      eax,eax
00404442 7433            je      seh_4!_except_handler3+0x87 (00404477)
             // 这儿的判断主要是判断用户设定的filter返回了什么值
             // seh_4测试程序返回EXCEPTION_EXECUTE_HANDLER ,表示异常得到了处理
seh_4!_except_handler3+0x54:
00404444 783c            js      seh_4!_except_handler3+0x92 (00404482)
seh_4!_except_handler3+0x56:
00404446 8b7b08          mov     edi,dword ptr [ebx+8]  // edi == scopetable
00404449 53              push    ebx
0040444a e8a9feffff      call    seh_4!_global_unwind2 (004042f8) // 这个函数再分析,主要是对内层的frame进行清理
0040444f 83c404          add     esp,4
00404452 8d6b10          lea     ebp,[ebx+10h]   // ebp == regi_frame->ebp,也就是恢复到触发异常时候的ebp
00404455 56              push    esi
00404456 53              push    ebx
00404457 e8defeffff      call    seh_4!_local_unwind2 (0040433a) // 在解决异常的frame作本地清理,这个主要清理c++ compiler增加的scopetable内层frame
0040445c 83c408          add     esp,8
0040445f 8d0c76          lea     ecx,[esi+esi*2]
00404462 6a01            push    1
00404464 8b448f08        mov     eax,dword ptr [edi+ecx*4+8] // scopetable->lpfnHandler 拿出真正的exceplt block代码执行
00404468 e861ffffff      call    seh_4!_NLG_Notify (004043ce)
0040446d 8b048f          mov     eax,dword ptr [edi+ecx*4]
00404470 89430c          mov     dword ptr [ebx+0Ch],eax
00404473 ff548f08        call    dword ptr [edi+ecx*4+8] // 由于ebp被替换成了异常触发ebp因此这个函数掉用将返回到触发异常的frame上下文
seh_4!_except_handler3+0x87:
00404477 8b7b08          mov     edi,dword ptr [ebx+8]  // EXCEPTION_REGISTRATION第3个参数 scopetable
0040447a 8d0c76          lea     ecx,[esi+esi*2] 
0040447d 8b348f          mov     esi,dword ptr [edi+ecx*4] // 定位到scopetable的previousTryLevel
00404480 eba1            jmp     seh_4!_except_handler3+0x33 (00404423)
seh_4!_except_handler3+0x92:
00404482 b800000000      mov     eax,0
00404487 eb1c            jmp     seh_4!_except_handler3+0xb5 (004044a5)
seh_4!_except_handler3+0x99:
00404489 b801000000      mov     eax,1   // 设置返回值
0040448e eb15            jmp     seh_4!_except_handler3+0xb5 (004044a5)
seh_4!_except_handler3+0xa0:
00404490 55              push    ebp
00404491 8d6b10          lea     ebp,[ebx+10h] // 第二个参数的第5个成员 , pRegisterFrame->_ebp
00404494 6aff            push    0FFFFFFFFh    // -1
00404496 53              push    ebx     //
00404497 e89efeffff      call    seh_4!_local_unwind2 (0040433a)  //_local_unwind2(pRegisterFrame , -1)
0040449c 83c408          add     esp,8
0040449f 5d              pop     ebp
004044a0 b801000000      mov     eax,1
seh_4!_except_handler3+0xb5:  // 最后推出函数前的清理
004044a5 5d              pop     ebp
004044a6 5f              pop     edi
004044a7 5e              pop     esi
004044a8 5b              pop     ebx
004044a9 8be5            mov     esp,ebp
004044ab 5d              pop     ebp
004044ac c3              ret
通过上面的分析,大致就了解了_except_handler3到底干了些啥,当然global_unwind/local_unwind这两个函数还没有分析,我们可以继续往下看.
 
为了分析全局global_unwind 需要对seh_5的例子增加一个全局frame来观察内层的全局frame 是如何rollback的.
 
0:000> uf seh_5!_global_unwind2
seh_5!_global_unwind2:
004013b8 55              push    ebp
004013b9 8bec            mov     ebp,esp
004013bb 53              push    ebx
004013bc 56              push    esi
004013bd 57              push    edi
004013be 55              push    ebp
004013bf 6a00            push    0
004013c1 6a00            push    0
004013c3 68d0134000      push    offset seh_5!_global_unwind2+0x18 (004013d0)
004013c8 ff7508          push    dword ptr [ebp+8]
004013cb e882c40000      call    seh_5!RtlUnwind (0040d852)
004013d0 5d              pop     ebp
004013d1 5f              pop     edi
004013d2 5e              pop     esi
004013d3 5b              pop     ebx
004013d4 8be5            mov     esp,ebp
004013d6 5d              pop     ebp
004013d7 c3              ret
这个相当于globalunwind 仅仅做了一层转发.
_rtlunwind(pRegisterFrame , [retaddr] , 0 , 0)
 
这样问题就变成了rtlwind干了啥?
非常爽,我们从w2k中直接拿到了这个source,这样我就不再一一分析asm了.
VOID
RtlUnwind (
    IN PVOID TargetFrame OPTIONAL,
    IN PVOID TargetIp OPTIONAL,
    IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL,
    IN PVOID ReturnValue
    )
{
    PCONTEXT ContextRecord;
    CONTEXT ContextRecord1;
    DISPATCHER_CONTEXT DispatcherContext;
    EXCEPTION_DISPOSITION Disposition;
    PEXCEPTION_REGISTRATION_RECORD RegistrationPointer;
    PEXCEPTION_REGISTRATION_RECORD PriorPointer;
    ULONG HighAddress;
    ULONG HighLimit;
    ULONG LowLimit;
    EXCEPTION_RECORD ExceptionRecord1;
    EXCEPTION_RECORD ExceptionRecord2;
    //
    // Get current stack limits.
    //
    RtlpGetStackLimits(&LowLimit, &HighLimit);
    //
    // If an exception record is not specified, then build a local exception
    // record for use in calling exception handlers during the unwind operation.
    //
    if (ARGUMENT_PRESENT(ExceptionRecord) == FALSE) {
        ExceptionRecord = &ExceptionRecord1;
        ExceptionRecord1.ExceptionCode = STATUS_UNWIND;
        ExceptionRecord1.ExceptionFlags = 0;
        ExceptionRecord1.ExceptionRecord = NULL;
        ExceptionRecord1.ExceptionAddress = RtlpGetReturnAddress();
        ExceptionRecord1.NumberParameters = 0;
    }
    //
    // If the target frame of the unwind is specified, then set EXCEPTION_UNWINDING
    // flag in the exception flags. Otherwise set both EXCEPTION_EXIT_UNWIND and
    // EXCEPTION_UNWINDING flags in the exception flags.
    //
    if (ARGUMENT_PRESENT(TargetFrame) == TRUE) {
        ExceptionRecord->ExceptionFlags |= EXCEPTION_UNWINDING;
    } else {
        ExceptionRecord->ExceptionFlags |= (EXCEPTION_UNWINDING |
                                                        EXCEPTION_EXIT_UNWIND);
    }
    //
    // Capture the context.
    //
    ContextRecord = &ContextRecord1;
    ContextRecord1.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL | CONTEXT_SEGMENTS;
    RtlpCaptureContext(ContextRecord);
#ifdef STD_CALL
    //
    // Adjust captured context to pop our arguments off the stack
    //
    ContextRecord->Esp += sizeof(TargetFrame) +
                          sizeof(TargetIp)    +
                          sizeof(ExceptionRecord) +
                          sizeof(ReturnValue);
#endif
    ContextRecord->Eax = (ULONG)ReturnValue;
    //
    // Scan backward through the call frame hierarchy, calling exception
    // handlers as they are encountered, until the target frame of the unwind
    // is reached.
    //
    RegistrationPointer = RtlpGetRegistrationHead();
    while (RegistrationPointer != EXCEPTION_CHAIN_END) {
        //
        // If this is the target of the unwind, then continue execution
        // by calling the continue system service.
        //
        if ((ULONG)RegistrationPointer == (ULONG)TargetFrame) {
            ZwContinue(ContextRecord, FALSE);
        //
        // If the target frame is lower in the stack than the current frame,
        // then raise STATUS_INVALID_UNWIND exception.
        //
        } else if ( (ARGUMENT_PRESENT(TargetFrame) == TRUE) &&
                    ((ULONG)TargetFrame < (ULONG)RegistrationPointer) ) {
            ExceptionRecord2.ExceptionCode = STATUS_INVALID_UNWIND_TARGET;
            ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
            ExceptionRecord2.ExceptionRecord = ExceptionRecord;
            ExceptionRecord2.NumberParameters = 0;
            RtlRaiseException(&ExceptionRecord2);
        }
        //
        // If the call frame is not within the specified stack limits or the
        // call frame is unaligned, then raise the exception STATUS_BAD_STACK.
        // Else restore the state from the specified frame to the context
        // record.
        //
        HighAddress = (ULONG)RegistrationPointer +
            sizeof(EXCEPTION_REGISTRATION_RECORD);
        if ( ((ULONG)RegistrationPointer < LowLimit) ||
             (HighAddress > HighLimit) ||
             (((ULONG)RegistrationPointer & 0x3) != 0) ) {
#if defined(NTOS_KERNEL_RUNTIME)
            //
            // Allow for the possibility that the problem occured on the
            // DPC stack.
            //
            ULONG TestAddress = (ULONG)RegistrationPointer;
            if (((TestAddress & 0x3) == 0) &&
                KeGetCurrentIrql() >= DISPATCH_LEVEL) {
                PKPRCB Prcb = KeGetCurrentPrcb();
                ULONG DpcStack = (ULONG)Prcb->DpcStack;
                if ((Prcb->DpcRoutineActive) &&
                    (HighAddress <= DpcStack) &&
                    (TestAddress >= DpcStack - KERNEL_STACK_SIZE)) {
                    //
                    // This error occured on the DPC stack, switch
                    // stack limits to the DPC stack and restart
                    // the loop.
                    //
                    HighLimit = DpcStack;
                    LowLimit = DpcStack - KERNEL_STACK_SIZE;
                    continue;
                }
            }
#endif
            ExceptionRecord2.ExceptionCode = STATUS_BAD_STACK;
            ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
            ExceptionRecord2.ExceptionRecord = ExceptionRecord;
            ExceptionRecord2.NumberParameters = 0;
            RtlRaiseException(&ExceptionRecord2);
        } else {
            //
            // The handler must be executed by calling another routine
            // that is written in assembler. This is required because
            // up level addressing of the handler information is required
            // when a collided unwind is encountered.
            //
            Disposition = RtlpExecuteHandlerForUnwind(
                ExceptionRecord,
                (PVOID)RegistrationPointer,
                ContextRecord,
                (PVOID)&DispatcherContext,
                RegistrationPointer->Handler);
            //
            // Case on the handler disposition.
            //
            switch (Disposition) {
                //
                // The disposition is to continue the search. Get next
                // frame address and continue the search.
                //
            case ExceptionContinueSearch :
                break;
                //
                // The disposition is colided unwind. Maximize the target
                // of the unwind and change the context record pointer.
                //
            case ExceptionCollidedUnwind :
                //
                // Pick up the registration pointer that was active at
                // the time of the unwind, and simply continue.
                //
                RegistrationPointer = DispatcherContext.RegistrationPointer;
                break;

                //
                // All other disposition values are invalid. Raise
                // invalid disposition exception.
                //
            default :
                ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION;
                ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
                ExceptionRecord2.ExceptionRecord = ExceptionRecord;
                ExceptionRecord2.NumberParameters = 0;
                RtlRaiseException(&ExceptionRecord2);
                break;
            }
            //
            // Step to next registration record
            //
            PriorPointer = RegistrationPointer;
            RegistrationPointer = RegistrationPointer->Next;
            //
            // Unlink the unwind handler, since it's been called.
            //
            RtlpUnlinkHandler(PriorPointer);
            //
            // If chain goes in wrong direction or loops, raise an
            // exception.
            //
        }
    }
    if (TargetFrame == EXCEPTION_CHAIN_END) {
        //
        //  Caller simply wants to unwind all exception records.
        //  This differs from an exit_unwind in that no "exit" is desired.
        //  Do a normal continue, since we've effectively found the
        //  "target" the caller wanted.
        //
        ZwContinue(ContextRecord, FALSE);
    } else {
        //
        //  Either (1) a real exit unwind was performed, or (2) the
        //  specified TargetFrame is not present in the exception handler
        //  list.  In either case, give debugger and subsystem a chance
        //  to see the unwind.
        //
        ZwRaiseException(ExceptionRecord, ContextRecord, FALSE);
    }
    return;
}
 
 
cPublicProc _RtlpExecuteHandlerForException,5
        mov     edx,offset FLAT:ExceptionHandler    ; Set who to register
        jmp     ExecuteHandler                      ; jump to common code
stdENDP _RtlpExecuteHandlerForException
ExecuteHandler 的代码将给exception_handler一次机会也就是unwind.
然后rtlunwind 将从fs:[0] 中删除当前frame并且以prevframe 替代.
 
 
// _local_unwind2 的汇编分析的不太细,大致就是做了个循环,判断trylevel 是否=自己或者已经是最后一个了. 执行后,实际上将内层的scopetable entry丢弃了.
0:000> uf seh_5!_local_unwind2
seh_5!_local_unwind2:
004013fa 53              push    ebx
004013fb 56              push    esi
004013fc 57              push    edi
004013fd 8b442410        mov     eax,dword ptr [esp+10h] // trylevel
00401401 50              push    eax    
00401402 6afe            push    0FFFFFFFEh  // -1
00401404 68d8134000      push    offset seh_5!_global_unwind2+0x20 (004013d8)  // _global_uwnind2 ret后的地址
00401409 64ff3500000000  push    dword ptr fs:[0]  // 保存当前frame
00401410 64892500000000  mov     dword ptr fs:[0],esp  // 装了一个新的
seh_5!_local_unwind2+0x1d:
00401417 8b442420        mov     eax,dword ptr [esp+20h]  // trylevel
0040141b 8b5808          mov     ebx,dword ptr [eax+8]   // handler
0040141e 8b700c          mov     esi,dword ptr [eax+0Ch]  // prev
00401421 83feff          cmp     esi,0FFFFFFFFh       // 判断是否已经是最后一个
00401424 742e            je      seh_5!_NLG_Return2+0x2 (00401454)
seh_5!_local_unwind2+0x2c:
00401426 3b742424        cmp     esi,dword ptr [esp+24h]  // 判断是否=自己
0040142a 7428            je      seh_5!_NLG_Return2+0x2 (00401454)
seh_5!_local_unwind2+0x32:
0040142c 8d3476          lea     esi,[esi+esi*2] 
0040142f 8b0cb3          mov     ecx,dword ptr [ebx+esi*4] // previousTryLevel
00401432 894c2408        mov     dword ptr [esp+8],ecx   //
00401436 89480c          mov     dword ptr [eax+0Ch],ecx
00401439 837cb30400      cmp     dword ptr [ebx+esi*4+4],0 // lpfnFilter 是否== 0
0040143e 7512            jne     seh_5!_NLG_Return2 (00401452)
seh_5!_local_unwind2+0x46:
00401440 6801010000      push    101h
00401445 8b44b308        mov     eax,dword ptr [ebx+esi*4+8] // lpfnHandler
00401449 e840000000      call    seh_5!_NLG_Notify (0040148e)
0040144e ff54b308        call    dword ptr [ebx+esi*4+8]
seh_5!_NLG_Return2:
00401452 ebc3            jmp     seh_5!_local_unwind2+0x1d (00401417)
seh_5!_NLG_Return2+0x2:
00401454 648f0500000000  pop     dword ptr fs:[0]
0040145b 83c40c          add     esp,0Ch
0040145e 5f              pop     edi
0040145f 5e              pop     esi
00401460 5b              pop     ebx
00401461 c3              ret
 
 
当local_unwind返回后,就拿出当前registration_frame的handler执行.

 
未完待续!
阅读(1500) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~