2014-03-14 14:13:41 -04:00
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
# include "CrashDebugHelperPrivatePCH.h"
# include "Database.h"
# include "ISourceControlModule.h"
# include "AllowWindowsPlatformTypes.h"
# include <DbgHelp.h>
FCrashDebugHelperWindows : : FCrashDebugHelperWindows ( )
{
}
FCrashDebugHelperWindows : : ~ FCrashDebugHelperWindows ( )
{
}
//
// Helper functions for processing windows crash dumps
//
/**
* Verify the given crash dump is Windows based
*
* @ param InMiniDumpHeader The header from the crash dump
*
* @ return bool true if it is a valid crash dump , false if not
*/
bool VerifyCrashDump_Windows ( MINIDUMP_HEADER & InMiniDumpHeader )
{
// See if the signature is "MDMP"
if ( InMiniDumpHeader . Signature = = 0x504d444d )
{
return true ;
}
return false ;
}
/**
* Extract the UE engine version from the crash dump
*
* @ param InFileHandle The handle to the opened crash dump file .
* @ param InMiniDumpHeader The header from the crash dump
* @ param OutEngineVersion The engine version if successfully extracted
*
* @ return bool true if successfully extracted , false if not
*/
bool ExtractEngineVersion_Windows ( HANDLE InFileHandle , MINIDUMP_HEADER & InMiniDumpHeader , int32 & OutEngineVersion , FString & OutPlatform )
{
bool bResult = false ;
// Read the directories
MINIDUMP_DIRECTORY ModuleListDirectory ;
FMemory : : Memzero ( & ModuleListDirectory , sizeof ( MINIDUMP_DIRECTORY ) ) ;
int32 ModuleListStreamIdx = - 1 ;
bool bFailed = false ;
LONG StreamOffset = InMiniDumpHeader . StreamDirectoryRva ;
for ( ULONG StreamIdx = 0 ; ( StreamIdx < InMiniDumpHeader . NumberOfStreams ) & & ! bFailed ; StreamIdx + + )
{
SetFilePointer ( InFileHandle , StreamOffset , NULL , FILE_BEGIN ) ;
MINIDUMP_DIRECTORY MDDirectory ;
DWORD NumByteToRead = sizeof ( MINIDUMP_DIRECTORY ) ;
DWORD NumBytesRead = 0 ;
if ( ReadFile ( InFileHandle , & MDDirectory , NumByteToRead , & NumBytesRead , NULL ) = = TRUE )
{
switch ( MDDirectory . StreamType )
{
// Reserved. Do not use this enumeration value.
case UnusedStream :
case ReservedStream0 :
case ReservedStream1 :
break ;
// * The stream contains thread information and stack. For more information, see MINIDUMP_THREAD_LIST.
case ThreadListStream :
break ;
// * The stream contains module information with the base, size and version info. For more information, see MINIDUMP_MODULE_LIST.
case ModuleListStream :
ModuleListStreamIdx = StreamIdx ;
FMemory : : Memcpy ( & ModuleListDirectory , & MDDirectory , sizeof ( MINIDUMP_DIRECTORY ) ) ;
break ;
// The stream contains memory allocation information. For more information, see MINIDUMP_MEMORY_LIST.
case MemoryListStream :
break ;
// * The stream contains exception information with the code. For more information, see MINIDUMP_EXCEPTION_STREAM.
case ExceptionStream :
break ;
// * The stream contains general system information. For more information, see MINIDUMP_SYSTEM_INFO.
case SystemInfoStream :
break ;
// The stream contains extended thread information. For more information, see MINIDUMP_THREAD_EX_LIST.
case ThreadExListStream :
break ;
// The stream contains memory allocation information. For more information, see MINIDUMP_MEMORY64_LIST.
case Memory64ListStream :
break ;
// The stream contains an ANSI string used for documentation purposes.
case CommentStreamA :
break ;
// The stream contains a Unicode string used for documentation purposes.
case CommentStreamW :
break ;
// The stream contains high-level information about the active operating system handles. For more information, see MINIDUMP_HANDLE_DATA_STREAM.
case HandleDataStream :
break ;
// The stream contains function table information. For more information, see MINIDUMP_FUNCTION_TABLE_STREAM.
case FunctionTableStream :
break ;
// The stream contains module information for the unloaded modules. For more information, see MINIDUMP_UNLOADED_MODULE_LIST.
case UnloadedModuleListStream :
break ;
// The stream contains miscellaneous information. For more information, see MINIDUMP_MISC_INFO or MINIDUMP_MISC_INFO_2.
case MiscInfoStream :
break ;
// The stream contains memory region description information. It corresponds to the information that would be returned for the process from the
// VirtualQuery function. For more information, see MINIDUMP_MEMORY_INFO_LIST.
case MemoryInfoListStream :
break ;
// * The stream contains thread state information. For more information, see MINIDUMP_THREAD_INFO_LIST.
case ThreadInfoListStream :
break ;
// This stream contains operation list information. For more information, see MINIDUMP_HANDLE_OPERATION_LIST.
case HandleOperationListStream :
break ;
// Any value greater than this value will not be used by the system and can be used to represent application-defined data streams. For more information, see MINIDUMP_USER_STREAM.
case LastReservedStream :
default :
break ;
}
}
else
{
bFailed = true ;
}
StreamOffset + = sizeof ( MINIDUMP_DIRECTORY ) ;
}
if ( ModuleListStreamIdx ! = - 1 )
{
// We found the module list stream. Parse that...
// Prep the file pointer to the right location
SetFilePointer ( InFileHandle , ModuleListDirectory . Location . Rva , NULL , FILE_BEGIN ) ;
ULONG32 NumberOfModules = 0 ;
DWORD NumByteToRead = sizeof ( ULONG32 ) ;
DWORD NumBytesRead = 0 ;
if ( ReadFile ( InFileHandle , & NumberOfModules , NumByteToRead , & NumBytesRead , NULL ) = = TRUE )
{
//@todo. This is excess as we really only care about the executable module... clean this up.
TMap < FString , MINIDUMP_MODULE > MDModulesMap ;
TArray < MINIDUMP_MODULE > MDModules ;
MDModules . Empty ( NumberOfModules ) ;
MDModules . AddZeroed ( NumberOfModules ) ;
bool bFailed = false ;
for ( uint32 ModuleIdx = 0 ; ( ModuleIdx < NumberOfModules ) & & ! bFailed ; ModuleIdx + + )
{
NumByteToRead = sizeof ( MINIDUMP_MODULE ) ;
NumBytesRead = 0 ;
if ( ReadFile ( InFileHandle , & ( MDModules [ ModuleIdx ] ) , NumByteToRead , & NumBytesRead , NULL ) = = FALSE )
{
bFailed = true ;
}
}
if ( bFailed = = false )
{
for ( uint32 NameIdx = 0 ; ( NameIdx < ( uint32 ) MDModules . Num ( ) ) & & ! bFailed ; NameIdx + + )
{
MINIDUMP_MODULE & Module = MDModules [ NameIdx ] ;
SetFilePointer ( InFileHandle , Module . ModuleNameRva , NULL , FILE_BEGIN ) ;
WCHAR TempBuffer [ 8192 ] ;
ULONG32 NameLength ;
NumByteToRead = sizeof ( ULONG32 ) ;
NumBytesRead = 0 ;
if ( ReadFile ( InFileHandle , & NameLength , NumByteToRead , & NumBytesRead , NULL ) = = TRUE )
{
if ( ( NameLength / 2 ) < 8192 )
{
NumByteToRead = NameLength ;
NumBytesRead = 0 ;
if ( ReadFile ( InFileHandle , & TempBuffer , NumByteToRead , & NumBytesRead , NULL ) = = TRUE )
{
TempBuffer [ NameLength / 2 ] = 0 ;
FString ModuleName = TempBuffer ;
MDModulesMap . Add ( ModuleName , Module ) ;
}
else
{
bFailed = true ;
}
}
else
{
bFailed = true ;
}
}
else
{
bFailed = true ;
}
}
}
if ( bFailed = = false )
{
// Find the executable module...
for ( TMap < FString , MINIDUMP_MODULE > : : TIterator DumpIt ( MDModulesMap ) ; DumpIt ; + + DumpIt )
{
FString & ModuleName = DumpIt . Key ( ) ;
MINIDUMP_MODULE & Module = DumpIt . Value ( ) ;
//@todo. We need to be able to determine other UE4 executables like SlateViewer...
//@todo rocket Game suffix! We need another way to discover game executables
FString GameExeName = TEXT ( " Game.exe " ) ;
FString Win64GameDirName ( TEXT ( " Game \\ Binaries \\ Win64 \\ " ) ) ;
FString Win64UE4ExeName ( TEXT ( " Binaries \\ Win64 \\ UE4 " ) ) ;
FString Win32GameDirName ( TEXT ( " Game \\ Binaries \\ Win32 \\ " ) ) ;
FString Win32UE4ExeName ( TEXT ( " Binaries \\ Win32 \\ UE4 " ) ) ;
FString UE4ExeName = TEXT ( " .exe " ) ;
bool bIsOurExecutable = false ;
if ( ( ModuleName . Contains ( Win64GameDirName ) & & ModuleName . EndsWith ( GameExeName ) ) | |
( ModuleName . Contains ( Win64UE4ExeName ) & & ModuleName . EndsWith ( UE4ExeName ) ) )
{
bIsOurExecutable = true ;
OutPlatform = TEXT ( " Win64 " ) ;
}
if ( ( ModuleName . Contains ( Win32GameDirName ) & & ModuleName . EndsWith ( GameExeName ) ) | |
( ModuleName . Contains ( Win32UE4ExeName ) & & ModuleName . EndsWith ( UE4ExeName ) ) )
{
bIsOurExecutable = true ;
OutPlatform = TEXT ( " Win32 " ) ;
}
if ( bIsOurExecutable = = true )
{
int32 VerA = ( Module . VersionInfo . dwProductVersionMS & 0xFFFF0000 ) > > 16 ;
int32 VerB = ( Module . VersionInfo . dwProductVersionMS & 0x0000FFFF ) ;
int32 VerC = ( Module . VersionInfo . dwProductVersionLS & 0xFFFF0000 ) > > 16 ;
int32 VerD = ( Module . VersionInfo . dwProductVersionLS & 0x0000FFFF ) ;
if ( VerD = = 0 )
{
// Format is #.#.ENGINEVERSION.#
OutEngineVersion = VerC ;
}
else
{
// Format is #.#.HIChangeList.LOWChangeList
OutEngineVersion = ( VerC < < 16 ) | VerD ;
}
bResult = true ;
}
}
}
}
else
{
UE_LOG ( LogCrashDebugHelper , Warning , TEXT ( " ParseMiniDumpFile: Failed to read module list " ) ) ;
}
}
else
{
UE_LOG ( LogCrashDebugHelper , Warning , TEXT ( " Failed to find module list stream in crash dump... " ) ) ;
}
return bResult ;
}
bool FCrashDebugHelperWindows : : LaunchDebugger ( const FString & InCrashDumpFilename , FCrashDebugInfo & OutCrashDebugInfo )
{
// Ensure we are in a valid state to launch the debugger
return false ;
}
bool FCrashDebugHelperWindows : : ParseCrashDump_Windows ( const TCHAR * InCrashDumpFilename , FCrashDebugInfo & OutCrashDebugInfo )
{
bool bResult = false ;
HANDLE FileHandle = CreateFileW ( InCrashDumpFilename , GENERIC_READ , FILE_SHARE_READ , NULL , OPEN_EXISTING , 0 , NULL ) ;
if ( FileHandle ! = INVALID_HANDLE_VALUE )
{
// Read the header
MINIDUMP_HEADER MDHeader ;
FMemory : : Memzero ( & MDHeader , sizeof ( MINIDUMP_HEADER ) ) ;
DWORD NumByteToRead = sizeof ( MINIDUMP_HEADER ) ;
DWORD NumBytesRead ;
if ( ReadFile ( FileHandle , & MDHeader , NumByteToRead , & NumBytesRead , NULL ) = = TRUE )
{
UE_LOG ( LogCrashDebugHelper , Display , TEXT ( " ParseCrashDump_Windows: Read header from %s " ) , InCrashDumpFilename ) ;
// Verify it is a legitimate crash dump
if ( VerifyCrashDump_Windows ( MDHeader ) = = true )
{
UE_LOG ( LogCrashDebugHelper , Display , TEXT ( " ParseCrashDump_Windows: Verified windows crash dump: %s " ) , InCrashDumpFilename ) ;
// Extract the engine version
if ( ExtractEngineVersion_Windows ( FileHandle , MDHeader , OutCrashDebugInfo . EngineVersion , OutCrashDebugInfo . PlatformName ) = = true )
{
UE_LOG ( LogCrashDebugHelper , Display , TEXT ( " ParseCrashDump_Windows: Extracted engine version from crash dump: %s " ) , InCrashDumpFilename ) ;
UE_LOG ( LogCrashDebugHelper , Display , TEXT ( " \t Engine Version = %d " ) , OutCrashDebugInfo . EngineVersion ) ;
UE_LOG ( LogCrashDebugHelper , Display , TEXT ( " \t Platform = %s " ) , * ( OutCrashDebugInfo . PlatformName ) ) ;
2014-07-11 10:04:45 -04:00
// Obsolete?
//OutCrashDebugInfo.SourceControlLabel = GetLabelFromEngineVersion(OutCrashDebugInfo.EngineVersion);
2014-03-14 14:13:41 -04:00
if ( OutCrashDebugInfo . SourceControlLabel . Len ( ) > 0 )
{
UE_LOG ( LogCrashDebugHelper , Display , TEXT ( " ParseCrashDump_Windows: Retrieve changelist label: %s " ) , * ( OutCrashDebugInfo . SourceControlLabel ) ) ;
bResult = true ;
}
else
{
UE_LOG ( LogCrashDebugHelper , Warning , TEXT ( " ParseCrashDump_Windows: Failed to retrieve changelist label for engine version: %d " ) , OutCrashDebugInfo . EngineVersion ) ;
}
}
else
{
UE_LOG ( LogCrashDebugHelper , Warning , TEXT ( " ParseCrashDump_Windows: Failed to extract engine version from crash dump: %s " ) , InCrashDumpFilename ) ;
}
}
else
{
UE_LOG ( LogCrashDebugHelper , Warning , TEXT ( " ParseCrashDump_Windows: Invalid windows crash dump: %s " ) , InCrashDumpFilename ) ;
}
CloseHandle ( FileHandle ) ;
}
else
{
UE_LOG ( LogCrashDebugHelper , Warning , TEXT ( " ParseCrashDump_Windows: Failed to read header from %s " ) , InCrashDumpFilename ) ;
}
}
else
{
UE_LOG ( LogCrashDebugHelper , Warning , TEXT ( " ParseCrashDump_Windows: Failed to open crash dump file: %s " ) , InCrashDumpFilename ) ;
}
return bResult ;
}
bool FCrashDebugHelperWindows : : ParseCrashDump ( const FString & InCrashDumpName , FCrashDebugInfo & OutCrashDebugInfo )
{
if ( bInitialized = = false )
{
UE_LOG ( LogCrashDebugHelper , Warning , TEXT ( " ParseCrashDump: CrashDebugHelper not initialized " ) ) ;
return false ;
}
if ( ParseCrashDump_Windows ( * InCrashDumpName , OutCrashDebugInfo ) = = true )
{
return true ;
}
return false ;
}
bool FCrashDebugHelperWindows : : SyncAndDebugCrashDump ( const FString & InCrashDumpName )
{
if ( bInitialized = = false )
{
UE_LOG ( LogCrashDebugHelper , Warning , TEXT ( " SyncAndDebugCrashDump: CrashDebugHelper not initialized " ) ) ;
return false ;
}
FCrashDebugInfo CrashDebugInfo ;
// Parse the crash dump if it hasn't already been...
if ( ParseCrashDump ( InCrashDumpName , CrashDebugInfo ) = = false )
{
UE_LOG ( LogCrashDebugHelper , Warning , TEXT ( " SyncAndDebugCrashDump: Failed to parse crash dump %s " ) , * InCrashDumpName ) ;
return false ;
}
// Now sync the required pdb files
if ( SyncRequiredFilesForDebuggingFromLabel ( CrashDebugInfo . SourceControlLabel , CrashDebugInfo . PlatformName ) = = true )
{
// Launch the debugger
if ( LaunchDebugger ( InCrashDumpName , CrashDebugInfo ) = = true )
{
return true ;
}
else
{
UE_LOG ( LogCrashDebugHelper , Warning , TEXT ( " SyncAndDebugCrashDump: Failed to launch debugger for crash dump %s " ) , * InCrashDumpName ) ;
}
}
else
{
UE_LOG ( LogCrashDebugHelper , Warning , TEXT ( " SyncAndDebugCrashDump: Failed to sync required files for debugging crash dump %s " ) , * InCrashDumpName ) ;
}
return false ;
}
# include "HideWindowsPlatformTypes.h"