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 鏈表也會被清理到當前的異常幀爲止。

發佈了59 篇原創文章 · 獲贊 9 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章