Win32 SEH异常深度探索_1

最近学习 C++ 异常实现机制,面对一堆汇编代码无所适从,无意中看到这样一篇文章,读完后感觉对整个异常的原理清晰很多。看来学习还是要多去读读大牛的文章呀。

这里严格来说并不算是翻译,仅仅是提炼的一些总结而已。具体内容还是参照原文把。

原文地址: http://www.microsoft.com/msj/0197/exception/exception.aspx

 

A Crash Course on the Depths of Win32™ Structured Exception Handling

At its heart, Win32 structured exception handling is an operating system-provided service. All the docs you're likely to find about SEH describe one particular compiler's runtime library wrapping around the operating system implementation. I'll strip SEH to its most fundamental concepts.

 

Win32 结构化异常处理是由操作系统提供的服务。不过所有你能接触的文档都是某特定编译器包装后的代码,这里我将深入到 SEH 的最基本概念。

 

Imagine I told you that when a thread faults, the operating system gives you an opportunity to be informed of the fault. More specifically, when a thread faults, the operating system calls a user-defined callback function. This callback function can do pretty much whatever it wants. For instance, it might fix whatever caused the fault, or it might play a Beavis and Butt-head .WAV file. Regardless of what the callback function does, its last act is to return a value that tells the system what to do next. (This isn't strictly true, but it's close enough for now.)

 

当一个线程发生某些错误时,操作系统会调用一个由用户定义的回调函数来处理错误。这个回调函数可以做些事情来处理错误。

 

Given that the system calls you back when your code makes a mess, what should the callback function look like? In other words, what sort of information would you want to know about the exception? It really doesn't matter because Win32 has made up your mind for you. An exception callback function looks like this:

 

这个处理异常的回调函数原型如下:


 

这个函数在 EXCPT.H 中定义。

其中返回值类型定义如下:

 

ExceptionContinueExecution ( 第一次调用时返回)代表这个函数愿意处理这个异常。

ExceptionContinueSearch ( 第一次调用时返回)代表我不愿意处理这个异常,继续查找其他的函数。

ExceptionNestedException ( 第二次调用时返回) 代表处理异常时用户代码再次抛出异常

ExceptionCollidedUnwind ( 第二次调用时返回) 代表作清理工作时用户代码再次抛出异常

 

The first parameter to an _except_handler callback is a pointer to an EXCEPTION_RECORD. This structure is defined in WINNT.H, shown below:

参数 1 是一个指向 _EXCEPTION_RECORD 的指针,在< WINNT.H> 中定义

typedef struct _EXCEPTION_RECORD {

    DWORD    ExceptionCode;

    DWORD ExceptionFlags;

    struct _EXCEPTION_RECORD *ExceptionRecord;

    PVOID ExceptionAddress;

    DWORD NumberParameters;

    ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];

} EXCEPTION_RECORD;

 

The ExceptionCode parameter is the number that the operating system assigned to the exception. You can see a list of various exception codes in WINNT.H by searching for #defines that start with "STATUS_". For example, the code for the all-too-familiar STATUS_ACCESS_VIOLATION is 0xC0000005. A more complete set of exception codes can be found in NTSTATUS.H from the Windows NT DDK. The fourth element in the EXCEPTION_RECORD structure is the address where the exception occurred. The remaining EXCEPTION_RECORD fields can be ignored for the moment.

 

ExceptionCode 由操作系统给异常分配的一个数字,代表异常的编号,在 <WINNT.h>中定义,由 STATUS_ 开头。查找到的一些定义如下:

#define STATUS_ACCESS_VIOLATION          ((DWORD   )0xC0000005L)   

#define STATUS_IN_PAGE_ERROR             ((DWORD   )0xC0000006L)   

#define STATUS_INVALID_HANDLE            ((DWORD   )0xC0000008L)   

#define STATUS_NO_MEMORY                 ((DWORD   )0xC0000017L)   

#define STATUS_ILLEGAL_INSTRUCTION       ((DWORD   )0xC000001DL)   

#define STATUS_NONCONTINUABLE_EXCEPTION  ((DWORD   )0xC0000025L)   

#define STATUS_INVALID_DISPOSITION       ((DWORD   )0xC0000026L)   

#define STATUS_ARRAY_BOUNDS_EXCEEDED     ((DWORD   )0xC000008CL)   

 

可见眼熟的STATUS_ACCESS_VIOLATION 在这里。

另外 ExceptionAddress 保存了异常发生的指令地址。

 

The second parameter to the _except_handler function is a pointer to an establisher frame structure. This is a vital parameter in SEH, but for now you can ignore it.

第二个参数指向一个异常帧结构 ( 插入在相关用户函数的栈头部 ) 。暂时不考虑

 

The third parameter to the _except_handler callback is a pointer to a CONTEXT structure. The CONTEXT structure is defined in WINNT.H and represents the register values of a particular thread. Figure 1 shows the fields of a CONTEXT structure. When used for SEH, the CONTEXT structure represents the register values at the time of the exception. Incidentally, this CONTEXT structure is the same structure used with the GetThreadContext and SetThreadContext APIs.
The fourth and final parameter to the _except_handler callback is called the DispatcherContext. It also can be ignored for the moment.

第三个参数指向一个 CONTEXT 结构,和线程的 CONTEXT 结构完全一样,可以在 <WINNT.h> 中找到定义,主要包含了当时的寄存器状态。

 

typedef struct _CONTEXT {

DWORD ContextFlags;

DWORD   Dr0;

    DWORD   Dr1;

    DWORD   Dr2;

    DWORD   Dr3;

    DWORD   Dr6;

DWORD   Dr7;

FLOATING_SAVE_AREA FloatSave;

    DWORD   SegGs;

    DWORD   SegFs;

    DWORD   SegEs;

    DWORD   SegDs;

    DWORD   Edi;

    DWORD   Esi;

    DWORD   Ebx;

    DWORD   Edx;

    DWORD   Ecx;

    DWORD   Eax;

    DWORD   Ebp;

    DWORD   Eip;

    DWORD   SegCs;              // MUST BE SANITIZED

    DWORD   EFlags;             // MUST BE SANITIZED

    DWORD   Esp;

    DWORD   SegSs;

    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

} CONTEXT;

 

The fourth and final parameter to the _except_handler callback is called the DispatcherContext. It also can be ignored for the moment.

第四个参数也暂时不考虑。

 

To briefly recap thus far, you have a callback function that's called when an exception occurs. The callback takes four parameters, three of which are pointers to structures. Within these structures, some fields are important, others not so important. The key point is that the _except_handler callback function receives a wealth of information, such as what type of exception occurred and where it occurred. Using this information, the exception callback needs to decide what to do.

总结一下,就是当异常发生时,会有一个函数被调用,它会得到四个参数,参数中包含了大量的信息能帮助你处理异常。

 

While it's tempting for me to throw together a quickie sample program that shows the _except_handler callback in action, there's still something missing. In particular, how does the operating system know where to call when a fault occurs? The answer is yet another structure called an EXCEPTION_REGISTRATION. You'll see this structure throughout this article, so don't skim past this part. The only place I could find a formal definition of an EXCEPTION_REGISTRATION was in the EXSUP.INC file from the Visual C++ runtime library sources:

操作系统如何知道当异常发生时去哪找异常处理函数 (_except_handler) ?会有一个叫做 EXCEPTION_REGISTRATION 的东西。他在 < EXSUP.INC > 文件中定义。找到定义如下:

 

;typedef struct _EXCEPTION_REGISTRATION PEXCEPTION_REGISTRATION;

;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;

;};

_EXCEPTION_REGISTRATION struc

    prev                dd      ?

    handler             dd      ?

_EXCEPTION_REGISTRATION ends

 

看到其中一个 handler 的变量了吧。

 

You'll also see this structure referred to as an _EXCEPTION_REGISTRATION_RECORD in the definition of the NT_TIB structure from WINNT.H. Alas, nowhere is an _EXCEPTION_REGISTRATION_RECORD defined, so all I have to work from is the assembly language struc definition in EXSUP.INC. This is just one example of what I meant earlier when I said that SEH was underdocumented.

同时,在 <WINNT.h> 中你可以找到一个 _NT_TIB 的定义,里面有一个指向_EXCEPTION_REGISTRATION_RECORD 的指针。可惜,你无法找到_EXCEPTION_REGISTRATION_RECORD 的定义。所以我说 SEH 有很多没写入文档的东西。

typedef struct _NT_TIB {

    struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;

    PVOID StackBase;

    PVOID StackLimit;

    PVOID SubSystemTib;

    union {

        PVOID FiberData;

        DWORD Version;

    };

    PVOID ArbitraryUserPointer;

    struct _NT_TIB *Self;

} NT_TIB;

typedef NT_TIB *PNT_TIB;

 

In any event, let's return to the question at hand. How does the OS know where to call when an exception occurs? The EXCEPTION_REGISTRATION structure consists of two fields, the first of which you can ignore for now. The second field, handler, contains a pointer to an _except_ handler callback function. This gets you a little closer, but now the question becomes, where does the OS look to find the EXCEPTION_REGISTRATION structure?

言归正传,操作系统如何知道当异常发生时去哪调用处理函数? EXCEPTION_REGISTRATION 结构包含两个部分,其中一个指向了 _except_ handler 函数。那么 OS 去哪找这个 EXCEPTION_REGISTRATION 结构?

 

To answer this question, it's helpful to remember that structured exception handling works on a per-thread basis. That is, each thread has its own exception handler callback function. In my May 1996 column, I described a key Win32 data structure, the thread information block (aka the TEB or TIB). Certain fields of this data structure are the same between Windows NT, Windows® 95, Win32s, and OS/2. The first DWORD in a TIB is a pointer to the thread's EXCEPTION_REGISTRATION structure. On the Intel Win32 platform, the FS register always points to the current TIB. Thus, at FS:[0] you can find a pointer to an EXCEPTION_REGISTRATION structure.

 

结构化异常处理是基于线程处理的,每个线程有自己的一个异常处理回调函数。每个线程都有一个叫做线程环境块的东西 (TEB/TIB) ,其中的第一个 DWORD 保存了一个指向线程的 EXCEPTION_REGISTRATION 结构的指针 ( 见上面的 _NT_TIB 的定义 ) 。而 FS 段寄存器指向这个 TIB 所以可以通过 FS[0] 找到 EXCEPTION_REGISTRATION 结构。

 

With the minimal pieces finally put together, I wrote a small program to demonstrate this very simple description of OS-level structured exception handling. Figure 3 shows MYSEH.CPP, which has only two functions. Function main uses three inline ASM blocks. The first block builds an EXCEPTION_REGISTRATION structure on the stack via two PUSH instructions ("PUSH handler" and "PUSH FS:[0]"). The PUSH FS:[0] saves the previous value of FS:[0] as part of the structure, but that's not important at the moment. The significant thing is that there's an 8-byte EXCEPTION_REGISTRATION structure on the stack. The very next instruction (MOV FS:[0],ESP) makes the first DWORD in the thread information block point at the new EXCEPTION_REGISTRATION structure.

好,这里有个小程序演示前面的内容。程序中有三个 ASM 块,第一个块设置当前线程的异常处理函数,第二个块中会发生一个异常,第三个块中恢复线程的处理函数。

 

//==================================================

// MYSEH - Matt Pietrek 1997

// Microsoft Systems Journal, January 1997

// FILE: MYSEH.CPP

// To compile: CL MYSEH.CPP

//==================================================

#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#include <stdio.h>

 

DWORD  scratch;

 

EXCEPTION_DISPOSITION

__cdecl

_except_handler(

    struct _EXCEPTION_RECORD *ExceptionRecord,

    void * 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;

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值