source-engine/external/vpc/tier0/minidump.cpp

318 lines
9.5 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "pch_tier0.h"
#include "tier0/minidump.h"
#include "tier0/platform.h"
#if defined( _WIN32 ) && !defined(_X360 ) && ( _MSC_VER >= 1300 )
#include "tier0/valve_off.h"
#define WIN_32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0403
#include <windows.h>
#include <time.h>
#include <dbghelp.h>
#endif
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
#if defined( _WIN32 ) && !defined( _X360 )
#if _MSC_VER >= 1300
// 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
);
// true if we're currently writing a minidump caused by an assert
static bool g_bWritingNonfatalMinidump = false;
// counter used to make sure minidump names are unique
static int g_nMinidumpsWritten = 0;
//-----------------------------------------------------------------------------
// 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,
ExceptionInfo_t * pExceptionInfo,
uint32 minidumpType,
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
struct tm curtime;
Plat_GetLocalTime( &curtime );
++g_nMinidumpsWritten;
// strip off the rest of the path from the .exe name
tchar rgchModuleName[MAX_PATH];
#ifdef TCHAR_IS_WCHAR
::GetModuleFileNameW( NULL, rgchModuleName, sizeof(rgchModuleName) / sizeof(tchar) );
#else
::GetModuleFileName( NULL, rgchModuleName, sizeof(rgchModuleName) / sizeof(tchar) );
#endif
tchar *pch = _tcsrchr( rgchModuleName, '.' );
if ( pch )
{
*pch = 0;
}
pch = _tcsrchr( rgchModuleName, '\\' );
if ( pch )
{
// move past the last slash
pch++;
}
else
{
pch = _T("unknown");
}
// can't use the normal string functions since we're in tier0
tchar rgchFileName[MAX_PATH];
_sntprintf( rgchFileName, sizeof(rgchFileName) / sizeof(tchar),
_T("%s_%s_%d%.2d%2d%.2d%.2d%.2d_%d.mdmp"),
pch,
g_bWritingNonfatalMinidump ? "assert" : "crash",
curtime.tm_year + 1900, /* Year less 2000 */
curtime.tm_mon + 1, /* month (0 - 11 : 0 = January) */
curtime.tm_mday, /* day of month (1 - 31) */
curtime.tm_hour, /* hour (0 - 23) */
curtime.tm_min, /* minutes (0 - 59) */
curtime.tm_sec, /* seconds (0 - 59) */
g_nMinidumpsWritten // ensures the filename is unique
);
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 = (PEXCEPTION_POINTERS)pExceptionInfo;
ExInfo.ClientPointers = FALSE;
bMinidumpResult = (*pfnMiniDumpWrite)( ::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, (MINIDUMP_TYPE)minidumpType, &ExInfo, NULL, NULL );
::CloseHandle( hFile );
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 );
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, ExceptionInfo_t * pExceptionInfo )
{
// 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.
uint32 iType = MINIDUMP_WithDataSegs | MINIDUMP_WithIndirectlyReferencedMemory;
if ( !WriteMiniDumpUsingExceptionInfo( uStructuredExceptionCode, pExceptionInfo, iType ) )
{
iType = MINIDUMP_WithDataSegs;
WriteMiniDumpUsingExceptionInfo( uStructuredExceptionCode, pExceptionInfo, iType );
}
}
// 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, (ExceptionInfo_t*)pExceptionInfo );
return EXCEPTION_CONTINUE_SEARCH;
}
void MinidumpSetUnhandledExceptionFunction( FnMiniDump pfn )
{
g_UnhandledExceptionFunction = pfn;
SetUnhandledExceptionFilter( ValveUnhandledExceptionFilter );
}
//-----------------------------------------------------------------------------
// Purpose: writes out a minidump from the current process
//-----------------------------------------------------------------------------
typedef void (*FnMiniDumpInternal_t)( unsigned int uStructuredExceptionCode, _EXCEPTION_POINTERS * pExceptionInfo );
void WriteMiniDump()
{
// throw an exception so we can catch it and get the stack info
g_bWritingNonfatalMinidump = true;
__try
{
::RaiseException
(
0, // 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( 0, (ExceptionInfo_t*)GetExceptionInformation() ), EXCEPTION_EXECUTE_HANDLER )
{
}
g_bWritingNonfatalMinidump = false;
}
PLATFORM_OVERLOAD bool g_bInException = false;
#include <eh.h>
//-----------------------------------------------------------------------------
// Purpose: Catches and writes out any exception throw by the specified function
//-----------------------------------------------------------------------------
void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] )
{
if ( Plat_IsInDebugSession() )
{
// don't mask exceptions when running in the debugger
pfn( argc, argv );
}
else
{
try
{
#pragma warning(push)
#pragma warning(disable : 4535) // warning C4535: calling _set_se_translator() requires /EHa
_set_se_translator( (FnMiniDumpInternal_t)g_pfnWriteMiniDump );
#pragma warning(pop)
pfn( argc, argv );
}
catch (...)
{
g_bInException = true;
Log_Msg( LOG_CONSOLE, _T("Fatal exception caught, minidump written\n") );
// handle everything and just quit, we've already written out our minidump
}
}
}
#else
PLATFORM_INTERFACE void WriteMiniDump()
{
}
PLATFORM_INTERFACE void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] )
{
pfn( argc, argv );
}
#endif
#elif defined(_X360 )
PLATFORM_INTERFACE void WriteMiniDump()
{
#if !defined( _CERT )
DmCrashDump(false);
#endif
}
#else // !_WIN32
PLATFORM_INTERFACE void WriteMiniDump()
{
}
PLATFORM_INTERFACE void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] )
{
pfn( argc, argv );
}
#endif