Win32 SEH异常深度探索_2 异常链表遍历

With this simplest of scenarios behind us, let's go back and fill in some of the blanks. While this exception callback is great, it's not a perfect solution. In an application of any size, it would be exceedingly messy to write a single function to handle exceptions that could occur anywhere in your application. A much more workable scenario would be to have multiple exception handling routines, each one customized to a particular section of your application. Wouldn't you know it, the operating system provides just this functionality.

希望只用一个异常回调函数就处理一个线程中的所有异常是很不现实的。如果对程序中的每一块都有独立的异常回调函数就好了。 OS 提供了这个功能。

 

Remember the EXCEPTION_REGISTRATION structure that the system uses to find the exception callback function? The first member of this structure, which I ignored earlier, is called prev. It's really a pointer to another EXCEPTION_REGISTRATION structure. This second EXCEPTION_REGISTRATION structure can have a completely different handler function. What's more, its prev field can point to a third EXCEPTION_REGISTRATION structure, and so on. Simply put, there's a linked list of EXCEPTION_REGISTRATION structures. The head of the list is always pointed to by the first DWORD in the thread information block (FS:[0] on Intel-based machines).

EXCEPTION_REGISTRATION 结构中还有一个部分,它是一个指针,指向另一个 EXCEPTION_REGISTRATION 结构,在那个结构中可能有另一个异常回调。 这样 EXCEPTION_REGISTRATION 其实形成了一个链表,链表头为 FS[0]

 

What does the operating system do with this linked list of EXCEPTION_REGISTRATION structures? When an exception occurs, the system walks the list of structures and looks for an EXCEPTION_REGISTRATION whose handler callback agrees to handle the exception. In the case of MYSEH.CPP, the handler callback agreed to handle the exception by returning the value ExceptionContinueExecution. The exception callback can also decline to handle the exception. In this case, the system moves on to the next EXCEPTION_REGISTRATION structure in the list and asks its exception callback if it wants to handle the exception. Figure 4 shows this process. Once the system finds a callback that handles the exception, it stops walking the linked list of EXCEPTION_REGISTRATIONs.

异常处理函数返回一个 EXCEPTION_DISPOSITION 中的一个枚举值,当它为 ExceptionContinueExecution ,表明这个函数愿意处理异常。当异常发生时, OS 会遍历这个链表,直到找到一个愿意处理异常的函数为止。

 

To show an example of an exception callback that doesn't handle an exception, check out MYSEH2.CPP in Figure 5 . To keep the code simple, I cheated a bit and used a little compiler-level exception handling. Function main just sets up a _try/_except block. Inside the _try block is a call to the HomeGrownFrame function. This function is very similar to the code in the earlier MYSEH program. It creates an EXCEPTION_REGISTRATION record on the stack and points FS:[0] at it. After establishing the new handler, the function intentionally causes a fault by writing to a NULL pointer:

这里有了另一个例子,演示 OS 如何遍历链表,直到找到一个能处理异常的函数。

 

 

……

A key point to bring up here is execution control. When a handler declines to handle an exception, it effectively declines to decide where control will eventually resume. The handler that accepts the exception is the one that decides where control will continue after all the exception handling code is finished. This has an important implication that's not immediately obvious.

这里有一个很关键的地方,就是是运行流程控制,那个接受异常的处理函数将决定当异常处理代码结束后从哪开始继续执行代码,这点非常关键。

换句话说就是哪个 catch() 语句捕获了异常,控制流程将运行 {} 块内的代码和其后的代码。

 

When using structured exception handling, a function may exit in an abnormal manner if it has an exception handler that doesn't handle the exception. For instance, in MYSEH2 the minimal handler in the HomeGrownFrame function didn't handle the exception. Since somebody later in the chain handled the exception (function main), the printf after the faulting instruction never executes. In some ways, using structured exception handling is similar to using the setjmp and longjmp runtime library functions.

如果一个函数收到异常却没处理,那么这个函数将以非正常方式退出。如上例中的 HomeGrownFrame 函数中的 printf 就不会被调用。从某些角度说, SHE 很类似 setjmp longjmp

 

If you run MYSEH2, you'll find something surprising in the output. It seems that there's two calls to the _except_handler function. The first call is certainly understandable, given what you know so far. But why the second?

如果你运行上面那个例子,你会很奇怪的发现 _except_handler 被调用了两次,第二次是怎么回事?

Home Grown handler: Exception Code: C0000005 Exception Flags 0

Home Grown handler: Exception Code: C0000027 Exception Flags 2                                               EH_UNWINDING

Caught the Exception in main()

 

There's obviously a difference: compare the two lines that start with "Home Grown Handler." In particular, the exception flags are zero the first time though, and 2 the second time. This brings me to the subject of unwinding. To jump ahead a bit, when an exception callback declines to handle an exception, it gets called a second time. This callback doesn't happen immediately, though. It's a bit more complicated then that. I'll need to refine the exception scenario one final time.

那两次输出有一点明显不同。第一次时 exception flags 0 ,而第二次为 2 。这将引入回退 (unwinding) 的概念。当一个异常处理函数拒绝处理异常,它会被再调一次 ( 用于做些清理工作 ) ,不过不是马上被调用。

 

When an exception occurs, the system walks the list of EXCEPTION_REGISTRATION structures until it finds a handler for the exception. Once a handler is found, the system walks the list again, up to the node that will handle the exception. During this second traversal, the system calls each handler function a second time. The key distinction is that in the second call, the value 2 is set in the exception flags. This value corresponds to EH_UNWINDING. (The definition for EH_UNWINDING is in EXCEPT.INC, which is in the Visual C++ runtime library sources, but nothing equivalent appears in the Win32 SDK.)

当异常发生,系统遍历 EXCEPTION_REGISTRATION 链表直到找到一个愿意处理异常的回调函数。然后再第二次遍历链表知道那个愿意处理异常的节点,对每个节点调用节点中的回调函数,并将 exception flags 设为 2 (2 代表了 EH_UNWINDING ,在 EXCEPT.INC 中定义 )

 

; unwind settings in fHandlerFlags

 

_EH_UNWINDING   equ     2

_EH_EXIT_UNWIND equ     4

UNWIND          equ     _EH_UNWINDING OR _EH_EXIT_UNWIND

 

 

; return values (to the exception dispatcher)

 

IFDEF   _WIN32

 

_XCPT_CONTINUE_SEARCH           equ     000000001h

_XCPT_CONTINUE_EXECUTION        equ     000000000h

 

What does EH_UNWINDING mean? When an exception callback is invoked a second time (with the EH_UNWINDING flag), the operating system is giving the handler function an opportunity to do any cleanup it needs to do. What sort of cleanup? A perfect example is that of a C++ class destructor. When a function's exception handler declines to handle an exception, control typically doesn't exit from that function in a normal manner. Now, consider a function with a C++ class declared as a local variable. The C++ specification says that the destructor must be called. The second exception handler callback with the EH_UNWINDING flag is the opportunity for the function to do cleanup work such as invoking destructors and _finally blocks.

EH_UNWINDING 意味着什么?当一个异常回调函数被第二次调用,操作系统给这个函数一次做清理工作的机会。什么类型的清理呢?可能是 C++ 类的析构函数的调用。当一个函数的异常回调函数拒绝处理异常,这个函数不正常退出,而 C++ 标准又要求必须调用局部类对象的析构函数,这时第二次异常回调函数的调用就给了函数这个清理的机会用来调用析构函数和 __finally 块。

 

After an exception is handled and all the previous exception frames have been called to unwind, execution continues wherever the handling callback decides. Remember though that it's just not enough to set the instruction pointer to the desired code address and plunge ahead. The code where execution resumes expects that the stack and frame pointer (the ESP and EBP registers on Intel CPUs) are set to their values within the stack frame that handled the exception. Therefore, the handler that accepts a particular exception is responsible for setting the stack and frame pointers to values that they had in the stack frame that contains the SEH code that handled the exception.

当之前的异常帧都以回退方式调用了异常回调函数,异常也被处理后,由处理了异常的回调函数决定接下来代码从哪开始执行。这包括设置 IP 到下一步的执行地址,还需要设置 ESP, EBP 指向当前函数的栈 (ESP 保存在 [EBP-18h])

 

In more general terms, the act of unwinding from an exception causes all things on the stack below the handling frame's stack region to be removed. It's almost as if those functions were never called. Another effect of unwinding is that all EXCEPTION_REGISTRATIONs in the list prior to the one that handled the exception are removed from the list. This makes sense, as these EXCEPTION_REGISTRATIONs are typically built on the stack. After the exception has been handled, the stack and frame pointers will be higher in memory than the EXCEPTION_REGISTRATIONs that were removed from the list. Figure 6 shows what I'm talking about.

概括的说,回退操作会使包含回调函数之前的函数的栈都被清理,就像那些函数从没被调用一样。另外就是 EXCEPTION_REGISTRATIONs 链表也会被清理到当前的异常帧为止。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值