Click here to Skip to main content
15,867,453 members
Articles / General Programming / Exceptions

Effective Exception Handling in Visual C++

Rate me:
Please Sign up or sign in to vote.
4.94/5 (118 votes)
6 Jan 2012CPOL18 min read 343.1K   7.2K   296   47
An overview of the standard exception handling techniques provided by Visual C++.

Introduction

This article describes the standard techniques of handling exceptions and errors in Visual C++ programs running in Windows.

An exception (or a critical error, or crash) typically means your program stops working normally and needs to stop its execution. For example, an exception may occur because of your program accessing an invalid memory address (such as NULL pointer), memory buffer can't be allocated (out of memory), the C run-time libraries (CRT) detect an error and request program termination, and so on.

A C++ program may handle several types of exceptions: SEH exceptions produced through the Operating System's structured exception handling mechanism, CRT errors produced by the C run-time libraries, and finally, signals. Each error type requires installing an exception handler function that would intercept an exception and do some error recovery actions.

If your application has several execution threads, things may be even more complicated. Some exception handlers work for an entire process, but some work for the current thread only. So you have to install exception handlers in each thread.

Each module (EXE or DLL) in your application is linked to CRT libraries (either statically or dynamically). Exception handling techniques strongly depend on the CRT linkage type.

The variety of error types, differences of handling exceptions in multi-threaded programs, and dependence of exception handling on CRT linkage requires a lot of work to handle exactly all exceptions that your application is allowed to handle. This article is aimed to help you better understand the exception handling mechanisms and effectively use exception handling in your C++ applications.

A small console demo application ExceptionHandler is attached to the article. The demo can raise and catch different types of exceptions and generate a crash minidump file allowing to see the line of the code where exception occurred.

Background

Some time ago I needed a way for intercepting exceptions for one of my Open-Source projects, CrashRpt - A crash reporting library for Windows applications. The CrashRpt library handles exceptions occurring in your application, collects technical information about the error (such as crash minidump, error logs, desktop screenshots), and offers the user to send an error report over the Internet (figure 1).

Figure 1 - Error report window and error report details dialogs of the CrashRpt library

crashrpt.png

Maybe you have seen the Windows Error Reporting window (figure 2) suddenly appearing on your desktop, and the CrashRpt library does the same things, except it sends the error report to your own web-server instead of Microsoft's server.

Figure 2 - Windows Error Reporting (Dr. Watson) window

wer.gif

Browsing the MSDN gave me the SetUnhandledExceptionFilter() function that I used to handle access violations. But soon I figured out that some of exceptions in my application are somehow left unhandled and Dr. Watson's window still appears instead of the CrashRpt window.

I browsed MSDN some more and found many other CRT-provided functions that could be used to handle CRT errors. Here are some examples of such functions: set_terminate(), _set_invalid_parameter_handler(), _set_purecall_handler().

Then I found that some CRT handlers are valid for the current thread only, but some of them work for all threads of the process.

Continuing my research, I found out that there are many nuances a developer must understand to use exception handling effectively. Results of my research are presented below.

Some Words About Exceptions

As you already know, an exception or a critical error typically means that a program stops working normally and needs to stop its execution.

For example, an exception may occur because of the following:

  • program accesses an invalid memory address (such as NULL pointer)
  • stack overflows due to infinite recursion
  • large block of data is written to a small buffer
  • a pure virtual method of a C++ class is called
  • memory buffer can't be allocated (out of memory)
  • invalid parameter is passed to a C++ system function
  • C run-time libraries detect an error and request program termination

There are two kinds of exceptions that have a different nature: SEH exceptions (Structured Exception Handling, SEH) and typed C++ exceptions. You can find an in-depth description of exception mechanism implementation in the excellent How a Compiler Implements Exception Handling article by Vishal Kochhar.

Structured exception handling mechanism is provided by the Operating System (this means that all Windows applications can raise and handle SEH exceptions). SEH exceptions were originally designed for the C language, but they can be used in C++ too.

SEH exceptions are handled using the __try{}__except(){} construction. The main() function of your program is guarded with such a construction, so by default all unhandled SEH exceptions are caught and Dr. Watson is invoked. SEH exceptions are Visual C++ compiler-specific. If you write portable code, you should guard the structured exception handling construction with #ifdef/#endif.

Here is a code example:

C++
int* p = NULL;   // pointer to NULL
__try
{
    // Guarded code
    *p = 13; // causes an access violation exception
}
__except(EXCEPTION_EXECUTE_HANDLER) // Here is exception filter expression
{  
    // Here is exception handler
 
    // Terminate program
    ExitProcess(1);
}

On the other hand, the C++ typed exception mechanism is provided by the C run-time libraries (this means that only C++ applications can raise and handle such exceptions). C++ typed exceptions are handled using the try{}catch{} construction. An example is presented below (the code taken from http://www.cplusplus.com/doc/tutorial/exceptions/):

C++
// exceptions
#include <iostream>
using namespace std;
int main () {
    try
    {
        throw 20;
    }
    catch (int e)
    {
        cout << "An exception occurred. Exception Nr. " << e << endl;
    }
    return 0;
}

Structured Exception Handling

When a SEH exception occurs, you typically see a window of Dr. Watson (see figure 2) that offers to send an error report to Microsoft. You even can generate an SEH exception yourself using the RaiseException() function.

Each SEH exception has an associated exception code. You can extract the exception code inside of the __except statement using the GetExceptionCode() intrinsic function. You can extract the exception information inside of the __except statement using the GetExceptionInformation() intrinsic function. To use these intrinsic functions, you usually create your custom exception filter as shown in the example below.

The following example shows how to use an SEH exception filter:

C++
int seh_filter(unsigned int code, struct _EXCEPTION_POINTERS* ep)
{
  // Generate error report
  // Execute exception handler
  return EXCEPTION_EXECUTE_HANDLER;
}
void main()
{
  __try
  {
    // .. some buggy code here
  }
  __except(seh_filter(GetExceptionCode(), GetExceptionInformation()))
  {    
    // Terminate program
    ExitProcess(1);
  }
}

The __try{}__except(){} construction is mostly C oriented. However, you can redirect an SEH exception to a C++ typed exception and handle it as you do with C++ typed exceptions. This can be done using the _set_se_translator() function provided by the C++ runtime libraries (CRT).

Here is a code example (taken from MSDN):

C++
// crt_settrans.cpp
// compile with: /EHa
#include <stdio.h>
#include <windows.h>
#include <eh.h>
void SEFunc();
void trans_func( unsigned int, EXCEPTION_POINTERS* );
class SE_Exception
{
private:
    unsigned int nSE;
public:
    SE_Exception() {}
    SE_Exception( unsigned int n ) : nSE( n ) {}
    ~SE_Exception() {}
    unsigned int getSeNumber() { return nSE; }
};
int main( void )
{
    try
    {
        _set_se_translator( trans_func );
        SEFunc();
    }
    catch( SE_Exception e )
    {
        printf( "Caught a __try exception with SE_Exception.\n" );
    }
}
void SEFunc()
{
    __try
    {
        int x, y=0;
        x = 5 / y;
    }
    __finally
    {
        printf( "In finally\n" );
    }
}
void trans_func( unsigned int u, EXCEPTION_POINTERS* pExp )
{
    printf( "In trans_func.\n" );
    throw SE_Exception();
}

However, the disadvantage of the __try{}__catch(Expression){} construction is that you may forget to guard a potentially incorrect code that may cause an exception that won't be handled by your program. Such an unhandled SEH exception can be caught using the top-level unhandled exception filter set with the SetUnhandledExceptionFilter() function.

Note: The word top-level means that if someone calls the SetUnhandledExceptionFilter() function after your call, the exception filter will be replaced. This is a disadvantage because you can't chain top-level handlers one above another. Such a disadvantage can be eliminated by a Vectored Exception Handling mechanism discussed later.

The exception information (CPU state before the exception occurred) is passed to the exception handler through the EXCEPTION_POINTERS structure.

Here is a code example:

C++
LONG WINAPI MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionPtrs)
{
  // Do something, for example generate error report
  //..
  // Execute default exception handler next
  return EXCEPTION_EXECUTE_HANDLER; 
} 
void main()
{ 
  SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
  // .. some unsafe code here 
}

The top-level SEH exception handler works for all threads of the caller process, so it's enough to call it once in the beginning of your main() function.

The top-level SEH exception handler is called in the context of the thread where the exception has occurred. This can affect the exception handler's ability to recover from certain exceptions, such as an invalid stack.

If your exception handler function is located inside of a DLL, you should be careful when using the SetUnhandledExceptionFilter() function. If your DLL is unloaded at the moment of crash, the behavior may be unpredictable.

Note: In Windows 7, there is a new function RaiseFailFastException(). This function allows to ignore all installed exception handlers (either SEH or vectored) and pass the exception directly to Dr. Watson. Typically, you call this function if your application is in a bad state and you want to terminate the application immediately and have a Windows Error Report created. For additional information, see the Reference section below.

Vectored Exception Handling

Vectored Exception Handling (VEH) is an extension to structured exception handling. It was introduced in Windows XP.

To add a vectored exception handler, you can use the AddVectoredExceptionHandler() function. The disadvantage is that VEH is only available in Windows XP and later, so the presence of AddVectoredExceptionHandler() function should be checked at run-time.

To remove the previously installed handler, use the RemoveVectoredExceptionHandler() function.

VEH allows to watch or handle all SEH exceptions for the application. To preserve backward compatibility, when an SEH exception occurs in some part of the program, the system calls the installed VEH handlers in turn, after that it searches for the usual SEH handlers.

An advantage of VEH is the ability to chain exception handlers, so if somebody installs a vectored exception handler above yours, you still can intercept the exception.

Vectored exception handling is suitable when you need to monitor _all_ SEH exceptions, like a debugger does. But the problem is you have to decide which exception to handle and which to skip. In the program's code, some exceptions may be intentionally guarded by a __try{}__except(){} construction, and by handling such exceptions in VEH and not passing it to a frame-based SEH handler, you may introduce bugs into the application logics.

I think that the SetUnhandledExceptionFilter() function is more suitable for exception handling than VEH, because it is the top-level SEH handler. If nobody handles the exception, the top-level SEH handler is called and you don't need to decide if you should skip the exception or not.

CRT Error Handling

In addition to SEH exceptions and C++ typed exceptions, C runtime libraries (CRT) provides their own error handling mechanism that should be taken into account in your program. When a CRT error occurs, you typically see a CRT error message window (figure 3).

Figure 3 - CRT Error Message

invparam_error.png

Terminate Handler

When CRT encounters an unhandled C++ typed exception, it calls the terminate() function. To intercept such calls and take an appropriate action, you should set the error handler using the set_terminate() function.

Here is a code example:

C++
void my_terminate_handler()
{
  // Abnormal program termination (terminate() function was called)
  // Do something here
  // Finally, terminate program
  exit(1); 
}
void main()
{
  set_terminate(my_terminate_handler);
  terminate();
}

There is the unexpected() function that is not used with the current implementation of Visual C++ exception handling. However, consider using the set_unexpected() function to set a handler for the unexpected() function, too.

Note: In a multi-threaded environment, unexpected and terminate functions are maintained separately for each thread. Each new thread needs to install its own unexpected and terminate function. Thus, each thread is in charge of its own unexpected and terminate handling.

Pure Call Handler

Use the _set_purecall_handler() function to handle pure virtual function calls. This function can be used in VC++ .NET 2003 and later. This function works for all threads of the caller process.

Here is a code example (taken from MSDN):

C++
// _set_purecall_handler.cpp
// compile with: /W1
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
class CDerived;
class CBase
{
public:
   CBase(CDerived *derived): m_pDerived(derived) {};
   ~CBase();
   virtual void function(void) = 0;
   CDerived * m_pDerived;
};
class CDerived : public CBase
{
public:
   CDerived() : CBase(this) {};   // C4355
   virtual void function(void) {};
};
CBase::~CBase()
{
   m_pDerived -> function();
}
void myPurecallHandler(void)
{
   printf("In _purecall_handler.");
   exit(0);
}
int _tmain(int argc, _TCHAR* argv[])
{
   _set_purecall_handler(myPurecallHandler);
   CDerived myDerived;
}

New Operator Fault Handler

Use the _set_new_handler() function to handle memory allocation faults. This function can be used in VC++ .NET 2003 and later. This function works for all threads of the caller process. Consider using the _set_new_mode() function to define error behaviour for the malloc() function.

Here is a code example (taken from MSDN):

C++
#include <new.h>
int handle_program_memory_depletion( size_t )
{
   // Your code
}
int main( void )
{
   _set_new_handler( handle_program_memory_depletion );
   int *pi = new int[BIG_NUMBER];
}

Invalid Parameter Handler

Use the _set_invalid_parameter_handler() function to handle situations when CRT detects an invalid argument in a system function call. This function can be used in VC++ 2005 and later. This function works for all threads of the caller process.

Here is a code example (taken from MSDN):

C++
// crt_set_invalid_parameter_handler.c
// compile with: /Zi /MTd
#include <stdio.h>
#include <stdlib.h>
#include <crtdbg.h>  // For _CrtSetReportMode
void myInvalidParameterHandler(const wchar_t* expression,
   const wchar_t* function, 
   const wchar_t* file, 
   unsigned int line, 
   uintptr_t pReserved)
{
   wprintf(L"Invalid parameter detected in function %s."
            L" File: %s Line: %d\n", function, file, line);
   wprintf(L"Expression: %s\n", expression);
}
int main( )
{
   char* formatString;
   _invalid_parameter_handler oldHandler, newHandler;
   newHandler = myInvalidParameterHandler;
   oldHandler = _set_invalid_parameter_handler(newHandler);
   // Disable the message box for assertions.
   _CrtSetReportMode(_CRT_ASSERT, 0);
   // Call printf_s with invalid parameters.
   formatString = NULL;
   printf(formatString);
}

C++ Signal Handling

C++ provides a program interruption mechanism called signals. You can handle the signals with the signal() function.

In Visual C++, there are six types of signals:

  • SIGABRT Abnormal termination
  • SIGFPE Floating-point error
  • SIGILL Illegal instruction
  • SIGINT CTRL+C signal
  • SIGSEGV Illegal storage access
  • SIGTERM Termination request

MSDN says that the SIGILL, SIGSEGV, and SIGTERM signals are not generated under Windows and included for ANSI compatibility. However, practice shows that if you set the SIGSEGV signal handler in the main thread, it is called by CRT instead of the SEH exception handler set with the SetUnhandledExceptionFilter() function and the global variable _pxcptinfoptrs contains a pointer to the exception information. In other threads, the exception filter set with SetUnhandledExceptionFilter() function is called instead of the SIGSEGV handler.

Note: In Linux, signals are the main way of exception handling (Linux's implementation of C run-time, glibc, also provides set_unexpected() and set_terminate() handlers). As you can see, in Windows, signals are not used as intensively as they deserve. Instead of using signals, C run-time libraries provide several Visual C++-specific error handler functions, such as _invalid_parameter_handler() and so on.

The _pxcptinfoptrs global variable can also be used in the SIGFPE handler. In all other signal handlers, it seems to be NULL.

The SIGFPE signal handler is called by CRT when a floating point error occurs, such as division by zero. However, by default, floating point exceptions are not generated, instead NaN or infinity numbers are generated as the result of a floating point operation. Use the _controlfp_s() function to enable floating point exception generation.

You can generate all six signals manually using the raise() function.

Here is an example:

C++
void sigabrt_handler(int)
{
  // Caught SIGABRT C++ signal
  // Terminate program
  exit(1);
}
void main()
{
  signal(SIGABRT, sigabrt_handler);
     
  // Cause abort
  abort();       
}

Note: Although it is not well documented in MSDN, it seems that you should install SIGFPE, SIGILL, and SIGSEGV signal handlers for each new thread in your program. The SIGABRT, SIGINT, and SIGTERM signal handlers seems to work for all threads of the caller process, so you should install them once in your main() function.

Retrieving Exception Information

When an exception occurs, you typically want to get the CPU state to determine the place in your code that caused the problem. You may want to pass this information to the MiniDumpWriteDump() function to debug the problem later (for an example of how to do this, see the XCrashReport: Exception Handling and Crash Reporting - Part 3 article by Hans Dietrich). The way you retrieve the exception information differs depending on the exception handler you use.

In the SEH exception handler set with the SetUnhandledExceptionFilter() function, the exception information is retrieved from the EXCEPTION_POINTERS structure passed as the function parameter.

In the __try{}__catch(Expression){} construction, you retrieve the exception information using the GetExceptionInformation() intrinsic function and pass it to the SEH exception filter function as a parameter.

In the SIGFPE and SIGSEGV signal handlers, you can retrieve the exception information from the _pxcptinfoptrs global CRT variable that is declared in <signal.h>. This variable is not documented well in MSDN.

In other signal handlers and in CRT error handlers, you have no ability to easily extract the exception information. I found a workaround used in the CRT code (see CRT 8.0 source files, invarg.c, line 104).

The following code shows how to get the current CPU state used as exception information:

C++
#if _MSC_VER>=1300
#include <rtcapi.h>
#endif
#ifndef _AddressOfReturnAddress
// Taken from: http://msdn.microsoft.com/en-us/library/s975zw7k(VS.71).aspx
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif
// _ReturnAddress and _AddressOfReturnAddress should be prototyped before use 
EXTERNC void * _AddressOfReturnAddress(void);
EXTERNC void * _ReturnAddress(void);
#endif 
// The following function retrieves exception info
void GetExceptionPointers(DWORD dwExceptionCode, 
  EXCEPTION_POINTERS** ppExceptionPointers)
{
  // The following code was taken from VC++ 8.0 CRT (invarg.c: line 104)
  
  EXCEPTION_RECORD ExceptionRecord;
  CONTEXT ContextRecord;
  memset(&ContextRecord, 0, sizeof(CONTEXT));
  
#ifdef _X86_
  __asm {
      mov dword ptr [ContextRecord.Eax], eax
      mov dword ptr [ContextRecord.Ecx], ecx
      mov dword ptr [ContextRecord.Edx], edx
      mov dword ptr [ContextRecord.Ebx], ebx
      mov dword ptr [ContextRecord.Esi], esi
      mov dword ptr [ContextRecord.Edi], edi
      mov word ptr [ContextRecord.SegSs], ss
      mov word ptr [ContextRecord.SegCs], cs
      mov word ptr [ContextRecord.SegDs], ds
      mov word ptr [ContextRecord.SegEs], es
      mov word ptr [ContextRecord.SegFs], fs
      mov word ptr [ContextRecord.SegGs], gs
      pushfd
      pop [ContextRecord.EFlags]
  }
  ContextRecord.ContextFlags = CONTEXT_CONTROL;
#pragma warning(push)
#pragma warning(disable:4311)
  ContextRecord.Eip = (ULONG)_ReturnAddress();
  ContextRecord.Esp = (ULONG)_AddressOfReturnAddress();
#pragma warning(pop)
  ContextRecord.Ebp = *((ULONG *)_AddressOfReturnAddress()-1);
#elif defined (_IA64_) || defined (_AMD64_)
  /* Need to fill up the Context in IA64 and AMD64. */
  RtlCaptureContext(&ContextRecord);
#else  /* defined (_IA64_) || defined (_AMD64_) */
  ZeroMemory(&ContextRecord, sizeof(ContextRecord));
#endif  /* defined (_IA64_) || defined (_AMD64_) */
  ZeroMemory(&ExceptionRecord, sizeof(EXCEPTION_RECORD));
  ExceptionRecord.ExceptionCode = dwExceptionCode;
  ExceptionRecord.ExceptionAddress = _ReturnAddress();
  
  EXCEPTION_RECORD* pExceptionRecord = new EXCEPTION_RECORD;
  memcpy(pExceptionRecord, &ExceptionRecord, sizeof(EXCEPTION_RECORD));
  CONTEXT* pContextRecord = new CONTEXT;
  memcpy(pContextRecord, &ContextRecord, sizeof(CONTEXT));
  *ppExceptionPointers = new EXCEPTION_POINTERS;
  (*ppExceptionPointers)->ExceptionRecord = pExceptionRecord;
  (*ppExceptionPointers)->ContextRecord = pContextRecord;  
}

Exception Handling and CRT Linkage

Each module (EXE, DLL) in your application is linked to CRT (C run-time libraries). You may link CRT as a multi-threaded static library or as a multi-threaded dynamic link library. When you set CRT error handlers, such as terminate handler, unexpected handler, pure call handler, invalid parameter handler, new operator error handler, or a signal handler, they will work for the CRT the caller module is linked to and won't intercept exceptions in different CRT modules (if exist), because each CRT module has its own internal state.

Several project modules may share a single CRT DLL. This reduces to minimum the overall size of the linked CRT code. And all exceptions within that CRT DLL can be handled at once. That's why a multi-threaded CRT DLL is the recommended way of CRT linkage. However, many developers still prefer static CRT linkage because it's easier to distribute a single executable module statically linked with CRT than to distribute the same executable linked with several dynamic-link CRT libraries (for more information, see the Create projects easily with private MFC, ATL, and CRT assemblies article by Martin Richter).

If you plan to use CRT as a static link library (which is not recommended) and want to use some exception handling functionality, you have to build the functionality as a static library with the /NODEFAULTLIB linker flag and then link this functionality to each EXE and DLL module of your application. You would also have to install the CRT error handlers for each module of your application, while the SEH exception handler would still be installed once.

Visual C++ Compiler Flags

There are several Visual C++ compiler switches that are related to exception handling. You can find the switches if you open project Properties->Configuration Properties->C/C++->Code Generation.

Exception Handling Model

You can set an exception handling model for your Visual C++ compiler with /EHs (or EHsc) to specify synchronous exception handling model, or /EHa to specify asynchronous exception handling model. The asynchronous model can be used to force the try{}catch(){} construction to catch both SEH and C++ typed exceptions (the same effect can be achieved with the _set_se_translator() function). If a synchronous model is utilized, SEH exceptions are not caught by the try{}catch(){} construction. Asynchronous model was the default in previous versions of Visual C++, but the synchronous one is the default in the newer versions.

Floating Point Exceptions

You can enable floating point exceptions using the /fp:except compiler flag. This option is disabled by default, so floating point exceptions are not raised. For more information, see /fp(Specify Floating Point Behavior) in the References section below.

Buffer Security Checks

By default, you have the /GS (Buffer Security Check) compiler flag enabled that forces the compiler to inject code that would check for buffer overruns. A buffer overrun is a situation when a large block of data is written to a small buffer.

Note; In Visual C++ .NET (CRT 7.1), you can use the _set_security_error_handler() function that CRT calls when a buffer overrun is detected. However, this function is deprecated in the later versions of CRT.

Since CRT 8.0, you can't intercept the buffer overrun errors in your code. When a buffer overrun is detected, CRT invokes Dr. Watson directly instead of calling the unhandled exception filter. This is done because of security reasons and Microsoft doesn't plan to change this behavior. For additional info, please see these links:

Using the Code

A small console demo application ExceptionHandler is attached to the article. The demo can raise and catch different types of exceptions and generate a crash minidump file. The app is displayed in the figure below.

Figure 4 - ExceptionHandler Sample Application

exception_handler_demo.png

To see how this works, you need to chose an exception type (enter a number between 0 and 13) and press Enter. Then an exception is raised and caught by an exception handler. The exception handler then invokes the code that generates a crash minidump file using the MiniDumpWriteDump() function from dbghelp.dll. The minidump file is written to the current folder and named crashdump.dmp. You can double-click the file to open it in Visual Studio, then press F5 to run it and see the line of the code where exception occurred.

There are two logical parts in the application: the part that raises exceptions and the part that catches the exceptions and generates crash minidump. These two parts are described below.

The file main.cpp contains the main() function that contains the code for exception type selection and a big switch to choose what exception to rise. To raise an exception, the main function may call raise() or RaiseException() functions, throw a C++ typed exception, call some helper code, like RecurseAlloc() or sigfpe_test() and so on.

In the beginning of the main() function, an instance of CCrashHandler class is created. This instance is used to later catch an exception. We call CCrashHandler::SetProcessExceptionHandlers() method to set exception handlers working for the entire process (like SEH exception handler). We call CCrashHandler::SetThreadExceptionHandlers() method to install exception handlers that work for the current thread only (like unexpected or terminate handlers).

Below, the code of the main function is presented.

C++
void main()
{
    CCrashHandler ch;
    ch.SetProcessExceptionHandlers();
    ch.SetThreadExceptionHandlers();
    
    printf("Choose an exception type:\n");
    printf("0 - SEH exception\n");
    printf("1 - terminate\n");
    printf("2 - unexpected\n");
    printf("3 - pure virtual method call\n");
    printf("4 - invalid parameter\n");
    printf("5 - new operator fault\n");    
    printf("6 - SIGABRT\n");
    printf("7 - SIGFPE\n");
    printf("8 - SIGILL\n");
    printf("9 - SIGINT\n");
    printf("10 - SIGSEGV\n");
    printf("11 - SIGTERM\n");
    printf("12 - RaiseException\n");
    printf("13 - throw C++ typed exception\n");
    printf("Your choice >  ");

    int ExceptionType = 0;
    scanf_s("%d", &ExceptionType);

    switch(ExceptionType)
    {
    case 0: // SEH
        {
            // Access violation
            int *p = 0;
#pragma warning(disable : 6011)
// warning C6011: Dereferencing NULL pointer 'p'
            *p = 0;
#pragma warning(default : 6011)   
        }
        break;
    case 1: // terminate
        {
            // Call terminate
            terminate();
        }
        break;
    case 2: // unexpected
        {
            // Call unexpected
            unexpected();
        }
        break;
    case 3: // pure virtual method call
        {
            // pure virtual method call
            CDerived derived;
        }
        break;
    case 4: // invalid parameter
        {      
            char* formatString;
            // Call printf_s with invalid parameters.
            formatString = NULL;
#pragma warning(disable : 6387)
// warning C6387: 'argument 1' might be '0': this does
// not adhere to the specification for the function 'printf'
            printf(formatString);
#pragma warning(default : 6387)   

        }
        break;
    case 5: // new operator fault
        {
            // Cause memory allocation error
            RecurseAlloc();
        }
        break;
    case 6: // SIGABRT 
        {
            // Call abort
            abort();
        }
        break;
    case 7: // SIGFPE
        {
            // floating point exception ( /fp:except compiler option)
            sigfpe_test();            
        }    
        break;
    case 8: // SIGILL 
        {
            raise(SIGILL);              
        }    
        break;
    case 9: // SIGINT 
        {
            raise(SIGINT);              
        }    
        break;
    case 10: // SIGSEGV 
        {
            raise(SIGSEGV);              
        }    
        break;
    case 11: // SIGTERM
        {
            raise(SIGTERM);            
        }
        break;
    case 12: // RaiseException 
        {
            // Raise noncontinuable software exception
            RaiseException(123, EXCEPTION_NONCONTINUABLE, 0, NULL);        
        }
        break;
    case 13: // throw 
        {
            // Throw typed C++ exception.
            throw 13;
        }
        break;
    default:
        {
            printf("Unknown exception type specified.");    
            _getch();
        }
        break;
    }
}

The files CrashHandler.h andCrashHandler.cpp contain the implementation of the exception handling and crash minidump generation functionality. Below the declaration of the class is presented.

C++
class CCrashHandler  
{
public:

    // Constructor
    CCrashHandler();

    // Destructor
    virtual ~CCrashHandler();

    // Sets exception handlers that work on per-process basis
    void SetProcessExceptionHandlers();

    // Installs C++ exception handlers that function on per-thread basis
    void SetThreadExceptionHandlers();

    // Collects current process state.
    static void GetExceptionPointers(
        DWORD dwExceptionCode, 
        EXCEPTION_POINTERS** pExceptionPointers);

    // This method creates minidump of the process
    static void CreateMiniDump(EXCEPTION_POINTERS* pExcPtrs);

    /* Exception handler functions. */

    static LONG WINAPI SehHandler(PEXCEPTION_POINTERS pExceptionPtrs);
    static void __cdecl TerminateHandler();
    static void __cdecl UnexpectedHandler();

    static void __cdecl PureCallHandler();

    static void __cdecl InvalidParameterHandler(const wchar_t* expression, 
           const wchar_t* function, const wchar_t* file, 
           unsigned int line, uintptr_t pReserved);

    static int __cdecl NewHandler(size_t);

    static void SigabrtHandler(int);
    static void SigfpeHandler(int /*code*/, int subcode);
    static void SigintHandler(int);
    static void SigillHandler(int);
    static void SigsegvHandler(int);
    static void SigtermHandler(int);
};

As you could see from the code above, the CCrashHandler class has two methods for setting exception handlers: SetProcessExceptionHandlers() and SetThreadExceptionHandlers(), for the entire process and for the current thread, respectively. The code of these two methods is presented below.

C++
void CCrashHandler::SetProcessExceptionHandlers()
{
    // Install top-level SEH handler
    SetUnhandledExceptionFilter(SehHandler);    

    // Catch pure virtual function calls.
    // Because there is one _purecall_handler for the whole process, 
    // calling this function immediately impacts all threads. The last 
    // caller on any thread sets the handler. 
    // http://msdn.microsoft.com/en-us/library/t296ys27.aspx
    _set_purecall_handler(PureCallHandler);    

    // Catch new operator memory allocation exceptions
    _set_new_handler(NewHandler);

    // Catch invalid parameter exceptions.
    _set_invalid_parameter_handler(InvalidParameterHandler); 

    // Set up C++ signal handlers

    _set_abort_behavior(_CALL_REPORTFAULT, _CALL_REPORTFAULT);

    // Catch an abnormal program termination
    signal(SIGABRT, SigabrtHandler);  

    // Catch illegal instruction handler
    signal(SIGINT, SigintHandler);     

    // Catch a termination request
    signal(SIGTERM, SigtermHandler);          
}

void CCrashHandler::SetThreadExceptionHandlers()
{

    // Catch terminate() calls. 
    // In a multithreaded environment, terminate functions are maintained 
    // separately for each thread. Each new thread needs to install its own 
    // terminate function. Thus, each thread is in charge of its own termination handling.
    // http://msdn.microsoft.com/en-us/library/t6fk7h29.aspx
    set_terminate(TerminateHandler);       

    // Catch unexpected() calls.
    // In a multithreaded environment, unexpected functions are maintained 
    // separately for each thread. Each new thread needs to install its own 
    // unexpected function. Thus, each thread is in charge of its own unexpected handling.
    // http://msdn.microsoft.com/en-us/library/h46t5b69.aspx  
    set_unexpected(UnexpectedHandler);    

    // Catch a floating point error
    typedef void (*sigh)(int);
    signal(SIGFPE, (sigh)SigfpeHandler);     

    // Catch an illegal instruction
    signal(SIGILL, SigillHandler);     

    // Catch illegal storage access errors
    signal(SIGSEGV, SigsegvHandler);   

}

In the class declaration, you also can see several exception handler functions, such as SehHandler(), TerminateHandler() and so on. Any of these exception handlers can be called when an exception occurrs. A handler function (optionally) retrieves exception information and invokes crash minidump generation code, then it terminates process with TerminateProcess() function call.

The GetExceptionPointers() static method is used to retrieve exception information. I've described how this works in the section Retrieving Exception Information above.

The CreateMiniDump() method is used for crash minidump generation. It takes a pointer to the EXCEPTION_POINTERS structure containing exception information. The method calls the MiniDumpWriteDump() function from Microsoft Debug Help Library to generate a minidump file. The code of the method is presented below.

C++
// This method creates minidump of the process
void CCrashHandler::CreateMiniDump(EXCEPTION_POINTERS* pExcPtrs)
{   
    HMODULE hDbgHelp = NULL;
    HANDLE hFile = NULL;
    MINIDUMP_EXCEPTION_INFORMATION mei;
    MINIDUMP_CALLBACK_INFORMATION mci;
    
    // Load dbghelp.dll
    hDbgHelp = LoadLibrary(_T("dbghelp.dll"));
    if(hDbgHelp==NULL)
    {
        // Error - couldn't load dbghelp.dll
        return;
    }

    // Create the minidump file
    hFile = CreateFile(
        _T("crashdump.dmp"),
        GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    if(hFile==INVALID_HANDLE_VALUE)
    {
        // Couldn't create file
        return;
    }
   
    // Write minidump to the file
    mei.ThreadId = GetCurrentThreadId();
    mei.ExceptionPointers = pExcPtrs;
    mei.ClientPointers = FALSE;
    mci.CallbackRoutine = NULL;
    mci.CallbackParam = NULL;

    typedef BOOL (WINAPI *LPMINIDUMPWRITEDUMP)(
        HANDLE hProcess, 
        DWORD ProcessId, 
        HANDLE hFile, 
        MINIDUMP_TYPE DumpType, 
        CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, 
        CONST PMINIDUMP_USER_STREAM_INFORMATION UserEncoderParam, 
        CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);

    LPMINIDUMPWRITEDUMP pfnMiniDumpWriteDump = 
        (LPMINIDUMPWRITEDUMP)GetProcAddress(hDbgHelp, "MiniDumpWriteDump");
    if(!pfnMiniDumpWriteDump)
    {    
        // Bad MiniDumpWriteDump function
        return;
    }

    HANDLE hProcess = GetCurrentProcess();
    DWORD dwProcessId = GetCurrentProcessId();

    BOOL bWriteDump = pfnMiniDumpWriteDump(
        hProcess,
        dwProcessId,
        hFile,
        MiniDumpNormal,
        &mei,
        NULL,
        &mci);

    if(!bWriteDump)
    {    
        // Error writing dump.
        return;
    }

    // Close file
    CloseHandle(hFile);

    // Unload dbghelp.dll
    FreeLibrary(hDbgHelp);
}

References

History

  • 7 June 2011 - Initial release.
  • 10 June 2011 - Added Visual C++ compiler flags and the References section.
  • 6 January 2012 - Added ExceptionHandler sample code and added the Using the Code section.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Russian Federation Russian Federation
I am a software developer currently living in Tomsk, Russia. I received a PhD degree in Computer Science from Tomsk Polytechnic University in 2010. I have been professionally developing C/C++ and PHP software since 2005. I like contributing to open-source and writing programming articles for popular web resources, like CodeProject. Besides writing, I love skiing and watching Formula-1.

Comments and Discussions

 
AnswerRe: GetExceptionPointers() Pin
Member 162182618-Oct-16 2:08
Member 162182618-Oct-16 2:08 
QuestionMy vote of 5 Pin
marc ochsenmeier24-Sep-11 23:32
marc ochsenmeier24-Sep-11 23:32 
GeneralMy vote of 4 Pin
maximka_rus1-Aug-11 20:04
maximka_rus1-Aug-11 20:04 
GeneralMy vote of 5 Pin
magicpapacy1-Aug-11 15:31
magicpapacy1-Aug-11 15:31 
GeneralMy vote of 5 Pin
hfrmobile1-Aug-11 9:43
hfrmobile1-Aug-11 9:43 
GeneralMy vote of 5 Pin
Dr.Luiji16-Jul-11 11:49
professionalDr.Luiji16-Jul-11 11:49 
Questionnice Pin
BillW3315-Jul-11 7:07
professionalBillW3315-Jul-11 7:07 
GeneralMy vote of 5 Pin
Rhuros10-Jul-11 23:47
professionalRhuros10-Jul-11 23:47 
Nice Work
QuestionIncorrect link Pin
QuiOui22-Jun-11 18:29
QuiOui22-Jun-11 18:29 
AnswerRe: Incorrect link Pin
OlegKrivtsov23-Jun-11 1:16
OlegKrivtsov23-Jun-11 1:16 
GeneralMy vote of 5 Pin
Gordon Brandly15-Jun-11 8:35
Gordon Brandly15-Jun-11 8:35 
GeneralUmm Pin
xComaWhitex14-Jun-11 20:17
xComaWhitex14-Jun-11 20:17 
GeneralRe: Umm Pin
OlegKrivtsov14-Jun-11 21:38
OlegKrivtsov14-Jun-11 21:38 
GeneralRe: Umm Pin
Mike Diack15-Jun-11 5:14
Mike Diack15-Jun-11 5:14 
GeneralMy vote of 5 Pin
User 4041113-Jun-11 19:10
User 4041113-Jun-11 19:10 
GeneralMy vote of 5 Pin
R. Hoffmann10-Jun-11 11:32
professionalR. Hoffmann10-Jun-11 11:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.