Files
UnrealEngineUWP/Engine/Source/Developer/CrashDebugHelper/Private/CrashDebugHelper.cpp
Robert Manuszewski dfc39a6ca5 Copying //UE4/Dev-Core to //UE4/Main
==========================
MAJOR FEATURES + CHANGES
==========================

Change 2783106 on 2015/11/30 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream2

	Introduced GC UObject clusters. GC clusters provide means to create disregard for GC subsets at load time (e.g. Materials with material expressions and their textures).

	- Saves about 25ms in reachability analysis (58ms -> 33ms)
	- UObject classes/instances can now be marked as cluster root objects with CanBeClusterRoot() function override.
	- Cluster creation is automatic. Clusters don't require any manual handling for GC to collect them when nothing is referencing them.
	- Moved token stream processing to a new class FFastReferenceFinder to make it more generic and re-usable by other code
	- Removed REFERENCE_INFO macro from GC code and replaced it with a local variable (saves about ~1.9ms: 33.2ms -> 31.3ms)

Change 2773094 on 2015/11/19 by Steve.Robb@Dev-Core

	Multicast script delegate check for existing bindings replaced with ensure.
	Multicast native delegate no longer checks for existing bindings.
	Removal of old delegate code.
	Some FORCEINLINEing to improve debugging experience of stepping into delegate code.

Change 2782180 on 2015/11/27 by Graeme.Thornton@GThornton_DesktopMaster

	Make scoped seconds timer class available outside of stats build. Normal usage macros still remain guarded
	Added SCOPE_SECONDS_COUNTER_RECURSION_SAFE which only times during the outmost instance of a recursive function
	Added SCOPE_SECONDS_COUNTER_RECURSION_SAFE_BASE and SCOPE_SECONDS_COUNTER_BASE which are defined in all build types, for easy temporary timing in Test/Shipping builds.
	Added a boolean parameter to the timer class which can be used to disable it without having to mess around with scoping the calling code

Change 2782635 on 2015/11/30 by Graeme.Thornton@GThornton_DesktopMaster

	Added GetTimeStampPair() to the filemanager and platformfile interfaces. Requests timestamps for a pair of files where we assume that both files would always exist at the same wrapper level. Allows us to skip file system queries for localization package lookups where the native file is in a pak but the localized file doesn't exist.

Change 2775153 on 2015/11/20 by Jaroslaw.Surowiec@Stream.1.JarekSurowiec

	CrashReportServer moved out of the not for licencees, a few fixes, removed RegisterPII

Change 2775560 on 2015/11/20 by Steve.Robb@Dev-Core

	FDelegateBase::GetDelegateInstance deprecated and replaced with FDelegateBase::GetDelegateInstanceProtected.

Change 2781138 on 2015/11/25 by Jaroslaw.Surowiec@Stream.1.JarekSurowiec

	Stats - Converted is using a new stats reader, a few more optimizations, should be 10x times faster

Change 2772990 on 2015/11/19 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream2

	Fixing potential dead lock when suspending and resuming async loading multiple times

Change 2773023 on 2015/11/19 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream2

	Support for references added through AddReferencedObjects in FArchiveReplaceObjectRef

Change 2781055 on 2015/11/25 by Steve.Robb@Dev-Core

	Changes to IDelegateInstance reverted to allow licensees an easier time when upgrading their use of the now-deprecated GetDelegateInstance() code path.
	New TryGetBoundFunctionName() to aid the debugging of delegate bindings in non-shipping configs.

Change 2773114 on 2015/11/19 by Steve.Robb@Dev-Core

	FMath::IsPowerOfTwo is now templated to take any type.

Change 2773643 on 2015/11/19 by Steve.Robb@Dev-Core

	GetDelegateInstance() calls replaced - delegate instances never compare equal unless you are comparing two unbound delegates (both null) or comparing a delegate with itself.

Change 2777686 on 2015/11/23 by Steve.Robb@Dev-Core

	GitHub #1793 - File write flags argument

Change 2780590 on 2015/11/25 by Steve.Robb@Dev-Core

	Fix for FArchiveProxy::operator<< overloads.

Change 2780845 on 2015/11/25 by Jaroslaw.Surowiec@Stream.1.JarekSurowiec

	#jira UE-23358 - MDD relies on hard-coded P4 depot paths (fixed source context for streams)

Change 2780962 on 2015/11/25 by Jaroslaw.Surowiec@Stream.1.JarekSurowiec

	Stats - Added FStatsWriteStream for basic saving stat messages into a stream, initial support for reading a regular stats file, minor performance optimization, coding standard fixes

	#jira UECORE-170 - Improve profiler loading performance (wip)

Change 2781887 on 2015/11/26 by Jaroslaw.Surowiec@Stream.1.JarekSurowiec

	Profiler/ProfilerClient - Removed unneeded synchronization points, replaces with task graph SendTo jobs, removed PROFILER_THREADED_LOAD, replaced with new stats loading mechanism,  should be around 2x times faster (4x since the optimization pass)

	#jira UECORE-170 - Improve profiler loading performance (wip)

Change 2781893 on 2015/11/26 by Steve.Robb@Dev-Core

	TCachedOSPageAllocator abstracted from MallocBinned2.
	Misc tidy-ups.

Change 2782198 on 2015/11/27 by Jaroslaw.Surowiec@Stream.1.JarekSurowiec

	Profiler - Better indication of the loading progress, should no longer freeze without a progress bar

	#jira UECORE-170 - Improve profiler loading performance (wip)

Change 2782446 on 2015/11/29 by Steve.Robb@Dev-Core

	Warn when calling delegates' Create* functions when they're not assigned to anything.

	#codereview robert.manuszewski

Change 2782538 on 2015/11/30 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	#UE4 Removed DiskCachedAssetDataBuffer as it was not strictly necessary and was triggering a crash when loading the cached registry from disk. This data is now stored directly in DiskCachedAssetDataMap. It was already true that this map does not change outside of SerializeCache but now it is critical since NewCachedAssetDataMap keeps pointers directly to its values.

	Asset registry fixes by Bob Tellez. Possible fix for UE-23783.

Change 2782564 on 2015/11/30 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	FReferenceCollector::AddReferenceObjects performance improvements for TArrays. ARO will no longer call HandleObjectReference multiple times but instead will call HandleObjectReferences just once (currently only implemented for FGCCollector). Reduces the number of virtual function calls while GC'ing.

Change 2782716 on 2015/11/30 by Steve.Robb@Dev-Core

	UObject serial number array initialized for debug visualization.

Change 2782933 on 2015/11/30 by Steve.Robb@Dev-Core

	Critical sections are no longer copyable.

Change 2783061 on 2015/11/30 by Steve.Robb@Dev-Core
2015-12-03 14:21:29 -05:00

845 lines
28 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "CrashDebugHelperPrivatePCH.h"
#include "CrashDebugPDBCache.h"
#include "EngineVersion.h"
#include "ISourceControlModule.h"
#include "ISourceControlLabel.h"
#include "ISourceControlRevision.h"
#ifndef MINIDUMPDIAGNOSTICS
#define MINIDUMPDIAGNOSTICS 0
#endif
/*-----------------------------------------------------------------------------
FCrashDebugHelperConfig
-----------------------------------------------------------------------------*/
/**
* Holds FullCrashDump properties from the config.
*
* PDBCache_0_Branch=UE4-Branch
* PDBCache_0_ExecutablePathPattern=ue4.net\Builds\UE4-Branch\%ENGINE_VERSION%
* PDBCache_0_SymbolPathPattern=ue4.net\Builds\UE4-Branch\%ENGINE_VERSION%
*
* If PDBCache_0_SymbolPathPattern is missing, the value from PDBCache_0_ExecutablePathPattern will be used
*/
struct FPDBCacheConfigEntry
{
/** Initialization constructor. */
FPDBCacheConfigEntry( const FString& InBranch, const FString& InExecutablePathPattern, const FString& InSymbolPathPattern )
: Branch( InBranch )
, ExecutablePathPattern( InExecutablePathPattern )
, SymbolPathPattern( InSymbolPathPattern )
{}
/** Branch name. */
const FString Branch;
/** Location of the executables */
const FString ExecutablePathPattern;
/** Location of the symbols, usually the same as the executables. */
const FString SymbolPathPattern;
};
/** Helper struct for reading PDB cache configuration. */
struct FCrashDebugHelperConfig
{
static FCrashDebugHelperConfig& Get()
{
static FCrashDebugHelperConfig Instance;
return Instance;
}
bool IsValid() const
{
// We need a least one entry to proceed.
return PDBCacheConfigEntries.Num() > 0;
}
/** Reads configuration. */
void ReadFullCrashDumpConfigurations();
/** Gets the config for branch. */
FPDBCacheConfigEntry GetCacheConfigEntryForBranch( const FString& Branch ) const
{
const FString BranchFixed = Branch.Replace( TEXT( "/" ), TEXT( "+" ) );
for (const auto& It : PDBCacheConfigEntries)
{
if (BranchFixed.Contains( It.Branch ))
{
return It;
}
}
// Invalid entry.
return FPDBCacheConfigEntry( Branch, FString(), FString() );
}
protected:
/** Returns empty string if couldn't read. */
FString GetKey( const FString& KeyName );
/** Configuration for PDB Cache. */
TArray<FPDBCacheConfigEntry> PDBCacheConfigEntries;
};
void FCrashDebugHelperConfig::ReadFullCrashDumpConfigurations()
{
for (int32 NumEntries = 0;; ++NumEntries)
{
const FString Branch = GetKey( FString::Printf( TEXT( "PDBCache_%i_Branch" ), NumEntries ) );
if (Branch.IsEmpty())
{
break;
}
const FString ExecutablePathPattern = GetKey( FString::Printf( TEXT( "PDBCache_%i_ExecutablePathPattern" ), NumEntries ) );
if (ExecutablePathPattern.IsEmpty())
{
break;
}
FString SymbolPathPattern = GetKey( FString::Printf( TEXT( "PDBCache_%i_SymbolPathPattern" ), NumEntries ) );
if (SymbolPathPattern.IsEmpty())
{
SymbolPathPattern = ExecutablePathPattern;
}
PDBCacheConfigEntries.Add( FPDBCacheConfigEntry( Branch, ExecutablePathPattern, SymbolPathPattern ) );
UE_LOG( LogCrashDebugHelper, Log, TEXT( "PDBCacheConfigEntry: Branch:%s ExecutablePathPattern:%s SymbolPathPattern:%s" ), *Branch, *ExecutablePathPattern, *SymbolPathPattern );
}
}
FString FCrashDebugHelperConfig::GetKey( const FString& KeyName )
{
const FString SectionName = TEXT( "Engine.CrashDebugHelper" );
FString Result;
if (!GConfig->GetString( *SectionName, *KeyName, Result, GEngineIni ))
{
return TEXT( "" );
}
return Result;
}
/*-----------------------------------------------------------------------------
ICrashDebugHelper
-----------------------------------------------------------------------------*/
void ICrashDebugHelper::SetDepotIndex( FString& PathToChange )
{
FString CmdDepotIndex;
FParse::Value( FCommandLine::Get(), TEXT( "DepotIndex=" ), CmdDepotIndex );
// %DEPOT_INDEX% - Index of the depot, when multiple processor are used.
PathToChange.ReplaceInline( TEXT( "%DEPOT_INDEX%" ), *CmdDepotIndex );
}
bool ICrashDebugHelper::Init()
{
bInitialized = true;
// Check if we have a valid EngineVersion, if so use it.
FString CmdEngineVersion;
const bool bHasEngineVersion = FParse::Value( FCommandLine::Get(), TEXT( "EngineVersion=" ), CmdEngineVersion );
if( bHasEngineVersion )
{
FEngineVersion EngineVersion;
FEngineVersion::Parse( CmdEngineVersion, EngineVersion );
// Clean branch name.
CrashInfo.DepotName = EngineVersion.GetBranch();
CrashInfo.BuiltFromCL = (int32)EngineVersion.GetChangelist();
CrashInfo.EngineVersion = CmdEngineVersion;
}
else
{
// Obsolete.
}
UE_LOG( LogCrashDebugHelper, Log, TEXT( "DepotName: %s" ), *CrashInfo.DepotName );
UE_LOG( LogCrashDebugHelper, Log, TEXT( "BuiltFromCL: %i" ), CrashInfo.BuiltFromCL );
UE_LOG( LogCrashDebugHelper, Log, TEXT( "EngineVersion: %s" ), *CrashInfo.EngineVersion );
GConfig->GetString( TEXT( "Engine.CrashDebugHelper" ), TEXT( "SourceControlBuildLabelPattern" ), SourceControlBuildLabelPattern, GEngineIni );
FCrashDebugHelperConfig::Get().ReadFullCrashDumpConfigurations();
const bool bUsePDBCache = FCrashDebugHelperConfig::Get().IsValid();
UE_CLOG( !bUsePDBCache, LogCrashDebugHelper, Warning, TEXT( "CrashDebugHelperConfig invalid" ) );
if (bUsePDBCache)
{
FPDBCache::Get().Init();
}
else
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "PDB Cache disabled" ) );
}
return bInitialized;
}
/**
* Initialise the source control interface, and ensure we have a valid connection
*/
bool ICrashDebugHelper::InitSourceControl(bool bShowLogin)
{
// Ensure we are in a valid state to sync
if (bInitialized == false)
{
UE_LOG(LogCrashDebugHelper, Warning, TEXT("InitSourceControl: CrashDebugHelper is not initialized properly."));
return false;
}
// Initialize the source control if it hasn't already been
if( !ISourceControlModule::Get().IsEnabled() || !ISourceControlModule::Get().GetProvider().IsAvailable() )
{
// make sure our provider is set to Perforce
ISourceControlModule::Get().SetProvider("Perforce");
// Attempt to load in a source control module
ISourceControlModule::Get().GetProvider().Init();
#if !MINIDUMPDIAGNOSTICS
if ((ISourceControlModule::Get().GetProvider().IsAvailable() == false) || bShowLogin)
{
// Unable to connect? Prompt the user for login information
ISourceControlModule::Get().ShowLoginDialog(FSourceControlLoginClosed(), ELoginWindowMode::Modeless, EOnLoginWindowStartup::PreserveProvider);
}
#endif
// If it's still disabled, none was found, so exit
if( !ISourceControlModule::Get().IsEnabled() || !ISourceControlModule::Get().GetProvider().IsAvailable() )
{
UE_LOG(LogCrashDebugHelper, Warning, TEXT("InitSourceControl: Source control unavailable or disabled."));
return false;
}
}
return true;
}
/**
* Shutdown the connection to source control
*/
void ICrashDebugHelper::ShutdownSourceControl()
{
ISourceControlModule::Get().GetProvider().Close();
}
bool ICrashDebugHelper::SyncModules()
{
// Check source control
if( !ISourceControlModule::Get().IsEnabled() )
{
return false;
}
if( !FPDBCache::Get().UsePDBCache() )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "The PDB Cache is disabled, cannot proceed, %s" ), *CrashInfo.EngineVersion );
return false;
}
// @TODO yrx 2015-02-23 Obsolete, remove after 4.8
const TCHAR* UESymbols = TEXT( "Rocket/Symbols/" );
const bool bHasExecutable = !CrashInfo.ExecutablesPath.IsEmpty();
const bool bHasSymbols = !CrashInfo.SymbolsPath.IsEmpty();
TArray< TSharedRef<ISourceControlLabel> > Labels = ISourceControlModule::Get().GetProvider().GetLabels( CrashInfo.LabelName );
const bool bContainsProductVersion = FPDBCache::Get().ContainsPDBCacheEntry( CrashInfo.EngineVersion );
if( bHasExecutable && bHasSymbols )
{
if( bContainsProductVersion )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "Using cached storage: %s" ), *CrashInfo.EngineVersion );
CrashInfo.PDBCacheEntry = FPDBCache::Get().FindAndTouchPDBCacheEntry( CrashInfo.EngineVersion );
}
else
{
SCOPE_LOG_TIME_IN_SECONDS( TEXT( "SyncExecutableAndSymbolsFromNetwork" ), nullptr );
// Find all executables.
TArray<FString> NetworkExecutables;
// Don't duplicate work.
if (CrashInfo.ExecutablesPath != CrashInfo.SymbolsPath)
{
IFileManager::Get().FindFilesRecursive( NetworkExecutables, *CrashInfo.ExecutablesPath, TEXT( "*.dll" ), true, false, false );
IFileManager::Get().FindFilesRecursive( NetworkExecutables, *CrashInfo.ExecutablesPath, TEXT( "*.exe" ), true, false, false );
}
// Find all symbols.
TArray<FString> NetworkSymbols;
IFileManager::Get().FindFilesRecursive( NetworkSymbols, *CrashInfo.SymbolsPath, TEXT( "*.pdb" ), true, false, false );
IFileManager::Get().FindFilesRecursive( NetworkSymbols, *CrashInfo.SymbolsPath, TEXT( "*.dll" ), true, false, false );
IFileManager::Get().FindFilesRecursive( NetworkSymbols, *CrashInfo.SymbolsPath, TEXT( "*.exe" ), true, false, false );
// From=Full pathname
// To=Relative pathname
TMap<FString, FString> FilesToBeCached;
for( const auto& ExecutablePath : NetworkExecutables )
{
const FString NetworkRelativePath = ExecutablePath.Replace( *CrashInfo.ExecutablesPath, TEXT( "" ) );
FilesToBeCached.Add( ExecutablePath, NetworkRelativePath );
}
for( const auto& SymbolPath : NetworkSymbols )
{
const FString SymbolRelativePath = SymbolPath.Replace( *CrashInfo.SymbolsPath, TEXT( "" ) );
FilesToBeCached.Add( SymbolPath, SymbolRelativePath );
}
// Initialize and add a new PDB Cache entry to the database.
CrashInfo.PDBCacheEntry = FPDBCache::Get().CreateAndAddPDBCacheEntryMixed( CrashInfo.EngineVersion, FilesToBeCached );
}
}
// Get all labels associated with the crash info's label.
// OBSOLETE PATH
else if( Labels.Num() >= 1 )
{
TSharedRef<ISourceControlLabel> Label = Labels[0];
TSet<FString> FilesToSync;
// Use product version instead of label name to make a distinguish between chosen methods.
const bool bContainsLabelName = FPDBCache::Get().ContainsPDBCacheEntry( CrashInfo.LabelName );
if( bContainsProductVersion )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "Using cached storage: %s" ), *CrashInfo.EngineVersion );
CrashInfo.PDBCacheEntry = FPDBCache::Get().FindAndTouchPDBCacheEntry( CrashInfo.EngineVersion );
}
else if( bContainsLabelName )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "Using cached storage: %s" ), *CrashInfo.LabelName );
CrashInfo.PDBCacheEntry = FPDBCache::Get().FindAndTouchPDBCacheEntry( CrashInfo.LabelName );
}
else if( bHasExecutable )
{
SCOPE_LOG_TIME_IN_SECONDS( TEXT( "SyncModulesAndNetwork" ), nullptr );
// Grab information about symbols.
TArray< TSharedRef<class ISourceControlRevision, ESPMode::ThreadSafe> > PDBSourceControlRevisions;
const FString PDBsPath = FString::Printf( TEXT( "%s/%s....pdb" ), *CrashInfo.DepotName, UESymbols );
Label->GetFileRevisions( PDBsPath, PDBSourceControlRevisions );
TSet<FString> PDBPaths;
for( const auto& PDBSrc : PDBSourceControlRevisions )
{
PDBPaths.Add( PDBSrc->GetFilename() );
}
// Now, sync symbols.
for( const auto& PDBPath : PDBPaths )
{
if( Label->Sync( PDBPath ) )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "Synced PDB: %s" ), *PDBPath );
}
}
// Find all the executables in the product network path.
TArray<FString> NetworkExecutables;
IFileManager::Get().FindFilesRecursive( NetworkExecutables, *CrashInfo.ExecutablesPath, TEXT( "*.dll" ), true, false, false );
IFileManager::Get().FindFilesRecursive( NetworkExecutables, *CrashInfo.ExecutablesPath, TEXT( "*.exe" ), true, false, false );
// From=Full pathname
// To=Relative pathname
TMap<FString, FString> FilesToBeCached;
// If a symbol matches an executable, add the pair to the list of files that should be cached.
for( const auto& NetworkExecutableFullpath : NetworkExecutables )
{
for( const auto& PDBPath : PDBPaths )
{
const FString PDBRelativePath = PDBPath.Replace( *CrashInfo.DepotName, TEXT( "" ) ).Replace( UESymbols, TEXT( "" ) );
const FString PDBFullpath = FPDBCache::Get().GetDepotRoot() / PDBPath;
const FString PDBMatch = PDBRelativePath.Replace( TEXT( "pdb" ), TEXT( "" ) );
const FString NetworkRelativePath = NetworkExecutableFullpath.Replace( *CrashInfo.ExecutablesPath, TEXT( "" ) );
const bool bMatch = NetworkExecutableFullpath.Contains( PDBMatch );
if( bMatch )
{
// From -> Where
FilesToBeCached.Add( NetworkExecutableFullpath, NetworkRelativePath );
FilesToBeCached.Add( PDBFullpath, PDBRelativePath );
break;
}
}
}
// Initialize and add a new PDB Cache entry to the database.
CrashInfo.PDBCacheEntry = FPDBCache::Get().CreateAndAddPDBCacheEntryMixed( CrashInfo.EngineVersion, FilesToBeCached );
}
else
{
TArray<FString> FilesToBeCached;
//@TODO: MAC: Excluding labels for Mac since we are only syncing windows binaries here...
if( Label->GetName().Contains( TEXT( "Mac" ) ) )
{
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Skipping Mac label: %s" ), *Label->GetName() );
}
else
{
// Sync all the dll, exes, and related symbol files
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Syncing modules with label: %s" ), *Label->GetName() );
SCOPE_LOG_TIME_IN_SECONDS( TEXT( "SyncModules" ), nullptr );
// Grab all dll and pdb files for the specified label.
TArray< TSharedRef<class ISourceControlRevision, ESPMode::ThreadSafe> > DLLSourceControlRevisions;
const FString DLLsPath = FString::Printf( TEXT( "%s/....dll" ), *CrashInfo.DepotName );
Label->GetFileRevisions( DLLsPath, DLLSourceControlRevisions );
TArray< TSharedRef<class ISourceControlRevision, ESPMode::ThreadSafe> > EXESourceControlRevisions;
const FString EXEsPath = FString::Printf( TEXT( "%s/....exe" ), *CrashInfo.DepotName );
Label->GetFileRevisions( EXEsPath, EXESourceControlRevisions );
TArray< TSharedRef<class ISourceControlRevision, ESPMode::ThreadSafe> > PDBSourceControlRevisions;
const FString PDBsPath = FString::Printf( TEXT( "%s/....pdb" ), *CrashInfo.DepotName );
Label->GetFileRevisions( PDBsPath, PDBSourceControlRevisions );
TSet<FString> ModulesPaths;
for( const auto& DLLSrc : DLLSourceControlRevisions )
{
ModulesPaths.Add( DLLSrc->GetFilename().Replace( *CrashInfo.DepotName, TEXT( "" ) ) );
}
for( const auto& EXESrc : EXESourceControlRevisions )
{
ModulesPaths.Add( EXESrc->GetFilename().Replace( *CrashInfo.DepotName, TEXT( "" ) ) );
}
TSet<FString> PDBPaths;
for( const auto& PDBSrc : PDBSourceControlRevisions )
{
PDBPaths.Add( PDBSrc->GetFilename().Replace( *CrashInfo.DepotName, TEXT( "" ) ) );
}
// Iterate through all module and see if we have dll and pdb associated with the module, if so add it to the files to sync.
for( const auto& ModuleName : CrashInfo.ModuleNames )
{
const FString ModuleNamePDB = ModuleName.Replace( TEXT( ".dll" ), TEXT( ".pdb" ) ).Replace( TEXT( ".exe" ), TEXT( ".pdb" ) );
for( const auto& ModulePath : ModulesPaths )
{
const bool bContainsModule = ModulePath.Contains( ModuleName );
if( bContainsModule )
{
FilesToSync.Add( ModulePath );
}
}
for( const auto& PDBPath : PDBPaths )
{
const bool bContainsPDB = PDBPath.Contains( ModuleNamePDB );
if( bContainsPDB )
{
FilesToSync.Add( PDBPath );
}
}
}
// Now, sync all files.
for( const auto& Filename : FilesToSync )
{
const FString DepotPath = CrashInfo.DepotName + Filename;
if( Label->Sync( DepotPath ) )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "Synced binary: %s" ), *DepotPath );
}
FilesToBeCached.Add( DepotPath );
}
}
// Initialize and add a new PDB Cache entry to the database.
CrashInfo.PDBCacheEntry = FPDBCache::Get().CreateAndAddPDBCacheEntry( CrashInfo.LabelName, CrashInfo.DepotName, FilesToBeCached );
}
}
// OBSOLETE PATH
else
{
UE_LOG( LogCrashDebugHelper, Error, TEXT( "Could not find label: %s"), *CrashInfo.LabelName );
return false;
}
return true;
}
bool ICrashDebugHelper::SyncSourceFile()
{
// Check source control
if( !ISourceControlModule::Get().IsEnabled() )
{
return false;
}
// Sync a single source file to requested CL.
FString DepotPath = CrashInfo.DepotName / CrashInfo.SourceFile + TEXT( "@" ) + TTypeToString<int32>::ToString( CrashInfo.BuiltFromCL );
ISourceControlModule::Get().GetProvider().Execute(ISourceControlOperation::Create<FSync>(), DepotPath);
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "Syncing a single source file: %s"), *DepotPath );
return true;
}
bool ICrashDebugHelper::ReadSourceFile( TArray<FString>& OutStrings )
{
const bool bUsePDBCache = FPDBCache::Get().UsePDBCache();
FString FilePath;
if (bUsePDBCache)
{
// We assume a special folder for syncing all streams and //depot/ is not used in the view mapping.
/*
//depot/... //machine/...
//UE4/... //machine/​​Stream/UE4/...​
*/
const TCHAR* DepotDelimiter = TEXT( "//depot/" );
const bool bIsDepot = CrashInfo.DepotName.Contains( DepotDelimiter );
if (bIsDepot)
{
FilePath = FPDBCache::Get().GetDepotRoot() / CrashInfo.DepotName.Replace( DepotDelimiter, TEXT( "" ) ) / CrashInfo.SourceFile;
}
else
{
FilePath = FPDBCache::Get().GetDepotRoot() / TEXT( "Stream" ) / CrashInfo.DepotName / CrashInfo.SourceFile;
FilePath.ReplaceInline( TEXT( "//" ), TEXT( "/" ) );
}
}
else
{
FilePath = FPaths::RootDir() / CrashInfo.SourceFile;
}
FString Line;
if (FFileHelper::LoadFileToString( Line, *FilePath ))
{
Line = Line.Replace( TEXT( "\r" ), TEXT( "" ) );
Line.ParseIntoArray( OutStrings, TEXT( "\n" ), false );
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Reading a single source file: %s" ), *FilePath );
return true;
}
else
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "Failed to open source file: %s" ), *FilePath );
return false;
}
}
void ICrashDebugHelper::AddSourceToReport()
{
if( CrashInfo.SourceFile.Len() > 0 && CrashInfo.SourceLineNumber != 0 )
{
TArray<FString> Lines;
ReadSourceFile( Lines );
const uint32 MinLine = FMath::Clamp( CrashInfo.SourceLineNumber - 15, (uint32)1, (uint32)Lines.Num() );
const uint32 MaxLine = FMath::Clamp( CrashInfo.SourceLineNumber + 15, (uint32)1, (uint32)Lines.Num() );
for( uint32 Line = MinLine; Line < MaxLine; Line++ )
{
if( Line == CrashInfo.SourceLineNumber - 1 )
{
CrashInfo.SourceContext.Add( FString::Printf( TEXT( "%5u ***** %s" ), Line, *Lines[Line] ) );
}
else
{
CrashInfo.SourceContext.Add( FString::Printf( TEXT( "%5u %s" ), Line, *Lines[Line] ) );
}
}
}
}
bool ICrashDebugHelper::AddAnnotatedSourceToReport()
{
// Make sure we have a source file to interrogate
if( CrashInfo.SourceFile.Len() > 0 && CrashInfo.SourceLineNumber != 0 && !CrashInfo.LabelName.IsEmpty() )
{
// Check source control
if( !ISourceControlModule::Get().IsEnabled() )
{
return false;
}
// Ask source control to annotate the file for us
FString DepotPath = CrashInfo.DepotName / CrashInfo.SourceFile;
TArray<FAnnotationLine> Lines;
SourceControlHelpers::AnnotateFile( ISourceControlModule::Get().GetProvider(), CrashInfo.BuiltFromCL, DepotPath, Lines );
uint32 MinLine = FMath::Clamp( CrashInfo.SourceLineNumber - 15, (uint32)1, (uint32)Lines.Num() );
uint32 MaxLine = FMath::Clamp( CrashInfo.SourceLineNumber + 15, (uint32)1, (uint32)Lines.Num() );
// Display a source context in the report, and decorate each line with the last editor of the line
for( uint32 Line = MinLine; Line < MaxLine; Line++ )
{
if( Line == CrashInfo.SourceLineNumber )
{
CrashInfo.SourceContext.Add( FString::Printf( TEXT( "%5u ***** %20s: %s" ), Line, *Lines[Line].UserName, *Lines[Line].Line ) );
}
else
{
CrashInfo.SourceContext.Add( FString::Printf( TEXT( "%5u %20s: %s" ), Line, *Lines[Line].UserName, *Lines[Line].Line ) );
}
}
return true;
}
return false;
}
void FCrashInfo::Log( FString Line )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT("%s"), *Line );
Report += Line + LINE_TERMINATOR;
}
const TCHAR* FCrashInfo::GetProcessorArchitecture( EProcessorArchitecture PA )
{
switch( PA )
{
case PA_X86:
return TEXT( "x86" );
case PA_X64:
return TEXT( "x64" );
case PA_ARM:
return TEXT( "ARM" );
}
return TEXT( "Unknown" );
}
int64 FCrashInfo::StringSize( const ANSICHAR* Line )
{
int64 Size = 0;
if( Line != nullptr )
{
while( *Line++ != 0 )
{
Size++;
}
}
return Size;
}
void FCrashInfo::WriteLine( FArchive* ReportFile, const ANSICHAR* Line )
{
if( Line != NULL )
{
int64 StringBytes = StringSize( Line );
ReportFile->Serialize( ( void* )Line, StringBytes );
}
ReportFile->Serialize( TCHAR_TO_UTF8( LINE_TERMINATOR ), FCStringWide::Strlen(LINE_TERMINATOR) );
}
void FCrashInfo::GenerateReport( const FString& DiagnosticsPath )
{
FArchive* ReportFile = IFileManager::Get().CreateFileWriter( *DiagnosticsPath );
if( ReportFile != NULL )
{
FString Line;
WriteLine( ReportFile, TCHAR_TO_UTF8( TEXT( "Generating report for minidump" ) ) );
WriteLine( ReportFile );
if ( EngineVersion.Len() > 0 )
{
Line = FString::Printf( TEXT( "Application version %s" ), *EngineVersion );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
}
else if( Modules.Num() > 0 )
{
Line = FString::Printf( TEXT( "Application version %d.%d.%d" ), Modules[0].Major, Modules[0].Minor, Modules[0].Patch );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
}
Line = FString::Printf( TEXT( " ... built from changelist %d" ), BuiltFromCL );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
if( LabelName.Len() > 0 )
{
Line = FString::Printf( TEXT( " ... based on label %s" ), *LabelName );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
}
WriteLine( ReportFile );
Line = FString::Printf( TEXT( "OS version %d.%d.%d.%d" ), SystemInfo.OSMajor, SystemInfo.OSMinor, SystemInfo.OSBuild, SystemInfo.OSRevision );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
Line = FString::Printf( TEXT( "Running %d %s processors" ), SystemInfo.ProcessorCount, GetProcessorArchitecture( SystemInfo.ProcessorArchitecture ) );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
Line = FString::Printf( TEXT( "Exception was \"%s\"" ), *Exception.ExceptionString );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
WriteLine( ReportFile );
Line = FString::Printf( TEXT( "Source context from \"%s\"" ), *SourceFile );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
WriteLine( ReportFile );
Line = FString::Printf( TEXT( "<SOURCE START>" ) );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
for( int32 LineIndex = 0; LineIndex < SourceContext.Num(); LineIndex++ )
{
Line = FString::Printf( TEXT( "%s" ), *SourceContext[LineIndex] );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
}
Line = FString::Printf( TEXT( "<SOURCE END>" ) );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
WriteLine( ReportFile );
Line = FString::Printf( TEXT( "<CALLSTACK START>" ) );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
for( int32 StackIndex = 0; StackIndex < Exception.CallStackString.Num(); StackIndex++ )
{
Line = FString::Printf( TEXT( "%s" ), *Exception.CallStackString[StackIndex] );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
}
Line = FString::Printf( TEXT( "<CALLSTACK END>" ) );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
WriteLine( ReportFile );
Line = FString::Printf( TEXT( "%d loaded modules" ), Modules.Num() );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
for( int32 ModuleIndex = 0; ModuleIndex < Modules.Num(); ModuleIndex++ )
{
FCrashModuleInfo& Module = Modules[ModuleIndex];
FString ModuleDirectory = FPaths::GetPath(Module.Name);
FString ModuleName = FPaths::GetBaseFilename( Module.Name, true ) + FPaths::GetExtension( Module.Name, true );
FString ModuleDetail = FString::Printf( TEXT( "%40s" ), *ModuleName );
FString Version = FString::Printf( TEXT( " (%d.%d.%d.%d)" ), Module.Major, Module.Minor, Module.Patch, Module.Revision );
ModuleDetail += FString::Printf( TEXT( " %22s" ), *Version );
ModuleDetail += FString::Printf( TEXT( " 0x%016x 0x%08x" ), Module.BaseOfImage, Module.SizeOfImage );
ModuleDetail += FString::Printf( TEXT( " %s" ), *ModuleDirectory );
WriteLine( ReportFile, TCHAR_TO_UTF8( *ModuleDetail ) );
}
WriteLine( ReportFile );
// Write out the processor debugging log
WriteLine( ReportFile, TCHAR_TO_UTF8( *Report ) );
Line = FString::Printf( TEXT( "Report end!" ) );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
ReportFile->Close();
delete ReportFile;
}
}
void ICrashDebugHelper::FindSymbolsAndBinariesStorage()
{
CrashInfo.ExecutablesPath.Empty();
CrashInfo.SymbolsPath.Empty();
CrashInfo.LabelName.Empty();
if( CrashInfo.BuiltFromCL == FCrashInfo::INVALID_CHANGELIST )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "Invalid parameters" ) );
return;
}
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Engine version: %s" ), *CrashInfo.EngineVersion );
const FPDBCacheConfigEntry ConfigEntry = FCrashDebugHelperConfig::Get().GetCacheConfigEntryForBranch( CrashInfo.DepotName );
FString ExecutablePathPattern = ConfigEntry.ExecutablePathPattern;
FString SymbolPathPattern = ConfigEntry.ExecutablePathPattern;
if (!ExecutablePathPattern.IsEmpty() && !SymbolPathPattern.IsEmpty())
{
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Using branch: %s" ), *CrashInfo.DepotName );
}
else
{
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Branch not found: %s" ), *CrashInfo.DepotName );
return;
}
const FString StrENGINE_VERSION = CrashInfo.EngineVersion;
const FString StrPLATFORM_NAME = TEXT( "" ); // Not implemented yet
const FString StrOLD_ENGINE_VERSION = FString::Printf( TEXT( "%s-CL-%i" ), *CrashInfo.DepotName.Replace( TEXT( "+" ), TEXT( "/" ) ), CrashInfo.BuiltFromCL )
.Replace( TEXT("/"), TEXT("+") );
const FString TestExecutablesPath = ExecutablePathPattern
.Replace( TEXT( "%ENGINE_VERSION%" ), *StrENGINE_VERSION )
.Replace( TEXT( "%PLATFORM_NAME%" ), *StrPLATFORM_NAME )
.Replace( TEXT( "%OLD_ENGINE_VERSION%" ), *StrOLD_ENGINE_VERSION );
const FString TestSymbolsPath = SymbolPathPattern
.Replace( TEXT( "%ENGINE_VERSION%" ), *StrENGINE_VERSION )
.Replace( TEXT( "%PLATFORM_NAME%" ), *StrPLATFORM_NAME )
.Replace( TEXT( "%OLD_ENGINE_VERSION%" ), *StrOLD_ENGINE_VERSION );
// Try to find the network path by using the pattern supplied via ini.
// If this step successes, we will grab the executable from the network path instead of P4.
bool bFoundDirectory = false;
const bool bHasExecutables = IFileManager::Get().DirectoryExists( *TestExecutablesPath );
const bool bHasSymbols = IFileManager::Get().DirectoryExists( *TestSymbolsPath );
if( bHasExecutables && bHasSymbols )
{
CrashInfo.ExecutablesPath = TestExecutablesPath;
CrashInfo.SymbolsPath = TestSymbolsPath;
bFoundDirectory = true;
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Using path for executables and symbols: %s" ), *CrashInfo.ExecutablesPath );
}
else if( bHasExecutables )
{
CrashInfo.ExecutablesPath = TestExecutablesPath;
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Using path for executables: %s" ), *CrashInfo.ExecutablesPath );
}
else
{
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Path for executables not found: %s" ), *TestExecutablesPath );
}
// Try to find the label directly in source control by using the pattern supplied via ini.
if( !bFoundDirectory && !SourceControlBuildLabelPattern.IsEmpty() )
{
const FString ChangelistString = FString::Printf( TEXT( "%d" ), CrashInfo.BuiltFromCL );
const FString LabelWithCL = SourceControlBuildLabelPattern.Replace( TEXT( "%CHANGELISTNUMBER%" ), *ChangelistString, ESearchCase::CaseSensitive );
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Label matching pattern: %s" ), *LabelWithCL );
TArray< TSharedRef<ISourceControlLabel> > Labels = ISourceControlModule::Get().GetProvider().GetLabels( LabelWithCL );
if( Labels.Num() > 0 )
{
const int32 LabelIndex = 0;
CrashInfo.LabelName = Labels[LabelIndex]->GetName();;
// If we found more than one label, warn about it and just use the first one
if( Labels.Num() > 1 )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "More than one build label found, using label: %s" ), *LabelWithCL, *CrashInfo.LabelName );
}
else
{
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Using label: %s" ), *CrashInfo.LabelName );
}
}
}
}