A Crash Course On TheDepths of Win32 Structured Exception Handling, MSJ January 1997
A Crash Course On TheDepths of Win32 Structured Exception Handling, MSJ January 1997
A Crash Course On TheDepths of Win32 Structured Exception Handling, MSJ January 1997
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
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
microsoft.com/msj//Exception.aspx
1/19
6/10/2010
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
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
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
microsoft.com/msj//Exception.aspx
5/19
6/10/2010
*(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
microsoft.com/msj//Exception.aspx
7/19
6/10/2010
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.
microsoft.com/msj//Exception.aspx
8/19
6/10/2010
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
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:
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
;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
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
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
microsoft.com/msj//Exception.aspx
14/19
6/10/2010
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:
microsoft.com/msj//Exception.aspx
15/19
6/10/2010
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
6/10/2010
microsoft.com/msj//Exception.aspx
18/19
6/10/2010
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