A Crash Course On TheDepths of Win32 Structured Exception Handling, MSJ January 1997

Download as pdf or txt
Download as pdf or txt
You are on page 1of 19

6/10/2010

Click Here to Install Silverlight

A Crash Course on theDepths of Win3


United States Change | All Microsoft Sites

Search Microsoft.com
MSDN Home
Sea rch for

| Developer C enters | Library | Downloads | C ode C enter | Subscriptions | MSDN Worldwide MSDN Home > MSJ > January 1997

January 1997
MSDN Magazine
Advance d Se arch
M SJ H ome J anuary 1 9 9 7 S earc h S ourc e C ode B ac k I s s ues S ubs c ribe Reader Servic es Write to U s M SD N M agazine M I N D A rc hive M agazine N ews group

Go

A Crash Course on the Depths of Win32 Structured Exception Handling


At its heart, Win32 structured exception handling is an operating systemprovided service. All the docs you're likely to find about SEH describe one particular com-piler's runtime library wrapping around the operating system implementation. I'll strip SEH to its most fundamental concepts.
Matt Pietrek
This article assumes you're familiar with C++, Win32

Code for this article: Exception.exe (33KB) Matt Pietrek is the author of Windows 95 System Programming Sec rets (IDG Books, 1995). He works at NuMega Tec hnologies Inc., and c an be reac hed at [email protected] om. provided by Win32 operating systems, perhaps the most widely used but underdocumented is structured exception handling (SEH). When you think of Win32 structured exc eption 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 struc tured exception handling with _try, _finally, and _exc ept. With all this doc umentation, where do I get off saying that SEH is underdoc umented? At its heart, Win32 struc tured exception handling is an operating systemprovided service. All the documentation you're likely to find about SEH describes one particular compiler's runtime library wrapping around the operating system implementation. There's nothing magical about the keywords _try, _finally, or _exc ept. Microsoft's OS and compiler groups defined these keywords and what they do. Other C++ compiler vendors have simply gone along with their semantic s. While the compiler SEH layer tames the nastiness of raw operating system SEH, it's had the effec t of keeping the raw operating system SEH details from public view. I've received numerous email messages from people who have needed to implement c ompiler-level SEH and couldn't find much in the way of doc umentation for the operating system fac ilities. In a rational world, I'd be able to point to the runtime library sources for Visual C++ or Borland C++ and be done with it. Alas, for some unknown reason, compiler-level SEH seems to be a big

Of all the facilities

microsoft.com/msj//Exception.aspx

1/19

6/10/2010

A Crash Course on theDepths of Win3


sec ret. Neither Mic rosoft nor Borland provide the source code for the innermost layer of their SEH support. In this article, I'll strip structured exception handling down to its most fundamental c oncepts. In doing so, I'll separate what the OS provides from what c ompilers provide via code generation and runtime library support. When I dive into the code for key operating system routines, I'll use the Intel version of Windows NT 4.0 as my base. Most of what I'll desc ribe is equally applicable on other processors, however. I'm going to avoid the issue of true C++ exc eption handling, whic h uses c atch() instead of _except. Under the hood, true C++ exception handling is implemented very similarly to what I'll desc ribe here. However, true C++ exception handling has some additional c omplexities that would just cloud the concepts I want to c over. In digging through obscure .H and .INC files to piec e together what constitutes Win32 SEH, one of the best sourc es of information turned out to be the IBM OS/2 header files (particularly BSEXCPT.H). This shouldn't be too surprising if you've been in this business for a while. The SEH mechanisms desc ribed here were defined bac k when Microsoft was still working on OS/2. For this reason, you'll find SEH under Win32 and OS/2 to be remarkably similar.

SEH in the Buff


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 struc tured 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. Imagine I told you that when a thread faults, the operating system gives you an opportunity to be informed of the fault. More specific ally, when a thread faults, the operating system calls a user-defined callbac k function. This c allback function c an do pretty much whatever it wants. For instanc e, 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 c allback 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 exc eption callback function looks like this:
EXCEPTION_DISPOSITION __cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext );

This prototype, whic h c omes 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 _exc ept_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:

microsoft.com/msj//Exception.aspx

2/19

6/10/2010

A Crash Course on theDepths of Win3


typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD;

The Exc eptionCode parameter is the number that the operating system assigned to the exception. You can see a list of various exc eption 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 c omplete set of exception codes c an 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 _exc ept_handler func tion 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 partic ular 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 exc eption. Inc identally, this CONTEXT struc ture is the same structure used with the GetThreadContext and SetThreadContext APIs. The fourth and final parameter to the _except_handler c allback 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 exc eption occ urs. 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 c allback function rec eives a wealth of information, such as what type of exception occurred and where it occurred. Using this information, the exception callback needs to dec ide what to do. While it's tempting for me to throw together a quickie sample program that shows the _except_handler c allback in ac tion, there's still something missing. In particular, how does the operating system know where to call when a fault occ urs? 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

microsoft.com/msj//Exception.aspx

3/19

6/10/2010

A Crash Course on theDepths of Win3


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 c an ignore for now. The second field, handler, contains a pointer to an _exc ept_ handler callback func tion. 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, eac h thread has its own exc eption handler callbac k function. In my May 1996 column, I desc ribed 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 c urrent TIB. Thus, at FS:[0] you can find a pointer to an EXCEPTION_REGISTRATION struc ture. Now I'm getting somewhere! When an exc eption 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 c allback function. The operating system now knows enough to call the _exc ept_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 desc ription of OS-level structured exc eption handling. Figure 3 shows MYSEH.CPP, which has only two func tions. Function main uses three inline ASM blocks. The first bloc k 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 instruc tion (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/_exc ept syntax, the c ompiler also builds the EXCEPTION_REGISTRATION struct on the stack. I'm simply showing you a simplified version of what a c ompiler would do if you used _try/_exc ept. Back to function main, the next __asm bloc k 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

microsoft.com/msj//Exception.aspx

4/19

6/10/2010

A Crash Course on theDepths of Win3


the previous contents of FS:[0], and then it pops the EXCEPTION_REGISTRATION rec ord off the stac k (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 func tion in MYSEH.CPP. The system then pushes the four required parameters (whic h I described earlier) onto the stack and calls the _except_handler func tion. Once inside _except_handler, the code first indic ates "Yo! I made it here!" via a printf statement. Next, _exc ept_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 ac t of the _except_handler func tion 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 instruc tion should be restarted. Since my _exc ept_handler function tweaked the EAX register to point to valid memory, the MOV EAX,1 instruction works the second time and func tion main continues normally. See, that wasn't so complicated, was it?

Moving In a Little Deeper


With this simplest of scenarios behind us, let's go back and fill in some of the blanks. While this exception c allback 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 c ould occ ur anywhere in your applic ation. A muc h more workable sc enario would be to have multiple exc eption handling routines, eac h one customized to a particular section of your application. Wouldn't you know it, the operating system provides just this functionality. Remember the EXCEPTION_REGISTRATION structure that the system uses to find the exception c allback func tion? The first member of this struc ture, which I ignored earlier, is called prev. It's really a pointer to another EXCEPTION_REGISTRATION struc ture. This second EXCEPTION_REGISTRATION structure c an 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). 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 callbac k agrees to handle the exc eption. In the c ase of MYSEH.CPP, the handler callbac k agreed to handle the exception by returning the value ExceptionContinueExecution. The exception callback c an also decline to handle the exc eption. In this case, the system moves on to the next EXCEPTION_REGISTRATION structure in the list and

microsoft.com/msj//Exception.aspx

5/19

6/10/2010

A Crash Course on theDepths of Win3


asks its exc eption callbac k if it wants to handle the exception. Figure 4 shows this proc ess. Onc e the system finds a callbac k that handles the exception, it stops walking the linked list of EXCEPTION_REGISTRATIONs. To show an example of an exception c allback that doesn't handle an exception, c hec k out MYSEH2.CPP in Figure 5. To keep the code simple, I cheated a bit and used a little c ompiler-level exception handling. Function main just sets up a _try/_except block. Inside the _try block is a call to the HomeGrownFrame function. This func tion is very similar to the c ode in the earlier MYSEH program. It creates an EXCEPTION_REGISTRATION record on the stac k and points FS:[0] at it. After establishing the new handler, the function intentionally c auses a fault by writing to a NULL pointer:

*(PDWORD)0 = 0;

The exception callback func tion, again called _exc ept_ handler, is quite different than the earlier version. The code first prints out the exception code and flags from the Exc eptionRecord structure that the func tion received as a pointer parameter. The reason for printing out the exception flags will become clear later. Since this _except_handler func tion has no intention of fixing the offending code, the func tion returns ExceptionContinueSearch. This causes the operating system to c ontinue its search at the next EXCEPTION_REGISTRATION rec ord in the linked list. For now, take my word for it; the next installed exception c allback 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 exc eption, it effectively dec lines to decide where c ontrol will eventually resume. The handler that accepts the exception is the one that decides where control will c ontinue after all the exception handling code is finished. This has an important implication that's not immediately obvious. When using struc tured 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 exec utes. 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 c alls to the _exc ept_handler function. The first call is c ertainly 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: c ompare the two lines that start with "Home Grown Handler." In particular, the exception flags are zero the first time though, and 2

microsoft.com/msj//Exception.aspx

6/19

6/10/2010

A Crash Course on theDepths of Win3


the second time. This brings me to the subject of unwinding. To jump ahead a bit, when an exception c allback declines to handle an exception, it gets called a second time. This callbac k doesn't happen immediately, though. It's a bit more complicated then that. I'll need to refine the exc eption sc enario one final time. When an exception oc curs, 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 func tion a second time. The key distinction is that in the second c all, 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 c allback 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 func tion's exception handler dec lines to handle an exception, c ontrol typically doesn't exit from that func tion in a normal manner. Now, consider a function with a C++ c lass declared as a local variable. The C++ spec ification 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 c ontinues wherever the handling c allback decides. Remember though that it's just not enough to set the instruction pointer to the desired code address and plunge ahead. The c ode where exec ution resumes expects that the stac k and frame pointer (the ESP and EBP registers on Intel CPUs) are set to their values within the stac k 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 c ontains the SEH c ode that handled the exception.

microsoft.com/msj//Exception.aspx

7/19

6/10/2010

A Crash Course on theDepths of Win3

Figure 6 Unwinding from an Exception

In more general terms, the ac t of unwinding from an exception c auses all things on the stack below the handling frame's stack region to be removed. It's almost as if those functions were never c alled. Another effec t of unwinding is that all EXCEPTION_REGISTRATIONs in the list prior to the one that handled the exc eption are removed from the list. This makes sense, as these EXCEPTION_REGISTRATIONs are typic ally built on the stac k. After the exc eption 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.

Help! Nobody Handled It!


So far, I've implic itly 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 exc eption handler for each and every thread. The default handler is always the last node in the linked list, and it always c hooses to handle the exception. Its actions are somewhat different than a normal exception callback routine, as I'll show later. Let's look at where the system inserts the default, last resort exception handler. This obviously needs to occur very early in the thread's execution, before any user c ode exec utes. Figure 7 shows some pseudocode

microsoft.com/msj//Exception.aspx

8/19

6/10/2010

A Crash Course on theDepths of Win3


I wrote for BaseProc essStart, an internal routine in the Windows NT KERNEL32.DLL. BaseProcessStart takes one parameter, the address of the thread's entry point. BaseProcessStart runs in the c ontext of the new proc ess and calls the entry point to kick off execution of the first thread in the process. In the pseudocode, notice that the call to lpfnEntryPoint is wrapped within an _try and _except c onstruct. This _try block is what installs the default, last resort exception handler in the linked list of handlers. All subsequent registered exception handlers will be inserted ahead of this handler in the list. If the lpfnEntryPoint function returns, the thread ran to c ompletion without causing an exc eption. When this happens, BaseProc essStart c alls ExitThread to terminate the tread. On the other hand, what if the thread faults and no other exception handlers handle it? In this case, control goes to the c ode inside the parens after the _except keyword. In BaseProcessStart, this code calls the UnhandledExceptionFilter API, which I'll come back to later. For now, the key point is that the UnhandledExceptionFilter API c ontains the meat of the default exception handler. If UnhandledExceptionFilter returns EXCEPTION_EXECUTE_HANDLER, the _except block in BaseProcessStart executes. All the _except bloc k code does is terminate the current proc ess by calling ExitProc ess. Thinking about it for a second, this makes sense; it's common knowledge that if a program causes a fault and nobody handles the fault, the system terminates the process. What you're seeing in the pseudoc ode is exactly where and how this happens. There's one final addition to what I've just described. If the thread that faulted is running as a service and is for a thread-based servic e, the _except bloc k code doesn't call ExitProcessit calls ExitThread instead. You wouldn't want to terminate the entire service proc ess just bec ause one servic e went bad. So what does the default exc eption handler code in UnhandledExceptionFilter do? When I ask this question at seminars, very few people c an guess what the default behavior of the operating system is when an unhandled exception occurs. With a very simple demonstration of the default handler's behavior, things usually click and people understand. I simply run a program that intentionally c auses a fault, and point out the results (see Figure 8).

Figure 8 Unhandled Exception Dialog

At a high level, UnhandledExc eptionFilter 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 sc enes, and I'll describe these things towards the end of the article. As I've shown, when an exc eption occ urs, userwritten code can (and often does) get executed. Likewise, during an unwind operation, user-written code c an execute. This user-written c ode may have bugs and could c ause another exception. For this reason,

microsoft.com/msj//Exception.aspx

9/19

6/10/2010

A Crash Course on theDepths of Win3


there are two other values that an exception callback c an 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.

Compiler-level SEH
While I've made oc casional reference to _try and _exc ept, 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 c ompiler wrapper around this functionality is definitely in order. Let's now see how Visual C++ builds its struc tured exception handling support on top of the system-level SEH fac ilities. Before moving on, it's important to remember that another c ompiler c ould do something completely different with the raw system-level SEH facilities. Nothing says that a compiler must implement the _try/_exc ept model that the Win32 SDK documentation desc ribes. For example, the upcoming Visual Basic 5.0 uses structured exception handling in its runtime code, but the data struc tures and algorithms are completely different from what I'll desc ribe here. If you read through the Win32 SDK documentation on structured exception handling, you'll c ome ac ross the following syntax for a so called "frame-based" exception handler:

try { // guarded body of code } except (filter-expression) { // exception-handler block }

To be a bit simplistic , all of the c ode within a try bloc k in a function is protec ted by an EXCEPTION_REGISTRATION that's built on the function's stac k 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 struc ture on the stack, what about the callback func tion within the EXCEPTION_ REGISTRATION? Using Win32 terminology, the exception callback func tion 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 c ode that decides whether the code in the subsequent {} block will

microsoft.com/msj//Exception.aspx

10/19

6/10/2010

A Crash Course on theDepths of Win3


exec ute. Since you write the filter-expression c ode, you get to decide if a partic ular exception will be handled at this particular point in your code. Your filter-expression c ode might be as simple as saying "EXCEPTION_EXECUTE_ HANDLER." Alternatively, the filter-expression might invoke a func tion that c alculates p to 20 million plac es before returning a c ode telling the system what to do next. It's your choic e. The key point: your filter-expression code is effec tively the exception c allback 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 exc eption 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 c alls your filter-expression c ode, and I'll come back to it a bit later. Another twist to the simplistic view that I desc ribed 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 c reated in any function that uses SEH. In other words, you can have multiple _try/_exc ept c onstruc ts in a function, yet only one EXCEPTION_REGISTRATION is created on the stac k. Likewise, you might have a _try block nested within another _try block in a function. Still, Visual C++ c reates only one EXCEPTION_REGISTRATION. If a single exc eption handler (that is, __except_handler3) suffic es for the whole EXE or DLL, and if a single EXCEPTION_REGISTRATION handles multiple _try bloc ks, there's obviously more going on here than meets the eye. This magic is acc omplished through data in tables that you don't normally see. However, since the whole purpose of this artic le is to dissec t structured exc eption handling, let's take a look at these data structures.

The Extended Exception Handling Frame


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 c ritic al to allowing a single func tion (__except_handler3) to handle all exc eptions and route c ontrol to the appropriate filter-expressions and _except bloc ks 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; ;};

microsoft.com/msj//Exception.aspx

11/19

6/10/2010

A Crash Course on theDepths of Win3


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 bec omes 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 ac cessible 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 GetExc eption Information is a standard Win32 API, the truth is that GetExceptionInformation is a compiler-intrinsic function. When you c all the function, Visual C++ generates the following:
MOV EAX,DWORD PTR [EBP-14]

Just as GetExceptionInformation is a c ompiler intrinsic func tion, so is the related func tion GetExceptionCode. GetExceptionCode just reac hes in and returns the value of a field from one of the data struc tures that GetExceptionInformation returns. I'll leave it as an exercise for the reader to figure out exac tly 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 exec uted. 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 c alling 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-04 EBP-08 EBP-0C EBP-10 EBP-14 EBP-18 _ebp trylevel scopetable pointer handler function address previous EXCEPTION_REGISTRATION GetExceptionPointers Standard ESP in frame

microsoft.com/msj//Exception.aspx

12/19

6/10/2010

A Crash Course on theDepths of Win3


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 [EBP10] 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 c ompiler level SEH, __exc ept_handler3.

__except_handler3 and the scopetable


While I'd dearly love to point you to the Visual C++ runtime library sourc es and have you check out the __except_handler3 func tion yourself, I can't. It's c onspicuously absent. In its plac e, you'll have to make due with some pseudocode for __except_handler3 that I c obbled together (see Figure 9). While __exc ept_handler3 looks like a lot of code, remember that it's just an exception c allback like I desc ribed at the beginning of this article. It takes the identic al four parameters as my homegrown exception c allbacks in MYSEH.EXE and MYSEH2.EXE. At the topmost level, __except_handler3 is split into two parts by an if statement. This is bec ause the function c an be c alled twice, onc e normally and once during the unwind phase. The larger portion of the function is devoted to the non-unwinding c allback. The beginning of this code first c reates an EXCEPTION_POINTERS structure on the stack, initializing it with two of the __exc ept_handler3 parameters. The address of this struc ture, which I've c alled exc eptPtrs in the pseudoc ode, is placed at [EBP14]. This initializes the pointer that the GetExceptionInformation and GetExc eptionCode func tions use. Next, __exc ept_handler3 retrieves the c urrent trylevel from the EXCEPTION_REGISTRATION frame (at [EBP04]). The trylevel variable ac ts as an index into the scopetable array, which allows a single EXCEPTION_REGISTRATION to be used for multiple _try blocks within a function, as well as nested _try bloc ks. Each scopetable entry looks like this:

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 c ode. 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. As I mentioned earlier, the current trylevel specifies the sc opetable array entry to be used. This, in turn, spec ifies the filter-expression and _except block addresses. Now, consider a scenario with a _try block nested within another _try block. If the inner _try block's filter-expression doesn't handle the exc eption, the outer _try block's filter-expression must get a crack at it. How does __exc ept_handler3 know which SCOPETABLE entry corresponds to the outer _try block? Its index is given by the previousTryLevel field in a

microsoft.com/msj//Exception.aspx

13/19

6/10/2010

A Crash Course on theDepths of Win3


SCOPETABLE entry. Using this sc heme, you c an create arbitrarily nested _try blocks. The previousTryLevel field acts as a node in a linked list of possible exception handlers within the func tion. The end of the list is indic ated by a trylevel of 0xFFFFFFFF. Returning to the __except_handler3 code, after it retrieves the current trylevel the code points to the c orresponding SCOPETABLE entry and c alls the filterexpression code. If the filter-expression returns EXCEPTION_CONTINUE_SEARCH, __except_handler3 moves on to the next SCOPETABLE entry, which is spec ified in the previousTryLevel field. If no handler is found by traversing the list, __exc ept_handler3 returns DISPOSITION_CONTINUE_SEARCH, whic h causes the system to move on to the next EXCEPTION_REGISTRATION frame. If the filter-expression returns EXCEPTION_EXECUTE_HANDLER, it means that the exception should be handled by the corresponding _exc ept block code. This means that any previous EXCEPTION_REGISTRATION frames have to be removed from the list and the _exc ept block needs to be exec uted. The first of these chores is handled by calling __global_unwind2, which I'll desc ribe later on. After some other intervening cleanup code that I'll ignore for the moment, execution leaves __except_handler3 and goes to the _exc ept block. What's strange is that c ontrol never returns from the _except block, even though __exc ept_handler3 makes a CALL to it. How is the current trylevel set? This is handled implicitly by the compiler, which performs on-the-fly modifications of the trylevel field in the extended EXCEPTION_REGISTRATION structure. If you examine the assembler code generated for a function that uses SEH, you'll see code that modifies the c urrent trylevel at [EBP-04] at different points in the function's code. How does __except_handler3 make a CALL to the _exc ept block code, yet control never returns? Since a CALL instruction pushes a return address onto the stac k, you'd think this would mess up the stac k. If you examine the generated code for an _except bloc k, you'll find that the first thing it does is load the ESP register from the DWORD that's 8 bytes below the EXCEPTION_REGISTRATION structure. As part of its prologue code, the func tion saves the ESP value away so that an _except bloc k can retrieve it later.

The ShowSEHFrames Program


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 c ompiler-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 inc lination is to write code that applies the concepts I'm learning. If the program works, I know that my understanding is (usually) c orrec t. Figure 10 is the source code for ShowSEHFrames.EXE. It uses _try/_exc ept blocks to set up a list of several Visual C++ SEH frames. Afterwards, it displays information about eac h frame, as well as the scopetables that Visual C++ builds for eac h frame. The program doesn't generate or expect any exceptions. Rather, I included all the _try blocks to force Visual C++ to generate multiple EXCEPTION_ REGISTRATION frames, with multiple scopetable entries per frame.

microsoft.com/msj//Exception.aspx

14/19

6/10/2010

A Crash Course on theDepths of Win3


The important func tions in ShowSEHFrames are WalkSEHFrames and ShowSEHFrame. WalkSEHFrames first prints out the address of __exc ept_handler3, the reason for whic h will be c lear in a moment. Next, the func tion obtains a pointer to the head of the exception list from FS:[0] and walks each node in the list. Eac h node is of type VC_EXCEPTION_REGISTRATION, which is a structure that I defined to desc ribe a Visual C++ exception handling frame. For each node in the list, WalkSEHFrames passes a pointer to the node to the ShowSEHFrame func tion. ShowSEHFrame starts by printing the address of the exception frame, the handler c allbac k address, the address of the previous exc eption frame, and a pointer to the scopetable. Next, for each scopetable entry, the c ode prints out the previous trylevel, the filterexpression address, and the _except bloc k address. How do I know how many entries are in a scopetable? I don't really. Rather, I assume that the current trylevel in the VC_EXCEPTION_REGISTRATION struc ture is one less than the total number of scopetable entries. Figure 11 shows the results of running ShowSEHFrames. First, examine every line that begins with "Frame:". Notice how each successive instance shows an exc eption frame that's at a higher address on the stack. Next, on the first three Frame: lines, notice that the Handler value is the same (004012A8). Looking at the beginning of the output, you'll see that this 004012A8 is none other than the address of the __except_handler3 func tion in the Visual C++ runtime library. This proves my earlier assertion that a single entry point handles all exceptions. You may be wondering why there are three exception frames using __except_handler3 as their callback sinc e ShowSEHFrames plainly has only two functions that use SEH. The third frame comes from the Visual C++ runtime library. The code in CRT0.C from the Visual C++ runtime library sourc es shows that the call to main or WinMain is wrapped in an _try/_except block. The filterexpression code for this _try block is found in the WINXFLTR.C file. Returning to ShowSEHFrames, the Handler for the last Frame: line contains a different address, 77F3AB6C. Poking around a bit, you'll find that this address is in KERNEL32.DLL. This partic ular frame is installed by KERNEL32.DLL in the BaseProcessStart function that I desc ribed earlier.

Unwinding
Let's briefly rec ap what unwinding means before digging into the code that implements it. Earlier, I desc ribed 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 ac tually deals with the exc eption. As you saw in the Visual C++ __except_handler3 func tion, unwinding is performed by the __global_unwind2 RTL function. This func tion is just a very thin wrapper around the undoc umented RtlUnwind API:

__global_unwind2(void * pRegistFrame) { _RtlUnwind( pRegistFrame, &__ret_label,

microsoft.com/msj//Exception.aspx

15/19

6/10/2010

A Crash Course on theDepths of Win3


0, 0 ); __ret_label: }

While RtlUnwind is a critic al API for implementing c ompiler-level SEH, it's not doc umented anywhere. While technic ally a KERNEL32 function, the Windows NT KERNEL32.DLL forwards the c all to NTDLL.DLL, which also has an RtlUnwind function. I was able to piece together some pseudocode for it, which appears in Figure 12. While RtlUnwind looks imposing, it's not hard to understand if you methodic ally break it down. The API begins by retrieving the c urrent 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 stac k region. RtlUnwind next builds a dummy EXCEPTION_RECORD on the stac k and sets the ExceptionCode field to STATUS_UNWIND. Also, the EXCEPTION_UNWINDING flag is set in the Exc eptionFlags field of the EXCEPTION_RECORD. A pointer to this structure will later be passed as a parameter to each exception c allback. Afterwards, the code c alls the _RtlpCaptureContext func tion to c reate a dummy CONTEXT struc ture that also becomes a parameter for the unwind call of the exception callbac k. The remainder of RtlUnwind traverses the linked list of EXCEPTION_REGISTRATION structures. For each frame, the code calls the RtlpExec uteHandlerForUnwind func tion, which I'll c over later. It's this function that c alls the exception callback with the EXCEPTION_UNWINDING flag set. After eac h callbac k, the corresponding exc eption 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 desc ribed is sanity-c hec king code to ensure that everything looks okay. If some sort of problem crops up, RtlUnwind raises an exc eption 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.

Unhandled Exceptions
Earlier in the article, I put off a full desc ription of the UnhandledExceptionFilter API. You normally don't call this API direc tly (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 pseudoc ode for BaseProcessStart. Figure 13 shows my pseudocode for UnhandledExceptionFilter. The API starts out a bit strangely (at least in my opinion). If the fault is an EXCEPTION_ACCESS_ VIOLATION, the code calls _BasepCheckForReadOnlyResource. While I haven't provided pseudocode for this function, I can summarize it. If the exception oc curred bec ause a resource section (.rsrc ) of an EXE or DLL was written to, _BasepCurrentTopLevelFilter changes the faulting page's attributes from its normal read-only state, thereby allowing the write to occur. If this particular scenario oc curs, UnhandledExc eptionFilter returns EXCEPTION_ CONTINUE_EXECUTION and execution restarts at the faulting instruction. The next task of UnhandledExceptionFilter is to determine if the process is being run under a Win32

microsoft.com/msj//Exception.aspx

16/19

6/10/2010

A Crash Course on theDepths of Win3


debugger. That is, the proc ess was created with the DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS flag. UnhandledExceptionFilter uses the NtQueryInformationProc ess Func tion that I desc ribe in this month's Under the Hood column to tell if the proc ess is being debugged. If so, the API returns EXCEPTION_CONTINUE_SEARCH, whic h tells some other part of the system to wake up the debugger process and tell it that an exception oc curred in the debuggee. Next on UnhandledExceptionFilter's plate is a call to the user-installed unhandled exc eption filter, if present. Normally, there isn't a user-installed c allback, but one c an be installed via the SetUnhandledExceptionFilter API. I've also provided pseudocode for this API. The API simply bashes a global variable with the new user c allback address, and returns the value of the old c allback. With the preliminaries out of the way, UnhandledExceptionFilter can get down to its primary job: informing you of your ignominious programming blunder with the ever- stylish Applic ation Error dialog. There are two ways that this dialog c an be avoided. The first is if the process has called SetErrorMode and spec ified the SEM_NOGPFAULTERRORBOX flag. The other method is to have the Auto value under the AeDebug registry key set to 1. In this c ase, UnhandledExceptionFilter skips the Application Error dialog and automatically fires up whatever debugger is spec ified in the Debugger value of the AeDebug key. If you're familiar with "just in time debugging," this is where the operating system supports it. More on this later. In most cases, neither of these dialog avoidance c onditions are true and UnhandledExceptionFilter calls the NtRaiseHardError func tion in NTDLL.DLL. It's this func tion that brings up the Application Error dialog. This dialog waits for you to hit the OK button to terminate the process, or Cancel to debug it. (Maybe it's just me, but hitting Cancel to launch a debugger seems a little backward.) If you hit OK in the Application Error dialog box, UnhandledExceptionFilter returns EXCEPTION_EXECUTE_HANDLER. The code that called UnhandledExceptionFilter usually responds by terminating itself (as you saw in the BaseProcessStart c ode). This brings up an interesting point. Most people assume that the system terminates a process with an unhandled exception. It's actually more correct to say that the system sets up things so that an unhandled exception c auses the process to terminate itself. The truly interesting c ode in UnhandledExceptionFilter exec utes if you select Cancel in the Application Error dialog, thereby bringing up a debugger on the faulting proc ess. The c ode first calls CreateEvent to make an event that the debugger will signal after it has attached to the faulting proc ess. This event handle, along with the current proc ess ID, is passed to sprintf, whic h formats the command line used to start the debugger. Once everything is prepared, UnhandledExceptionFilter calls CreateProc ess to start the debugger. If CreateProcess succeeds, the code c alls NtWaitForSingleObject on the event c reated earlier. This c all bloc ks until the debugger proc ess signals the event, indicating that it has attached to the faulting process successfully. There are other little bits and pieces to the UnhandledExceptionFilter c ode, but I've covered the important highlights here.

Into the Inferno


microsoft.com/msj//Exception.aspx 17/19

6/10/2010

A Crash Course on theDepths of Win3


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 func tion when an exception oc curs. I've shown what typically goes on inside of those callbac ks, and how c ompilers 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 c allbacks originate from in the first place. Yes, let's plunge into the bowels of the system and see the beginning stages of the struc tured exception handling sequence. Figure 14 shows some pseudoc ode I whipped up for KiUserExceptionDispatcher and some related functions. KiUserExceptionDispatcher is in NTDLL.DLL and is where exec ution begins after an exception occurs. To be 100 perc ent accurate, what I just said isn't exactly true. For instance, in the Intel architecture an exc eption c auses control to vector to a ring 0 (kernel mode) handler. The handler is defined by the interrupt desc riptor table entry that corresponds to an exception. I'm going to skip all that kernel mode code and pretend that the CPU goes straight to KiUserExceptionDispatcher upon an exception The heart of KiUserExc eptionDispatcher is its call to RtlDispatc hException. This kicks off the search for any registered exception handlers. If a handler handles the exception and continues execution, the c all to RtlDispatc hException never returns. If RtlDispatc hException returns, there are two possible paths: either NtContinue is called, which lets the proc ess c ontinues, or another exception is raised. This time, the exc eption isn't continuable, and the process must terminate. Moving on to the RtlDispatc hExceptionCode, this is where you'll find the exception frame walking c ode that I've referred to throughout this artic le. The func tion grabs a pointer to the linked list of EXCEPTION_REGISTRATIONs and iterates over every node, looking for a handler. Because of the possibility of stac k corruption, the routine is very paranoid. Before c alling the handler specified in eac h EXCEPTION_REGISTRATION, the code ensures that the EXCEPTION_REGISTRATION is DWORD-aligned, within the thread's stac k, and higher on the stack than the previous EXCEPTION_REGISTRATION. RtlDispatchException doesn't directly call the address spec ified in the EXCEPTION_REGISTRATION structure. Instead, it calls RtlpExecuteHandlerForException to do the dirty work. Depending on what happens inside RtlpExecuteHandlerForExc eption, RtlDispatc hException either continues walking the exception frames or raises another exc eption. This secondary exception indic ates that something went wrong inside the exception c allback and that execution can't continue. The code for RtlpExecuteHandlerForException is c losely related to another func tion, RtlpExecutehandlerForUnwind. You may rec all that I mentioned this function earlier when I desc ribed unwinding. Both of these "func tions" simply load the EDX register with different values before sending c ontrol to the ExecuteHandler function. Put another way, RtlpExecuteHandlerForException and RtlpExecutehandlerForUnwind are separate front ends to a common function, ExecuteHandler. Exec uteHandler is where the handler field from the EXCEPTION_REGISTRATION is extracted and called. Strange as it may seem, the call to the exception

microsoft.com/msj//Exception.aspx

18/19

6/10/2010

A Crash Course on theDepths of Win3


c allback is itself wrapped by a struc tured exception handler. Using SEH within itself seems a bit funky but it makes sense if you ponder it for a moment. If an exception c allback c auses another exception, the operating system needs to know about it. Depending on whether the exception oc curred during the initial c allback or during the unwind c allback, ExecuteHandler returns either DISPOSITION_NESTED_ EXCEPTION or DISPOSITION_COLLIDED_UNWIND. Both are basically "Red Alert! Shut everything down now!" kind of codes. If you're like me, it's hard to keep all of the functions associated with SEH straight. Likewise, it's hard to remember who calls who. To help myself, I came up with the diagram shown in Figure 15. Now, what's the deal with setting EDX before getting to the Exec uteHandler code? It's simple, really. Exec uteHandler uses whatever's in EDX as the raw exception handler if something goes wrong while c alling the user-installed handler. It pushes the EDX register onto the stack as the handler field for a minimal EXCEPTION_REGISTRATION structure. In essenc e, Exec uteHandler uses raw struc tured exception handling like I showed in the MYSEH and MYSEH2 programs.

Conclusion
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 c an 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. Unfortunately, not much has been written about system-level SEH to date because almost everyone c onsiders it an extremely difficult subject. The lac k of documentation on the system-level details hasn't helped. In this article, I've shown that system-level SEH revolves around a relatively simple callbac k. If you understand the nature of the c allback, and then build additional layers of understanding on top of that, system-level struc tured exception handling really isn't so hard to grasp. From the January 1997 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.
1997 Microsoft Corporation. All rights reserved. Legal Notices.

Manage Your Profile | Legal | C ontact us | MSDN Flash Newsletter 2010 Microsoft C orporation. All rights reserved. C ontact Us | Terms of Use | Trademarks | Privacy Statement

microsoft.com/msj//Exception.aspx

19/19

You might also like