Click here to Skip to main content
16,016,962 members
Articles / Desktop Programming / MFC

XCrashReport : Exception Handling and Crash Reporting - Part 4

Rate me:
Please Sign up or sign in to vote.
4.95/5 (78 votes)
19 Oct 2003CPOL10 min read 682.6K   4.7K   201   206
Add basic exception handling and crash reporting to your application
Update: Part 5 is now available with support for VS2008 and 64-bits.

Introduction

In Part 3, I said that I thought that article was the last one about exception handling and crash reporting. But I had ignored one important question: how are you going to get your customers to send you the crash dumps and error logs? This really worried me, until I saw the wonderful solution here that Mike Carruth came up with. His idea was to replace the standard crash dialog that looks like this:

screenshot

with one that prompts you to email the crash files to the software vendor. The really brilliant thing about Carruth's approach is that it allows the customer to review in detail what is being sent, and to choose what files are sent.

End of the Grail?

OK, now you are thinking, "Good! We can just use what Carruth has done." For me, I had some problems with his implementation.
Problem #1: It uses WTL. Not a big deal, but it is one more thing to configure and deal with, and I am very reluctant to tweak some of the older MFC apps I maintain.

Problem #2: The replacement crash dialog runs in the same process as the crashed app. This is a big concern, because you really want to do as little as possible in the exception handler, and GUI dialogs cross the line.

Problem #3: It uses zlib.dll to zip the crash files. This is one more file to add to the installation, and I get twitchy when I think about dealing with that.

Problem #4: There is extra stuff in the implementation that was not essential for me. For example, output of the crash info to XML using Microsoft's MSXML. While I think this is a very interesting use of XML, I wanted to keep the exception handler (and the code it pulls in) to a very small footprint.

Preparation

My first requirement was to run the crash dialog as a separate app, to avoid any problems with the app that just crashed; my second was to use only MFC.

Running as a separate app was no problem. Since the exception handler was doing all the work of creating the crash files, all the new crash dialog app had to do was to find them, zip them, and email the zip. To make the crash dialog app (I am calling it XCrashReport from now on) as independent as possible, the last thing the exception handler does is to invoke XCrashReport with the name of the crashed app on the command line. By default, I decided that the crashed app, the crash files, and XCrashReport must all reside in the same directory. This design decision could easily be changed, but so far it seems to work well for my needs.

Next came the MFC stuff. I knew there would be two dialogs - the main window, and then a dialog that showed the details of the files being sent, and allowed the user to select which ones to send. For the last, I saw that my XListCtrl would be perfect, using checkboxes to allow selection. I could also use my new XHyperLink and XColorStatic controls. And I could use my XZip code to zip up the crash files.

I would like to thank Grant McDorman for his enhancements to Mike Carruth's code, which you can find here. In particular, McDorman added the ability to dump a section of the registry to a text file, which can then be added to the error report.

Theory Into Practice

After modifying the ExceptionHandler.cpp code once more, to start up XCrashReport.exe, the new crash report dialog will be displayed if it is found in the same directory as the crashed app:

screenshot

The user can then click on the click here link to review the contents of the error report before it is sent. This shows the ERRORLOG.TXT file being displayed:

screenshot

This shows the CRASH.DMP file being displayed:

screenshot

This shows the REGISTRY.TXT file being displayed:

screenshot

Summary

This is now a complete solution to catching application exceptions, generating a detailed error report, and prompting the user to send the report to you. Thanks to the work of Bruce Dawson, Mike Carruth, and Grant McDorman, XCrashReport offers a minimal footprint along with a user-friendly crash dialog.

Implementation Notes

Dismissing the Exception

At the end of the RecordExceptionInfo() function in ExceptionHandler.cpp, there is code that will detect whether the app is running under a debugger, and dismiss the exception accordingly:
  • If running under a debugger, the handler returns EXCEPTION_CONTINUE_SEARCH. This tells Win32 that this handler didn't actually handle the exception, so that things will proceed as per normal, and the debugger will catch the exception.
  • If not running under a debugger, the handler attempts to run XCrashReport.exe. If this is successful, the handler returns EXCEPTION_EXECUTE_HANDLER, which suppresses the standard crash dialog. If not successful, the handler returns EXCEPTION_CONTINUE_SEARCH, which again lets things proceed as per normal (the standard crash dialog will pop up).

Customizable Files

There are some files that you may want to customize for your own application and company:
  • CrashFileNames.h - this contains the names of the error output files:
    #define XCRASHREPORT_MINI_DUMP_FILE     _T("CRASH.DMP")
    #define XCRASHREPORT_ERROR_LOG_FILE     _T("ERRORLOG.TXT")
    #define XCRASHREPORT_REGISTRY_DUMP_FILE _T("REGISTRY.TXT")
    #define XCRASHREPORT_CRASH_REPORT_APP   _T("XCrashReport.exe")
  • EmailDefines.h - this contains strings for email headers:
    #define XCRASHREPORT_SEND_TO_NAME      _T("Software Support")
    #define XCRASHREPORT_SEND_TO_ADDRESS   _T("support@softwarevendor.com")
  • IniDefines.h - this contains defines for the XCrashReport.ini file:
    #define INI_FILE_NAME      _T("XCrashReport.ini")
    #define INI_FILE_SECTION   _T("FilesToAdd")
    #define INI_FILE_TEMPLATE  _T("File%03d")
    #define INI_REG_SECTION    _T("RegistryToAdd")
    #define INI_REG_TEMPLATE   _T("Registry%03d")
    #define MAX_INI_ITEMS      999
  • RegistryDefines.h - this contains the define which specifies whether any registry sections will be dumped, and also the default registry section:
    #define XCRASHREPORT_DUMP_REGISTRY
    #define XCRASHREPORT_REGISTRY_KEY \
                         _T("HKCU\\Software\\CodeProject\\XCrashReportTest")
  • XCrashReport.ini - this ini file is read by XCrashReport.exe and contains the registry sections to be dumped to file and the files that are to be included in the error report zip. It is not necessary to include the registry files in the [FilesToAdd] section.
    [RegistryToAdd]
    ;Registry001=HKCU\Software\CodeProject\XCrashReportTest,Main reg key
    ;Registry002=HKCU\Software\CodeProject\XCrashReportTest\Program,new reg key
    
    [FilesToAdd]
    ;File001=CRASH.DMP,Crash Dump,DMP File
    ;File002=ERRORLOG.TXT,Crash log,Text Document

Using XCrashReport in Debug Mode

To simplify working on XCrashReport, in debug builds XCrashReport will simulate receiving the string "XCrashReportTest.exe" on the command line.

How To Use

  1. Set up your release build to generate debug symbols (pdb)
  2. Include these files in your project:
  3. Recompile the entire project.

    • In your VC++ project, go to Project | Settings. Make sure the Release configuration is selected in the Settings For combobox on the left. Go to the C/C++ tab, select the General category, and select Program Database in the Debug Info combobox. This tells the compiler to generate debug information.

      screenshot

    • Go to the Link tab and check Generate debug info. This tells the linker to collate debug information into .pdb files. The linker also puts the name of the .pdb file in the executable, so the debugger can find it.
    • On the same Link tab, enter /OPT:REF at the end of the Project Options list. This tells the linker to eliminate functions and/or data that are never referenced. This is the usually the default for release builds, but it gets turned off when you tell the linker to generate debug information. Failing to specify /OPT:REF will cause your executables and DLLs to get 10-20% larger.

      screenshot


    • ExceptionAttacher.cpp
    • ExceptionHandler.cpp - should be set to Not using precompiled headers on the C/C++ tab (Precompiled Headers).
    • ExceptionHandler.h
    • GetWinVer.cpp
    • GetWinVer.h
    • MiniVersion.cpp
    • MiniVersion.h
    • CrashFileNames.h


    Tuck away the exe and pdb files. Do not ship the pdb file to customers - this is both unnecessary and may be helpful to someone wanting to reverse-engineer your program.

A Note on dbghelp.dll

The same download for WinDbg also includes the latest dbghelp.dll. You can download it here. In this download, the redist.txt file states that dbghelp.dll version 6.2.13.1 is redistributable.

The download also includes the latest dbghelp.lib and dbghelp.h.

Known Limitations

  • XCrashReport.exe must reside in the same directory as the application's exe.
  • The Unicode implementation is not complete or tested.
  • The amount of text that can be entered in the user's comments editbox is limited to 64 KB characters.
  • The amount of the file that will be displayed in the Error Report Contents dialog is limited to 64 KB.
  • There must be a default email client installed in order for the error report to be sent by email. This has been tested with Outlook, Outlook Express, and Eudora. If there is no default email client installed, the user is requested to email the zip file, but there is no attempt to configure MAPI or create an email session.

Frequently Asked Questions

  1. Can I use ExceptionHandler.cpp in non-MFC apps?
    Yes! It has been implemented to compile with any Win32 program. See Part 1 for an example.
  2. How can I add some additional files to the error report?
    How can I add some additional registry sections to the error report?
    Use the optional XCrashReport.ini file. (There is a sample XCrashReport.ini file included.) When you specify files in the XCrashReport.ini file, the default files (CRASH.DMP and ERRORLOG.TXT) are not included - you must add them to the XCrashReport.ini file if you want to include them. Also, When you specify registry sections in XCrashReport.ini file, the default registry section (defined by XCRASHREPORT_REGISTRY_KEY) is not included - you must add it to the XCrashReport.ini file if you want to include it. On the target system, the XCrashReport.ini file must be placed in the exe directory.

    Here is an example XCrashReport.ini file:

    ;
    ; sample XCrashReport.ini file
    ;
    [FilesToAdd]
    File001=CRASH.DMP,Crash Dump,DMP File
    File002=ERRORLOG.TXT,Crash log,Text Document
    File003=BozoSoft.db,Database File,DB File
    
    [RegistryToAdd]
    Registry001=HKCU\Software\BozoSoft\BozoApp1,Main reg key
    Registry002=HKCU\Software\BozoSoft\BozoApp2\Program,another reg key
    There is no limit on the size of the file that may be added, although internally only the first 64 KB of the file will be displayed on the Error Report Contents dialog.
  3. When I try to include ExceptionHandler.cpp in my MFC project, I get the compiler error ExceptionHandler.cpp(823) : fatal error C1010: unexpected end of file while looking for precompiled header directive. How can I fix this?
    When using ExceptionHandler in project that uses precompiled headers, you must change C/C++ Precompiled Headers settings to Not using precompiled headers for ExceptionHandler.cpp. Be sure to do this for All Configurations.

    XCrashReport screenshot

  4. I don't need registry dumps. Can I exclude them?
    Yes. Comment out the following line in RegistryDefines.h:
    #define XCRASHREPORT_DUMP_REGISTRY
    This file is included in the XCrashReport.exe project.
  5. I don't need minidumps. Can I exclude them?
    Yes. Comment out the following line at the top of ExceptionHandler.cpp:
    #define XCRASHREPORT_WRITE_MINIDUMP
    This file is one of the files that you include in your app's project.
  6. I want to rename the default error files that are created. How can I do this?
    You can edit the file CrashFileNames.h:
    // CrashFileNames.h  Version 1.0
    //
    // Author:  Hans Dietrich
    //          hdietrich2@hotmail.com
    //
    // This software is released into the public domain.
    // You are free to use it in any way you like, except
    // that you may not sell this source code.
    //
    // This software is provided "as is" with no expressed
    // or implied warranty.  I accept no liability for any
    // damage or loss of business that this software may cause.
    //
    /////////////////////////////////////////////////////////////////////
    
    #ifndef CRASHFILENAMES_H
    #define CRASHFILENAMES_H
    
    #define XCRASHREPORT_MINI_DUMP_FILE     _T("CRASH.DMP")
    #define XCRASHREPORT_ERROR_LOG_FILE     _T("ERRORLOG.TXT")
    #define XCRASHREPORT_REGISTRY_DUMP_FILE _T("REGISTRY.TXT")
    #define XCRASHREPORT_CRASH_REPORT_APP   _T("XCrashReport.exe")
    
    #endif //CRASHFILENAMES_H
    This file is one of the files that you include in your app's project, and it is also used in XCrashReport.exe. Both will need to be recompiled.
  7. How do I change the registry section that gets dumped?
    You can use the XCrashReport.ini file (see above), or you can edit the file RegistryDefines.h:
    // RegistryDefines.h  Version 1.0
    //
    // Author:  Hans Dietrich
    //          hdietrich2@hotmail.com
    //
    // This software is released into the public domain.
    // You are free to use it in any way you like, except
    // that you may not sell this source code.
    //
    // This software is provided "as is" with no expressed
    // or implied warranty.  I accept no liability for any
    // damage or loss of business that this software may cause.
    //
    //////////////////////////////////////////////////////////////////////
    
    #ifndef REGISTRYDEFINES_H
    #define REGISTRYDEFINES_H
    
    #define XCRASHREPORT_DUMP_REGISTRY
    #define XCRASHREPORT_REGISTRY_KEY \
                         _T("HKCU\\Software\\CodeProject\\XCrashReportTest")
    
    #endif //REGISTRYDEFINES_H
    This file is used in XCrashReport.exe.
  8. Can we use XCrashReport in our (shareware/commercial) app?
    Yes, you can use XCrashReport without charge or license fee. It would be nice to acknowledge my Copyright in your About box or splash screen, but this is up to you.

Links

This is a cumulative list of all the links that have been mentioned in Parts 1 - 4.
Bruce Dawson's article "Release mode debugging with VC++"http://www.cygnus-software.com/papers/release_debugging.html
Bruce Dawson's original exception handler code from Game Developer Magazineftp://ftp.gdmag.com/pub/src/jan99.zip
Mike Carruth's article "Add Crash Reporting to Your Applications with the CrashRpt Library"http://www.codeproject.com/debug/crash_report.asp
Grant McDorman's enhancements to Mike Carruth's codehttp://www3.sympatico.ca/grant.mcdorman/
XListCtrl - A custom-draw list control with subitem formattinghttp://www.codeproject.com/listctrl/xlistctrl.asp
XColorStatic - a colorizing static controlhttp://www.codeproject.com/useritems/XColorStatic.asp
XHyperLink - yet another hyperlink controlhttp://www.codeproject.com/useritems/XHyperLink.asp
XZip and XUnzip - Add zip and/or unzip to your app with no extra .lib or .dllhttp://www.codeproject.com/cpp/xzipunzip.asp
Microsoft Debugging Tools, including WinDbg and dbghelp.dllhttp://www.microsoft.com/whdc/ddk/debugging
MiniDumpWriteDump APIhttp://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/minidumpwritedump.asp
Part 1http://www.codeproject.com/useritems/XCrashReportPt1.asp
Part 2http://www.codeproject.com/useritems/XCrashReportPt2.asp
Part 3http://www.codeproject.com/useritems/XCrashReportPt3.asp

Revision History

Version 1.1 - 2003 October 19

  • Initial public release

Usage

This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.

License

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


Written By
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.

Recently, I have moved to Los Angeles where I am doing consulting and development work.

For consulting and custom software development, please see www.hdsoft.org.






Comments and Discussions

 
GeneralRe: Bug in CDetails + fix Pin
Hans Dietrich30-Jun-10 6:47
mentorHans Dietrich30-Jun-10 6:47 
GeneralLicense for the code Pin
Thomas Hammer24-Feb-04 0:24
Thomas Hammer24-Feb-04 0:24 
GeneralRe: License for the code Pin
Hans Dietrich27-Feb-04 5:24
mentorHans Dietrich27-Feb-04 5:24 
GeneralRe: License for the code Pin
MerlinTheGreen25-Mar-04 22:52
MerlinTheGreen25-Mar-04 22:52 
GeneralAnother improvement Pin
Hans-Georg Ulrich17-Feb-04 4:21
Hans-Georg Ulrich17-Feb-04 4:21 
GeneralRe: Another improvement Pin
Hans Dietrich27-Feb-04 5:31
mentorHans Dietrich27-Feb-04 5:31 
GeneralRe: Another improvement Pin
Hans Dietrich30-Jun-10 6:48
mentorHans Dietrich30-Jun-10 6:48 
GeneralImprovements Pin
poltrone8-Feb-04 5:32
poltrone8-Feb-04 5:32 
When i read about your crash handler i thought it was a cool feature to implement
in the apps i'm working on. But then i realized that i wanted to eliminate the two
major drawbacks of your implementationSigh | :sigh: :

- It works only for MFC apps.
- Only crashes in the main thread are caught.

As i found out, the solution is quite simple. Only these steps were neccessarySmile | :) :

- redeclare the crash handler as
LONG WINAPI RecordExceptionInfo(PEXCEPTION_POINTERS pExceptPtrs)
and omit the references to the former parameter 'const char *Message'.

- remove 'ExceptionAttacher.cpp' and 'ExceptionAttacher.h' from the project.

- as early as possible call 'SetUnhandledExceptionFilter(RecordExceptionInfo)'
(e.g. in CWinApp::InitInstance())

With these modifications something like the following code of a console app will work:

#include "stdafx.h"
#include "ExceptionHandler.h"
#include "crashtest.h"

void main()
{
	SetUnhandledExceptionFilter(RecordExceptionInfo);

	int c = 'R';
	while (_kbhit()) getch(); // clear keyboard buffer
	do
	{
		printf("\nDo you want to crash this process (y/n) ?");
		c = toupper(getch());
	}
	while (c != 'N' && c != 'Y');

	if (c == 'Y')
	{
		while (_kbhit()) getch();
		c = 'R';
		do
		{
			printf("\nDo you want to crash in a different thread (y/n) ?");
			c = toupper(getch());
		}
		while (c != 'N' && c != 'Y');

		if (c == 'Y')
		{
			// to make the following code work 'CrashTestFunction' is now declared as
			// unsigned __stdcall CrashTestFunction(void *pParam);
			unsigned dummy;
			HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, CrashTestFunction, (void*)4, 0, &dummy);
			if (hThread != NULL)
			{
				// make sure we don't exit the process before the new thread
				// had a chance to crash.
				WaitForSingleObject(hThread, INFINITE);
			}
		}
		else
		{
			CrashTestFunction(0);
		}
	}
}

There is a little drawback that comes along with 'SetUnhandledExceptionFilter'. The handler installed with
this API is NOT used when running in a debugger. Rather the exception is passed directly to the debugger.
So if you want to debug the handler you have to explicitly invoke it like this:

__try
{
	*(PBYTE)NULL = 42;
}
__except(RecordExceptionInfo(GetExceptionInformation()))
{
}

Another issue that still isn't adressed is that any crashes that occur before 'AfxWinMain' or
the call to 'SetUnhandledExceptionFilter' (like constructors of global objects) will still be
caught by the standard handler Sigh | :sigh:
GeneralRe: Improvements Pin
Hans Dietrich8-Feb-04 12:14
mentorHans Dietrich8-Feb-04 12:14 
GeneralRe: Improvements Pin
jan_9-Feb-04 3:30
jan_9-Feb-04 3:30 
GeneralNote about Unicode builds Pin
J W Payne5-Feb-04 22:04
J W Payne5-Feb-04 22:04 
GeneralRe: Note about Unicode builds Pin
smokey25-Feb-04 7:36
smokey25-Feb-04 7:36 
GeneralTour de Force Pin
sdaymond23-Dec-03 8:29
sdaymond23-Dec-03 8:29 
GeneralRe: Tour de Force Pin
Hans Dietrich30-Jun-10 6:49
mentorHans Dietrich30-Jun-10 6:49 
GeneralMFC DLL Pin
sdaymond23-Dec-03 5:46
sdaymond23-Dec-03 5:46 
GeneralXCrashReport does not send mail Pin
csperber22-Nov-03 9:31
csperber22-Nov-03 9:31 
GeneralRe: XCrashReport does not send mail Pin
nmacfarl1230-Nov-03 4:26
nmacfarl1230-Nov-03 4:26 
GeneralErrorlog.txt content not displayed when listbox item is doubleclicked Pin
18-Nov-03 15:26
suss18-Nov-03 15:26 
GeneralRe: Errorlog.txt content not displayed when listbox item is doubleclicked Pin
Hans Dietrich29-Nov-03 1:59
mentorHans Dietrich29-Nov-03 1:59 
GeneralRe: Errorlog.txt content not displayed when listbox item is doubleclicked Pin
ExtraLean10-Mar-04 4:54
ExtraLean10-Mar-04 4:54 
GeneralRe: Errorlog.txt content not displayed when listbox item is doubleclicked Pin
Hans Dietrich10-Mar-04 9:58
mentorHans Dietrich10-Mar-04 9:58 
GeneralVery professional work Pin
yetanotherwoo7-Nov-03 8:35
yetanotherwoo7-Nov-03 8:35 
GeneralRe: Very professional work Pin
Hans Dietrich7-Nov-03 12:58
mentorHans Dietrich7-Nov-03 12:58 
GeneralRe: Very professional work Pin
yetanotherwoo8-Nov-03 10:27
yetanotherwoo8-Nov-03 10:27 
GeneralNot working with a service.... Pin
SIerus2-Nov-03 13:48
SIerus2-Nov-03 13:48 

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.