Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

XCrashReport: Exception Handling and Crash Reporting for Win32/64 - Part 5

0.00/5 (No votes)
2 Jul 2010 1  
Add exception handling and crash reporting to your application. No STL, WTL, MFC, or boost required.

Introduction

When I went to update Part 4 of this article, I thought all I had to do was fix up some VS2008 compiler problems. I soon discovered there were many other things I wanted to fix or improve, so I decided to leave Part 4 as it was with Visual Studio 6, and start a fresh article and code base with VS2008.

Again I want to thank Bruce Dawson, Mike Carruth, and Grant McDorman, on whose work I was able to build XCrashReport.

Changes in XCrashReport v1.4

screenshot All code and projects converted to VS2008
screenshot All projects now have ANSI and Unicode builds
screenshot XCrashHandler now supports 64-bits. Major parts of the exception handler were rewritten to accommodate 64-bit registers, and all the inline asm code was replaced with compiler intrinsics, since Visual Studio does not allow inline asm code in x64 builds. The XCrashHandlerTest demo has both x86 and x64 builds.
screenshot You now can optionally choose to send screenshots of each monitor on your user's system. The user is shown these screenshot files in the XCrashReport application, and can choose whether to send them or not. Included in v1.4 is XBmpViewer, which will display the screenshot when the user double-clicks a screenshot file name.
screenshot All files generated by XCrashHandler are now put in the user's documents folder (CSIDL_PERSONAL) to avoid UAC interaction.
screenshot The email send-to address and name are now read from XCrashReport.ini, which is loaded from the user's documents folder (CSIDL_PERSONAL), again because of UAC.
screenshot Another crash test was added to the demo app, to generate a crash from a worker thread.
screenshot It is no longer necessary to include ExceptionAttacher.cpp. XCrashHandler now automatically calls SetUnhandledExceptionFilter() to install its exception handler. This is done by using init_seg(lib), so that XCrashHandler is initialized before your app starts.
screenshot File paths in the [FilesToAdd] section of XCrashReport.ini may now incorporate environment variables.
screenshot The computer and domain names are now included in ERRORLOG.TXT.
screenshot A dump of the environment variables is now included in ERRORLOG.TXT.

A Note About CSIDL_PERSONAL
On Windows XP, this folder is named C:\Documents and Settings\<user>\My Documents. On Windows 7, this folder is named C:\Users\<user>\Documents. MSDN has a complete list of CSIDL values and their recommended uses. Unfortunately many online articles contain the bad advice to switch to KNOWNFOLDERID values. If you follow this advice, your code will not work on pre-Vista systems. The code for this article uses only CSIDL_PERSONAL when reading or writing files.

Demo App: XCrashReportTest.exe

Here is the demo app:

screenshot

If running under a debugger, the exception 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).

XCrashReportTest.exe includes the XCrashHandler exception handler, which catches the exception, gathers system information, and writes ERRORLOG.TXT, the minidump file CRASH.DMP, and the screenshot image files (one for each monitor).

Next Stop: XCrashReport.exe

As with the previous version, I try to do as little as possible in the exception handler, which is running in the process space of the crashed app. When the handler completes, it attempts to run XCrashReport.exe. Of course, the assumption is that XCrashReport.exe is in the same folder as the exe of the crashed app.

When XCrashReport.exe starts, it immediately exports any registry keys you have specified, and then shows this dialog:

screenshot

At this point your user will be offered the opportunity to inspect the files that will be zipped and sent via email:

screenshot

Double-clicking any file in the list will display the file in the view window. Text files will be shown as text, and binary files will be shown in hex. Screenshot bmp files will be displayed in the standalone XBmpViewer app.

Notice the two .bmp screenshot files. Although they are 4.5 MB each, they will be compressed by over 90% when zipped.

You may want to send other files that are associated with your app. In the above screenshot, you can see files mobydick_ansi.txt, etc. These files and other settings are specified in XCrashReport.ini.

Your user can now choose whether to send the files he has approved, or just cancel.

Back at the Ranch

You are relaxing with the latest CodeProject Newsletter when something hits your inbox. A crash dump for the company's new product! What to do?!?!

Fortunately you don't need to use WinDbg anymore. Extract CRASH.DMP from the zip file to the folder where you saved the app's .pdb file (you did save it, didn't you?), double-click the CRASH.DMP file and VS2008 starts up. Click Start Debugging (F5) and you will see the unhandled exception message box:

screenshot

Click the Break button, and you will be taken to the source line that caused the exception:

screenshot

VS2008 is not the only tool that you can use. WinDbg has even more powerful capabilities. You can read about it in Part 3. Download it here.

How Does It Work?

This all works because VS2008 uses the app's .pdb file. To generate a .pdb file for release builds, go to

Project | Properties | C/C++ | Debug Information Format
and select Program Database (/Zi):

screenshot

Every time you create a new release build, you should archive both the .pdb and .exe files.

The .pdb file is not something you want to send outside your company, because it could be used to reverse-engineer your app.

XCrashReport Architecture

I have stepped through what happens when your app crashes and the XCrashHandler exception handler is invoked, and what the XCrashReport GUI app looks like. This diagram shows how all the pieces fit together:

screenshot

"Other files" include any additional files you have specified in XCrashReport.ini.

Verified Platforms

XCrashReport has been verified to work correctly (with no UAC interaction) on the following platforms:

OS Version Service Pack Notes
2000 Professional SP4 Requires dbghelp.dll
XP 32-bit Professional SP2  
Vista 32-bit Ultimate   Standard (restricted) user
Windows 7 32-bit Professional   Standard (restricted) user
Windows 7 64-bit Professional   Standard (restricted) user

Windows 2000 was the only OS that was missing dbghelp.dll; on all other systems, XCrashReport worked fine using the dbghelp.dll already on the system.

If you have verified that XCrashReport works on other platforms/service packs, please send me email so I can add to list.

How To Use

Step 1: Include XCrashHandler Files in your project

All the files you need to include are in the XCrashHandler folder in the download:
  • CrashOptions.h
  • MiniVersion.cpp
  • MiniVersion.h
  • Screenshot.cpp
  • Screenshot.h
  • XCrashHandler.cpp
  • XCrashHandler.h
  • XGetWinVer.cpp
  • XGetWinVer.h
All of the .cpp files above should be set to Not Using Precompiled Headers. CrashOptions.h includes all the defines previously contained in CrashFileNames.h, EmailDefines.h, IniDefines.h, and RegistryDefines.h.

To assist in development, in debug builds XCrashReport will simulate receiving the string "XCrashReportTest.exe" on the command line. The release build of XCrashReport expects to find an argument on the command line. If the argument is missing, then XCrashReport exits immediately.

Aside from editing two files (see below), that's all you have to do. There is no more "ExceptionAttacher" as in the previous version. Setting up the exception handler is now done automatically via static object g_XCrashHandler in XCrashHandler.cpp. The init_seg(lib) initializes this static as if it was a library, which means it's set up before your app is initialized. You can simply include XCrashHandler files in any project, customize XCrashReport.ini and CrashOptions.h, and you're good to go.

Step 2: Edit XCrashReport.ini

Edit XCrashReport.ini to customize registry, files, and email settings. Note that trying to access a registry key outside of HKCU may involve interaction with UAC.
;
; sample XCrashReport.ini file 
;
[RegistryToAdd]
;RegistryNNN=registry path,description
Registry001=HKCU\Software\CodeProject\XCrashReportTestRU86,Main reg key
Registry002=HKCU\Software\CodeProject\XCrashReportTestRU86\Program,another reg key

[FilesToAdd]
;FileNNN=file path,description,type,text flag
File001=mobydick_ansi.txt,moby dick ansi,Text Document,1
File002=d:\temp\mobydick_unicode_with_bom.txt,moby dick unicode+bom,Text Document,1
File003=d:\temp\mobydick_unicode_no_bom.txt,moby dick unicode,Text Document,1
File004=%TEMP%\mydata.txt,my important data,Text Document,1

[Email]
SendToName=BozoSoft Software Support
SendToAddress=support@bozosoft.com
As the FILE004 entry shows, you can use environment variables in the [FilesToAdd] section. You must ensure that the resulting path is properly formed - i.e., that the backslash '\' character is added if necessary.

XCrashReport.ini is not written to by XCrashHandler or XCrashReport.

Step 3: Edit XCrashOptions.h

Edit XCrashOptions.h to customize program options.
///////////////////////////////////////////////////////////////////////////////
// SCREENSHOT
///////////////////////////////////////////////////////////////////////////////
// comment out this line if you don't want screenshots
#define XCRASHREPORT_WRITE_SCREENSHOT

// Up to 16 screenshots will be generated, one for each monitor on the user's 
// system.  You can reduce this to any number below 16.
#define XCRASHREPORT_SCREENSHOT_MAX_MONITORS        16

#define XCRASHREPORT_SCREENSHOT_RESOLUTION_LOW       1
#define XCRASHREPORT_SCREENSHOT_RESOLUTION_MEDIUM    2
#define XCRASHREPORT_SCREENSHOT_RESOLUTION_HIGH      3
// This controls how big the screenshot files are.  The files are BMP files,
// and so will be compressed substantially when zipped.
#define XCRASHREPORT_SCREENSHOT_RESOLUTION   XCRASHREPORT_SCREENSHOT_RESOLUTION_HIGH

///////////////////////////////////////////////////////////////////////////////
// MINIDUMP
///////////////////////////////////////////////////////////////////////////////
// comment out this line if you don't want minidumps
#define XCRASHREPORT_WRITE_MINIDUMP

///////////////////////////////////////////////////////////////////////////////
// FILE NAMES
///////////////////////////////////////////////////////////////////////////////
#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_BMP_FILE              _T("XCRASHREPORT_Mon")
#ifdef _UNICODE
    #define XCRASHREPORT_CRASH_REPORT_APP  _T("XCrashReportRU.exe")
    #define XCRASHREPORT_BMPVIEWER_APP     _T("XBmpViewerRU.exe")
#else
    #define XCRASHREPORT_CRASH_REPORT_APP  _T("XCrashReportRA.exe")
    #define XCRASHREPORT_BMPVIEWER_APP     _T("XBmpViewerRA.exe")
#endif

///////////////////////////////////////////////////////////////////////////////
// INI FILE
///////////////////////////////////////////////////////////////////////////////
#define INI_FILE_NAME                      _T("XCrashReport.ini")
#define INI_EMAIL_SECTION                  _T("Email")
#define INI_EMAIL_SEND_TO_NAME             _T("SendToName")
#define INI_EMAIL_SEND_TO_ADDRESS          _T("SendToAddress")
#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

///////////////////////////////////////////////////////////////////////////////
// EMAIL - name and address are specified in INI file
///////////////////////////////////////////////////////////////////////////////


///////////////////////////////////////////////////////////////////////////////
// REGISTRY - keys are specified in INI file
///////////////////////////////////////////////////////////////////////////////
// comment out this line if you don't want to export registry keys
#define XCRASHREPORT_DUMP_REGISTRY

About Minidumps
The minidump file CRASH.DMP is written by the MiniDumpWriteDump API. As you saw earlier, this file is key to having VS2008 quickly locate the source line that caused the crash. You can read more about minidumps here, here, and here.

Step 4: Compile and Prepare Installation Package

After compiling your app to include the XCrashHandler files, you need to include the following files in the installation package:
  • XCrashReport.ini - configuration settings for XCrashReport. Make sure its name matches whatever is in CrashOptions.h. Install to CSIDL_PERSONAL.
  • XCrashReport.exe - include the desired version of the GUI report app. Make sure its name matches whatever is in CrashOptions.h. Install to your app's exe folder.
  • XBmpViewer.exe - necessary if you want to allow your users to view the screenshots. Make sure its name matches whatever is in CrashOptions.h. Install to your app's exe folder.
  • dbghelp.dll - This might already be on your users' systems; according to MSDN, XP and later systems ship with dbghelp.dll. If you want to include it, there is a dbghelp folder in the download that contains x86 and x64 versions of dbghelp.dll from the latest Windows SDK download. Install to your app's exe folder.
    DO NOT INSTALL DBGHELP.DLL TO A SYSTEM FOLDER!

Building with Visual Studio 2005

kinar was kind enough to provide the following information for building XCrashReport with VS2005:
  1. Edit the .sln files and replace
    Microsoft Visual Studio Solution File, Format Version 10.00
    # Visual Studio 2008
    with
    Microsoft Visual Studio Solution File, Format Version 9.00
    # Visual Studio 2005
  2. Edit the .vcproj files and replace
    <?xml version="1.0" encoding="Windows-1252"?>
    <VisualStudioProject
        ProjectType="Visual C++"
        Version="9.00"
    with
    <?xml version="1.0" encoding="Windows-1252"?>
    <VisualStudioProject
    	ProjectType="Visual C++"
    	Version="8.00"

Known Limitations

  • XCrashReport.exe must reside in the same folder as the application's exe.
  • XCrashReport.ini must be placed in CSIDL_PERSONAL.
  • Applications that are packed with UPX or protection systems like Armadillo will generate a CRASH.DMP file, but this file cannot be loaded into VS2008 to show source location.
  • Stack overflows are not caught by XCrashHandler.
  • For standard (restricted) users, trying to export registry keys that are in HKLM, or trying to access files that are not in a user folder, may result in interaction with UAC.
  • There are some situations where XCrashHandler will not be called. Jochen Kalmbach talks about this in his blog, and has a solution that works in some cases.
  • There must be a default email client installed in order for the error report to be sent by email. Some non-corporate users may not have an email client, instead using a web-based service such as gmail. If you want to support these types of email accounts, it is possible to include code such as PJ Naughter's SMTP wrapper classes in XCrashReport.exe. This means that XCrashReport.exe would need to have user information such as SMTP server, account name, and password.
  • If you get the error "invalid email address", try adding "SMTP:" to the send-to address in XCrashReport.ini:
    SendToAddress=SMTP:support@bozosoft.com

Links

This is a cumulative list of all the links that have been mentioned in Parts 1 - 5.

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 Magazine http://www.gdmag.com/src/jan99.zip
Mike Carruth's article "Add Crash Reporting to Your Applications with the CrashRpt Library" http://www.codeproject.com/kb/debug/crash_report.aspx
XListCtrl - A custom-draw list control with subitem formatting http://www.codeproject.com/KB/list/xlistctrl.aspx
XColorStatic - a colorizing static control http://www.codeproject.com/KB/static/XColorStatic.aspx
XHyperLink - yet another hyperlink control http://www.codeproject.com/KB/miscctrl/XHyperLink.aspx
XZip and XUnzip - Add zip and/or unzip to your app with no extra .lib or .dll http://www.codeproject.com/KB/cpp/xzipunzip.aspx
Debugging Tools from the Windows SDK, including WinDbg dbghelp.dll http://www.microsoft.com/whdc/devtools/debugging/default.mspx
#pragma init_seg(lib) http://support.microsoft.com/kb/104248
Compiler intrinsics http://msdn.microsoft.com/en-us/library/azcs88h2.aspx
MiniDumpWriteDump API http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/minidumpwritedump.asp
XCrashReport Part 1 XCrashReportPt1.aspx
XCrashReport Part 2 XCrashReportPt2.aspx
XCrashReport Part 3 XCrashReportPt3.aspx
XCrashReport Part 4 XCrashReportPt4.aspx

Revision History

Version 1.4.1 - 2010 July 2

  • Fixed problem with file path display in XCrashReportTest, reported by kinar

Version 1.4 - 2010 June 30

  • All code and projects converted to VS2008
  • All projects now have ANSI and Unicode builds
  • Now supports 64-bits
  • Optional screenshots
  • All files now put in the user's documents folder (CSIDL_PERSONAL) to avoid UAC interaction
  • XCrashReport.ini now read from user's documents folder (CSIDL_PERSONAL) to avoid UAC interaction
  • The email send-to address and name are now read from XCrashReport.ini
  • Crash test was added to the demo app to generate a crash from a worker thread
  • It is no longer necessary to include ExceptionAttacher.cpp. XCrashHandler now automatically calls SetUnhandledExceptionFilter() to install its exception handler
  • File paths in the [FilesToAdd] section of XCrashReport.ini may now incorporate environment variables
  • The computer and domain names are now included in ERRORLOG.TXT
  • A dump of environment variables is now included in ERRORLOG.TXT

Version 1.1 - 2003 October 19

  • Initial public release

Usage

This software is released under the Code Project Open License (CPOL). You are free to use this software 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here