Click here to Skip to main content
15,891,828 members
Articles / Programming Languages / C++

Part 3: Windows Debugging Techniques - Debugging Application Crash (Self Dump Generation)

Rate me:
Please Sign up or sign in to vote.
4.90/5 (14 votes)
13 Feb 2014CPOL5 min read 47.4K   57   13
This is Part 3 of various Techniques to Debug Applications on Windows, focussed on automated way of generating Dump On Application Crash

Introduction

This article is Part 3 of Windows Debugging Techniques. This article is concentrated on how to automate the dump generation process in the application itself.

Note:This is series of articles divided into 5 parts
Part 1: Windows Debugging Techniques - Debugging Application Crash (Windbg)
Part 2: Windows Debugging Techniques - Debugging Application Crash (DebugDiag, AppVerifier)
Part 3: Windows Debugging Techniques - Debugging Application Crash (Self Dump Generation)
Part 4: Windows Debugging Techniques - Debugging Memory Leaks (Perfmon)
Part 5: Windows Debugging Techniques - Debugging Memory Leaks (CRT APIs)

Background

As you have noticed in the first two parts we were generating dumps when required, which means we need to reproduce the problem and also we need to put procdump on the target machine to get the dump. On a production system customers will very often not agree to installation of any applications on the system. The other side of the coin is we might not get the issue again, it might be an intermittent, which would still be production but the customer is not able to give the exact steps to reproduce the problem. This could be a really problematic situation for the product management. It would be really great if when the application crashes it generates its crash dump on its own which could be collected later for analysis.

Definition

 A dump file is a snapshot of an app at the point in time the dump is taken. It shows what process was executing and what modules were loaded. Dumps are primarily used for debugging issues that occur on machines that the developer doesn’t have access to. For example, you can use a dump file from a customer's machine when you can’t reproduce the customer's crash or hang on your machine. 

 There are 2 kinds of Dumps

1. Mini Dump : Stores only a Subset of Process Information, Typically some information related to Threads Stack, Process Environment etc. A Mini Dump is smaller in size typically 1.5 MB or even less.

2. Full Dump : Stores all information concerning the virtual memory of the process, like Threads, Call Stack, heap state, Library Loaded. Basically every information related to a process is available. Full Dump is as big as process's virtual memory Typically some hundreds of MB's or even GB's.

To Analyze what is going wrong in our process it is better to have a Full Dump, MiniDump could also be helpful in some cases.

 Let's understand step by step how can we do a Self Dump Generation.

The Source Code

The exception is an unexpected situation, that, when encountered, leads to the application exiting. So we should keep a tabs on this exception. How do we do that? On Windows Platform there is an API.

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
  _In_  LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);

This API would be called for any unhandled exception in any thread inside a given process. This API should be called in the first entry point of the application [normally in unmanaged code it is main()]. This API expects a parameter that is an exception handler, which defines what should be done when this situation arrives. So now code would look something like this:

C++
int main()
{
 SetUnhandledExceptionFilter( unhandled_handler);
}

LONG CALLBACK unhandled_handler(EXCEPTION_POINTERS* e)
{
    return EXCEPTION_CONTINUE_SEARCH;
}
Now we need to decide what should be done inside the unhandled_handler, in our case create the dump
C++
int main()
{
 SetUnhandledExceptionFilter( unhandled_handler);
}

LONG CALLBACK unhandled_handler(EXCEPTION_POINTERS* e)
{
	make_minidump(e);
    return EXCEPTION_CONTINUE_SEARCH;
}

void make_minidump(EXCEPTION_POINTERS* e)
{
	TCHAR tszFileName[MAX_BUFF_SIZE] = {0};
	TCHAR tszPath[MAX_BUFF_SIZE] = {0};
	SYSTEMTIME stTime = {0};
	GetSystemTime(&stTime);
	SHGetSpecialFolderPath(NULL,tszPath, CSIDL_APPDATA, FALSE);
	StringCbPrintf(tszFileName, 
			       _countof(tszFileName), 
				   _T("%s\\%s__%4d%02d%02d_%02d%02d%02d.dmp"), 
				   tszPath, _T("CrashDump"),  
				   stTime.wYear, 
				   stTime.wMonth, 
				   stTime.wDay, 
				   stTime.wHour, 
				   stTime.wMinute, 
				   stTime.wSecond);

    HANDLE hFile = CreateFile(tszFileName, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if(hFile == INVALID_HANDLE_VALUE)
        return;

    MINIDUMP_EXCEPTION_INFORMATION exceptionInfo;
    exceptionInfo.ThreadId = GetCurrentThreadId();
    exceptionInfo.ExceptionPointers = e;
    exceptionInfo.ClientPointers = FALSE;

    MiniDumpWriteDump(
        GetCurrentProcess(),
        GetCurrentProcessId(),
        hFile,
        MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory | MiniDumpWithFullMemory),
        e ? &exceptionInfo : NULL,
        NULL,
        NULL);

	if(hFile)
	{
		CloseHandle(hFile);
		hFile = NULL;
	}
    return; 
}

So now inside the function unhandled_handler we are calling make_minidump, which actually writes the dump. The function unhandled_handler is a callback function which receives the EXCEPTION_POINTERS. This structure contains the exception information based on the machine generating it. EXCEPTION_POINTERS is being passed to function make_minidump, which actually writes the dump to the disk. The first portion of the function make_minidump is just creating the name of the file based on the application name and the timestamp. Then the file is being created using "CreateFile".

"MINIDUMP_EXCEPTION_INFORMATION" structure is being filled with CurrentThreadID and Exception pointers which we received from the parent function. Then call the function MiniDumpWriteDump. The interesting parameter here is MINIDUMP_TYPE. If we see msdn help on this API there is quite a big list, but based on my analysis I have added three flags, out of these the third is the most important is MiniDumpWithFullMemory. This gives all the referenced locations of the particular process in the main memory, which really helps in debugging.

With a full memory dump the size of the dump is quite big, but this really helps in analyzing the dump. With similar flows you can explore more on types of dumps and change it according to your requirements. Let's take an example and see how this works.

C++
#include <Windows.h>
#include <Dbghelp.h>
#include <iostream>
#include <tchar.h>
#include <strsafe.h>
#include <shlobj.h>

#pragma comment(lib, "DbgHelp")
#define MAX_BUFF_SIZE 1024

using namespace std;
DWORD WINAPI func();

void make_minidump(EXCEPTION_POINTERS* e)
{
	TCHAR tszFileName[MAX_BUFF_SIZE] = {0};
	TCHAR tszPath[MAX_BUFF_SIZE] = {0};
	SYSTEMTIME stTime = {0};
	GetSystemTime(&stTime);
	SHGetSpecialFolderPath(NULL,tszPath, CSIDL_APPDATA, FALSE);
	StringCbPrintf(tszFileName, 
			       _countof(tszFileName), 
				   _T("%s\\%s__%4d%02d%02d_%02d%02d%02d.dmp"), 
				   tszPath, _T("CrashDump"),  
				   stTime.wYear, 
				   stTime.wMonth, 
				   stTime.wDay, 
				   stTime.wHour, 
				   stTime.wMinute, 
				   stTime.wSecond);

    HANDLE hFile = CreateFile(tszFileName, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if(hFile == INVALID_HANDLE_VALUE)
        return;

    MINIDUMP_EXCEPTION_INFORMATION exceptionInfo;
    exceptionInfo.ThreadId = GetCurrentThreadId();
    exceptionInfo.ExceptionPointers = e;
    exceptionInfo.ClientPointers = FALSE;

    MiniDumpWriteDump(
        GetCurrentProcess(),
        GetCurrentProcessId(),
        hFile,
        MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory | MiniDumpWithFullMemory),
        e ? &exceptionInfo : NULL,
        NULL,
        NULL);

	if(hFile)
	{
		CloseHandle(hFile);
		hFile = NULL;
	}
    return; 
}

LONG CALLBACK unhandled_handler(EXCEPTION_POINTERS* e)
{
    make_minidump(e);
    return EXCEPTION_CONTINUE_SEARCH;
}

int main()
{
    SetUnhandledExceptionFilter( unhandled_handler);
	DWORD dwThreadID = 0;
	HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)func, NULL, 0, &dwThreadID);
	if(hThread == NULL)
	{
		return 1;
	}
	WaitForSingleObject(hThread, INFINITE);
	return 0;
    
}

DWORD WINAPI func()
{
	int *p = NULL;
	*p = 10;
	return 0;
}

This is the same example from Part 1, with a slight deviation of adding a thread that will crash. After executing this code the Dump file will be generated in the %Appdata% folder. Launch Windbg, set the symbols and analyze you should get the following.

Image 1

So here we see some interesting facts:

  1. DEFAULT_BUCKET_ID: NULL_POINTER_WRITE, since we are writing to a NULL Pointer
  2. CrashDump!func+2 [e:\study\windows internals\training\sample code\crashdump\crashdump\source.cpp @ 82]

    This is exactly the line number on which the crash happened

  3. Stack Trace
    00000080`26ddf7f8 00007ffb`87c115cd : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : CrashDump!func+0x2
    00000080`26ddf800 00007ffb`884c43d1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0xd
    00000080`26ddf830 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x1d
    This gives the stack trace of the faulty thread and not the one starting from the main.

Summary

So with this we now know to add the code to our application, which will generate dump whenever the application crashes. As a thumb rule this should be part of any application that is being developed.

History 

  • 2014-01-09: Article upload
  • 2014-01-20: updated with links to other parts  
  • 2014-01-27: updated with Definition for Mini Dump and Full Dump
  • 2014-01-30: updated the Definition for Dump 

License

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


Written By
Architect
India India
A Technology Enthusiast since 14 Years, have been contributing in varied Domains. Interested in developing technology which goes more meaning to Human Life, Nature and the entire ecosystem.

#Developer, #Architect, #Enthusiast, #Contributor #Mentor

To all Developers out there, You touch a Billion Lives a Billion Times
Before you code Think Twice !!!

Comments and Discussions

 
SuggestionSuggestions/Improvements Pin
Hexadigm Systems13-Sep-16 1:38
Hexadigm Systems13-Sep-16 1:38 
GeneralRe: Suggestions/Improvements Pin
shailesh9108214-Sep-16 19:41
shailesh9108214-Sep-16 19:41 
GeneralRe: Suggestions/Improvements Pin
Hexadigm Systems15-Oct-17 10:38
Hexadigm Systems15-Oct-17 10:38 
GeneralRe: Suggestions/Improvements Pin
shailesh9108216-Oct-17 7:06
shailesh9108216-Oct-17 7:06 
GeneralRe: Suggestions/Improvements Pin
Hexadigm Systems17-Oct-17 2:32
Hexadigm Systems17-Oct-17 2:32 
GeneralRe: Suggestions/Improvements Pin
shailesh9108217-Oct-17 7:35
shailesh9108217-Oct-17 7:35 
GeneralRe: Suggestions/Improvements Pin
shailesh9108216-Oct-17 7:13
shailesh9108216-Oct-17 7:13 
GeneralRe: Suggestions/Improvements Pin
Hexadigm Systems17-Oct-17 2:23
Hexadigm Systems17-Oct-17 2:23 
I don't know what most developers do and neither do you of course (respectfully). It doesn't matter what examples you may have seen on the web. MSFT's documentation clearly states:

"MiniDumpWriteDump should be called from a separate process if at all possible, rather than from within the target process being dumped. This is especially true when the target process is already not stable. For example, if it just crashed. A loader deadlock is one of many potential side effects of calling MiniDumpWriteDump from within the target process."

That only stands to reason of course. If the program crashed, it may be inherently unstable by definition, so sometimes "MiniDumpWriteDump()" may work and other times it may not (and I have seen it fail doing it your way). I agree that having a separate ".exe" is inconvenient, but it's effectively the safest way to (almost) guarantee that you'll get a dump when you really need it. The last thing you want is for a customer to contact you after a serious crash and you can't diagnose it because your dump failed. You can certainly take the chance and not rely on a separate ".exe", and that's your call of course, but IMHO it's too risky a move, though it does depend on the nature of your app to some extent. For simple, trivial programs I agree it may not be worth it. For a major app however, just install a separate app with your main app called "MiniDumpOnCrash.exe" (or whatever you want to call it - the latter name may be too scary for users though they won't normally see it - it just sits in the installation directory with the main app since it's not intended for end users to call directly). You can invoke this program yourself in two ways. The safest way is when your own program starts. Just launch it at program start-up and it runs in the background. When your crash handler ("unhandled_handler" in your case) kicks in, just set a semaphore of some kind that "MiniDumpOnCrash.exe" is waiting on, probably via "CreateEvent()" and cousins). You would pass it your EXCEPTION_POINTERS and the thread id where the crash occurred (via a call to "::GetCurrentThreadId()"), and "MiniDumpOnCrash.exe" will then take care of calling "MiniDumpWriteDump()" on behalf of the crashed app. You can pass this information to "MiniDumpOnCrash.exe" via shared memory using "::CreateFileMapping()" and "::OpenFileMapping()". All of this should be set up when your main program starts however, so that it doesn't have to do it after a crash (again, because things may be unstable). The crash handler itself simply has to update the shared memory with the EXCEPTION_POINTERS and the current thread id and then set the semaphore, so it's doing the minimal amount of work possible. It may fail anyway (to set the semaphore) if the program is badly unstable, but there's nothing more you can do of course (but it's unlikely to fail in most cases). As a variation of all this, if you don't want to launch "MiniDumpOnCrash.exe" when your main program starts to handle a crash that's not supposed to occur anyway (in theory), then you can simply launch it in your crash handler (so no semaphore is required). Calling "CreateProcess()" after after a crash is more likely to fail though than setting a semaphore (because it takes a greater effort for the OS to launch a process than set a semaphore), but in practice it probably will work (usually). That's how I do it in my own app but I realize it's not quite as safe as going the semaphore route. Everything needed to call "CreateProcess()" in my crash handler is set up at program start-up though, including the string containing the name of my "MiniDumpOnCrash.exe" app (and its arguments). I'm trying to do as little as possible in my own "unhandled_handler" that is, again, because the environment may be unstable (even creating a variable on the stack could fail, or simply pushing arguments onto the stack when you call a function, if the stack was corrupted in some way, or a stack overflow occurred, or whatever). It's an imperfect situation though of course but you should try to minimize what you're doing in a crash handler. Note BTW that I have all of this set up in a class called "MiniDumpOnCrash". I just create a single instance of it at the very start of my app (it's a generic class) and it does the job of handling everything (calling "SetUnhandledExceptionFilter()", invoking "MiniDumpOnCrash.exe" after the crash, etc.). It's also configurable to log the crash anyway I want (i.e., "MiniDumpOnCrash.exe" can email someone, pop up a message for the user if the crashed app has a UI, or whatever, all to ensure someone is informed that a crash occurred and that a minidump file exists, including the name of the minidump file). Users then just need to send the file to me. It's a standard minidump of course so I can use any tool I want to view it and get a complete stack trace (using VS normally, as explained in earlier posts - most will likely be using it but they can use any tool that can read a minidump of course).

modified 17-Oct-17 8:39am.

Bugword correction Pin
RuralMouse5-May-14 1:41
RuralMouse5-May-14 1:41 
GeneralRe: word correction Pin
shailesh910825-May-14 5:23
shailesh910825-May-14 5:23 
GeneralMy vote of 5 Pin
Gilberto Cuba Ricardo13-Apr-14 3:43
Gilberto Cuba Ricardo13-Apr-14 3:43 
QuestionNot all exceptions are 'crashes'. Pin
Blake Miller24-Jan-14 7:48
Blake Miller24-Jan-14 7:48 
GeneralRe: Not all exceptions are 'crashes'. Pin
waleri16-Feb-14 22:38
waleri16-Feb-14 22:38 

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.