You have already checked your trace or log files from your application (C++) on production environment (Release) and you have not clue where the program really crashes.
It's time to generate a dump file and analyze it with Microsoft WinDbg tool.
Speaking about code, when can we generate this dump file? The short answer is when the crash happens, but when it does, we normally can't control the program flow.
If your program is windowed (GUI), text-mode (console) or a service, it differs the mode which you can do it: Messaging system (WM_QUIT, WM_CLOSE...), signals (SIGABRT, SIGTERM...)...
Even these crashes generate exceptions we usually can't catch (try-catch). These exceptions are invalid memory access, memory corruption, invalid parameters... and Windows (OS) shows us a windowed message like "The program cannot continue" or the app dies silently. We have usual suspects and we start with trial/error tests in development to guess what it can be happening.
Ok, no more bla bla bla...
Dump File
Before we generate the dump file, we must set various exception handlers and try to prevent that others (third-party DLLs, headers or tools of our process) handle them.
#include <windows.h>
#include <winuser.h>
#include <dbghelp.h>
#include <signal.h>
#include <mutex>
#include <string>
#pragma comment(lib, "Dbghelp.lib")
void setExceptionHandlers()
{
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
SetUnhandledExceptionFilter(unhandledException);
AddVectoredExceptionHandler(TRUE, (PVECTORED_EXCEPTION_HANDLER)&VectoredHandler);
_set_invalid_parameter_handler(invalidParameter);
_set_purecall_handler(pureVirtualCall);
signal(SIGABRT, sigAbortHandler);
_set_abort_behavior(0, 0);
EnableCrashingOnCrashes();
PreventSetUnhandledExceptionFilter();
}
We should call this method as soon as our program starts, in our main thread or even another like our log subsystem (thread).
Let's analyze the calls our last method does.
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
We use 'SetErrorMode' to do not display message boxes, Windows Error Reporting dialog...SetUnhandledExceptionFilter(unhandledException);
We create a central method ('unhandledException') to catch exceptions and then we use 'SetUnhandledExceptionFilter' to bind exceptions like memory access violations, division by zero...static std::mutex unhandledExceptionMx;
static LONG WINAPI unhandledException(EXCEPTION_POINTERS* excpInfo = NULL)
{
unhandledExceptionMx.lock();
if (excpInfo == NULL)
{
__try
{
RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL);
}
__except (exceptionHandler(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER)
{
}
}
else
{
exceptionHandler(excpInfo);
}
unhandledExceptionMx.unlock();
return EXCEPTION_EXECUTE_HANDLER;
}
Two or more exceptions can be raised at same time so we set up a mutex to serialize access (unhandledExceptionMx). A new custom method ('exceptionHandler') appears and it will be called when we have info about the exceptions which it has just happened, but if we haven't info, we must raise an exception (programmatically as a breakpoint in debug mode) to generate a context/info and then call our exception handler method.
Finally in this method ('exceptionHandler') we'll generate the dump file and can take other actions.
static void exceptionHandler(EXCEPTION_POINTERS* excpInfo)
{
//Generate MiniDump
MINIDUMP_EXCEPTION_INFORMATION mei;
mei.ClientPointers = FALSE;
mei.ThreadId = GetCurrentThreadId();
mei.ExceptionPointers = ExceptionInfo;
string strlFich = "minidump.txt";
HANDLE hFile = NULL;
hFile = CreateFileA(strlFich.c_str(), GENERIC_WRITE, FALSE, NULL, CREATE_NEW, 0, NULL);
if (hFile != NULL)
{
MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFile,
MiniDumpNormal,
&mei,
nullptr,
nullptr
);
CloseHandle(hFile);
}
//***TO DO: Take another action to finish or close whetever we want, even print/log a message.
}
To do this, we fill a struct (MINIDUMP_EXCEPCTION_INFORMATION), create a file Handle and use MiniDumpWriteDump function.
Back to our main function ('setExceptionHandlers'), the next line is:
AddVectoredExceptionHandler(TRUE, (PVECTORED_EXCEPTION_HANDLER)&VectoredHandler);
It is for catching memory heap exceptions and the method ('VectoredHandler') does the following:
LONG CALLBACK VectoredHandler(_EXCEPTION_POINTERS *ExceptionInfo)
{
LONG lRet = EXCEPTION_CONTINUE_SEARCH;
if (0xC0000374 == ExceptionInfo->ExceptionRecord->ExceptionCode)
{
unhandledException(ExceptionInfo);
lRet = EXCEPTION_EXECUTE_HANDLER;
}
return lRet;
}
The following methods are self-descriptive in ('setExceptionHandlers'):
_set_invalid_parameter_handler(invalidParameter);
_set_purecall_handler(pureVirtualCall);
signal(SIGABRT, sigAbortHandler);
_set_abort_behavior(0, 0);
They call the next custom methods to catch invalid parameter, virtual call and abort exceptions:
static void invalidParameter(const wchar_t* expr, const wchar_t* func,
const wchar_t* file, unsigned int line, uintptr_t reserved)
{
//in release mode we don't usually have info to get expr, func, file or line data
unhandledException();
}
static void pureVirtualCall()
{
unhandledException();
}
static void sigAbortHandler(int sig)
{
//this is required, otherwise if there is another thread
//simultaneously tries to abort process will be terminated
signal(SIGABRT, sigAbortHandler);
unhandledException();
}
The penultimate function allows our code to handle the exceptions:
void EnableCrashingOnCrashes()
{
typedef BOOL(WINAPI *tGetPolicy)(LPDWORD lpFlags);
typedef BOOL(WINAPI *tSetPolicy)(DWORD dwFlags);
const DWORD EXCEPTION_SWALLOWING = 0x1;
HMODULE kernel32 = LoadLibraryA("kernel32.dll");
tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32,
"GetProcessUserModeExceptionPolicy");
tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32,
"SetProcessUserModeExceptionPolicy");
if (pGetPolicy && pSetPolicy)
{
DWORD dwFlags;
if (pGetPolicy(&dwFlags))
{
// Turn off the filter
pSetPolicy(dwFlags & ~EXCEPTION_SWALLOWING);
}
}
BOOL insanity = FALSE;
SetUserObjectInformationA(GetCurrentProcess(),
/*UOI_TIMERPROC_EXCEPTION_SUPPRESSION*/ 7, //value 7 from MSDN, try macro def
&insanity, sizeof(insanity));
}
And the final line is even the most important because it will be prevent another part of our code (library, DLL...) can mask our exception handlers, whatever our app is 32 or 64 bits:
static BOOL PreventSetUnhandledExceptionFilter()
{
HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll"));
if (hKernel32 == NULL) return FALSE;
void *pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter");
if (pOrgEntry == NULL) return FALSE;
#ifdef _M_IX86
// Code for x86:
// 33 C0 xor eax,eax
// C2 04 00 ret 4
unsigned char szExecute[] = { 0x33, 0xC0, 0xC2, 0x04, 0x00 };
#elif _M_X64
// 33 C0 xor eax,eax
// C3 ret
unsigned char szExecute[] = { 0x33, 0xC0, 0xC3 };
#else
#error "The following code only works for x86 and x64!"
#endif
SIZE_T bytesWritten = 0;
BOOL bRet = WriteProcessMemory(GetCurrentProcess(),
pOrgEntry, szExecute, sizeof(szExecute), &bytesWritten);
return bRet;
}
Example
Use the next dummy main method (Visual C++ 2017 with Release Mode) to force a crash:int main()
{
setExceptionHandlers();
int* objectInt = new int;
delete objectInt;
printf("Press enter to continue:\n");
getchar();
delete objectInt; //excep
printf("Hello!");
return 0;
}
We'll have a file called 'minidump.txt' in the folder where we run the example.
Then, we open WinDbg and follow these actions:
1. File -> Open Crash Dump and select it.
2. In Command Window, type '.ecxr'
3. If we're lucky then a new window will show up with the function which crashes and we have just finished. If not, in Command Window we will see more info about the exception: register values and the function with memory address, assembler function, etc... and select View -> Call Stack.
4. The Calls Window will show up and wait a second, then we'll have the call stack. We can check some options like Source, Source args... and we must find a function of our code in the stack. Once it is found, we click on it and a new window will appear with our desired function.
If we can't find a function of our program in the stack, we still can use the memory address (hex) of the exception to use it when we debug our Release program. To do this, we must set up a break point as soon as we can, and use the address to try to guess which function can be crashing.
That's all!
Please post any error or improvement in comment section.
Please post any error or improvement in comment section.