Click here to Skip to main content
15,888,527 members
Articles / Desktop Programming / MFC

Add Crash Reporting to Your Applications with the CrashRpt Library

Rate me:
Please Sign up or sign in to vote.
4.92/5 (85 votes)
19 Mar 2003BSD11 min read 734.3K   9.3K   336   215
This article describes how to use the CrashRpt library to generate crash report for your application that can be debugged using WinDbg or VS.NET.

The CrashRpt project is being hosted at http://code.google.com/p/crashrpt/.

Image 1

Figure 1 - Main Dialog

Contents

Overview

If you've ever been tasked with debugging a fatal exception, you probably know how difficult it can be given only the user's steps to reproduce. Many factors such as the application's version, user's operating system, and dependent modules may contribute to the eventual crash. This makes duplicating the user's environment and thereby the crash, nearly impossible for all but the most obvious bugs.

The CrashRpt library is a light weight error handling framework. This module will intercept any unhandled exception generated by your application, build a complete debug report, and optionally, mail the report to you.

The CrashRpt project is being hosted at http://code.google.com/p/crashrpt/.

Usage

In this article, a crash report refers to a collection of files intended to help the developer quickly diagnose the cause of a crash. Specifically the crash report includes a minidump, a crash log and up to ten additional application specific files supplied by the application via a crash callback. Of these the most useful will most likely be the application minidump. The minidump contains the call stack, local variables, details all of your application's modules, and can even help pinpoint the source line number that generated the exception.

The CrashRpt DLL works like the new Dr. Watson utility that ships with XP. It intercepts unhandled exceptions, creates a minidump, builds a crash log, presents an interface to allow the user to review the crash report, and finally it compresses and optionally emails the crash report back to you.

When an unhandled exception is detected, CrashRpt notifies the user and allows them to review the report contents. First the main dialog shown above is displayed. From here the user can enter their comments and email address or review the report contents by clicking the hyperlink. This takes them to the details dialog, where they are presented with the files that make up the report. Double clicking on the filename will open that file in its associated program, if an association exists for that file type.

Image 2

Figure 2 - Crash Details Dialog

Once the user is satisfied, he may close the details dialog and click the 'Send' button on the main dialog, which will email his complete crash report to you.

Using the CrashRpt library in Your Application

Building the library

Download and unzip the source code attached to this article. Open the complete.dsw workspace located in the top level directory. This workspace contains the source for the two sample applications and the CrashRpt library. You only need to build the CrashRpt project - crashrpt.dsp.

Notes:

  1. CrashRpt library uses the WTL for its GUI components, so WTL must be installed and properly configured in order to build the CrashRpt library. You can download the WTL here. If you're new to the WTL then CodeProject has some article here, and there is a good starting guide here.
  2. CrashRpt library uses Microsoft's Debug Help library (dbghelp.dll). The proper h and lib files must be installed and properly configured in order to build the CrashRpt library. These files are available in the Debugging SDK, which can be downloaded here (be sure to select the SDK setup option).

After the build completes, you should end up with the following:

crashrpt\include
crashrpt.h 

crashrpt\bin\debug or crashrpt\bin\release
crashrpt.dll
 
crashrpt\lib
crashrpt.lib 

crashrpt\src
[all source files...]

Linking against the library

To implicitly link against the library, you need to include the crashrpt.h file and link in the crashrpt.lib file. I find it easiest to add the following two lines to my main application file.

#include "[whateveryourpath]/crashrpt/include/crashrpt.h"
#pragma comment(lib, "[whateveryourpath]/crashrpt/lib/crashrpt")
Figure 3 - Integrating CrashRpt with your application

Initializing the library

The library needs to be initialized before it will catch any exceptions. You do this by calling the Install method, usually from your main function. The Install method is detailed below.

//-----------------------------------------------------------------------------
// Install
//    Initializes the library and optionally set the client crash callback and
//    set up the email details.
//
// Parameters
//    pfn         Client crash callback
//    lpTo        Email address to send crash report
//    lpSubject   Subject line to be used with email
//
// Return Values
//    If the function succeeds, the return value is a pointer to the underlying
//    crash object created.  This state information is required as the first
//    parameter to all other crash report functions.
//
// Remarks
//    Passing NULL for lpTo will disable the email feature and cause the crash 
//    report to be saved to disk.
//
CRASHRPTAPI 
LPVOID 
Install(
   IN LPGETLOGFILE pfn OPTIONAL,                // client crash callback
   IN LPCTSTR lpTo OPTIONAL,                    // Email:to
   IN LPCTSTR lpSubject OPTIONAL                // Email:subject
   );
Figure 4 - The Install() function

All of the parameters are optional. The first parameter is a pointer to a crash callback function defined as:

// Client crash callback
typedef BOOL (CALLBACK *LPGETLOGFILE) (LPVOID lpvState);
Figure 5 - Client crash callback

You would define this callback only if you wanted to be notified of an application failure, so that you could perform some basic clean up (i.e. close db connections, attempt to save, etc). Otherwise, you can simply pass NULL for this parameter.

The second parameter defines the email address you want the crash report mailed to, or NULL if you prefer the reports be saved to the user's workstation.

The third parameter is the subject line used in the generated mail message.

The Install function returns a pointer to the underlying object that implements the real functionality of this library. This value is required for all subsequent calls into the library.

If, after you have called Install, you decide to unhook the CrashRpt library, you would call Uninstall.

//-----------------------------------------------------------------------------
// Uninstall
//    Uninstalls the unhandled exception filter set up in Install().
//
// Parameters
//    lpState     State information returned from Install()
//
// Return Values
//    void
//
// Remarks
//    This call is optional.  The crash report library will automatically 
//    deinitialize when the library is unloaded.  Call this function to
//    unhook the exception filter manually.
//
CRASHRPTAPI 
void 
Uninstall(
   IN LPVOID lpState                            // State from Install()
   );
Figure 6 - The Uninstall function

You would only ever call Uninstall if you decided, after calling Install, that you did not want the CrashRpt library to intercept exceptions. So, basically you will probably never call this method directly.

Adding custom files to the report

The client application can, at any time, supply files to be included in the crash report by calling the AddFile function.

//-----------------------------------------------------------------------------
// AddFile
//    Adds a file to the crash report.
//
// Parameters
//    lpState     State information returned from Install()
//    lpFile      Fully qualified file name
//    lpDesc      Description of file, used by details dialog
//
// Return Values
//    void
//
// Remarks
//    This function can be called anytime after Install() to add one or more
//    files to the generated crash report.
//
CRASHRPTAPI 
void 
AddFile(
   IN LPVOID lpState,                           // State from Install()
   IN LPCTSTR lpFile,                           // File name
   IN LPCTSTR lpDesc                            // File desc
   );
Figure 7 - The AddFile function

This is useful when your application uses or produces external files such as initialization files or log files. When a report is generated, it will include these additional files.

Manually generating a report

You can force report generation by calling the GenerateErrorReport. This is useful if you want to provide an easy way to gather debugging information about your application to help debug a non-fatal bug.

//-----------------------------------------------------------------------------
// GenerateErrorReport
//    Generates the crash report.
//
// Parameters
//    lpState     State information returned from Install()
//    pExInfo     Pointer to an EXCEPTION_POINTERS structure
//
// Return Values
//    void
//
// Remarks
//    Call this function to manually generate a crash report.
//
CRASHRPTAPI 
void 
GenerateErrorReport(
   IN LPVOID lpState,
   IN PEXCEPTION_POINTERS pExInfo OPTIONAL
   );
Figure 8 - The GenerateErrorReport function

If you do not supply a valid EXCEPTION_POINTERS, structure the minidump callstack may be incomplete.

Generate debug symbols

To get the most out of the minidump, the debugger needs your application's debug symbols. By default release builds don't generate debug symbols. You can configure VC to generate debug symbols for release builds by changing a couple of project settings.

With the release build configuration selected, on the C/C++ tab under the General category, select 'Program Database' under Debug info.

Image 3

Figure 9 - C++ Project Settings

On the Link tab under the General category, check the 'Generate debug info' option.

Image 4
Figure 9 - Link Project Settings

Now release builds will generate debug symbols in a PDB file. Keep all executables and PDB files for each release that ships to customers. You will need these files to read minidump files in the debugger.

Using the Crash Report

Using the Crash Log

The crash log is an XML file that describes details about the crash including the type of exception, the module and offset where the exception occurred, as well as some cursory operating system and hardware information. I wrote the crash log to make it easier to catalog crashes. A crash can be uniquely identified by the module, offset and exception code. This information could be inspected by a developer or an automated process, and compared against previously reported problems. If a match is found, the developer or automated process, could inform the user of the solution without having to debug the error again.

The log is divided into four different sections or nodes. The first node is ExceptionRecord we discussed earlier.

Image 5

Figure 10 - ExceptionRecord Node

Next is the Processor node, which contains a little information about the user's CPU.

Image 6

Figure 11 - Process Node

Next is the OperatingSystem node, which contains the user's operating system version information.

Image 7

Figure 12 - OperatingSystem Node

Last is the Modules node. This node contains the path, version, base address, size, and time stamp for every module loaded by the deceased application.

Image 8

Figure 13 - Modules Node

Using the Crash Dump File

The crash dump file is a minidump created with the help of the DbgHelp DLL's MiniDumpWriteDump function. The minidump contains various information about the state of the application when the error occurred including the call stack, local variables, and loaded modules. For more on creating minidumps, check out Andy Pennell's article.

You can view minidump files in VS.NET or the WinDbg debugger. Because WinDbg is free, I'll use it in the following example. You can download WinDbg from here. I'm using version 6.1.0017.0 in the example.

The sample application included with this article does nothing but generate a null pointer exception. I'll use the sample to generate a crash and demonstrate how to use the resulting minidump.

When you run the sample application, click on the bomb button to generate a null pointer exception, and save the resulting crash report. Then extract the crash.dmp file from the crash report, launch WinDbg, and open the crash dump by pressing CTRL+D.

Next, you need to set the symbol path for WinDbg with the .sympath command. Switch to the command window (ALT+1) and enter .sympath followed by a space followed by the semi-colon delimited list of directories to search.

.sympath c:\downloads\CrashRptTest
Figure 14 - Setting the symbol path

Similarly you need to set the executable and source search paths with the .exepath and .srcpath commands.

.exepath c:\downloads\CrashRptTest
.srcpath c:\downloads\CrashRptTest
Figure 15 - Setting the source and executable paths

The final step is to change the debugger context to the context record associated with the exception by entering the .ecxr command.

.ecxr
Figure 15 - Setting the exception context record

If everything is configured correctly, you should now be able to walk the call stack, see local variables, and loaded modules. You can even have WinDbg highlight the offending line of code by double clicking the CrashRptTest frame in the Call Stack window (ALT+6). Note: The exact line number may be a little off due to linker optimizations.

Image 9

Figure 16 - The Promised Land: Using WinDbg to Locate the Cause of a Null Pointer Exception

Deployment

The CrashRpt library relies on a couple of redistributable libraries. To be sure the library has access to the required files, you can distribute the ZLib and DbgHelp I've included.

LibraryFile VersionDescription
CrashRpt.DLL3.0.2.1Crash report library
ZLib.DLL1.1.3.0ZLib compression library
DbgHlp.DLL6.1.17.1Microsoft debug help library
Figure 17 - Redistributable Libraries

What to ship and what to save

As I mentioned earlier, to debug a crash you need not only the minidupmp file, but also the symbol and executable files that make up your application. When preparing a build to be released to clients, you should always save the exact executable modules you ship to clients, along with the corresponding debug symbols. This way when a crash report comes in, you will have the modules and debug symbols that the debugger will need to properly interpret the minidump.

I've received several comments/inquiries about shipping debug builds or debug symbols. You should never ship debug builds or debug symbols as they will not only take up more space on your CD/download/client's workstation, but they will also make reverse engineering your code a trivial exercise. To be clear, what I'm suggesting is modify your release build configuration so that it generates debug symbols, saving both the release builds of your modules and their corresponding debug symbols in your source control system and delivering only the release builds of your modules to clients (as you do today). When a crash report comes in, you use the release build and debug symbols you archived, along with the minidump included in the crash report, to debug the crash.

Note: CrashRpt uses Microsoft's Debug Help library (dbghelp.dll). This library was shipped with Windows XP, but certain versions are redistributable. I recommend you to install the dbghelp.dll file, included in the source/demo attachments, along the crashrpt.dll into your application's directory to avoid the possible conflict or missing dependency issues..

A word about preferred base load addresses

Every executable module (EXE, DLL, OCX, whatever) has a preferred base load address. This is the address in the application's process space that the loader will try to map that module. If two or more modules list the same base load address, the loader will be forced to relocate the modules until each module loads at a unique address. Not only does this slow down the start up time of your application, but it also makes it impossible to debug fatal exceptions. In order to use the minidump file, you must ensure that your application's modules do not collide. You can use rebase.exe or manually override the preferred base load address for each conflicting module. Either way you need to make sure that your application modules always load at the same address for the minidump file to be useful. You can find more information about this in John Robbin's April 1998 MSJ column.

References

For additional information about topics directly related to this article, see the links below:

Change History

  • 03/17/2003

    Major Changes.

    • Replaced MFC with WTL.
    • Changed crashrpt interface.
    • Major refactoring.
    • Updated article.

    Minor Changes.

    • Details dialog preview window now uses system defined window color instead of white.
    • Directory structure not saved in ZIP.

    Bugs Fixed

    • Support for use by multiple apps.
    • Buffer overrun error when previewing files > 32k.
    • Main dialog now displays app icon.
  • 01/12/2003
    • Initial release.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Software Developer
United States United States
I have been developing Windows applications professionally since 1998. I currently live and work near Seattle, WA.

Comments and Discussions

 
QuestionHow do you catch the actualy exeption Pin
bcharra29-Jun-05 11:02
bcharra29-Jun-05 11:02 
AnswerRe: How do you catch the actualy exeption Pin
Mike Carruth29-Jun-05 13:37
Mike Carruth29-Jun-05 13:37 
GeneralRe: How do you catch the actualy exeption Pin
bcharra30-Jun-05 7:43
bcharra30-Jun-05 7:43 
GeneralRe: How do you catch the actualy exeption Pin
bcharra30-Jun-05 7:58
bcharra30-Jun-05 7:58 
GeneralCrash reporting for VB6 Pin
Volker von Einem28-Jun-05 3:13
Volker von Einem28-Jun-05 3:13 
GeneralRe: Crash reporting for VB6 Pin
Volker von Einem28-Jun-05 4:24
Volker von Einem28-Jun-05 4:24 
GeneralRe: Crash reporting for VB6 Pin
Josha Foust30-Jun-05 6:51
Josha Foust30-Jun-05 6:51 
GeneralRe: Crash reporting for VB6 Pin
Volker von Einem1-Jul-05 1:25
Volker von Einem1-Jul-05 1:25 
Oops,

I wasn't aware of this! Thanks a lot for the hint.
I'm not too sure, if it is possible to work around this:
Triggerd by, e.g. Division by Zero, we are getting 'Unknown exception - code c000008f'.
This exception is then handeled by msvbvm60.dll in the unusefull Cry | :(( MsgBox manner :

(f0c.e68): Unknown exception - code c000008f (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012f42c ebx=0014a5e8 ecx=00000000 edx=7ffb001c esi=0012f49c edi=0014a5e8
eip=7c81eb33 esp=0012f428 ebp=0012f47c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kernel32!RaiseException+0x53:
7c81eb33 5e pop esi
0:000> kb
ChildEBP RetAddr Args to Child
0012f47c 660d0956 c000008f 00000001 00000002 kernel32!RaiseException+0x53
0012f49c 660cddaf 0014a5e8 800a000b 0012f508 MSVBVM60!RaiseVbaException+0x23
0012f4b0 660cde12 0014a5e8 0012f514 0012f5e4 MSVBVM60!EbRaiseException+0x60
0012f4c0 6610756d 0000000b 80000000 00000000 MSVBVM60!EbRaiseExceptionCode+0x5f
0012f508 66051fb3 0014a9e0 0012f524 00401c22 MSVBVM60!FpException+0x18
0012f514 00401c22 0014aa1c 00401a88 0012f570 MSVBVM60!CallProcWithArgs+0x1e
0012f524 660522b4 00401c22 0012f5e0 00000002 Project1!__vba+0x3ee
0012f53c 6605239a 0014aa1c 0012f620 0012f5e0 MSVBVM60!InvokeVtblEvent+0x32
0012f570 6605271b 0012f620 0012f5e0 00000002 MSVBVM60!InvokeEvent+0xaf
0012f644 660528e7 01d816f4 01d80bac 01d743b8 MSVBVM60!EvtErrFireWorker+0x240
0012f668 660b4560 01d816f4 00000000 00000000 MSVBVM60!EvtErrFire+0x1b
0012f680 660b47f8 01d816f4 0012f714 01d816f4 MSVBVM60!_DoClick+0x23
0012f698 6605d0c6 01d816f4 0005018a 00002111 MSVBVM60!PushCtlProc+0x7c
0012f6c0 6605f855 01d816f4 0005018a 00002111 MSVBVM60!CommonGizWndProc+0xae
0012f71c 6605e4a9 0005018a 00002111 00000002 MSVBVM60!StdCtlWndProc+0x232
0012f740 6605deea 01d81374 00060162 00000111 MSVBVM60!_DefWmCommand+0xc7
0012f7ac 66082177 01d74bb8 00060162 00000111 MSVBVM60!VBDefControlProc+0xb47
0012f92c 6605d0c6 01d81374 00060162 00000111 MSVBVM60!FormCtlProc+0x10bd
0012f954 6605f855 01d81374 00060162 00000111 MSVBVM60!CommonGizWndProc+0xae
0012f9b0 77d48734 00060162 00000111 00000002 MSVBVM60!StdCtlWndProc+0x232
0:000> p
eax=0012f42c ebx=0014a5e8 ecx=0012f15c edx=7ffb001c esi=0012f49c edi=0014a5e8
eip=7c90eaf0 esp=0012f138 ebp=0012f47c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!KiUserExceptionDispatcher+0x4:
7c90eaf0 8b1c24 mov ebx,[esp] ss:0023:0012f138=0012f140
0:000> p
eax=0012f42c ebx=0012f140 ecx=0012f15c edx=7ffb001c esi=0012f49c edi=0014a5e8
eip=7c90eaf3 esp=0012f138 ebp=0012f47c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!KiUserExceptionDispatcher+0x7:
7c90eaf3 51 push ecx
0:000> p
eax=0012f42c ebx=0012f140 ecx=0012f15c edx=7ffb001c esi=0012f49c edi=0014a5e8
eip=7c90eaf4 esp=0012f134 ebp=0012f47c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!KiUserExceptionDispatcher+0x8:
7c90eaf4 53 push ebx
0:000> p
eax=0012f42c ebx=0012f140 ecx=0012f15c edx=7ffb001c esi=0012f49c edi=0014a5e8
eip=7c90eaf5 esp=0012f130 ebp=0012f47c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!KiUserExceptionDispatcher+0x9:
7c90eaf5 e8c78c0200 call ntdll!RtlDispatchException (7c9377c1)
0:000> p
(f0c.7ec): Break instruction exception - code 80000003 (first chance)
eax=7ffd5000 ebx=00000001 ecx=00000002 edx=00000003 esi=00000004 edi=00000005
eip=7c901230 esp=0221ffcc ebp=0221fff4 iopl=0 nv up ei pl zr na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246
ntdll!DbgBreakPoint:
7c901230 cc int 3
Now comes the msg box ...

I see no other way around that patching msvbvm60.dll as a resolution?
Any other ideas?

Thanks,
Volker




GeneralRe: Crash reporting for VB6 Pin
Dr Memory13-Jul-05 20:23
Dr Memory13-Jul-05 20:23 
GeneralRe: Crash reporting for VB6 Pin
Volker von Einem13-Jul-05 20:49
Volker von Einem13-Jul-05 20:49 
GeneralRe: Crash reporting for VB6 Pin
Dr Memory13-Jul-05 22:16
Dr Memory13-Jul-05 22:16 
GeneralRe: Crash reporting for VB6 Pin
granth525-Oct-06 5:33
granth525-Oct-06 5:33 
GeneralRe: Crash reporting for VB6 Pin
Dave Cross6-Jun-07 3:27
professionalDave Cross6-Jun-07 3:27 
QuestionRe: Crash reporting for VB6 Pin
vbtrek7-Feb-07 6:12
vbtrek7-Feb-07 6:12 
AnswerRe: Crash reporting for VB6 Pin
Volker von Einem8-Feb-07 3:31
Volker von Einem8-Feb-07 3:31 
GeneralRe: Crash reporting for VB6 Pin
vbtrek8-Feb-07 3:46
vbtrek8-Feb-07 3:46 
GeneralRe: Crash reporting for VB6 Pin
Volker von Einem8-Feb-07 22:53
Volker von Einem8-Feb-07 22:53 
GeneralRe: Crash reporting for VB6 Pin
MTeefy14-Jan-14 1:09
MTeefy14-Jan-14 1:09 
GeneralMinidump doesn't have an exception context Pin
IlyaCher3-Jun-05 9:16
IlyaCher3-Jun-05 9:16 
GeneralRe: Minidump doesn't have an exception context Pin
IlyaCher3-Jun-05 9:38
IlyaCher3-Jun-05 9:38 
GeneralCompiler errors Pin
IlyaCher2-Jun-05 10:58
IlyaCher2-Jun-05 10:58 
GeneralRe: Compiler errors Pin
Mike Carruth2-Jun-05 18:15
Mike Carruth2-Jun-05 18:15 
GeneralRe: Compiler errors Pin
IlyaCher3-Jun-05 5:40
IlyaCher3-Jun-05 5:40 
GeneralDebug button Pin
MarcH751-Jun-05 5:16
MarcH751-Jun-05 5:16 
GeneralSuggested Improvements Pin
Mike Carruth24-May-05 5:59
Mike Carruth24-May-05 5:59 

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.