You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
687 lines
24 KiB
687 lines
24 KiB
//========= 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 ];
|
|
}
|
|
|
|
|