Visual C++ exception handling

Thursday, 15 August 2002

Example: Turning Win32 exceptions into C++ exceptions

Here a sample implementation of a win32_exception class hierarchy. First, the header file for the exception classes:

Listing 5: win32_exception.h

#include "windows.h"
#include <exception>

class win32_exception: public std::exception
{
public:
    typedef const void* Address; // OK on Win32 platform

    static void install_handler();
    virtual const char* what() const { return mWhat; };
    Address where() const { return mWhere; };
    unsigned code() const { return mCode; };
protected:
    win32_exception(const EXCEPTION_RECORD& info);
    static void translate(unsigned code, EXCEPTION_POINTERS* info);
private:
    const char* mWhat;
    Address mWhere;
    unsigned mCode;
};

class access_violation: public win32_exception
{
public:
    bool isWrite() const { return mIsWrite; };
    Address badAddress() const { return mBadAddress; };
private:
    bool mIsWrite;
    Address mBadAddress;
    access_violation(const EXCEPTION_RECORD& info);
    friend void win32_exception::translate(unsigned code, EXCEPTION_POINTERS* info);
};

A few notes on these classes:

  • The basic class is win32_exception. To set up the enhanced exception handling in a thread, you should call win32_exception::install_handler(). This must be done in each thread for which you need the enhanced exception handling.
  • Since access violations represent a common program bug, and since such exceptions have more information available (the type and location of the bad memory access), there is a specialised derived class for this case only.
  • The win32_exception class inherits from the standard library exception classes for consistency. However, even though access violations and divisions by zero could legitimately inherit from std::logic_error, this is not the case in general for Win32 exceptions. Therefore, win32_exception inherits from std::exception only.
  • The constructors are private, since only win32_exception::translate() is allowed to instantiate these exceptions.
  • The two classes are tightly coupled (note that the base class’s win32_exception::translate() is a friend of the derived class access_violation). This is fine in this lean implementation, but if you wanted to create a large hierarchy of separate classes for each kind of Win32 exception, you’d want to change this design.

Now here’s the implementation of these classes:

Listing 6: win32_exception.cpp

#include "win32_exception.h"
#include "eh.h"

void win32_exception::install_handler()
{
    _set_se_translator(win32_exception::translate);
}

void win32_exception::translate(unsigned code, EXCEPTION_POINTERS* info)
{
    // Windows guarantees that *(info->ExceptionRecord) is valid
    switch (code) {
    case EXCEPTION_ACCESS_VIOLATION:
        throw access_violation(*(info->ExceptionRecord));
        break;
    default:
        throw win32_exception(*(info->ExceptionRecord));
    }
}

win32_exception::win32_exception(const EXCEPTION_RECORD& info)
: mWhat("Win32 exception"), mWhere(info.ExceptionAddress), mCode(info.ExceptionCode)
{
    switch (info.ExceptionCode) {
    case EXCEPTION_ACCESS_VIOLATION:
        mWhat = "Access violation";
        break;
    case EXCEPTION_FLT_DIVIDE_BY_ZERO:
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
        mWhat = "Division by zero";
        break;
    }
}

access_violation::access_violation(const EXCEPTION_RECORD& info)
: win32_exception(info), mIsWrite(false), mBadAddress(0)
{
    mIsWrite = info.ExceptionInformation[0] == 1;
    mBadAddress = reinterpret_cast<win32_exception::Address>(info.ExceptionInformation[1]);
}

For more details on EXCEPTION_RECORD and the suspicious-looking cast in the access_violation constructor, look up EXCEPTION_RECORD in the MSDN Library.

And finally, here’s part of a program that uses these classes:

Listing 7: main.cpp

#include "win32_exception.h"
#include <iostream>

int main() {
    win32_exception::install_handler();
    try {
        DoComplexAndErrorProneThings();
    }
    catch (const access_violation& e) {
        std::cerr << e.what() << " at " << std::hex << e.where()
            << ": Bad " << (e.isWrite()?"write":"read")
            << " on " << e.badAddress() << std::endl;
    }
    catch (const win32_exception& e) {
        std::cerr << e.what() << " (code " << std::hex << e.code()
            << ") at " << e.where() << std::endl;
    }
}

If DoComplexAndErrorProneThings() attempts to write through a null pointer, a win32_exception will be thrown and something like the following will appear.

Access violation at 72a5ee00: Bad write on 00000000

The great thing about this is that the call stack unwinds correctly as the exception is being thrown: local objects are destroyed, resources are released, and log messages can be written, just as if a normal C++ exception had been thrown — because it has!

The EXCEPTION_POINTERS structure that gets passed to win32_exception::translate() contains a lot of other information. In particular, you can use it to get a stack trace, which would be useful for debugging. This would complicate the exception classes a bit, so I haven’t done it here.

Summary

Visual C++ treats certain runtime errors such as access violations in a similar way to regular C++ exceptions. Unfortunately, the default compiler and project settings in pre-2005 versions cause this to happen in a half-baked way, leading to unstable programs. Visual C++ 2005 treats such errors in a more predictable way: by crashing your program.

However, by writing a small amount of extra code, such errors can be handled exactly the same way as C++ exceptions. This greatly aids tracking down mysterious crashes, and allows programs to recover from bugs that normally would prove fatal.

Pages: 1 2 3

Tags:

Share this page:
  • Twitter
  • Digg
  • Slashdot
  • del.icio.us
  • Google Bookmarks
  • DZone
  • LinkedIn
  • Reddit
  • Facebook
  • Print

39 comments

You can leave a comment, or trackback from your own site.

  1. Jason, it can take a bit of investigation and preparation to set everything up, but once you get it working it’s really nice. Now you too can recover from null pointer references, just like all your fancy C# friends!

  2. Great description of a potentially tricky issue. I rarely read such well written pieces.

    The C++ boost (v 1.33.1) unit testing library pre-built didn’t get built with the ‘async exception model’ and couple of the self-tests broke in a way which I think you’ve just explained for me.

    Also, good font and layout choice, which made it not only worthwhile to read, but also pleasant to read.

  3. Motti Lanzkron

    VS8 allows you to keep the (bad) old behaviour by using the /EHa flag (project properties -> C/C++ -> Enable C++ Exceptions = Yes with SEH Exceptions)

  4. Excellent article, a must!

  5. There is a pblm with me…that I want to upgrade a project that is successfully compiling in C++ compiler 5.02…I want to compile it in C++ builer 6.0..but it gives error..as in this project try and except is used …error is for except()…and error is “catch expected”…what I do to resolve this error…help me

  6. Hi,

    This article well describes the Exception Handling in C++. I am able to understand it very well. Is there any way we can get the File and line no where exception occured ?

    Raghavan

  7. Hi Raghavan, there is a way to know where the bad thing occurred: search for crashfidner. I remember it was in an article of ‘bugslayer’…

  8. Hi Raghavan,

    to do so, I#m using the predefined macros __FILE__ respectively __LINE__ .
    See MSDN library:
    http://msdn.microsoft.com/en-us/library/b0084kay(VS.71).aspx

    Cheers,
    Moni

  9. Very Nice Article.

    I have an .exe and around 15 dlls. Some of these dlls are loaded using loadlibrary.

    Would it be enough if i build the .exe with /EHa or perhaps build one of the dlls(which is actually a listening server and does the main work) with /EHa. Or do i need to build all dlls and the .exe with /EHa to be able to catch an exception occuring anywhere in the application.

  10. Thomas, it would be safest to build everything with /EHa unless you have some specific reason you need to disable it.

  11. The proposed solution to throw a C++ exception from the _set_se_translator registered handler is not a safe operation when compiling with /EHs – occasionally, you’ll get crashes in the stack unwinding code because of this. The reason is that all the instructions necessary for properly unwinding the stack at any given execution point are omitted when compiling with /EHs. Additionally, when compiling with optimizations, the compiler may elide C++ catch handlers that it determines cannot be entered (the compiler cannot infer that a Win32 exception may invoke a _set_se_translator registered handler that may throw a C++ exception) – which means the exception may not get caught where you expected it to.

    At the point that a Win32 exception occurs, the stack and objects on the stack may not be in a consistent state. Without the instructions added when compiling with /EHa, stack unwinding may result in destructors being called for partially constructed/destructed objects – which is a very bad thing. Additionally, partially constructed objects may not be unwound properly (or at all) leading to resource leaks.

    Win32 exceptions and exception handlers have all the same challenges as signals and signal handlers have on POSIX platforms. I once read about someone advocating throwing C++ exceptions from signal handlers – which is a bad idea for exactly the same reasons as described above.

    I recommend that catching of Win32 exceptions be limited to regions of code which do not require stack unwinding (ie, catch the exception in the stack frame that generated it and don’t place any C++ objects in the try/catch block). Or, use the async-exception safe longjmp/setjmp APIs (the ones in setjmp.h, NOT setjmpex.h). These longjmp/setjmp APIs will not call destructors for objects on the stack, but will ensure that the stack is unwound safely. Unfortunately, MS developers messed this up by not providing distinct interfaces to the async-exception safe setjmp/longjmp APIs (On POSIX systems, sigsetjmp and siglongjmp exist for this exact reason). You need to be very careful to ensure that setjmpex.h was not included prior to setjmp.h

  12. Thanks for your comment Tom. Just in case it wasn’t clear, I agree that _set_se_translator does not play well with /EHs. That’s why I said:

    You must also compile with the /EHa compiler option to enable asynchronous exception handling. At runtime, you install your handler by calling the global function _set_se_translator().

    The dire consequences you describe can indeed happen in code compiled with /EHs. But using /EHa pretty much solves all these problems. Following the approach I describe in the article should yield well-behaved and useful C++ exceptions. I’d like to know about any problems with this approach.

  13. Hi Bennett,

    I missed that you did mention that compilation with /EHa is required – sorry, my bad.

    There are two negative side effects with the use of /EHa. First, the overhead of the asynchronous unwind code affects performance and generates larger binaries. Second, compilation with /EHa causes catch(…) handlers to be entered for Win32 exceptions. Your post states that “This problem does not occur in Visual C++ 2005, where catch(…) never catches Win32 exceptions.”. This isn’t correct as the following code demonstrates:

    #include

    int main()
    {
    try
    {
    volatile int* pi = NULL;
    volatile int i = *pi;
    }
    catch(…)
    {
    std::cerr << “Caught exception” << std::endl;
    }
    return 0;
    }

    Compile this with “cl /O2 /EHa /Zi /MD main.cpp /Femain.exe” and run main.exe and you’ll see that the access violation does cause the catch(…) handler to be invoked.

    For all the reasons enumerated in your excellent post, this is disastrous for code quality and in my opinion sufficient reason to not compile with /EHa.

    Unfortunately, I was mistaken in my previous comment regarding async-exception safe setjmp/longjmp. I have since discovered that neither the “setjmp.h” or “setjmpex.h” setjmp/longjmp implementations are async-exception safe since both attempt to unwind the stack. The Microsoft documentation is confused about this – see http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx. This documentation currently contradicts itself with regard to whether or not C++ stack objects are destroyed when longjmp is called. In our environment (compiling with /EHsc), we have seen crashes with call stacks like this:

    msvcr80!raise+0×187
    msvcr80!abort+0x3d
    msvcr80!terminate+0x4d
    msvcr80!_inconsistency+0×22
    msvcr80!__FrameUnwindToState+0x4b
    msvcr80!__CxxLongjmpUnwind+0×14
    msvcr80!longjmp+0×76

    The fact that longjmp called a function named CxxLongjmpUnwind suggests that it does in fact destroy C++ stack objects while unwinding.

    This has left me in a bind. We need to do something like what you are suggesting – return to a previous execution point when a Win32 exception occurs, but compiling with /EHa is too burdensome since it will cause uses of catch(…) to catch Win32 exceptions. I had turned to setjmp/longjmp hoping to solve the problem that way (I don’t care about C++ stack objects being destroyed when returning to the previous execution point). Unfortunately, I’m probably going to have to implement my own setjmp/longjmp routines to do this safely without performing C++ stack unwinding.

    Ideally, Microsoft would provide versions of setjmp/longjmp that do not attempt to perform C++ stack unwinding – similar to how the Single UNIX Spec provides sigsetjmp/siglongjmp to handle asynchronous signals.

  14. This is one of the most informative articles I have seen on Exception Handling. It is definitly a must read for anyone who wants to know how to handle a Win32 exception in C++.

  15. Good article about EH. To avoid the side effects with /EHa, we can write code as following in VC 2005 with /EHs:
    void appGo()
    {
    try
    {
    // application run….
    }
    catch(…)
    {
    }
    }
    void main()
    {
    __try
    {
    appGo();
    }
    __except(1)
    {
    // do something
    }
    We put all application code into appGo() function. Thus __try/__except can handle the win32 exception, and all c++ exception can be hanlded in appGo().

    I dont know whether this method is practice.
    Please correct me.

  16. Thank you, from Spain!
    Sorry for my english.
    Very good article. It save me a migration VS2003 ==> VS2008.

Leave a comment