You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
426 lines
13 KiB
C++
426 lines
13 KiB
C++
|
|
// 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("\tEngine Version = %d"), OutCrashDebugInfo.EngineVersion);
|
||
|
|
UE_LOG(LogCrashDebugHelper, Display, TEXT("\tPlatform = %s"), *(OutCrashDebugInfo.PlatformName));
|
||
|
|
|
||
|
|
OutCrashDebugInfo.SourceControlLabel = GetLabelFromEngineVersion(OutCrashDebugInfo.EngineVersion);
|
||
|
|
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"
|