Of all the facilities provided by Win32®operating systems, perhaps the most widely used but underdocumented is structured exception handling (SEH). When you think of Win32 structured exception handling, you probably think of terms like _try, _finally, and _except. You can find good descriptions of SEH in just about any competent Win32 book (even the remedial ones). Even the Win32 SDK has a fairly complete overview of using structured exception handling with _try, _finally, and _except.
Since the details of SEH can be overwhelming if taken all at once, I'll start out small and work my way up through the layers. If you've never worked with structured exception handling before, you're in good shape; you have no preconceived notions. If you've used SEH before, try to clear your head of words like _try, GetExceptionCode, and EXCEPTION_EXECUTE_HANDLER. Pretend that this is all new to you. Take a deep breath. Are you ready? Good. |
EXCEPTION_DISPOSITION __cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext ); |
This prototype, which comes from the standard Win32 header file EXCPT.H, looks a little overwhelming at first. If you take it slowly, it's really not so bad. For starters, ignore the return type (EXCEPTION_DISPOSITION). What you basically have is a function called _except_handler that takes four parameters. The first parameter to an _except_handler callback is a pointer to an EXCEPTION_RECORD. This structure is defined in WINNT.H, shown below: |
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
DWORD 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. 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. 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: |
_EXCEPTION_REGISTRATION struc prev dd ? handler dd ? _EXCEPTION_REGISTRATION ends |
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. 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? 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. Now I'm getting somewhere! When an exception occurs, the system looks at the TIB of the faulting thread and retrieves a pointer to an EXCEPTION_REGISTRATION structure. In this structure is a pointer to an _except_handler callback function. The operating system now knows enough to call the _except_handler function, as shown in Figure 2. |
Figure 2 _except_handler_function |
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. If you're wondering why I built the EXCEPTION_REGISTRATION structure on the stack rather than using a global variable, there's a good reason. When you use a compiler's _try/_except syntax, the compiler also builds the EXCEPTION_REGISTRATION struct on the stack. I'm simply showing you a simplified version of what a compiler would do if you used _try/_except. Back to function main, the next __asm block intentionally causes a fault by zeroing out the EAX register (MOV EAX,0), and then uses the register's value as a memory address, which the next instruction writes to (MOV [EAX],1). The final __asm block removes this simple exception handler: first it restores the previous contents of FS:[0], and then it pops the EXCEPTION_REGISTRATION record off the stack (ADD ESP,8). Now, pretend that you're running MYSEH.EXE and you'll see what happens. When the MOV [EAX],1 instruction executes, it causes an access violation. The system looks at the FS:[0] in the TIB and finds a pointer to the EXCEPTION_REGISTRATION structure. In the structure is a pointer to the _except_handler function in MYSEH.CPP. The system then pushes the four required parameters (which I described earlier) onto the stack and calls the _except_handler function. Once inside _except_handler, the code first indicates "Yo! I made it here!" via a printf statement. Next, _except_handler fixes the problem that caused the fault. That is, the EAX register points to a memory address that can't be written to (address 0). The fix is to change the EAX value in the CONTEXT structure so that it points to a location where writing is allowed. In this simple program, a DWORD variable (scratch) has been designated for just this purpose. The last act of the _except_handler function is to return the value ExceptionContinueExecution, which is defined in the standard EXCPT.H file. When the operating system sees that ExceptionContinueExecution was returned, it interprets this to mean that you've fixed the problem and the faulting instruction should be restarted. Since my _except_handler function tweaked the EAX register to point to valid memory, the MOV EAX,1 instruction works the second time and function main continues normally. See, that wasn't so complicated, was it?
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. |
*(PDWORD)0 = 0;
|
The exception callback function, again called _except_ handler, is quite different than the earlier version. The code first prints out the exception code and flags from the ExceptionRecord structure that the function received as a pointer parameter. The reason for printing out the exception flags will become clear later. Since this _except_handler function has no intention of fixing the offending code, the function returns ExceptionContinueSearch. This causes the operating system to continue its search at the next EXCEPTION_REGISTRATION record in the linked list. For now, take my word for it; the next installed exception callback is for the _try/_except code in function main. The _except block simply prints out "Caught the exception in main()". In this case, handling the exception is as simple as ignoring that it happened. 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. 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. 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? |
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. 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.) 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. 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. |
Figure 6 Unwinding from an Exception |
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.
So far, I've implicitly assumed that the operating system always finds a handler somewhere in the linked list of EXCEPTION_REGISTRATIONs. What happens if nobody steps up to the plate? As it turns out, this almost never happens. The reason is that the operating system sneaks in a default exception handler for each and every thread. The default handler is always the last node in the linked list, and it always chooses to handle the exception. Its actions are somewhat different than a normal exception callback routine, as I'll show later. |
Figure 8 Unhandled Exception Dialog |
At a high level, UnhandledExceptionFilter displays a dialog telling you that a fault occurred. At that point, you're given the opportunity to either terminate or debug the faulting process. Much more happens behind the scenes, and I'll describe these things towards the end of the article. As I've shown, when an exception occurs, user-written code can (and often does) get executed. Likewise, during an unwind operation, user-written code can execute. This user-written code may have bugs and could cause another exception. For this reason, there are two other values that an exception callback can return: ExceptionNestedException and ExceptionCollidedUnwind. While obviously important, this is pretty advanced stuff and I'm not going to dwell on it here. It's difficult enough to understand the basic facts.
While I've made occasional reference to _try and _except, just about everything I've written about so far is implemented by the operating system. However, in looking at the contortions of my two small programs that used raw system SEH, a compiler wrapper around this functionality is definitely in order. Let's now see how Visual C++ builds its structured exception handling support on top of the system-level SEH facilities. |
try {
// guarded body of code
}
except (filter-expression) {
// exception-handler block
}
|
To be a bit simplistic, all of the code within a try block in a function is protected by an EXCEPTION_REGISTRATION that's built on the function's stack frame. On function entry, the new EXCEPTION_REGISTRATION is put at the head of the linked list of exception handlers. After the end of the _try block, its EXCEPTION_REGISTRATION is removed from the head of the list. As I mentioned earlier, the head of the exception handler chain is kept at FS:[0]. Thus, if you're stepping through assembly language in a debugger and you see instructions such as |
MOV DWORD PTR FS:[00000000],ESP
|
or |
MOV DWORD PTR FS:[00000000],ECX
|
you can be pretty sure that the code is setting up or tearing down a _try/_except block. Now that you know that a _try block corresponds to an EXCEPTION_REGISTRATION structure on the stack, what about the callback function within the EXCEPTION_ REGISTRATION? Using Win32 terminology, the exception callback function corresponds to the filter-expression code. To refresh your memory, the filter-expression is the code in parens after the _except keyword. It's this filter-expression code that decides whether the code in the subsequent {} block will execute. Since you write the filter-expression code, you get to decide if a particular exception will be handled at this particular point in your code. Your filter-expression code might be as simple as saying "EXCEPTION_EXECUTE_ HANDLER." Alternatively, the filter-expression might invoke a function that calculates p to 20 million places before returning a code telling the system what to do next. It's your choice. The key point: your filter-expression code is effectively the exception callback that I described earlier. What I've just described, while reasonably simple, is nonetheless a rose-colored-glasses view of the world. The ugly reality is that things are more complicated. For starters, your filter-expression code isn't called directly by the operating system. Rather, the exception handler field in every EXCEPTION_REGISTRATION points to the same function. This function is in the Visual C++ runtime library and is called __except_handler3. It's __except_handler3 that calls your filter-expression code, and I'll come back to it a bit later. Another twist to the simplistic view that I described earlier is that EXCEPTION_REGISTRATIONs aren't built and torn down every time a _try block is entered or exits. Instead, there's just one EXCEPTION_REGISTRATION created in any function that uses SEH. In other words, you can have multiple _try/_except constructs in a function, yet only one EXCEPTION_REGISTRATION is created on the stack. Likewise, you might have a _try block nested within another _try block in a function. Still, Visual C++ creates only one EXCEPTION_REGISTRATION. If a single exception handler (that is, __except_handler3) suffices for the whole EXE or DLL, and if a single EXCEPTION_REGISTRATION handles multiple _try blocks, there's obviously more going on here than meets the eye. This magic is accomplished through data in tables that you don't normally see. However, since the whole purpose of this article is to dissect structured exception handling, let's take a look at these data structures.
The Visual C++ SEH implementation doesn't use the raw EXCEPTION_REGISTRATION structure. Instead, it adds additional data fields to the end of the structure. This additional data is critical to allowing a single function (__except_handler3) to handle all exceptions and route control to the appropriate filter-expressions and _except blocks throughout the code. A hint to the format of the Visual C++ extended EXCEPTION_REGISTRATION structure is found in the EXSUP.INC file from the Visual C++ runtime library sources. In this file, you'll find the following (commented out) definition: |
;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;
;};
|
You've seen the first two fields, prev and handler, before. They make up the basic EXCEPTION_REGISTRATION structure. What's new are the last three fields: scopetable, trylevel, and _ebp. The scopetable field points to an array of structures of type scopetable_entries, while the trylevel field is essentially an index into this array. The last field, _ebp, is the value of the stack frame pointer (EBP) before the EXCEPTION_REGISTRATION was created. It's not coincidental that the _ebp field becomes part of the extended EXCEPTION_REGISTRATION structure. It's included in the structure via the PUSH EBP instruction that most functions begin with. This has the effect of making all of the other EXCEPTION_REGISTRATION fields accessible as negative displacements from the frame pointer. For example, the trylevel field is at [EBP-04], the scopetable pointer is at [EBP-08], and so on. Immediately below its extended EXCEPTION_REGISTRATION structure, Visual C++ pushes two additional values. In the DWORD immediately below, it reserves space for a pointer to an EXCEPTION_POINTERS structure (a standard Win32 structure). This is the pointer returned when you call the GetExceptionInformation API. While the SDK documentation implies that GetException Information is a standard Win32 API, the truth is that GetExceptionInformation is a compiler-intrinsic function. When you call the function, Visual C++ generates the following: |
MOV EAX,DWORD PTR [EBP-14]
|
Just as GetExceptionInformation is a compiler intrinsic function, so is the related function GetExceptionCode. GetExceptionCode just reaches in and returns the value of a field from one of the data structures that GetExceptionInformation returns. I'll leave it as an exercise for the reader to figure out exactly what's going on when Visual C++ generates the following instructions for GetExceptionCode: |
MOV EAX,DWORD PTR [EBP-14]
MOV EAX,DWORD PTR [EAX]
MOV EAX,DWORD PTR [EAX]
|
Returning to the extended EXCEPTION_REGISTRATION structure, 8 bytes before the start of the structure, Visual C++ reserves a DWORD to hold the final stack pointer (ESP) after all the prologue code has executed. This DWORD is the normal value of the ESP register as the function executes (except of course when parameters are being pushed in preparation for calling another function). If it seems like I've dumped a ton of information onto you, I have. Before moving on, let's pause for just a moment and review the standard exception frame that Visual C++ generates for a function that uses structured exception handling: |
EBP-00 _ebp
EBP-04 trylevel
EBP-08 scopetable pointer
EBP-0C handler function address
EBP-10 previous EXCEPTION_REGISTRATION
EBP-14 GetExceptionPointers
EBP-18 Standard ESP in frame
|
From the operating system's point of view, the only fields that exist are the two fields that make up a raw EXCEPTION_REGISTRATION: the prev pointer at [EBP-10] and the handler function pointer at [EBP-0Ch]. Everything else in the frame is specific to the Visual C++ implementation. With this in mind, let's look at the Visual C++ runtime library routine that embodies compiler level SEH, __except_handler3.
While I'd dearly love to point you to the Visual C++ runtime library sources and have you check out the __except_handler3 function yourself, I can't. It's conspicuously absent. In its place, you'll have to make due with some pseudocode for __except_handler3 that I cobbled together (see Figure 9). |
typedef struct _SCOPETABLE
{
DWORD previousTryLevel;
DWORD lpfnFilter
DWORD lpfnHandler
} SCOPETABLE, *PSCOPETABLE; |
The second and third parameters in a SCOPETABLE are easy to understand. They're the addresses of your filter-expression and the corresponding _except block code. The previous tryLevel field is bit trickier. In a nutshell, it's for nested try blocks. The important point here is that there's one SCOPETABLE entry for each _try block in a function.
If you're feeling a bit overwhelmed at this point by things like EXCEPTION_REGISTRATIONs, scopetables, trylevels, filter-expressions, and unwinding, so was I at first. The subject of compiler-level structured exception handling does not lend itself to learning incrementally. Much of it doesn't make sense unless you understand the whole ball of wax. When confronted with a lot of theory, my natural inclination is to write code that applies the concepts I'm learning. If the program works, I know that my understanding is (usually) correct.
Let's briefly recap what unwinding means before digging into the code that implements it. Earlier, I described how potential exception handlers are kept in a linked list, pointed to by the first DWORD of the thread information block (FS:[0]). Since the handler for a particular exception may not be at the head of the list, there needs to be an orderly way of removing all exception handlers in the list that are ahead of the handler that actually deals with the exception. |
__global_unwind2(void * pRegistFrame)
{
_RtlUnwind( pRegistFrame,
&__ret_label,
0, 0 );
__ret_label:
}
|
While RtlUnwind is a critical API for implementing compiler-level SEH, it's not documented anywhere. While technically a KERNEL32 function, the Windows NT KERNEL32.DLL forwards the call to NTDLL.DLL, which also has an RtlUnwind function. I was able to piece together some pseudocode for it, which appears inFigure 12. While RtlUnwind looks imposing, it's not hard to understand if you methodically break it down. The API begins by retrieving the current top and bottom of the thread's stack from FS:[4] and FS:[8]. These values are important later as sanity checks to ensure that all of the exception frames being unwound fall within the stack region. RtlUnwind next builds a dummy EXCEPTION_RECORD on the stack and sets the ExceptionCode field to STATUS_UNWIND. Also, the EXCEPTION_UNWINDING flag is set in the ExceptionFlags field of the EXCEPTION_RECORD. A pointer to this structure will later be passed as a parameter to each exception callback. Afterwards, the code calls the _RtlpCaptureContext function to create a dummy CONTEXT structure that also becomes a parameter for the unwind call of the exception callback. The remainder of RtlUnwind traverses the linked list of EXCEPTION_REGISTRATION structures. For each frame, the code calls the RtlpExecuteHandlerForUnwind function, which I'll cover later. It's this function that calls the exception callback with the EXCEPTION_UNWINDING flag set. After each callback, the corresponding exception frame is removed by calling RtlpUnlinkHandler. RtlUnwind stops unwinding frames when it gets to the frame with the address that was passed in as the first parameter. Interspersed with the code I've described is sanity-checking code to ensure that everything looks okay. If some sort of problem crops up, RtlUnwind raises an exception to indicate what the problem was, and this exception has the EXCEPTION_NONCONTINUABLE flag set. A process isn't allowed to continue execution when this flag is set, so it must terminate.
Earlier in the article, I put off a full description of the UnhandledExceptionFilter API. You normally don't call this API directly (although you can). Most of the time, it's invoked by the filter-expression code for KERNEL32's default exception callback. I showed this earlier in the pseudocode for BaseProcessStart.
If you've made it this far, it wouldn't be fair to finish without completing the entire circuit. I've shown how the operating system calls a user-defined function when an exception occurs. I've shown what typically goes on inside of those callbacks, and how compilers use them to implement _try and _catch. I've even shown what happens when nobody handles the exception and the system has to do the mopping up. All that remains is to show where the exception callbacks originate from in the first place. Yes, let's plunge into the bowels of the system and see the beginning stages of the structured exception handling sequence.
Structured exception handling is a wonderful Win32 feature. Thanks to the supporting layers that compilers like Visual C++ put on top of it, the average programmer can benefit from SEH with a relatively small investment in learning. However, at the operating system level, things are more complicated than the Win32 documentation would lead you to believe. |
|