Structured Exception Handling vs. C Exception Handling


Structured Exception Handling vs. C++ Exception Handling

Getting up to speed on exception handling can be tough partly because C++ can use two main types of exception handling: structured exception handling (SEH), which the operating system provides, and C++ exception handling, which the C++ language provides. Just figuring out which type of exception handling to use when can be a challenge, and it doesn't help that many people talk about both types as if they were interchangeable. I assure you that each type of exception handling has a distinctly different approach. The one common aspect is that exceptions of either type are for exceptional conditions, not for normal processing and logic. I think what confuses some people are the rumors that you can combine both types. In the following sections, I'll touch on the differences and similarities between these two types of exception handling. I'll also discuss how to avoid the biggest bug generator of them all when it comes to exceptions.

Structured Exception Handling

The operating system provides SEH, and it deals directly with crashes such as access violations. SEH is language-independent but is usually implemented in C and C++ programs with the __try/__except and __try/__finally keyword pairs. The way you use the __try/__except pair is to set your code inside a __try block and then determine how to handle the exception in the __except block (also called an exception handler). In a __try/__finally pair, the __finally block (also called a termination handler) ensures that a section of code will always be executed upon leaving a function, even if the code in the __try block terminates prematurely, so you can be guaranteed that resources will be cleaned up.

Listing 13-1 shows a typical function with SEH. The __except block looks almost like a function call, but the parentheses specify the value of a special expression called an exception filter. The exception filter value in Listing 13-1 is EXCEPTION_EXECUTE_HANDLER, which indicates that the code in the __except block must be executed every time any exception occurs inside the __try block. The two other possible exception filter values are EXCEPTION_CONTINUE_EXECUTION, which allows an exception to be ignored, and EXCEPTION_CONTINUE_SEARCH, which passes the exception up the call chain to the next __except block. You can make the exception filter expression as simple or as complicated as you like so that you target only those exceptions you're interested in handling.

Listing 13-1: Example SEH handler

start example
void Foo ( void ) {     __try         {                 __try         {             // Execute code to accomplish something.         }         __except ( EXCEPTION_EXECUTE_HANDLER )         {             // This block will be executed if the code in the __try              // block causes an access violation or some other hard crash.             // The code in here is also called the exception handler.         }     }     __finally     {         // This block will be executed regardless of whether the function          // causes a crash. Mandatory cleanup code goes here.     } }
end example

The process of finding and executing an exception handler is sometimes called unwinding the exception. Exception handlers are kept on an internal stack; as the function call chain grows, the exception handler (if one exists) for each new function is pushed onto this internal stack. When an exception occurs, the operating system finds the thread's exception handler stack and starts calling the exception handlers until one exception handler indicates that it will handle the exception. As the exception works its way down the exception handler stack, the operating system cleans up the call stack and executes any termination handlers it finds along the way. If the unwinding continues to the end of the exception handler stack, the Application Error dialog box or the installed JIT debugger pops up.

Your exception handler can determine the exception value by calling the special GetExceptionCode function, which can be called only in exception filters. If you were writing a math package, for example, you might have an exception handler that handles divide-by-zero attempts and returns NaN (not a number). The code in Listing 13-2 shows an example of such an exception handler. The exception filter calls GetExceptionCode, and if the exception is divide-by-zero, the exception handler executes. If any other exception occurs, EXCEPTION_CONTINUE_SEARCH tells the operating system to execute the next __except block up the call chain.

Listing 13-2: Example SEH handler with exception filter processing

start example
long IntegerDivide ( long x , long y ) {     long lRet ;         __try     {         lRet = x / y ;     }     __except ( EXCEPTION_INT_DIVIDE_BY_ZERO ==                GetExceptionCode ( )                    ? EXCEPTION_EXECUTE_HANDLER                    : EXCEPTION_CONTINUE_SEARCH               )     {         lRet = NaN ;     }     return ( lRet ) ; }
end example

If your exception filter requires more complexity, you can even call one of your own functions as the exception filter as long as it specifies how to handle the exception by returning one of the valid exception filter values. In addition to calling the special GetExceptionCode function, you can also call the GetExceptionInformation function in the exception filter expression. GetExceptionInformation returns a pointer to an EXCEPTION_POINTERS structure that completely describes the reason for a crash and the state of the CPU at that time. You might have guessed that the EXCEPTION_POINTERS structure will come in handy later in this chapter.

SEH isn't limited just to handling crashes. You can also create your own exceptions with the RaiseException API function. Most developers don't use RaiseException, but it does offer a way to quickly leave deeply nested conditional statements in code. The RaiseException exit technique is cleaner than using the old setjmp and longjmp run-time functions.

Before you dive in and start using SEH, you need to be aware of two of its limitations. The first is minor: your custom error codes are limited to a single unsigned integer. The second limitation is a little more serious: SEH doesn't mix well with C++ programming because C++ exceptions are implemented internally with SEH and the compiler complains when you try to combine them indiscriminately. The reason for the conflict is that when straight SEH unwinds out of a function, it doesn't call any of the C++ object destructors for objects created on the stack. Because C++ objects can do all sorts of initialization in their constructors, such as allocating memory for internal data structures, skipping the destructors can lead to memory leaks and other problems.

If you'd like to learn more about the basics of SEH, I recommend two references in addition to perusing the Microsoft Developer Network (MSDN). The best overview of SEH is in Jeffrey Richter's Programming Applications for Microsoft Windows (Microsoft Press, 1999). If you're curious about the actual SEH implementation, check out Matt Pietrek's article "A Crash Course on the Depths of Win32 Structured Exception Handling" in the January 1997 Microsoft Systems Journal.

One advanced feature of SEH I do want to mention, which first appeared with Microsoft Windows XP and Microsoft Windows Server 2003, is vectored exception handling. With regular SEH, there's no way to get globally notified when an exception occurs. Although generally not something you'd like to have as part of your day-to-day development, vectored exception handling allows you to get either the first notification or the last notification of all SEH exceptions occurring in your application. The first time I realized that Microsoft had added vectored exception handling to the operating system, I immediately saw how to write an exception monitor for SEH so that you could keep an eye on what exceptions your application was generating without running your application under a debugger.

To set up receiving vectored exceptions, simply call the AddVectoredExceptionHandler function, where the second parameter is a pointer to the function you want called when any first-chance exceptions occur in your application. The first parameter is a Boolean value that indicates whether you want notifications before or after the normal exception chain unwinding. Your callback function will get a pointer to an EXCEPTION_POINTERS structure describing the exception. As you can guess, armed with that information, getting the exceptions is a piece of cake.

The XPExceptMon project, which you can find with this book's sample files, shows exactly how to use the vectored exceptions because it writes out each exception your application encounters. All the work for setting up and tearing down the vectored exception hook takes place in the DllMain for XPExceptMon.DLL, so utilizing it from your applications is trivial. My interest was showing vectored exceptions, so all XPExceptMon does is report the exception type and the faulting address to a text file. If you're looking for a place to get some practice using the DBGHELP.DLL symbol engine, feel free to add function lookup and stack walking to XPExceptMon.

If you'd like to get exception notifications on earlier Windows versions, you're in luck. Eugene Gershnik wrote an excellent article, "Visual C++ Exception-Handling Instrumentation," in the December 2002 issue of Windows Developer Magazine. In addition to showing you how to hook exception handling, Eugene has a great discussion of how exception handling works.

C++ Exception Handling

Because C++ exception handling is part of the C++ language specification, it's probably more familiar to most programmers than SEH. The keywords for C++ exception handling are try and catch. The throw keyword allows you to initiate an exception unwind. Whereas SEH error codes are limited to just a single unsigned integer, a C++ catch statement can handle any variable type, including classes. If you derive your error handling classes from a common base class, you can handle just about any error you need to in your code. This class hierarchy approach to error handling is exactly what the Microsoft Foundation Class (MFC) library does with its CException base class. Listing 13-3 shows C++ exception handling in action with an MFC CFile class read.

Listing 13-3: C++ exception handler example

start example
 BOOL ReadFileHeader ( CFile * pFile , LPHEADERINFO pHeader ) {     ASSERT ( FALSE == IsBadReadPtr ( pFile , sizeof ( CFile * ) ) ) ;     ASSERT ( FALSE == IsBadReadPtr ( pHeader ,                                       sizeof ( LPHEADERINFO ) ) ) ;     if ( ( TRUE == IsBadReadPtr ( pFile , sizeof ( CFile * ) ) ) ||          ( TRUE == IsBadReadPtr ( pHeader ,                                    sizeof ( LPHEADERINFO )    ) )   )     {         return ( FALSE ) ;     }          BOOL bRet ;     try     {         pFile->Read ( pHeader , sizeof ( HEADERINFO ) ) ;         bRet = TRUE ;     }     catch ( CFileException * e )     {         // If the header couldn't be read because the file was         // truncated, handle it; otherwise, continue the unwind.         if ( CFileException::endOfFile == e->m_cause )         {             e->Delete();             bRet = false;         }         else         {             // The throw keyword just by itself throws the same exception              // as passed to this catch block.             throw ;         }     }     return ( bRet ) ; }
end example

You need to keep in mind the following drawbacks when you're using C++ exception handling. First, it doesn't handle your program crashes automatically. Second, C++ exception processing isn't free. The compiler might do a great deal of work setting up and removing the try and catch blocks even if you never throw any exceptions, so if you're working on performance-sensitive code you might not be able to afford that much overhead. If you're new to C++ exception handling, MSDN is a great place to start learning about it.

Avoid Using C++ Exception Handling

Probably one of the most consistently confusing issues that comes up for development shops in my company's consulting business is the issue of C++ exception handling. Developers have wasted more effort on C++ exception handling problems than on anything else (except memory corruptions) when it comes to Windows development. Based on all the horrific situations we've resolved, my recommendation is to avoid C++ exception handling because your life will get infinitely simpler and your code will be easier to debug.

The first problem with C++ exception handling is that it isn't a clean feature of the language. In many ways, it looks grafted on and unfinished. The fact that we don't have an ANSI standard class that contains information about an exception means that there's no consistent way of handling generic errors. Some of you might be thinking about the catch (...) construct as the standard approved catchall mechanism, but in the next section I'll permanently scare you off from ever using that construct again.

C++ exception handling is also one of those technologies that looks great in theory but breaks down the minute you implement anything more than "Hello World!" Repeatedly I've seen completely insane situations on teams where someone becomes enamored with C++ exceptions and starts implementing tons of them in his code. This forces the rest of the team to deal with C++ exception handling across the application, even though very few developers can deal with the ramifications of designing and using them. What invariably happens is that some code forgets to catch some random unexpected exception, and the application goes down. Additionally, the maintenance nightmare of trying to extend code that mixes return value failures as well as C++ exceptions means that many companies find it better to throw out the code and start again, thus massively increasing their costs.

Many people throw (pun intended!) out the argument that the best reason for using C++ exceptions is that developers never check return values from functions. Not only is this a complete red herring argument, but it's also an excuse for bad programming. If a developer on your team has a consistent problem checking return values, she needs counseling on the correct way to do it. If she still can't check return values, fire her. She is simply not doing her job.

Up to this point, I've been discussing issues with the design and management of C++ exceptions. What many developers fail to consider is that C++ exceptions have quite a bit of overhead associated with them. The code necessary to set up the try and catch blocks takes a lot of work, adding to your performance woes even if you rarely (if ever) cause an exception.

Another implementation issue is that Microsoft implemented C++ exceptions under the covers with SEH, meaning that every C++ throw is calling RaiseException. There's nothing wrong with that, but each throw causes the happy trip to kernel mode. Although the actual transition from user mode to kernel mode is very fast, the work done in kernel mode to manipulate your exception leads to a ton of overhead. Back in Chapter 7's "Tips and Tricks" section, I discussed monitoring C++ exceptions in your applications to help pinpoint this overhead.

Developers sometimes seem oblivious to the cost of C++ exception handling. Working on one company's code performance problem, I couldn't understand why the _except_handler3 function, which is executed as part of exception processing, was called so many times. As I inspected the code, it dawned on me that a developer on the team was using C++ exception handling in place of the tried-and-true switch...case construct. To speed up the application, the company had to redesign large portions of that developer's code simply to return enumerated type values. When I asked the developer why he used C++ exception handling, he told me that he thought a throw statement just changed the instruction pointer. Only code for which performance isn't important can use C++ exception handling.

Absolutely, Positively, NEVER EVER Use catch ( ... )

The catch (...)construct has been very good to my bank account because it has caused more bugs in people's code than you can ever imagine. There are two huge problems with catch (...). The first is with its design as specified by the ANSI standard. The ellipsis means that the catch block catches any type of throw.

However, since there's no variable in the catch, you have absolutely no way of knowing how you got there and why. The design of catch (...) means you might as well change the instruction pointer to random spots in your code and start executing. With no ability to know what was thrown or how you got there, the only safe and logical action you can take is to terminate the application. Some of you might be thinking that's a drastic step, but there's nothing else you can safely do.

The second problem with catch (...) concerns implementation. What many people don't realize is that in the Windows C run time, catch (...) eats not only C++ exceptions but also SEH exceptions! Not only do you have a situation in which you don't know how you ended up executing inside the catch block, but you also might have ended up in the catch block because of an access violation or another hard error. In addition to being lost, your program is probably completely unstable in the catch block, so you have to terminate the process immediately.

It boggles my mind how many times I've seen developers—and senior developers at that—implement code like the following:

BOOL DoSomeWork ( void ) {     BOOL bRet = TRUE ;     try     {         ...Do a bunch of code...     }     catch ( ... )     {         // NOTICE THERE'S NO CODE IN HERE!     }     return ( bRet ) ; }

What happens in these catch (...) situations is that your code will have some sort of access violation, which gets eaten so you don't even know it happened. After 20 minutes or more, your program crashes and you have no earthly idea why it crashed because the call stack doesn't capture the causal relationship. You're left wondering how the problem occurred. Based on what I've seen while debugging many applications, the number-one cause of unexplained bugs is catch (...). You're far better off letting the application crash, because at least you'll stand a reasonable chance of finding the bug. With catch (...) involved, the odds decrease to less than a 5 percent chance of finding it.

If you can't tell, I'm passionate about ridding your code of catch (...) statements. In fact, I expect you to put this book down, search your source files, and remove any catch (...)'s immediately. If you don't, you'll probably be hiring me soon to help find the bug—and while you're at it, helping me make another car payment.

start sidebar
Debugging War Story: The Case Against catch (...)

The Battle

I was sitting in my car driving to the airport to go visit a client. Our office manager called and said that we'd gotten a call for consulting work that sounded not only desperate but also absolutely frantic. As we were in the business of helping frantic people, I called to see what was up. The manager wasn't just frantic—he was apoplectic! He said they had a completely random bug, still unsolved, which was holding up their release. He also said that this was the least of his problems, because if they couldn't fix this bug and ship their product, their company was going out of business. They also had over 10 engineers working on this bug for three weeks straight, with no luck whatsoever. Because I don't get much excitement in my life these days compared with what I experienced in previous jobs, the opportunity to save a company certainly piqued my interest. This person then asked how fast I could make it down to their site. I told him that I was driving to the airport on my way to Seattle for the week and so I couldn't make it down until after that. We'd recently started Wintellect and didn't have anyone else on staff who was free.

At this point, he started speaking much, much louder (OK, screaming), exclaiming that he couldn't wait that long. He asked if my work in Seattle was a 24-hour-a-day job. It wasn't, so he told me I could work on his problem in the evenings. That sounded fine to me, and I told him I'd work on his bug until midnight, at which point he pulled the phone away from his ear and screamed, "He's gonna be in Seattle. Get on a plane now!" He told me that he had two engineers on their way to the airport with all the equipment necessary to debug the problems. When I asked what was going to happen if we didn't get it fixed before I had to leave Seattle, his response was, "We'll be following you to New Hampshire." This bug had already moved into the super-serious category.

The next evening I showed up at the apartment the engineers had rented and was confronted with two people who were so tired they were wobbling on their feet. They'd been working on this bug for nearly three weeks straight without much of a break. After showing me their application, I immediately broke out in a complete and utter flop sweat! They were working on a custom GINA (Graphical Identification and Authentication), the logon screen that used a custom smart card reader to log all across a custom terminal server session! Talk about a nasty application to debug! Since much of the application ran inside LSASS.EXE, you could get debugging started, but if you clicked anywhere outside the debugger, you locked the machine. To make my life even more interesting, the engineers used the Standard Template Library (STL) all over the place, so in addition to a very tough debugging problem, they had unreadable code. As we all should know, STL's main claim to fame isn't reusable data structures but rather job security. Since STL code is unreadable and unmaintainable, your company will be forced to keep paying you because only you can understand the code.

I asked them whether they could show me anything resembling duplicable crashes or data corruption. They had a document listing the 10 or 12 places they had seen crashes. My initial hypothesis was that they had a wild write to uninitialized memory. After spending a few hours figuring out how the system fit together and getting used to debugging their application, I started trying to determine whether we could find those uninitialized pointers. Grinding through the source code, I noticed they had lots of catch (...) statements all over the place. At the end of the first evening, I told them they needed to remove all the catch (...) statements so that we could see the corruption immediately and try to start narrowing down the problem.

The Outcome

When I went back to the apartment the second day, these engineers were pushed right to the edge of their physical limits. They told me that once they'd commented out the catch (...) statements, the application didn't initialize. While the developers took a nap, I started looking through the startup code and quickly found the following:

//catch ( ... ) //{     return ( FALSE ) ; //}

In their sleep-addled state, they'd forgotten to comment out the return statement. I commented it out, recompiled, and ran the program. It crashed almost immediately. On the second run, it crashed in the same place, which was the first time they'd seen a consistent crash. The third crash was a charm, and I started inspecting every single thing happening up the stack.

After carefully reading the code, we found the error in only a couple of hours. The documentation called for a buffer that was passed to another function to be 250 characters in size. A developer was passing a local variable as the buffer and had typed 25 instead of 250. Once we fixed the typo, we were able to run the application to completion!

The Lesson

The lesson is simple: don't use catch (...)! This particular company had already wasted weeks of work (and tons of money) attempting to track down a bug that was completely solvable but not reproducible because catch (...) was involved.

end sidebar

Don't Use _set_se_translator

In the first edition of this book, I covered the use of an interesting API named _set_se_translator. It has the magical ability to turn your SEH errors into C++ exceptions by calling a translation function that you define, which simply calls throw on the type you want to use for the conversion. I might as well confess now that although I was well intentioned, my advice was wrong. When you use _set_se_translator, you quickly find out that it doesn't work in release builds.

The first problem with _set_se_translator is that it isn't global in scope; it works only on a per-thread basis. That means you probably have to redesign your code to ensure that you call _set_se_translator at the beginning of each thread. Unfortunately, doing that isn't always easy. Additionally, if you're writing a component used by other processes you don't control, using _set_se_translator can and will completely mess up those processes' exception handling if they are expecting SEH exceptions and getting C++ exceptions instead.

The bigger problem with _set_se_translator has to do with the arcane implementation details of C++ exception handling. C++ exception handling can be implemented in two ways: asynchronous and synchronous. In asynchronous mode, the code generator assumes that every instruction can throw an exception. In synchronous mode, exceptions are generated explicitly only by a throw statement. The differences between asynchronous and synchronous exception handling don't seem that great, but they certainly are.

The drawback of asynchronous exceptions is that the compiler must generate what's called object lifetime tracking code for every function. Since the compiler is assuming that every instruction can throw an exception, every function that puts a C++ class onto the stack has to have code in it to hook up the destructor calls for each object in case an exception is thrown. Since exceptions are supposed to be rare or nearly impossible events, the downside to asynchronous exceptions is that you're paying quite a performance cost for all that object lifetime tracking code you'll never use.

Synchronous exception handling, on the other hand, solves the overhead problem by generating the object lifetime tracking code only when a method in the call tree for that method has an explicit throw. In fact, synchronous exception handling is such a good idea that it's the exception type the compiler uses. However, with the compiler assuming that exceptions occur only with an explicit throw in the call stack, the translator function does the throw, which is outside the normal code flow and is thus asynchronous. Consequently your carefully constructed C++ exception wrapper class never gets handled and your application crashes anyway. If you want to experiment with the differences between asynchronous and synchronous exception handling, add /EHa to the compiler command line to turn on asynchronous and remove any /GX or /EHs switches.

What makes using _set_se_translator even worse is that it works correctly in debug builds. Only in release builds will you experience the problems. That's because debug builds use synchronous exception handling instead of the release-build default of asynchronous. Because of the inherent problems with _set_se_translator, you'll definitely want to look for it in your code reviews to ensure that no one is using it.




Debugging Applications for Microsoft. NET and Microsoft Windows
Debugging Applications for MicrosoftВ® .NET and Microsoft WindowsВ® (Pro-Developer)
ISBN: 0735615365
EAN: 2147483647
Year: 2003
Pages: 177
Authors: John Robbins

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net