summaryrefslogtreecommitdiff
path: root/tier0/minidump.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tier0/minidump.cpp')
-rw-r--r--tier0/minidump.cpp687
1 files changed, 687 insertions, 0 deletions
diff --git a/tier0/minidump.cpp b/tier0/minidump.cpp
new file mode 100644
index 0000000..a4c2f72
--- /dev/null
+++ b/tier0/minidump.cpp
@@ -0,0 +1,687 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "pch_tier0.h"
+
+#include "tier0/minidump.h"
+#include "tier0/platform.h"
+
+#if defined( _WIN32 ) && !defined( _X360 )
+
+#if _MSC_VER >= 1300
+#include "tier0/valve_off.h"
+#define WIN_32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <dbghelp.h>
+
+#include <time.h>
+
+// MiniDumpWriteDump() function declaration (so we can just get the function directly from windows)
+typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)
+ (
+ HANDLE hProcess,
+ DWORD dwPid,
+ HANDLE hFile,
+ MINIDUMP_TYPE DumpType,
+ CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
+ CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
+ CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam
+ );
+
+
+// counter used to make sure minidump names are unique
+static int g_nMinidumpsWritten = 0;
+
+// process-wide prefix to use for minidumps
+static tchar g_rgchMinidumpFilenamePrefix[MAX_PATH];
+
+// Process-wide comment to put into minidumps
+static char g_rgchMinidumpComment[2048];
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates a new file and dumps the exception info into it
+// Input : uStructuredExceptionCode - windows exception code, unused.
+// pExceptionInfo - call stack.
+// minidumpType - type of minidump to write.
+// ptchMinidumpFileNameBuffer - if not-NULL points to a writable tchar buffer
+// of length at least _MAX_PATH to contain the name
+// of the written minidump file on return.
+//-----------------------------------------------------------------------------
+bool WriteMiniDumpUsingExceptionInfo(
+ unsigned int uStructuredExceptionCode,
+ _EXCEPTION_POINTERS * pExceptionInfo,
+ int minidumpType,
+ const char *pszFilenameSuffix,
+ tchar *ptchMinidumpFileNameBuffer /* = NULL */
+ )
+{
+ if ( ptchMinidumpFileNameBuffer )
+ {
+ *ptchMinidumpFileNameBuffer = tchar( 0 );
+ }
+
+ // get the function pointer directly so that we don't have to include the .lib, and that
+ // we can easily change it to using our own dll when this code is used on win98/ME/2K machines
+ HMODULE hDbgHelpDll = ::LoadLibrary( "DbgHelp.dll" );
+ if ( !hDbgHelpDll )
+ return false;
+
+ bool bReturnValue = false;
+ MINIDUMPWRITEDUMP pfnMiniDumpWrite = (MINIDUMPWRITEDUMP) ::GetProcAddress( hDbgHelpDll, "MiniDumpWriteDump" );
+
+ if ( pfnMiniDumpWrite )
+ {
+ // create a unique filename for the minidump based on the current time and module name
+ time_t currTime = ::time( NULL );
+ struct tm * pTime = ::localtime( &currTime );
+ ++g_nMinidumpsWritten;
+
+ // If they didn't set a dump prefix, then set one for them using the module name
+ if ( g_rgchMinidumpFilenamePrefix[0] == TCHAR(0) )
+ {
+ tchar rgchModuleName[MAX_PATH];
+ #ifdef TCHAR_IS_WCHAR
+ ::GetModuleFileNameW( NULL, rgchModuleName, sizeof(rgchModuleName) / sizeof(tchar) );
+ #else
+ ::GetModuleFileName( NULL, rgchModuleName, sizeof(rgchModuleName) / sizeof(tchar) );
+ #endif
+
+ // strip off the rest of the path from the .exe name
+ tchar *pch = _tcsrchr( rgchModuleName, '.' );
+ if ( pch )
+ {
+ *pch = 0;
+ }
+ pch = _tcsrchr( rgchModuleName, '\\' );
+ if ( pch )
+ {
+ // move past the last slash
+ pch++;
+ }
+ else
+ {
+ pch = _T("unknown");
+ }
+ strcpy( g_rgchMinidumpFilenamePrefix, pch );
+ }
+
+
+ // can't use the normal string functions since we're in tier0
+ tchar rgchFileName[MAX_PATH];
+ _sntprintf( rgchFileName, sizeof(rgchFileName) / sizeof(tchar),
+ _T("%s_%d%02d%02d_%02d%02d%02d_%d%hs%hs.mdmp"),
+ g_rgchMinidumpFilenamePrefix,
+ pTime->tm_year + 1900, /* Year less 2000 */
+ pTime->tm_mon + 1, /* month (0 - 11 : 0 = January) */
+ pTime->tm_mday, /* day of month (1 - 31) */
+ pTime->tm_hour, /* hour (0 - 23) */
+ pTime->tm_min, /* minutes (0 - 59) */
+ pTime->tm_sec, /* seconds (0 - 59) */
+ g_nMinidumpsWritten, // ensures the filename is unique
+ ( pszFilenameSuffix != NULL ) ? "_" : "",
+ ( pszFilenameSuffix != NULL ) ? pszFilenameSuffix : ""
+ );
+ // Ensure null-termination.
+ rgchFileName[ Q_ARRAYSIZE(rgchFileName) - 1 ] = 0;
+
+ // Create directory, if our dump filename had a directory in it
+ for ( char *pSlash = rgchFileName ; *pSlash != '\0' ; ++pSlash )
+ {
+ char c = *pSlash;
+ if ( c == '/' || c == '\\' )
+ {
+ *pSlash = '\0';
+ ::CreateDirectory( rgchFileName, NULL );
+ *pSlash = c;
+ }
+ }
+
+ BOOL bMinidumpResult = FALSE;
+#ifdef TCHAR_IS_WCHAR
+ HANDLE hFile = ::CreateFileW( rgchFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
+#else
+ HANDLE hFile = ::CreateFile( rgchFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
+#endif
+
+ if ( hFile )
+ {
+ // dump the exception information into the file
+ _MINIDUMP_EXCEPTION_INFORMATION ExInfo;
+ ExInfo.ThreadId = ::GetCurrentThreadId();
+ ExInfo.ExceptionPointers = pExceptionInfo;
+ ExInfo.ClientPointers = FALSE;
+
+ // Do we have a comment?
+ MINIDUMP_USER_STREAM_INFORMATION StreamInformationHeader;
+ MINIDUMP_USER_STREAM UserStreams[1];
+ memset( &StreamInformationHeader, 0, sizeof(StreamInformationHeader) );
+ StreamInformationHeader.UserStreamArray = UserStreams;
+
+ if ( g_rgchMinidumpComment[0] != '\0' )
+ {
+ MINIDUMP_USER_STREAM *pCommentStream = &UserStreams[StreamInformationHeader.UserStreamCount++];
+ pCommentStream->Type = CommentStreamA;
+ pCommentStream->Buffer = g_rgchMinidumpComment;
+ pCommentStream->BufferSize = (ULONG)strlen(g_rgchMinidumpComment)+1;
+ }
+
+ bMinidumpResult = (*pfnMiniDumpWrite)( ::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, (MINIDUMP_TYPE)minidumpType, &ExInfo, &StreamInformationHeader, NULL );
+ ::CloseHandle( hFile );
+
+ // Clear comment for next time
+ g_rgchMinidumpComment[0] = '\0';
+
+ if ( bMinidumpResult )
+ {
+ bReturnValue = true;
+
+ if ( ptchMinidumpFileNameBuffer )
+ {
+ // Copy the file name from "pSrc = rgchFileName" into "pTgt = ptchMinidumpFileNameBuffer"
+ tchar *pTgt = ptchMinidumpFileNameBuffer;
+ tchar const *pSrc = rgchFileName;
+ while ( ( *( pTgt ++ ) = *( pSrc ++ ) ) != tchar( 0 ) )
+ continue;
+ }
+ }
+
+ // fall through to trying again
+ }
+
+ // mark any failed minidump writes by renaming them
+ if ( !bMinidumpResult )
+ {
+ tchar rgchFailedFileName[_MAX_PATH];
+ _sntprintf( rgchFailedFileName, sizeof(rgchFailedFileName) / sizeof(tchar), "(failed)%s", rgchFileName );
+ // Ensure null-termination.
+ rgchFailedFileName[ Q_ARRAYSIZE(rgchFailedFileName) - 1 ] = 0;
+ rename( rgchFileName, rgchFailedFileName );
+ }
+ }
+
+ ::FreeLibrary( hDbgHelpDll );
+
+ // call the log flush function if one is registered to try to flush any logs
+ //CallFlushLogFunc();
+
+ return bReturnValue;
+}
+
+
+void InternalWriteMiniDumpUsingExceptionInfo( unsigned int uStructuredExceptionCode, _EXCEPTION_POINTERS * pExceptionInfo, const char *pszFilenameSuffix )
+{
+ // If this is is a real crash (not an assert or one we purposefully triggered), then try to write a full dump
+ // only do this on our GC (currently GC is 64-bit, so we can use a #define rather than some run-time switch
+#ifdef _WIN64
+ if ( uStructuredExceptionCode != EXCEPTION_BREAKPOINT )
+ {
+ if ( WriteMiniDumpUsingExceptionInfo( uStructuredExceptionCode, pExceptionInfo, MiniDumpWithFullMemory, pszFilenameSuffix ) )
+ {
+ return;
+ }
+ }
+#endif
+
+ // First try to write it with all the indirectly referenced memory (ie: a large file).
+ // If that doesn't work, then write a smaller one.
+ int iType = MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory;
+ if ( !WriteMiniDumpUsingExceptionInfo( uStructuredExceptionCode, pExceptionInfo, (MINIDUMP_TYPE)iType, pszFilenameSuffix ) )
+ {
+ iType = MiniDumpWithDataSegs;
+ WriteMiniDumpUsingExceptionInfo( uStructuredExceptionCode, pExceptionInfo, (MINIDUMP_TYPE)iType, pszFilenameSuffix );
+ }
+}
+
+// minidump function to use
+static FnMiniDump g_pfnWriteMiniDump = InternalWriteMiniDumpUsingExceptionInfo;
+
+//-----------------------------------------------------------------------------
+// Purpose: Set a function to call which will write our minidump, overriding
+// the default function
+// Input : pfn - Pointer to minidump function to set
+// Output : Previously set function
+//-----------------------------------------------------------------------------
+FnMiniDump SetMiniDumpFunction( FnMiniDump pfn )
+{
+ FnMiniDump pfnTemp = g_pfnWriteMiniDump;
+ g_pfnWriteMiniDump = pfn;
+ return pfnTemp;
+}
+
+
+//-----------------------------------------------------------------------------
+// Unhandled exceptions
+//-----------------------------------------------------------------------------
+static FnMiniDump g_UnhandledExceptionFunction;
+static LONG STDCALL ValveUnhandledExceptionFilter( _EXCEPTION_POINTERS* pExceptionInfo )
+{
+ uint uStructuredExceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode;
+ g_UnhandledExceptionFunction( uStructuredExceptionCode, pExceptionInfo, 0 );
+ return EXCEPTION_CONTINUE_SEARCH;
+}
+
+void MinidumpSetUnhandledExceptionFunction( FnMiniDump pfn )
+{
+ g_UnhandledExceptionFunction = pfn;
+ SetUnhandledExceptionFilter( ValveUnhandledExceptionFilter );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: set prefix to use for filenames
+//-----------------------------------------------------------------------------
+void SetMinidumpFilenamePrefix( const char *pszPrefix )
+{
+ #ifdef TCHAR_IS_WCHAR
+ mbstowcs( g_rgchMinidumpFilenamePrefix, pszPrefix, sizeof(g_rgchMinidumpFilenamePrefix) / sizeof(g_rgchMinidumpFilenamePrefix[0]) - 1 );
+ #else
+ strncpy( g_rgchMinidumpFilenamePrefix, pszPrefix, sizeof(g_rgchMinidumpFilenamePrefix) / sizeof(g_rgchMinidumpFilenamePrefix[0]) - 1 );
+ #endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: set comment to put into minidumps
+//-----------------------------------------------------------------------------
+void SetMinidumpComment( const char *pszComment )
+{
+ if ( pszComment == NULL )
+ pszComment = "";
+ strncpy( g_rgchMinidumpComment, pszComment, sizeof(g_rgchMinidumpComment) - 1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: writes out a minidump from the current process
+//-----------------------------------------------------------------------------
+void WriteMiniDump( const char *pszFilenameSuffix )
+{
+ // throw an exception so we can catch it and get the stack info
+ __try
+ {
+ ::RaiseException
+ (
+ EXCEPTION_BREAKPOINT, // dwExceptionCode
+ EXCEPTION_NONCONTINUABLE, // dwExceptionFlags
+ 0, // nNumberOfArguments,
+ NULL // const ULONG_PTR* lpArguments
+ );
+
+ // Never get here (non-continuable exception)
+ }
+ // Write the minidump from inside the filter (GetExceptionInformation() is only
+ // valid in the filter)
+ __except ( g_pfnWriteMiniDump( EXCEPTION_BREAKPOINT, GetExceptionInformation(), pszFilenameSuffix ), EXCEPTION_EXECUTE_HANDLER )
+ {
+ }
+}
+
+DBG_OVERLOAD bool g_bInException = false;
+
+//-----------------------------------------------------------------------------
+// Purpose: Catches and writes out any exception throw by the specified function.
+// Input: pfn - Function to call within protective exception block
+// pv - Void pointer to pass that function
+//-----------------------------------------------------------------------------
+void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] )
+{
+ CatchAndWriteMiniDumpEx( pfn, argc, argv, k_ECatchAndWriteMiniDumpAbort );
+}
+
+// message types
+enum ECatchAndWriteFunctionType
+{
+ k_eSCatchAndWriteFunctionTypeInvalid = 0,
+ k_eSCatchAndWriteFunctionTypeWMain = 1, // typedef void (*FnWMain)( int , tchar *[] );
+ k_eSCatchAndWriteFunctionTypeWMainIntReg = 2, // typedef int (*FnWMainIntRet)( int , tchar *[] );
+ k_eSCatchAndWriteFunctionTypeVoidPtr = 3, // typedef void (*FnVoidPtrFn)( void * );
+};
+
+struct CatchAndWriteContext_t
+{
+ ECatchAndWriteFunctionType m_eType;
+ void *m_pfn;
+ int *m_pargc;
+ tchar ***m_pargv;
+ void **m_ppv;
+ ECatchAndWriteMinidumpAction m_eAction;
+
+ void Set( ECatchAndWriteFunctionType eType, ECatchAndWriteMinidumpAction eAction, void *pfn, int *pargc, tchar **pargv[], void **ppv )
+ {
+ m_eType = eType;
+ m_eAction = eAction;
+ m_pfn = pfn;
+ m_pargc = pargc;
+ m_pargv = pargv;
+ m_ppv = ppv;
+
+ ErrorIfNot( m_pfn, ( "CatchAndWriteContext_t::Set w/o a function pointer!" ) );
+ }
+
+ int Invoke()
+ {
+ switch ( m_eType )
+ {
+ default:
+ case k_eSCatchAndWriteFunctionTypeInvalid:
+ break;
+ case k_eSCatchAndWriteFunctionTypeWMain:
+ ErrorIfNot( m_pargc && m_pargv, ( "CatchAndWriteContext_t::Invoke with bogus argc/argv" ) );
+ ((FnWMain)m_pfn)( *m_pargc, *m_pargv );
+ break;
+ case k_eSCatchAndWriteFunctionTypeWMainIntReg:
+ ErrorIfNot( m_pargc && m_pargv, ( "CatchAndWriteContext_t::Invoke with bogus argc/argv" ) );
+ return ((FnWMainIntRet)m_pfn)( *m_pargc, *m_pargv );
+ case k_eSCatchAndWriteFunctionTypeVoidPtr:
+ ErrorIfNot( m_ppv, ( "CatchAndWriteContext_t::Invoke with bogus void *ptr" ) );
+ ((FnVoidPtrFn)m_pfn)( *m_ppv );
+ break;
+ }
+
+ return 0;
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Catches and writes out any exception throw by the specified function
+// Input: pfn - Function to call within protective exception block
+// pv - Void pointer to pass that function
+// eAction - Specifies what to do if it catches an exception
+//-----------------------------------------------------------------------------
+#if defined(_PS3)
+
+int CatchAndWriteMiniDump_Impl( CatchAndWriteContext_t &ctx )
+{
+ // we dont handle minidumps on ps3
+ return ctx.Invoke();
+}
+
+#else
+
+static const char *GetExceptionCodeName( unsigned long code )
+{
+ switch ( code )
+ {
+ case EXCEPTION_ACCESS_VIOLATION: return "accessviolation";
+ case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "arrayboundsexceeded";
+ case EXCEPTION_BREAKPOINT: return "breakpoint";
+ case EXCEPTION_DATATYPE_MISALIGNMENT: return "datatypemisalignment";
+ case EXCEPTION_FLT_DENORMAL_OPERAND: return "fltdenormaloperand";
+ case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "fltdividebyzero";
+ case EXCEPTION_FLT_INEXACT_RESULT: return "fltinexactresult";
+ case EXCEPTION_FLT_INVALID_OPERATION: return "fltinvalidoperation";
+ case EXCEPTION_FLT_OVERFLOW: return "fltoverflow";
+ case EXCEPTION_FLT_STACK_CHECK: return "fltstackcheck";
+ case EXCEPTION_FLT_UNDERFLOW: return "fltunderflow";
+ case EXCEPTION_INT_DIVIDE_BY_ZERO: return "intdividebyzero";
+ case EXCEPTION_INT_OVERFLOW: return "intoverflow";
+ case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "noncontinuableexception";
+ case EXCEPTION_PRIV_INSTRUCTION: return "privinstruction";
+ case EXCEPTION_SINGLE_STEP: return "singlestep";
+ }
+
+ // Unknown exception
+ return "crash";
+}
+
+int CatchAndWriteMiniDump_Impl( CatchAndWriteContext_t &ctx )
+{
+ // Sorry, this is the only action currently implemented!
+ Assert( ctx.m_eAction == k_ECatchAndWriteMiniDumpAbort );
+
+ if ( Plat_IsInDebugSession() )
+ {
+ // don't mask exceptions when running in the debugger
+ return ctx.Invoke();
+ }
+
+// g_DumpHelper.Init();
+
+// Win32 code gets to use a special handler
+#if defined( _WIN32 )
+ __try
+ {
+ return ctx.Invoke();
+ }
+ __except ( g_pfnWriteMiniDump( GetExceptionCode(), GetExceptionInformation(), GetExceptionCodeName( GetExceptionCode() ) ), EXCEPTION_EXECUTE_HANDLER )
+ {
+ TerminateProcess( GetCurrentProcess(), EXIT_FAILURE ); // die, die RIGHT NOW! (don't call exit() so destructors will not get run)
+ }
+
+ // if we get here, we definitely are not in an exception handler
+ g_bInException = false;
+
+ return 0;
+#else
+// if ( ctx.m_pargv != 0 )
+// {
+// g_DumpHelper.ComputeExeNameFromArgv0( (*ctx.m_pargv)[ 0 ] );
+// }
+//
+// ICrashHandler *handler = g_DumpHelper.GetHandlerAPI();
+// CCrashHandlerScope scope( handler, g_DumpHelper.GetProduct(), g_DumpHelper.GetVersion(), g_DumpHelper.GetBuildID(), false );
+// if ( handler )
+// handler->SetSteamID( g_DumpHelper.GetSteamID() );
+
+ return ctx.Invoke();
+#endif
+}
+
+#endif // _PS3
+
+//-----------------------------------------------------------------------------
+// Purpose: Catches and writes out any exception throw by the specified function
+// Input: pfn - Function to call within protective exception block
+// pv - Void pointer to pass that function
+// eAction - Specifies what to do if it catches an exception
+//-----------------------------------------------------------------------------
+void CatchAndWriteMiniDumpEx( FnWMain pfn, int argc, tchar *argv[], ECatchAndWriteMinidumpAction eAction )
+{
+ CatchAndWriteContext_t ctx;
+ ctx.Set( k_eSCatchAndWriteFunctionTypeWMain, eAction, (void *)pfn, &argc, &argv, NULL );
+ CatchAndWriteMiniDump_Impl( ctx );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Catches and writes out any exception throw by the specified function
+// Input: pfn - Function to call within protective exception block
+// pv - Void pointer to pass that function
+// eAction - Specifies what to do if it catches an exception
+//-----------------------------------------------------------------------------
+int CatchAndWriteMiniDumpExReturnsInt( FnWMainIntRet pfn, int argc, tchar *argv[], ECatchAndWriteMinidumpAction eAction )
+{
+ CatchAndWriteContext_t ctx;
+ ctx.Set( k_eSCatchAndWriteFunctionTypeWMainIntReg, eAction, (void *)pfn, &argc, &argv, NULL );
+ return CatchAndWriteMiniDump_Impl( ctx );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Catches and writes out any exception throw by the specified function
+// Input: pfn - Function to call within protective exception block
+// pv - Void pointer to pass that function
+// eAction - Specifies what to do if it catches an exception
+//-----------------------------------------------------------------------------
+void CatchAndWriteMiniDumpExForVoidPtrFn( FnVoidPtrFn pfn, void *pv, ECatchAndWriteMinidumpAction eAction )
+{
+ CatchAndWriteContext_t ctx;
+ ctx.Set( k_eSCatchAndWriteFunctionTypeVoidPtr, eAction, (void *)pfn, NULL, NULL, &pv );
+ CatchAndWriteMiniDump_Impl( ctx );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Catches and writes out any exception throw by the specified function
+// Input: pfn - Function to call within protective exception block
+// pv - Void pointer to pass that function
+// bExitQuietly - If true (for client) just exit after mindump, with no visible error for user
+// If false, re-throws.
+//-----------------------------------------------------------------------------
+void CatchAndWriteMiniDumpForVoidPtrFn( FnVoidPtrFn pfn, void *pv, bool bExitQuietly )
+{
+ return CatchAndWriteMiniDumpExForVoidPtrFn( pfn, pv, bExitQuietly ? k_ECatchAndWriteMiniDumpAbort : k_ECatchAndWriteMiniDumpReThrow );
+}
+
+
+/*
+Call this function to ensure that your program actually crashes when it crashes.
+
+Oh my god.
+
+When 64-bit Windows came out it turns out that it wasn't possible to throw
+and catch exceptions from user-mode, through kernel-mode, and back to user
+mode. Therefore, for crashes that happen in kernel callbacks such as Window
+procs Microsoft had to decide either to always crash when an exception
+is thrown (including an SEH such as an access violation) or else always silently
+swallow the exception.
+
+They chose badly.
+
+Therefore, for the last five or so years, programs on 64-bit Windows have been
+silently swallowing *some* exceptions. As a concrete example, consider this code:
+
+ case WM_PAINT:
+ {
+ hdc = BeginPaint(hWnd, &ps);
+ char* p = new char;
+ *(int*)0 = 0;
+ delete p;
+ EndPaint(hWnd, &ps);
+ }
+ break;
+
+It's in a WindowProc handling a paint message so it will generally be called from
+kernel mode. Therefore the crash in the middle of it is, by default, 'handled' for
+us. The "delete p;" and EndPaint() never happen. This means that the process is
+left in an indeterminate state. It also means that our error reporting never sees
+the exception. It is effectively as though there is a __try/__except handler at the
+kernel boundary and any crashes cause the stack to be unwound (without destructors
+being run) to the kernel boundary where execution continues.
+
+Charming.
+
+The fix is to use the Get/SetProcessUserModeExceptionPolicy API to tell Windows
+that we don't want to struggle on after crashing.
+
+For more scary details see this article. It actually suggests using the compatibility
+manifest, but that does not appear to work.
+http://blog.paulbetts.org/index.php/2010/07/20/the-case-of-the-disappearing-onload-exception-user-mode-callback-exceptions-in-x64/
+*/
+void EnableCrashingOnCrashes()
+{
+ typedef BOOL (WINAPI *tGetProcessUserModeExceptionPolicy)(LPDWORD lpFlags);
+ typedef BOOL (WINAPI *tSetProcessUserModeExceptionPolicy)(DWORD dwFlags);
+ #define PROCESS_CALLBACK_FILTER_ENABLED 0x1
+
+ HMODULE kernel32 = LoadLibraryA("kernel32.dll");
+ tGetProcessUserModeExceptionPolicy pGetProcessUserModeExceptionPolicy = (tGetProcessUserModeExceptionPolicy)GetProcAddress(kernel32, "GetProcessUserModeExceptionPolicy");
+ tSetProcessUserModeExceptionPolicy pSetProcessUserModeExceptionPolicy = (tSetProcessUserModeExceptionPolicy)GetProcAddress(kernel32, "SetProcessUserModeExceptionPolicy");
+ if (pGetProcessUserModeExceptionPolicy && pSetProcessUserModeExceptionPolicy)
+ {
+ DWORD dwFlags;
+ if (pGetProcessUserModeExceptionPolicy(&dwFlags))
+ {
+ pSetProcessUserModeExceptionPolicy(dwFlags & ~PROCESS_CALLBACK_FILTER_ENABLED); // turn off bit 1
+ }
+ }
+}
+
+#else
+
+PLATFORM_INTERFACE void WriteMiniDump( const char *pszFilenameSuffix )
+{
+}
+
+PLATFORM_INTERFACE void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] )
+{
+ pfn( argc, argv );
+}
+
+#endif
+#elif defined(_X360 )
+PLATFORM_INTERFACE void WriteMiniDump( const char *pszFilenameSuffix )
+{
+ DmCrashDump(false);
+}
+
+#else // !_WIN32
+#include "tier0/minidump.h"
+
+PLATFORM_INTERFACE void WriteMiniDump( const char *pszFilenameSuffix )
+{
+}
+
+PLATFORM_INTERFACE void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] )
+{
+ pfn( argc, argv );
+}
+
+#endif
+
+// User minidump stream info comment strings.
+//
+// Single header string of 512 bytes set via MinidumpUserStreamInfoSetHeader.
+static char g_UserStreamInfoHeader[ 512 ];
+// Array of 32 round robin 128 byte strings set via MinidumpUserStreamInfoAppend.
+static char g_UserStreamInfo[ 64 ][ 128 ];
+static int g_UserStreamInfoIndex = 0;
+
+// Set the single g_UserStreamInfoHeader string.
+void MinidumpUserStreamInfoSetHeader( const char *pFormat, ... )
+{
+ va_list marker;
+
+ va_start( marker, pFormat );
+ _vsnprintf( g_UserStreamInfoHeader, ARRAYSIZE( g_UserStreamInfoHeader ), pFormat, marker );
+ g_UserStreamInfoHeader[ ARRAYSIZE( g_UserStreamInfoHeader ) - 1 ] = 0;
+ va_end( marker );
+}
+
+// Set the next comment in the g_UserStreamInfo array.
+void MinidumpUserStreamInfoAppend( const char *pFormat, ... )
+{
+ va_list marker;
+ char *pData = g_UserStreamInfo[ g_UserStreamInfoIndex ];
+ const int DataSize = ARRAYSIZE( g_UserStreamInfo[ g_UserStreamInfoIndex ] );
+
+ // Add tick count just so we have a general idea of when this event happened.
+ _snprintf( pData, DataSize, "[%x]", Plat_MSTime() );
+ pData[ DataSize - 1 ] = 0;
+ size_t HeaderLen = strlen( pData );
+
+ va_start( marker, pFormat );
+ _vsnprintf( pData + HeaderLen, DataSize - HeaderLen, pFormat, marker );
+ pData[ DataSize - 1 ] = 0;
+ va_end( marker );
+
+ // Bump up index, and go back to 0 if we've hit the end.
+ g_UserStreamInfoIndex++;
+ if( g_UserStreamInfoIndex >= ARRAYSIZE( g_UserStreamInfo ) )
+ {
+ g_UserStreamInfoIndex = 0;
+ }
+}
+
+// Retrieve the string given the Index.
+// Index 0: header string
+// Index 1+: comment string
+// Returns NULL when you've reached the end of the comment string array
+// Empty strings ("\0") can be returned if comment hasn't been set
+const char *MinidumpUserStreamInfoGet( int Index )
+{
+ if( ( Index < 0 ) || ( Index >= (ARRAYSIZE( g_UserStreamInfo ) + 1) ) ) //+1 because we map 0 to the header
+ return NULL;
+
+ if( Index == 0 )
+ return g_UserStreamInfoHeader;
+
+ Index = ( (Index + (ARRAYSIZE( g_UserStreamInfo ) - 1)) + //subtract 1 in a way that circularly wraps. Since 0 maps to the header, the comment indices are 1 based
+ g_UserStreamInfoIndex ) //start with our oldest comment
+ % ARRAYSIZE( g_UserStreamInfo ); //circular buffer wrapping
+
+ return g_UserStreamInfo[ Index ];
+}
+
+