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:
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:
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:
This shows the CRASH.DMP file being displayed:
This shows the REGISTRY.TXT file being displayed:
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
- Set up your release build to generate debug symbols (pdb)
- Include these files in your project:
- 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.
- 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.
- 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
- 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.
- 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.
- 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.
- 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.
- 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.
- I want to rename the default error files that are created. How can I do this?
You can edit the file CrashFileNames.h:
#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
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.
- 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:
#ifndef REGISTRYDEFINES_H
#define REGISTRYDEFINES_H
#define XCRASHREPORT_DUMP_REGISTRY
#define XCRASHREPORT_REGISTRY_KEY \
_T("HKCU\\Software\\CodeProject\\XCrashReportTest")
#endif
This file is used in XCrashReport.exe.
- 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.
Revision History
Version 1.1 - 2003 October 19
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.