Introduction
In
Part 1, I discussed the problems of debugging a program that has been put into production or shipped to a customer. On the developer's workstation, the program might work fine, but on the customer's system, there are random crashes. To fix the problem, you need to know where the program is crashing, and in Part 1 I discussed an approach to determining this.
In Part 2, I described a simple way to instrument the source code that would facilitate the analysis of stack dumps. But this technique has its own problems, and in this article I will describe my attempts to improve on this, and how I came to the perfect solution.
Deus Ex Minidump
After I wrote Part 2, I really thought this was as far as stack tracing could be taken, without resorting to a much larger exception handler footprint or other technology that might not work on Win9x systems.
I was aware of a new Visual Studio .Net technology called minidumps, which you could apparently load directly into the .Net debugger and see the call stack, but I could find very little information on how this was done. Not having Visual Studio .Net, it did not seem worth pursuing - even if I could generate a minidump, how could I look at it? Also, there was a question about whether Win9x systems could create a minidump.
Then two things happened. I found out that the dbghelp.dll - which contains the MiniDumpWriteDump()
API - could be redistributed. Second, I learned of a way to read the minidumps, without using Visual Studio .Net.
Preparation
The MiniDumpWriteDump()
API is pretty easy to use, for what it does. Here is what MSDN says:
MiniDumpWriteDump The
MiniDumpWriteDump function writes user-mode minidump information to the specified file.
BOOL MiniDumpWriteDump(
HANDLE hProcess,
DWORD ProcessId,
HANDLE hFile,
MINIDUMP_TYPE DumpType,
PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);
ParametershProcess
[in] Handle to the process for which the information is to be generated. This handle must have read and query access.
ProcessId
[in] Identifier of the process for which the information is to be generated.
hFile
[in] Handle to the file in which the information is to be written.
DumpType
[in] Type of information to be generated. This parameter can be one of the values from the MINIDUMP_TYPE enumeration.
ExceptionParam
[in] Pointer to a MINIDUMP_EXCEPTION_INFORMATION structure describing the client exception that caused the minidump to be generated. If the value of this parameter is NULL, no exception information is included in the minidump file.
UserStreamParam
[in] Pointer to an array of MINIDUMP_USER_STREAM_INFORMATION structures. If the value of this parameter is NULL, no user-defined information is included in the minidump file.
CallbackParam
[in] Pointer to a MINIDUMP_CALLBACK_INFORMATION structure that specifies a callback routine which is to receive extended minidump information. If the value of this parameter is NULL, no callbacks are performed.
Return ValuesIf the function succeeds, the return value is TRUE; otherwise, the return value is FALSE.
RemarksThe MiniDumpCallback function receives extended minidump information from MiniDumpWriteDump. It also provides a way for the caller to determine the granularity of information written to the minidump file, as the callback function can filter the default information.
MiniDumpWriteDump may not produce a valid stack trace for the calling thread. To work around this problem, you must capture the state of the calling thread before calling MiniDumpWriteDump and use it as the ExceptionParam parameter. One way to do this is to force an exception inside a __try/__except block and use the EXCEPTION_POINTERS information provided by GetExceptionInformation. Alternatively, you can call the function from a new worker thread and filter this worker thread from the dump.
RequirementsClient: Included in Windows XP.
Server: Included in Windows Server 2003.
Redistributable: Requires dbghelp.dll on Windows 2000, Windows NT 4.0, and Windows Me/98/95.
Header: Declared in DbgHelp.h.
Library: Use Dbghelp.lib.
The one tricky parameter is the DumpType
. The only value for DumpType
that I could get to work on Win9x systems was MiniDumpNormal
.
Theory Into Practice
In order to get set up to call
MiniDumpWriteDump()
, we need to open a file for writing and pass its handle to
MiniDumpWriteDump()
. In the newly revised
ExceptionHandler.cpp module, this is how we call
MiniDumpWriteDump()
:
static void DumpMiniDump(HANDLE hFile, PEXCEPTION_POINTERS excpInfo)
{
if (excpInfo == NULL)
{
__try
{
OutputDebugString(_T("raising exception\r\n"));
RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL);
}
__except(DumpMiniDump(hFile, GetExceptionInformation()),
EXCEPTION_CONTINUE_EXECUTION)
{
}
}
else
{
OutputDebugString(_T("writing minidump\r\n"));
MINIDUMP_EXCEPTION_INFORMATION eInfo;
eInfo.ThreadId = GetCurrentThreadId();
eInfo.ExceptionPointers = excpInfo;
eInfo.ClientPointers = FALSE;
MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFile,
MiniDumpNormal,
excpInfo ? &eInfo : NULL,
NULL,
NULL);
}
}
This new ExceptionHandler.cpp is incorporated into the XCrashReportTest demo program. When the program is run, you may choose different types of crashes:
When you click on one of the buttons, the program crashes and it generates a minidump file called CRASH.DMP Remember when I said that I learned of a way to read the minidumps, without using Visual Studio .Net? Here is how: use WinDbg, that is available for download from Microsoft Debugging Tools. WinDbg requires that the pdb file be in the same directory as the exe and the CRASH.DMP file. After starting WinDbg, go to File | Open Crash Dump and select the CRASH.DMP file. Next enter ALT+1 to switch to the command window, and type .ecxr and hit Enter. You will see:
In the lower window you will see that OnCrash
called Crash1
which called Crash2
which called CrashTestFunction
, and the crash occurred at line 33 in crashtest.cpp.
Summary
We now have a way of getting the call stack and finding the crash location, without needing to instrument the source code. Downloading and learning to use WinDbg require a few minutes - OK, maybe more than a few if you have dialup. But it is only a one-time thing, and it seems worthwhile. The bottom line:
exception handling and minidumps can be added to any existing MFC application by adding a few files, changing three build settings, and recompiling. The only additional work for you is to save the pdb file, each time you make a new version.
I thought this was going to be the last article about crash reporting, but then I came across something so brilliant that I knew I had to incorporate it into my approach to handling exceptions and crash reporting. I will tell you all about it in Part 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.