As a C# developer, I missed the luxury of having stack trace generated by CLR when errors occurred. I was happy to find that Microsoft compiler has a specific extension to the C language – Structured Exception Handling (SEH) – which allows to mimic this feature.

About Structured Exception Handling (SEH)

SEH allows the developer to gain control when fatal errors occurred before the OS brutally terminates the execution of the program. Those fatal errors called exceptions since they requires the execution of code outside the normal flow of program.

SEH allows the developer to terminate the program gracefully. The developer can ensure that resources (such as memory, files) are released, log the reason why the program has been terminated and save a dump file which can be loaded later by the debugger for further investigation.

The exceptions can divided to two kinds:

  • Hardware exceptions are exceptions that are initiated by the CPU. The CPU will initiate exceptions when the program try to execute invalid instruction. Examples of hardware exceptions can be trying to divide by zero or attempting to access an invalid memory ( i.e dereferencing a NULL pointer ).
  • Software exceptions are exceptions that are initiated by the program or the OS. Those exceptions are initiated by calling functions such as RaiseException

Using __try __except Construct

__try __except Construct
__try {
    // guarded-code
} __except ( execution-expression ) {
    // exception-handler-code
}

The code that will be executed when exception occurs in guarded-code depends on the value of the execution-expression :

  • If the value is EXCEPTION_EXECUTE_HANDLER then exception-handler-code will be executed.
  • If the value is EXCEPTION_CONTINUE_EXECUTION then execution will continue from the point at which the exception occurred in the guarded-code. Please note that some exceptions are noncontinuable - for them , EXCEPTION_NONCONTINUABLE_EXCEPTION exception will be raised.
  • If the value is EXCEPTION_CONTINUE_SEARCH then the program will continue to search for enclosing _try __except construct in the current function or upward the call stack. The search ends when it find construct whose execution-expression is one of the above values.

We can find information about the exception using the following macros:

  • GetExceptionCode() - The code which identifies the exception. It can be called inside execution-expression or exception-handler-code
  • GetExceptionInformation() - Retrieves a computer-independent description of an exception, and information about the computer state that exists for the thread when the exception occurs. It can be called inside execution-expression only.

The following program denonstrate the __try __except construct.

__try __except example
int main(int argc, char* argv[]) {
    __try {
        int *zz = NULL;
        *zz += 1;  // Raise hardware exception since we dereferencing a NULL pointer
    } __except ( EXCEPTION_EXECUTE_HANDLER ) {
        printf("Exception 1: 0x%08x\n", GetExceptionCode() );
    }
    __try {
        int zz = 0;
        int xx = 10 / zz; // Raise hardware exception since we divide by zero
    } __except ( EXCEPTION_EXECUTE_HANDLER ) {
        printf("Exception 2: 0x%08x\n", GetExceptionCode() );
    }
    __try {
        RaiseException( 0x1 , 0, 0 , NULL ); // Raise software exception with code 0x1
    } __except ( EXCEPTION_EXECUTE_HANDLER ) {
        printf("Exception 3: 0x%08x\n", GetExceptionCode() );
    }
    __try {
        GenerateException();
    } __except ( EXCEPTION_EXECUTE_HANDLER ) {
        printf("Exception 5: 0x%08x\n", GetExceptionCode());
    }
    return 0;
}
void GenerateException() {
    __try {
        RaiseException( 0x1000 , 0, 0 , NULL ); // Raise software exception with 0x1000 code
        printf("After Exception 4\n");// This will be displayed since
        //the execution-expression is EXCEPTION_CONTINUE_EXECUTION for exception with 0x1000 code
        RaiseException( 0x2000 , 0, 0 , NULL ); // Raise software exception with 0x2000 code.
        //Since execution-expression is EXCEPTION_CONTINUE_SEARCH for exception with 0x2000,
        //it will search enclosing handler in current function or upward the call stack
        printf("After Exception 5\n"); // This will not be displayed
    } __except ( GetExceptionCode() == 0x1000 ?
        EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH ) {
    }
}

The output of the program will be:

__try __except example - Output
Exception 1: 0xc0000005
Exception 2: 0xc0000094
Exception 3: 0x00000001
After Exception 4
Exception 5: 0x00002000

Investigating crashes

We will use SEH and the following DumpManager class to create stack trace text file and minidump file when errors occured. The minidump file can be loaded later by the debugger for further investigation while stack trace text file can be viewed in the text editor

The DumpManager class

DumpManager.h
class DumpManager {
public:
    DumpManager(char* buildId = "????", char* prefix = "");
    DWORD Add(LPEXCEPTION_POINTERS exception_pointers);
protected:
    virtual bool  Validate();
    virtual DWORD OnException();
    void WriteMinidump  (LPEXCEPTION_POINTERS exceptionPointers);     // write dump file
    void WriteStackTrace(LPEXCEPTION_POINTERS exceptionPointers);     // write stack trace
    virtual void FindNameStackTrace(char* outname, int size);
    virtual void FindNameDump(char* outname, int size);
    void FindName(char* outname, int size, char* extension);
    char*      _buildId;   // unique string which identifies the current version of the compiled program.
    char*      _prefix;    // the prefix to save the stack trace and dumps
    // exception properties
    HANDLE     _hProcess;  // The process handle where the exception occured
    DWORD      _processId; // The process id where the exception occured
    DWORD      _threadId ; // The thread  id where the exception occured
    SYSTEMTIME _time;      // the time the exception occoured
    DWORD      _code;      // the code of the exception
};

Using DumpManager.Add

The Add method is responsible for creating the files and updating the exception properties (_time,_code, … ).

DumpManager::Add
DWORD DumpManager::Add(LPEXCEPTION_POINTERS exceptionPointers) {
    _hProcess  = GetCurrentProcess();
    _processId = GetProcessId(_hProcess);
    _threadId  = GetCurrentThreadId();
    _code      = exceptionPointers->ExceptionRecord->ExceptionCode;
    GetSystemTime(&_time);
    if ( Validate() ) {
        WriteMinidump(exceptionPointers);
        WriteStackTrace(exceptionPointers);
    }
    return OnException();
}

It should be execution-expression of _try __except construct with GetExceptionInformation() macro.

DumpManager usage
DumpManager dumpManager("buildId", "prefix");
__try {
    // guarded-code
} __except ( dumpManager.Add( GetExceptionInformation() ) ) {
    // exception-handler-code
}

When exception occurred in above guarded-code the dumpManager.Add will be called:

  • The created files have the following format:
    • <prefix>_<buildId>_<year>-<month>-<day>_<hour>-<minute>_<second>-<process-id>_<thread-id>.dmp
    • <prefix>_<buildId>_<year>-<month>-<day>_<hour>-<minute>_<second>-<process-id>_<thread-id>_stack-trace.txt
  • By default prefix is empty string and therefore the files will be created in the current working directory. You can change the location of the created files by setting prefix in the constructor.
  • The buildId provided in the constructor should be unique string which identify the current build of the program. This will allow to associate the stack traces text files and the minidump files to binary which generate them.

Guidelines for building and distributing the program

  • The program should be linked with /DEBUG flag. This flag will generate the program database file (or pdb file) which contains debugging symbols which are needed by the debugger Enabling /DEBUG flag on vs2012

  • The source files, the binaries including the generated pdb file should be archived so they can loaded by the debugger later.
  • The pdb file should be distributed with the exe file, if you want that the stack trace text files will contain the function names, source files and line numbers. You can emit pdb file from the distribution if only the required file is minidump file.

Investigate crashes

You can open the stack trace text file in a text editor or use the debugger to load the minidump files:

  • Move the minidump file to directory where the binary file was compiled.
  • Click on the minidump, The debugger should be opened.
  • Use the debugger to investigate the problem.

In the following example, The program generates an exception for dereferencing a NULL pointer as we can see in the minidump file summary (The thread tried to read from or write to a virtual address for which it does not have the appropriate access).

The debugger with the with loaded dump file


The debugger is ready for investigation. The dump file is loaded and the c stack trace text file is displayed. Also call stack window and source code editor are opend.

We can also see that this exception happened at f3 fuction in call stack debugger window. clicking the f3 function should open the main.cpp at the position where the f3 function was defined (line 5). we can also find this infomation by viewing the stack trace text file in the text editor.

Customize functionality

  • You can add actions that should be done when exception occurred by overriding the OnException method. Those actions can be logging the exception to the system log, sending email or uploading the dump file to remote server. The function should return one of execution-expression values. The default implemention returns EXCEPTION_EXECUTE_HANDLER which will make the program execute the exception-handler-code.
  • You can decide which exceptions should generate stack trace and dump files by overriding Validate and return true only for exceptions which desired _code values.
  • The DumpManager.WriteMinidump use the MiniDumpWriteDump function located in dbghelp dll. You can adjust the parameters of this function to control the amount of data that should be saved in the dump ( i.e , Including all accessible memory in the process or specific module)

About WriteMinidump and WriteStackTrace

Those functions use functions located in dbghelp dll.

  • The WriteMinidump is using MiniDumpWriteDump
  • The WriteStackTrace is using StackWalk64 which allows to obtaining a stack trace in portable way.