1375 lines
43 KiB
C++
1375 lines
43 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SymslibResolver.h"
|
|
|
|
#if UE_SYMSLIB_AVAILABLE
|
|
|
|
#include "Algo/ForEach.h"
|
|
#include "Algo/Sort.h"
|
|
#include "Async/MappedFileHandle.h"
|
|
#include "Async/ParallelFor.h"
|
|
#include "Containers/StringView.h"
|
|
#include "GenericPlatform/GenericPlatformFile.h"
|
|
#include "HAL/PlatformFileManager.h"
|
|
#include "HAL/PlatformProcess.h"
|
|
#include "HAL/PlatformTLS.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/ConfigContext.h"
|
|
#include "Misc/CString.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/PathViews.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "Misc/ScopeRWLock.h"
|
|
#include "Misc/StringBuilder.h"
|
|
#include "TraceServices/Model/AnalysisSession.h"
|
|
#include "TraceServices/Model/Diagnostics.h"
|
|
#include <atomic>
|
|
|
|
#if PLATFORM_WINDOWS
|
|
#define USE_DBG_HELP_UNDECORATOR 1
|
|
#if USE_DBG_HELP_UNDECORATOR
|
|
#include <Microsoft/AllowMicrosoftPlatformTypes.h>
|
|
#include <DbgHelp.h>
|
|
#include <Microsoft/HideMicrosoftPlatformTypes.h>
|
|
#endif
|
|
#else
|
|
#define USE_DBG_HELP_UNDECORATOR 0
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogSymslib, Log, All);
|
|
|
|
namespace TraceServices {
|
|
|
|
static const TCHAR* GUnknownModuleTextSymsLib = TEXT("Unknown");
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace
|
|
{
|
|
struct FAutoMappedFile
|
|
{
|
|
FString FilePath;
|
|
|
|
TUniquePtr<IMappedFileHandle> Handle;
|
|
TUniquePtr<IMappedFileRegion> Region;
|
|
|
|
bool Load(const TCHAR* FileName)
|
|
{
|
|
FOpenMappedResult Result = FPlatformFileManager::Get().GetPlatformFile().OpenMappedEx(FileName);
|
|
if (Result.HasValue())
|
|
{
|
|
Handle = Result.StealValue();
|
|
Region.Reset(Handle->MapRegion(0, Handle->GetFileSize()));
|
|
}
|
|
else
|
|
{
|
|
Handle.Reset();
|
|
Region.Reset();
|
|
}
|
|
return Region.IsValid();
|
|
}
|
|
|
|
SYMS_String8 GetData() const
|
|
{
|
|
return Region.IsValid()
|
|
? syms_str8((SYMS_U8*)Region->GetMappedPtr(), Region->GetMappedSize())
|
|
: syms_str8(nullptr, 0);
|
|
}
|
|
};
|
|
|
|
static FString FindBinaryFileInPath(const FString& File, const FString& SearchPath, const FString& Platform)
|
|
{
|
|
IPlatformFile* PlatformFile = &FPlatformFileManager::Get().GetPlatformFile();
|
|
|
|
// On Linux and Mac find the non-stripped binary if available
|
|
// todo: We cannot actually rely on the Platform string being set yet, we can enable that check when that's part of trace files metadata.
|
|
// for now we always override the binary if a .debug file exists
|
|
//if (Platform.Equals(TEXT("Linux")) || Platform.Equals(TEXT("Mac")))
|
|
{
|
|
FString NonStrippedFile = FPaths::SetExtension(File, TEXT("debug"));
|
|
FString Result = FPaths::Combine(SearchPath, NonStrippedFile);
|
|
if (PlatformFile->FileExists(*Result))
|
|
{
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
FString FileName = FPaths::GetCleanFilename(File);
|
|
|
|
// If the search path is an absolute path to a file use this. Filenames does
|
|
// not need to match (e.g. eboot.bin <-> gamename.self);
|
|
// but except the *.pdb files (in which case, we need to load the exe or dll binary instead).
|
|
if (!SearchPath.EndsWith(TEXT(".pdb")))
|
|
{
|
|
if (PlatformFile->FileExists(*SearchPath))
|
|
{
|
|
return SearchPath;
|
|
}
|
|
}
|
|
|
|
// Look for exact filename of the module in the provided search path.
|
|
{
|
|
FString Result = FPaths::Combine(SearchPath, FileName);
|
|
if (PlatformFile->FileExists(*Result))
|
|
{
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
// Look for exact filename of the module in the provided search path
|
|
// (case where the search path includes a filename).
|
|
{
|
|
FString Result = FPaths::Combine(FPaths::GetPath(SearchPath), FileName);
|
|
if (PlatformFile->FileExists(*Result))
|
|
{
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
// In case File is relative
|
|
if (FPaths::IsRelative(File))
|
|
{
|
|
FString Result = FPaths::Combine(SearchPath, File);
|
|
if (PlatformFile->FileExists(*Result))
|
|
{
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
// Finally try the file path itself
|
|
if (PlatformFile->FileExists(*File))
|
|
{
|
|
return File;
|
|
}
|
|
|
|
// File not found
|
|
return FString();
|
|
}
|
|
|
|
static FString FindSymbolFileInPath(const FString& File, const FString& SearchPath)
|
|
{
|
|
IPlatformFile* PlatformFile = &FPlatformFileManager::Get().GetPlatformFile();
|
|
FString SearchPathBase;
|
|
|
|
// If search path is an absolute path to a file
|
|
if (PlatformFile->FileExists(*SearchPath))
|
|
{
|
|
// For symbol files, the filename do need to match.
|
|
if (FPaths::GetCleanFilename(SearchPath) == FPaths::GetCleanFilename(File))
|
|
{
|
|
return SearchPath;
|
|
}
|
|
|
|
// Strip the filename from the search path
|
|
SearchPathBase = FPaths::GetPath(SearchPath);
|
|
}
|
|
else
|
|
{
|
|
SearchPathBase = SearchPath;
|
|
}
|
|
|
|
// Extract only filename part in case Path is absolute path
|
|
FString FileName = FPaths::GetCleanFilename(File);
|
|
|
|
// Look in the search path
|
|
FString Result = FPaths::Combine(SearchPathBase, FileName);
|
|
if (PlatformFile->FileExists(*Result))
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
// In case File is relative
|
|
Result = FPaths::Combine(SearchPathBase, File);
|
|
if (PlatformFile->FileExists(*Result))
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
// File not found
|
|
return FString();
|
|
}
|
|
|
|
// Find a file assuming that search path is a root engine folder.
|
|
static FString FindFileInEngineFolder(const FString& FilePath, const FString& SearchPath, const FString& Platform, const FString& Project)
|
|
{
|
|
IPlatformFile* PlatformFile = &FPlatformFileManager::Get().GetPlatformFile();
|
|
|
|
FString File = FPaths::GetCleanFilename(FilePath);
|
|
|
|
// Look in engine directory
|
|
FString Result = FPaths::Combine(SearchPath, TEXT("Engine"), TEXT("Binaries"), Platform, File);
|
|
if (PlatformFile->FileExists(*Result))
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
// Look in project directory
|
|
Result = FPaths::Combine(SearchPath, Project, TEXT("Binaries"), Platform, File);
|
|
if (PlatformFile->FileExists(*Result))
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
return FString();
|
|
}
|
|
|
|
// use _NT_SYMBOL_PATH environment variable format
|
|
// for more information see: https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/advanced-symsrv-use
|
|
// to explicitly download symbol from MS Symbol Server for specific dll file, run:
|
|
// "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\symchk.exe" /r C:\Windows\system32\kernel32.dll /s srv*C:\Symbols*https://msdl.microsoft.com/download/symbols
|
|
static FString FindWindowsSymbolFile(const FString& GuidPath, const FString& SearchPath)
|
|
{
|
|
IPlatformFile* PlatformFile = &FPlatformFileManager::Get().GetPlatformFile();
|
|
|
|
if (SearchPath.StartsWith(TEXT("srv*")) || SearchPath.StartsWith(TEXT("cache*")))
|
|
{
|
|
TArray<FString> Srv;
|
|
SearchPath.RightChop(4).ParseIntoArray(Srv, TEXT("*"));
|
|
for (const FString& Path : Srv)
|
|
{
|
|
FString FilePath = FPaths::Combine(Path, GuidPath);
|
|
if (PlatformFile->FileExists(*FilePath))
|
|
{
|
|
return FilePath;
|
|
}
|
|
}
|
|
}
|
|
else if (SearchPath.Contains(TEXT("*")))
|
|
{
|
|
FString FilePath = FPaths::Combine(SearchPath, GuidPath);
|
|
if (PlatformFile->FileExists(*FilePath))
|
|
{
|
|
return FilePath;
|
|
}
|
|
}
|
|
|
|
return FString();
|
|
}
|
|
|
|
static bool LoadBinary(const TCHAR* ModuleFullName, SYMS_Arena* Arena, SYMS_ParseBundle& Bundle, TArray<FAutoMappedFile>& Files, const FString& SearchPath, const FString& Platform, const FString& AppName)
|
|
{
|
|
FString FilePath = ModuleFullName;
|
|
bool bFileFound = false;
|
|
|
|
// Remap known renamed binaries
|
|
if (FilePath.EndsWith(TEXT("eboot.bin")) && !Platform.IsEmpty() && !AppName.IsEmpty())
|
|
{
|
|
FilePath = FPaths::Combine(FPaths::GetPath(FilePath), FString::Printf(TEXT("%s.self"), *AppName));
|
|
}
|
|
|
|
// First lookup file in symbol path
|
|
FString BinaryPath = FindBinaryFileInPath(FilePath, SearchPath, Platform);
|
|
if (BinaryPath.IsEmpty() && !Platform.IsEmpty())
|
|
{
|
|
BinaryPath = FindFileInEngineFolder(FilePath, SearchPath, Platform, AppName);
|
|
}
|
|
bFileFound = !BinaryPath.IsEmpty();
|
|
|
|
if (!bFileFound)
|
|
{
|
|
UE_LOG(LogSymslib, Verbose, TEXT("Binary file '%s' not found"), *FilePath);
|
|
return false;
|
|
}
|
|
|
|
FAutoMappedFile& File = Files.AddDefaulted_GetRef();
|
|
if (!File.Load(*BinaryPath))
|
|
{
|
|
UE_LOG(LogSymslib, Warning, TEXT("Failed to load binary file '%s'"), *BinaryPath);
|
|
return false;
|
|
}
|
|
|
|
SYMS_FileAccel* Accel = syms_file_accel_from_data(Arena, File.GetData());
|
|
SYMS_BinAccel* BinAccel = syms_bin_accel_from_file(Arena, File.GetData(), Accel);
|
|
if (!syms_accel_is_good(BinAccel))
|
|
{
|
|
UE_LOG(LogSymslib, Warning, TEXT("Cannot parse binary file '%s'"), *BinaryPath);
|
|
return false;
|
|
}
|
|
|
|
// remember full path where binary file was found, so debug file can be looked up next to it
|
|
File.FilePath = BinaryPath;
|
|
|
|
Bundle.bin_data = File.GetData();
|
|
Bundle.bin = BinAccel;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool LoadDebug(SYMS_Arena* Arena, SYMS_ParseBundle& Bundle, TArray<FAutoMappedFile>& Files, const FString& SearchPath)
|
|
{
|
|
if (syms_bin_is_dbg(Bundle.bin))
|
|
{
|
|
// binary has debug info built-in (like dwarf file)
|
|
Bundle.dbg = syms_dbg_accel_from_bin(Arena, Files[0].GetData(), Bundle.bin);
|
|
Bundle.dbg_data = Bundle.bin_data;
|
|
UE_LOG(LogSymslib, Verbose, TEXT("Binary file '%s' has debug info built-in"), *Files[0].FilePath);
|
|
return true;
|
|
}
|
|
|
|
// we're loading extra file (pdb for exe)
|
|
SYMS_ExtFileList List = syms_ext_file_list_from_bin(Arena, Files[0].GetData(), Bundle.bin);
|
|
if (!List.first)
|
|
{
|
|
UE_LOG(LogSymslib, Warning, TEXT("Binary file '%s' built without debug info"), *Files[0].FilePath);
|
|
return false;
|
|
}
|
|
SYMS_ExtFile ExtFile = List.first->ext_file;
|
|
|
|
// debug file path from metadata in executable
|
|
FString FilePath = ANSI_TO_TCHAR(reinterpret_cast<char*>(ExtFile.file_name.str));
|
|
|
|
// try in the same path as the binary file
|
|
FString BinaryPath = FPaths::GetPath(Files[0].FilePath);
|
|
FString DebugPath = FindSymbolFileInPath(FilePath, BinaryPath);
|
|
|
|
if (DebugPath.IsEmpty())
|
|
{
|
|
// try in provided search path
|
|
DebugPath = FindSymbolFileInPath(FilePath, SearchPath);
|
|
}
|
|
|
|
if (DebugPath.IsEmpty())
|
|
{
|
|
// if executable is PE format, try Windows symbol path format with guid in path
|
|
if (Bundle.bin->format == SYMS_FileFormat_PE && SearchPath.Contains(TEXT("*")))
|
|
{
|
|
SYMS_PeBinAccel* PeAccel = reinterpret_cast<SYMS_PeBinAccel*>(Bundle.bin);
|
|
|
|
SYMS_PeGuid* Guid = &PeAccel->dbg_guid;
|
|
SYMS_U32 Age = PeAccel->dbg_age;
|
|
|
|
FString FileName = FPaths::GetCleanFilename(FilePath);
|
|
FString GuidPath = FString::Printf(TEXT("%s/%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%X/%s"),
|
|
*FileName,
|
|
Guid->data1, Guid->data2, Guid->data3,
|
|
Guid->data4[0], Guid->data4[1], Guid->data4[2], Guid->data4[3], Guid->data4[4], Guid->data4[5], Guid->data4[6], Guid->data4[7],
|
|
Age,
|
|
*FileName);
|
|
|
|
DebugPath = FindWindowsSymbolFile(GuidPath, SearchPath);
|
|
}
|
|
}
|
|
|
|
if (DebugPath.IsEmpty())
|
|
{
|
|
UE_LOG(LogSymslib, Verbose, TEXT("Debug symbols file '%s' not found"), *FilePath);
|
|
return false;
|
|
}
|
|
|
|
FAutoMappedFile& File = Files.AddDefaulted_GetRef();
|
|
if (!File.Load(*DebugPath))
|
|
{
|
|
UE_LOG(LogSymslib, Warning, TEXT("Failed to load debug symbols file '%s'"), *DebugPath);
|
|
return false;
|
|
}
|
|
|
|
SYMS_FileAccel* Accel = syms_file_accel_from_data(Arena, File.GetData());
|
|
SYMS_DbgAccel* DbgAccel = syms_dbg_accel_from_file(Arena, File.GetData(), Accel);
|
|
if (!syms_accel_is_good(DbgAccel))
|
|
{
|
|
UE_LOG(LogSymslib, Warning, TEXT("Cannot parse debug symbols file '%s'"), *DebugPath);
|
|
return false;
|
|
}
|
|
|
|
File.FilePath = DebugPath;
|
|
|
|
Bundle.dbg = DbgAccel;
|
|
Bundle.dbg_data = File.GetData();
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool MatchImageId(const TArray<uint8>& ImageId, SYMS_ParseBundle DataParsed)
|
|
{
|
|
if (DataParsed.dbg->format == SYMS_FileFormat_PDB)
|
|
{
|
|
// for Pdbs checksum is a 16 byte guid and 4 byte unsigned integer for age, but usually age is not used for matching debug file to exe
|
|
static_assert(sizeof(FGuid) == 16, "Expected 16 byte FGuid");
|
|
check(ImageId.Num() == 20);
|
|
FGuid* ModuleGuid = (FGuid*)ImageId.GetData();
|
|
|
|
SYMS_ExtMatchKey MatchKey = syms_ext_match_key_from_dbg(DataParsed.dbg_data, DataParsed.dbg);
|
|
FGuid* PdbGuid = (FGuid*)MatchKey.v;
|
|
|
|
if (*ModuleGuid != *PdbGuid)
|
|
{
|
|
// mismatch
|
|
return false;
|
|
}
|
|
}
|
|
else if (DataParsed.bin->format == SYMS_FileFormat_ELF)
|
|
{
|
|
// try different ways of getting build id from elf binary
|
|
SYMS_String8 FoundId = { 0, 0 };
|
|
|
|
SYMS_String8 Bin = DataParsed.bin_data;
|
|
SYMS_ElfSectionArray Sections = DataParsed.bin->elf_accel.sections;
|
|
for (SYMS_U64 SectionIndex = 0; SectionIndex < Sections.count; SectionIndex += 1)
|
|
{
|
|
SYMS_U64 SectionOffset = Sections.v[SectionIndex].file_range.min;
|
|
SYMS_U64 SectionSize = Sections.v[SectionIndex].file_range.max - SectionOffset;
|
|
|
|
if (syms_string_match(Sections.v[SectionIndex].name, syms_str8_lit(".note.gnu.build-id"), 0))
|
|
{
|
|
if (SectionSize > 12)
|
|
{
|
|
SYMS_U32 NameSize = *(SYMS_U32*)&Bin.str[SectionOffset + 0];
|
|
SYMS_U32 DescSize = *(SYMS_U32*)&Bin.str[SectionOffset + 4];
|
|
SYMS_U32 Type = *(SYMS_U32*)&Bin.str[SectionOffset + 8];
|
|
|
|
const SYMS_U32 NT_GNU_BUILD_ID = 3;
|
|
// name must be "GNU\0", contents must be at least 16 bytes, and type must be 3
|
|
if (NameSize == 4 && DescSize >= 16 && Type == NT_GNU_BUILD_ID)
|
|
{
|
|
SYMS_U64 NameOffset = sizeof(NameSize) + sizeof(DescSize) + sizeof(Type);
|
|
SYMS_String8 NameStr = syms_str8(&Bin.str[SectionOffset + NameOffset], 4);
|
|
if (NameSize <= SectionSize && syms_string_match(NameStr, syms_str8((SYMS_U8*)"GNU", 4), 0))
|
|
{
|
|
SYMS_U64 DescOffset = NameOffset + SYMS_AlignPow2(NameSize, 4);
|
|
if (DescOffset + 16 <= SectionSize)
|
|
{
|
|
FoundId = syms_str8(&Bin.str[SectionOffset + DescOffset], 16);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
else if (syms_hash_djb2(Sections.v[SectionIndex].name) == 0xaab84f54dfa67dee)
|
|
{
|
|
if (SectionSize >= 16)
|
|
{
|
|
FoundId = syms_str8(&Bin.str[SectionOffset], 16);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (FoundId.size == 16 && FoundId.size == ImageId.Num())
|
|
{
|
|
if (FMemory::Memcmp(ImageId.GetData(), FoundId.str, FoundId.size) != 0)
|
|
{
|
|
// mismatch
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// either ID's are matching, or ID is not found in which case return "success" case
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
class FSymbolStringAllocator
|
|
{
|
|
public:
|
|
FSymbolStringAllocator(ILinearAllocator& InAllocator, uint32 InBlockSize)
|
|
: Allocator(InAllocator)
|
|
, BlockSize(InBlockSize)
|
|
{}
|
|
|
|
const TCHAR* Store(const TCHAR* InString)
|
|
{
|
|
return Store(FStringView(InString));
|
|
}
|
|
|
|
const TCHAR* Store(const FStringView InString)
|
|
{
|
|
const uint32 StringSize = InString.Len() + 1;
|
|
check(StringSize <= BlockSize);
|
|
if (StringSize > BlockRemaining)
|
|
{
|
|
Block = (TCHAR*) Allocator.Allocate(BlockSize * sizeof(TCHAR));
|
|
BlockRemaining = BlockSize;
|
|
++BlockUsed;
|
|
}
|
|
const uint32 CopiedSize = InString.CopyString(Block, BlockRemaining - 1, 0);
|
|
check(StringSize == CopiedSize + 1);
|
|
Block[StringSize - 1] = TEXT('\0');
|
|
BlockRemaining -= StringSize;
|
|
const TCHAR* OutString = Block;
|
|
Block += StringSize;
|
|
return OutString;
|
|
}
|
|
|
|
private:
|
|
friend class FSymslibResolver;
|
|
ILinearAllocator& Allocator;
|
|
TCHAR* Block = nullptr;
|
|
uint32 BlockSize;
|
|
uint32 BlockRemaining = 0;
|
|
uint32 BlockUsed = 0;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FSymslibResolver::FSymslibResolver(IAnalysisSession& InSession, IResolvedSymbolFilter& InSymbolFilter)
|
|
: Modules(InSession.GetLinearAllocator(), 128)
|
|
, CancelTasks(false)
|
|
, Session(InSession)
|
|
, SymbolFilter(InSymbolFilter)
|
|
{
|
|
// Setup search paths. Paths are searched in the following order:
|
|
// 1. Any new path entered by the user this session
|
|
// 2. Path of the executable (if available)
|
|
// 3. Paths from UE_INSIGHTS_SYMBOL_PATH
|
|
// 4. Paths from _NT_SYMBOL_PATH
|
|
// 5. Paths from the user configuration file
|
|
|
|
auto SplitEnvPaths = [](FStringView EnvVariable, TArray<FString>& OutList)
|
|
{
|
|
FString SymbolPathPart, SymbolPathRemainder(EnvVariable);
|
|
while (SymbolPathRemainder.Split(TEXT(";"), &SymbolPathPart, &SymbolPathRemainder))
|
|
{
|
|
OutList.Emplace(SymbolPathPart);
|
|
}
|
|
if (!SymbolPathRemainder.IsEmpty())
|
|
{
|
|
OutList.Emplace(SymbolPathRemainder);
|
|
}
|
|
};
|
|
|
|
// Paths from environment
|
|
const FString InsightsSymbolPath = FPlatformMisc::GetEnvironmentVariable(TEXT("UE_INSIGHTS_SYMBOL_PATH"));
|
|
UE_LOG(LogSymslib, Log, TEXT("UE_INSIGHTS_SYMBOL_PATH: %s"), InsightsSymbolPath.IsEmpty() ? TEXT("not set") : *InsightsSymbolPath);
|
|
SplitEnvPaths(InsightsSymbolPath, ConfigSymbolSearchPaths);
|
|
|
|
#if PLATFORM_WINDOWS
|
|
const FString NTSymbolPath = FPlatformMisc::GetEnvironmentVariable(TEXT("_NT_SYMBOL_PATH"));
|
|
UE_LOG(LogSymslib, Log, TEXT("_NT_SYMBOL_PATH: %s"), NTSymbolPath.IsEmpty() ? TEXT("not set") : *NTSymbolPath);
|
|
SplitEnvPaths(NTSymbolPath, ConfigSymbolSearchPaths);
|
|
#endif
|
|
|
|
// Paths from configuration
|
|
FString SettingsIni;
|
|
if (FConfigContext::ReadIntoGConfig().Load(TEXT("UnrealInsightsSettings"), SettingsIni))
|
|
{
|
|
TArray<FString> SymbolSearchPaths;
|
|
GConfig->GetArray(TEXT("Insights.MemoryProfiler"), TEXT("SymbolSearchPaths"), SymbolSearchPaths, SettingsIni);
|
|
#if !NO_LOGGING
|
|
if (SymbolSearchPaths.IsEmpty())
|
|
{
|
|
UE_LOG(LogSymslib, Log, TEXT("[Insights.MemoryProfiler] SymbolSearchPaths not set"));
|
|
}
|
|
else
|
|
{
|
|
for (const FString& Path : SymbolSearchPaths)
|
|
{
|
|
UE_LOG(LogSymslib, Log, TEXT("[Insights.MemoryProfiler] +SymbolSearchPaths=%s"), *Path);
|
|
}
|
|
}
|
|
#endif
|
|
ConfigSymbolSearchPaths += SymbolSearchPaths;
|
|
}
|
|
|
|
UpdateSessionInfo();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FSymslibResolver::~FSymslibResolver()
|
|
{
|
|
CancelTasks = true;
|
|
// Wait for any existing cleanup tasks to finish
|
|
if (CleanupTask)
|
|
{
|
|
CleanupTask->Wait();
|
|
}
|
|
// Wait for module reload task to finish
|
|
if (ModuleReloadTask)
|
|
{
|
|
ModuleReloadTask->Wait();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FSymslibResolver::QueueModuleLoad(const uint8* ImageId, uint32 ImageIdSize, FModule* Module)
|
|
{
|
|
check(Module != nullptr);
|
|
ensure(GetModuleEntry(Module) == nullptr);
|
|
|
|
FWriteScopeLock _(ModulesLock);
|
|
|
|
check(!CleanupTask); // No queued addresses should be queued after cleanup task is started
|
|
|
|
// Add the new module entry.
|
|
FModuleEntry* Entry = &Modules.PushBack();
|
|
Entry->Module = Module;
|
|
Entry->ImageId = TArrayView<const uint8>(ImageId, ImageIdSize);
|
|
|
|
// Sort list according to base address.
|
|
SortedModules.Add(Entry);
|
|
Algo::Sort(SortedModules, [](const FModuleEntry* Lhs, const FModuleEntry* Rhs) { return Lhs->Module->Base < Rhs->Module->Base; });
|
|
|
|
++ModulesDiscovered;
|
|
|
|
// Set the Pending state before scheduling the background task (to allow calling code to wait, if needed).
|
|
Module->Status.store(EModuleStatus::Pending);
|
|
|
|
// Queue up module to have symbols loaded.
|
|
// Run as background task as to not interfere with Slate.
|
|
++TasksInFlight;
|
|
FFunctionGraphTask::CreateAndDispatchWhenReady(
|
|
[this, Entry]
|
|
{
|
|
LoadModuleTracked(Entry, FStringView());
|
|
--TasksInFlight;
|
|
}, TStatId{}, nullptr, ENamedThreads::AnyBackgroundThreadNormalTask);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FSymslibResolver::QueueModuleReload(FModule* Module, const TCHAR* Path, TFunction<void(SymbolArray&)> ResolveOnSuccess)
|
|
{
|
|
check(Module != nullptr);
|
|
|
|
// Find the entry
|
|
FModuleEntry* Entry = GetModuleEntry(Module);
|
|
if (!Entry)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// No use in trying reload already loaded modules
|
|
if (Module->Status == EModuleStatus::Loaded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Set the Pending state before scheduling the background task (to allow calling code to wait, if needed).
|
|
EModuleStatus PreviousStatus = Module->Status.exchange(EModuleStatus::Pending);
|
|
if (PreviousStatus >= EModuleStatus::FailedStatusStart)
|
|
{
|
|
--ModulesFailed;
|
|
}
|
|
|
|
FString PathStr(Path);
|
|
FPaths::NormalizeDirectoryName(PathStr);
|
|
const TCHAR* OverrideSearchPath = Session.StoreString(PathStr);
|
|
|
|
// Can only launch one task at a time
|
|
if (ModuleReloadTask && !ModuleReloadTask->IsComplete())
|
|
{
|
|
ModuleReloadTask->Wait();
|
|
}
|
|
|
|
FFunctionGraphTask::CreateAndDispatchWhenReady([this, Entry, OverrideSearchPath, ResolveOnSuccess]
|
|
{
|
|
FScopeLock CleanupExclusive(&CleanupLock);
|
|
|
|
LoadModuleTracked(Entry, OverrideSearchPath);
|
|
|
|
if (Entry->Module->Status.load() == EModuleStatus::Loaded)
|
|
{
|
|
// If an additional search path was specified and successful, add to the permanent
|
|
// list of search paths.
|
|
{
|
|
FWriteScopeLock _(CustomSymbolSearchPathsLock);
|
|
// Get the base path if path points to a file
|
|
IPlatformFile* PlatformFile = &FPlatformFileManager::Get().GetPlatformFile();
|
|
const FString Directory = PlatformFile->DirectoryExists(OverrideSearchPath) ? OverrideSearchPath : FPaths::GetPath(OverrideSearchPath);
|
|
if (!Directory.IsEmpty())
|
|
{
|
|
CustomSymbolSearchPaths.AddUnique(Directory);
|
|
}
|
|
}
|
|
|
|
// Reset stats for reloaded module.
|
|
// Note: If there are symbols pending to resolve (ex. busy spinning in ResolveSymbol()), the Stats.Discovered
|
|
// might be already incremented (see ResolveSymbolTracked()). So, resetting the stats here might result
|
|
// in slightly wrong stats where Discovered < Resolved + Failed.
|
|
FModule::SymbolStats& ModuleStats = Entry->Module->Stats;
|
|
ModuleStats.Discovered.store(0u);
|
|
ModuleStats.Resolved.store(0u);
|
|
ModuleStats.Failed.store(0u);
|
|
|
|
// Ask the caller for a list of symbols that should be resolved, now that module is properly loaded and
|
|
// resolve them immediately.
|
|
{
|
|
SymbolArray SymbolsToResolve;
|
|
FSymbolStringAllocator StringAllocator(Session.GetLinearAllocator(), (2 << 12) / sizeof(TCHAR));
|
|
ResolveOnSuccess(SymbolsToResolve);
|
|
for (TTuple<uint64, FResolvedSymbol*> Pair : SymbolsToResolve)
|
|
{
|
|
ResolveSymbolTracked(Pair.Get<0>(), *Pair.Get<1>(), StringAllocator);
|
|
}
|
|
}
|
|
|
|
// Finally if the cleanup task has already been dispatched, no other resolve request will come in and we
|
|
// can safely release our resources. CleanupTask and a reload task cannot run at the same time due to CleanupLock.
|
|
if (CleanupTask)
|
|
{
|
|
// Need to wait for tasks, since there could be queued symbol resolves for this module waiting
|
|
// in the queue.
|
|
WaitForTasks();
|
|
|
|
Algo::ForEach(Entry->Instance.Arenas, syms_arena_release);
|
|
Entry->Instance.Arenas.Empty();
|
|
}
|
|
}
|
|
}, TStatId{}, nullptr, ENamedThreads::AnyBackgroundThreadNormalTask);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FSymslibResolver::QueueSymbolResolve(uint64 Address, FResolvedSymbol* Symbol)
|
|
{
|
|
FScopeLock _(&SymbolsQueueLock);
|
|
check(!CleanupTask); // No queued addresses should be queued after cleanup task is started
|
|
MaybeDispatchQueuedAddresses();
|
|
ResolveQueue.Add(FQueuedAddress{ Address, Symbol });
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FSymslibResolver::OnAnalysisComplete()
|
|
{
|
|
// Dispatch any remaining requests.
|
|
{
|
|
FScopeLock _(&SymbolsQueueLock);
|
|
DispatchQueuedAddresses();
|
|
}
|
|
|
|
CleanupTask = FFunctionGraphTask::CreateAndDispatchWhenReady([this]
|
|
{
|
|
FScopeLock CleanupExclusive(&CleanupLock);
|
|
|
|
// Wait for outstanding batches to complete.
|
|
WaitForTasks();
|
|
|
|
// Release memory used by syms library
|
|
{
|
|
FReadScopeLock _(ModulesLock);
|
|
for (uint32 ModuleIndex = 0; ModuleIndex < Modules.Num(); ++ModuleIndex)
|
|
{
|
|
FModuleEntry& Entry = Modules[ModuleIndex];
|
|
Algo::ForEach(Entry.Instance.Arenas, syms_arena_release);
|
|
Entry.Instance.Arenas.Empty();
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogSymslib, Display, TEXT("Allocated %.02f MiB of strings (%.02f MiB wasted)."),
|
|
(double)SymbolBytesAllocated / (1024.0 * 1024.0), (double)SymbolBytesWasted / (1024.0 * 1024.0));
|
|
});
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FSymslibResolver::GetStats(IModuleProvider::FStats* OutStats) const
|
|
{
|
|
FReadScopeLock _(ModulesLock);
|
|
FMemory::Memzero(*OutStats);
|
|
for (uint32 ModuleIndex = 0; ModuleIndex < Modules.Num(); ++ModuleIndex)
|
|
{
|
|
FModule::SymbolStats& ModuleStats = Modules[ModuleIndex].Module->Stats;
|
|
OutStats->SymbolsDiscovered += ModuleStats.Discovered.load();
|
|
OutStats->SymbolsResolved += ModuleStats.Resolved.load();
|
|
OutStats->SymbolsFailed += ModuleStats.Failed.load();
|
|
}
|
|
OutStats->ModulesDiscovered = ModulesDiscovered.load();
|
|
OutStats->ModulesFailed = ModulesFailed.load();
|
|
OutStats->ModulesLoaded = ModulesLoaded.load();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FSymslibResolver::EnumerateSymbolSearchPaths(TFunctionRef<void(FStringView Path)> Callback) const
|
|
{
|
|
{
|
|
FReadScopeLock _(CustomSymbolSearchPathsLock);
|
|
Algo::ForEach(CustomSymbolSearchPaths, Callback);
|
|
}
|
|
|
|
Algo::ForEach(ConfigSymbolSearchPaths, Callback);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* Checks if there are no modules in flight and that the queue has reached
|
|
* the threshold for dispatching. Note that is up to the caller to synchronize.
|
|
*/
|
|
void FSymslibResolver::MaybeDispatchQueuedAddresses()
|
|
{
|
|
const bool bModulesInFlight = (ModulesDiscovered.load() - ModulesFailed.load() - ModulesLoaded.load()) > 0;
|
|
if (!bModulesInFlight && (ResolveQueue.Num() >= QueuedAddressLength))
|
|
{
|
|
DispatchQueuedAddresses();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* Dispatches the currently queued addresses to be resolved. Note that is up to the caller to synchronize.
|
|
*/
|
|
void FSymslibResolver::DispatchQueuedAddresses()
|
|
{
|
|
if (!ResolveQueue.IsEmpty())
|
|
{
|
|
TArray<FQueuedAddress> WorkingSet(ResolveQueue);
|
|
|
|
uint32 Stride = (WorkingSet.Num() - 1) / SymbolTasksInParallel + 1;
|
|
constexpr uint32 MinStride = 4;
|
|
Stride = FMath::Max(Stride, MinStride);
|
|
const uint32 ActualSymbolTasksInParallel = (WorkingSet.Num() + Stride - 1) / Stride;
|
|
TasksInFlight += ActualSymbolTasksInParallel;
|
|
|
|
// Use background priority in order to not interfere with Slate.
|
|
ParallelFor(ActualSymbolTasksInParallel, [this, &WorkingSet, Stride](uint32 Index) {
|
|
const uint32 StartIndex = Index * Stride;
|
|
const uint32 EndIndex = FMath::Min(StartIndex + Stride, (uint32)WorkingSet.Num());
|
|
TArrayView<FQueuedAddress> QueuedWork(&WorkingSet[StartIndex], EndIndex - StartIndex);
|
|
ResolveSymbols(QueuedWork);
|
|
--TasksInFlight;
|
|
}, EParallelForFlags::BackgroundPriority);
|
|
|
|
ResolveQueue.Reset();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FSymslibResolver::ResolveSymbols(TArrayView<FQueuedAddress>& QueuedWork)
|
|
{
|
|
// Create a local string allocator. We don't use the session string store due to contention when going wide. Since
|
|
// the ModuleProvider already de-duplicates symbols we do not need this feature from the string store.
|
|
FSymbolStringAllocator StringAllocator(Session.GetLinearAllocator(), (2 << 12) / sizeof(TCHAR) );
|
|
for (const FQueuedAddress& ToResolve : QueuedWork)
|
|
{
|
|
if (CancelTasks.load(std::memory_order_relaxed))
|
|
{
|
|
break;
|
|
}
|
|
ResolveSymbolTracked(ToResolve.Address, *ToResolve.Target, StringAllocator);
|
|
}
|
|
uint64 BytesAllocated = StringAllocator.BlockUsed * StringAllocator.BlockSize * sizeof(TCHAR);
|
|
uint64 BytesWasted = StringAllocator.BlockRemaining * sizeof(TCHAR);
|
|
UE_LOG(LogSymslib, VeryVerbose, TEXT("String allocator: %.02f KiB used (+ %.02f KiB wasted) in %d blocks"),
|
|
(double)(BytesAllocated - BytesWasted) / 1024.0,
|
|
(double)(BytesWasted) / 1024.0,
|
|
StringAllocator.BlockUsed);
|
|
SymbolBytesAllocated.fetch_add(BytesAllocated);
|
|
SymbolBytesWasted.fetch_add(BytesWasted);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FSymslibResolver::FModuleEntry* FSymslibResolver::GetModuleEntry(FModule* Module) const
|
|
{
|
|
FReadScopeLock _(ModulesLock);
|
|
for (FModuleEntry* Entry : SortedModules)
|
|
{
|
|
if (Entry->Module == Module)
|
|
{
|
|
return Entry;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FSymslibResolver::FModuleEntry* FSymslibResolver::GetModuleForAddress(uint64 Address) const
|
|
{
|
|
FReadScopeLock _(ModulesLock);
|
|
const int32 EntryIdx = Algo::LowerBoundBy(SortedModules, Address, [](const FModuleEntry* Entry) { return Entry->Module->Base; }) - 1;
|
|
if (EntryIdx < 0 || EntryIdx >= SortedModules.Num())
|
|
{
|
|
return nullptr;
|
|
}
|
|
return SortedModules[EntryIdx];
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FSymslibResolver::UpdateResolvedSymbol(FResolvedSymbol& Symbol, ESymbolQueryResult Result, const TCHAR* Module, const TCHAR* Name, const TCHAR* File, uint16 Line)
|
|
{
|
|
Symbol.Module = Module;
|
|
Symbol.Name = Name;
|
|
Symbol.File = File;
|
|
Symbol.Line = Line;
|
|
Symbol.Result.store(Result, std::memory_order_release);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FSymslibResolver::LoadModuleTracked(FModuleEntry* Entry, FStringView OverrideSearchPath)
|
|
{
|
|
FModule* Module = Entry->Module;
|
|
|
|
Module->Status.store(EModuleStatus::Pending);
|
|
|
|
// Build search paths
|
|
TArray<FString> SearchPaths;
|
|
if (OverrideSearchPath.IsEmpty())
|
|
{
|
|
// 1. Any new path entered by the user this session
|
|
{
|
|
FReadScopeLock _(CustomSymbolSearchPathsLock);
|
|
for (const FString& SearchPath : CustomSymbolSearchPaths)
|
|
{
|
|
SearchPaths.AddUnique(SearchPath);
|
|
}
|
|
}
|
|
|
|
// 2. Path of the executable (if available)
|
|
FString ModuleNamePath = FPaths::GetPath(Module->FullName);
|
|
FPaths::NormalizeDirectoryName(ModuleNamePath);
|
|
if (!ModuleNamePath.IsEmpty())
|
|
{
|
|
SearchPaths.AddUnique(ModuleNamePath);
|
|
}
|
|
|
|
// 3. Paths from UE_INSIGHTS_SYMBOL_PATH
|
|
// 4. Paths from _NT_SYMBOL_PATH
|
|
// 5. Paths from the user configuration file
|
|
for (const FString& SearchPath : ConfigSymbolSearchPaths)
|
|
{
|
|
SearchPaths.AddUnique(SearchPath);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SearchPaths.Add(FString(OverrideSearchPath));
|
|
}
|
|
|
|
// Exhaust all the search paths
|
|
EModuleStatus Status = EModuleStatus::Failed;
|
|
TStringBuilder<128> StatusMessage;
|
|
for (const FString& SearchPath : SearchPaths)
|
|
{
|
|
if (CancelTasks.load())
|
|
{
|
|
break;
|
|
}
|
|
StatusMessage.Reset();
|
|
Status = LoadModule(Entry, SearchPath, StatusMessage);
|
|
UE_LOG(LogSymslib, Display, TEXT("%s"), StatusMessage.ToString());
|
|
if (Status == EModuleStatus::Loaded)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Status == EModuleStatus::Loaded)
|
|
{
|
|
++ModulesLoaded;
|
|
}
|
|
else
|
|
{
|
|
++ModulesFailed;
|
|
}
|
|
|
|
// Make the final status visible to the world
|
|
Module->StatusMessage = Session.StoreString(StatusMessage.ToView());
|
|
Module->Status.store(Status);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
EModuleStatus FSymslibResolver::LoadModule(FModuleEntry* Entry, FStringView SearchPathView, FStringBuilderBase& OutStatusMessage) const
|
|
{
|
|
const FString SearchPath(SearchPathView);
|
|
|
|
// temporary memory used for loading
|
|
SYMS_Group* Group = syms_group_alloc();
|
|
|
|
// memory-mapped binary & debug files
|
|
TArray<FAutoMappedFile> Files;
|
|
|
|
// contents of binary & debug file
|
|
SYMS_ParseBundle Bundle;
|
|
|
|
if (Platform.IsEmpty())
|
|
{
|
|
UpdateSessionInfo();
|
|
}
|
|
|
|
if (!LoadBinary(Entry->Module->FullName, Group->arena, Bundle, Files, SearchPath, Platform, AppName))
|
|
{
|
|
syms_group_release(Group);
|
|
OutStatusMessage.Appendf(TEXT("Failed to load binary for '%s' in '%s'."), Entry->Module->Name, *SearchPath);
|
|
return EModuleStatus::Failed;
|
|
}
|
|
|
|
if (!LoadDebug(Group->arena, Bundle, Files, SearchPath))
|
|
{
|
|
syms_group_release(Group);
|
|
OutStatusMessage.Appendf(TEXT("Failed to load debug symbols for '%s' in '%s'."), Entry->Module->Name, *SearchPath);
|
|
return EModuleStatus::Failed;
|
|
}
|
|
|
|
// check debug data mismatch to captured module
|
|
if (!Entry->ImageId.IsEmpty() && !MatchImageId(Entry->ImageId, Bundle))
|
|
{
|
|
syms_group_release(Group);
|
|
OutStatusMessage << TEXT("Symbols for '") << Entry->Module->Name << TEXT("' found in '") << SearchPath << TEXT("' does not match trace binary.");
|
|
return EModuleStatus::VersionMismatch;
|
|
}
|
|
|
|
FSymsInstance* Instance = &Entry->Instance;
|
|
|
|
// initialize group
|
|
syms_set_lane(0);
|
|
syms_group_init(Group, &Bundle);
|
|
|
|
// unit storage
|
|
SYMS_U64 UnitCount = syms_group_unit_count(Group);
|
|
Instance->Units.SetNum(static_cast<int32>(UnitCount));
|
|
|
|
// per-thread arena storage (at least one)
|
|
int32 WorkerThreadCount = FMath::Max(1, FTaskGraphInterface::Get().GetNumWorkerThreads());
|
|
Instance->Arenas.SetNum(WorkerThreadCount);
|
|
|
|
// how many symbols are loaded
|
|
std::atomic<uint32> SymbolCount = 0;
|
|
|
|
// parse debug info in multiple threads
|
|
{
|
|
uint32 LaneSlot = FPlatformTLS::AllocTlsSlot();
|
|
|
|
std::atomic<uint32> LaneCount = 0;
|
|
syms_group_begin_multilane(Group, WorkerThreadCount);
|
|
ParallelFor(static_cast<int32>(UnitCount), [Instance, Group, LaneSlot, &LaneCount, &SymbolCount](int32 Index)
|
|
{
|
|
SYMS_Arena* Arena;
|
|
uint32 LaneValue = uint32(reinterpret_cast<intptr_t>(FPlatformTLS::GetTlsValue(LaneSlot)));
|
|
if (LaneValue == 0)
|
|
{
|
|
// first time we are on this thread
|
|
LaneValue = ++LaneCount;
|
|
FPlatformTLS::SetTlsValue(LaneSlot, reinterpret_cast<void*>(intptr_t(LaneValue)));
|
|
|
|
// syms lane index is 0-based
|
|
uint32 LaneIndex = LaneValue - 1;
|
|
syms_set_lane(LaneIndex);
|
|
Arena = Instance->Arenas[LaneIndex] = syms_arena_alloc();
|
|
}
|
|
else
|
|
{
|
|
uint32 LaneIndex = LaneValue - 1;
|
|
syms_set_lane(LaneIndex);
|
|
Arena = Instance->Arenas[LaneIndex];
|
|
}
|
|
|
|
SYMS_ArenaTemp Scratch = syms_get_scratch(0, 0);
|
|
|
|
SYMS_UnitID UnitID = static_cast<SYMS_UnitID>(Index) + 1; // syms unit id's are 1-based
|
|
FSymsUnit* Unit = &Instance->Units[Index];
|
|
|
|
SYMS_SpatialMap1D* ProcSpatialMap = syms_group_proc_map_from_uid(Group, UnitID);
|
|
Unit->ProcMap = syms_spatial_map_1d_copy(Arena, ProcSpatialMap);
|
|
|
|
SYMS_String8Array* FileTable = syms_group_file_table_from_uid_with_fallbacks(Group, UnitID);
|
|
Unit->FileTable = syms_string_array_copy(Arena, 0, FileTable);
|
|
|
|
SYMS_LineParseOut* LineParse = syms_group_line_parse_from_uid(Group, UnitID);
|
|
Unit->LineTable = syms_line_table_with_indexes_from_parse(Arena, LineParse);
|
|
|
|
SYMS_SpatialMap1D* LineSpatialMap = syms_group_line_sequence_map_from_uid(Group, UnitID);
|
|
Unit->LineMap = syms_spatial_map_1d_copy(Arena, LineSpatialMap);
|
|
|
|
SYMS_UnitAccel* UnitAccel = syms_group_unit_from_uid(Group, UnitID);
|
|
|
|
SYMS_IDMap ProcIdMap = syms_id_map_alloc(Scratch.arena, 4093);
|
|
|
|
SYMS_SymbolIDArray* ProcArray = syms_group_proc_sid_array_from_uid(Group, UnitID);
|
|
SYMS_U64 ProcCount = ProcArray->count;
|
|
|
|
FSymsSymbol* Symbols = syms_push_array(Arena, FSymsSymbol, ProcCount);
|
|
for (SYMS_U64 ProcIndex = 0; ProcIndex < ProcCount; ProcIndex++)
|
|
{
|
|
SYMS_SymbolID SymbolID = ProcArray->ids[ProcIndex];
|
|
|
|
SYMS_String8 Name = syms_group_symbol_name_from_sid(Arena, Group, UnitAccel, SymbolID);
|
|
Symbols[ProcIndex].Name = reinterpret_cast<char*>(Name.str);
|
|
|
|
syms_id_map_insert(Scratch.arena, &ProcIdMap, SymbolID, &Symbols[ProcIndex]);
|
|
}
|
|
|
|
SYMS_SpatialMap1D* ProcMap = &Unit->ProcMap;
|
|
for (SYMS_SpatialMap1DRange* Range = ProcMap->ranges, *EndRange = ProcMap->ranges + ProcMap->count; Range < EndRange; Range++)
|
|
{
|
|
void* SymbolPtr = syms_id_map_ptr_from_u64(&ProcIdMap, Range->val);
|
|
Range->val = SYMS_U64(reinterpret_cast<intptr_t>(SymbolPtr));
|
|
}
|
|
|
|
syms_release_scratch(Scratch);
|
|
|
|
SymbolCount += static_cast<uint32>(ProcCount);
|
|
});
|
|
syms_group_end_multilane(Group);
|
|
|
|
FPlatformTLS::FreeTlsSlot(LaneSlot);
|
|
}
|
|
|
|
SYMS_Arena* Arena = Instance->Arenas[0];
|
|
|
|
// store stripped format symbols
|
|
{
|
|
SYMS_LinkNameRecArray StrippedInfo = syms_group_link_name_records(Group);
|
|
|
|
FSymsSymbol* StrippedSymbols = syms_push_array(Arena, FSymsSymbol, StrippedInfo.count);
|
|
for (SYMS_U64 Index = 0; Index < StrippedInfo.count; Index++)
|
|
{
|
|
SYMS_LinkNameRec* Info = &StrippedInfo.recs[Index];
|
|
FSymsSymbol* StrippedSymbol = &StrippedSymbols[Index];
|
|
|
|
SYMS_String8 Name = syms_push_string_copy(Arena, Info->name);
|
|
StrippedSymbol->Name = reinterpret_cast<char*>(Name.str);
|
|
}
|
|
|
|
Instance->StrippedMap = syms_spatial_map_1d_copy(Arena, syms_group_link_name_spatial_map(Group));
|
|
Instance->StrippedSymbols = StrippedSymbols;
|
|
|
|
SymbolCount += (uint32)StrippedInfo.count;
|
|
}
|
|
|
|
Instance->UnitMap = syms_spatial_map_1d_copy(Arena, syms_group_unit_map(Group));
|
|
Instance->DefaultBase = syms_group_default_vbase(Group);
|
|
|
|
syms_group_release(Group);
|
|
Instance->Arenas.RemoveAll([](SYMS_Arena* arena) { return arena == nullptr; });
|
|
|
|
// In order to avoid listing all files, use only the last. On dwarf/elf this will be the main binary, on Windows
|
|
// this will be the pdb (the first is the exe/dll).
|
|
OutStatusMessage.Appendf(TEXT("%s loaded with %u symbols."), *Files.Last().FilePath, SymbolCount.load());
|
|
return EModuleStatus::Loaded;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FSymslibResolver::ResolveSymbolTracked(uint64 Address, FResolvedSymbol& Target, FSymbolStringAllocator& StringAllocator)
|
|
{
|
|
const ESymbolQueryResult PreviousResult = Target.Result.load();
|
|
if (PreviousResult == ESymbolQueryResult::OK)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FModuleEntry* Entry = GetModuleForAddress(Address);
|
|
if (!Entry)
|
|
{
|
|
UE_LOG(LogSymslib, Warning, TEXT("No module mapped to address 0x%016llx."), Address);
|
|
UpdateResolvedSymbol(Target,
|
|
ESymbolQueryResult::NotLoaded,
|
|
GUnknownModuleTextSymsLib,
|
|
GUnknownModuleTextSymsLib,
|
|
GUnknownModuleTextSymsLib,
|
|
0);
|
|
SymbolFilter.Update(Target);
|
|
return;
|
|
}
|
|
|
|
FModule::SymbolStats& ModuleStats = Entry->Module->Stats;
|
|
++ModuleStats.Discovered;
|
|
if (!ResolveSymbol(Address, Target, StringAllocator, Entry))
|
|
{
|
|
++ModuleStats.Failed;
|
|
}
|
|
else
|
|
{
|
|
++ModuleStats.Resolved;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#if USE_DBG_HELP_UNDECORATOR
|
|
FORCEINLINE void UndecorateAndCopySymbolName(ANSICHAR* OutStr, const char* Name, uint32 MaxStringLength)
|
|
{
|
|
// In case of stripped names we use the UnDecorateSymbolName function from the DbgHelp.
|
|
if (Name[0] == '?')
|
|
{
|
|
// todo: To get full parity with non-stripped pdbs we shouldn't print the argument types. There is a flag
|
|
// UNDNAME_NO_ARGUMENTS however whenever I tried that the symbol was not undecorated at all?
|
|
constexpr DWORD Flags =
|
|
UNDNAME_NO_MS_KEYWORDS |
|
|
UNDNAME_NO_FUNCTION_RETURNS |
|
|
UNDNAME_NO_ALLOCATION_MODEL |
|
|
UNDNAME_NO_ALLOCATION_LANGUAGE |
|
|
UNDNAME_NO_THISTYPE |
|
|
UNDNAME_NO_ACCESS_SPECIFIERS |
|
|
UNDNAME_NO_THROW_SIGNATURES |
|
|
UNDNAME_NO_MEMBER_TYPE |
|
|
UNDNAME_NO_RETURN_UDT_MODEL;
|
|
|
|
// It is unclear from official documentation if UnDecorateSymbolName is thread safe or not, so
|
|
// we take a local lock just in case.
|
|
static FCriticalSection Cs;
|
|
FScopeLock Lock(&Cs);
|
|
DWORD Length = UnDecorateSymbolName(Name, OutStr, MaxStringLength, Flags);
|
|
}
|
|
else
|
|
{
|
|
FCStringAnsi::Strncpy(OutStr, Name, MaxStringLength);
|
|
}
|
|
}
|
|
#endif // USE_DBG_HELP_UNDECORATOR
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool FSymslibResolver::ResolveSymbol(uint64 Address, FResolvedSymbol& Target, FSymbolStringAllocator& StringAllocator, FModuleEntry* Entry) const
|
|
{
|
|
EModuleStatus Status = Entry->Module->Status.load();
|
|
while ((Status == EModuleStatus::Pending || Status == EModuleStatus::Discovered) && !CancelTasks.load())
|
|
{
|
|
FPlatformProcess::YieldThread();
|
|
Status = Entry->Module->Status.load();
|
|
}
|
|
|
|
switch (Status)
|
|
{
|
|
case EModuleStatus::Failed:
|
|
UpdateResolvedSymbol(Target,
|
|
ESymbolQueryResult::NotLoaded,
|
|
Entry->Module->Name,
|
|
GUnknownModuleTextSymsLib,
|
|
GUnknownModuleTextSymsLib,
|
|
0);
|
|
SymbolFilter.Update(Target);
|
|
return false;
|
|
|
|
case EModuleStatus::VersionMismatch:
|
|
UpdateResolvedSymbol(Target,
|
|
ESymbolQueryResult::Mismatch,
|
|
Entry->Module->Name,
|
|
GUnknownModuleTextSymsLib,
|
|
GUnknownModuleTextSymsLib,
|
|
0);
|
|
SymbolFilter.Update(Target);
|
|
return false;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Find procedure and source file for address
|
|
|
|
FSymsInstance* Instance = &Entry->Instance;
|
|
SYMS_U64 VirtualOffset = Address + Instance->DefaultBase - Entry->Module->Base;
|
|
|
|
FSymsSymbol* SymsSymbol = nullptr;
|
|
|
|
constexpr uint32 MaxStringSize = 1024;
|
|
const TCHAR* SourceFilePersistent = nullptr;
|
|
uint32 SourceFileLine = 0;
|
|
|
|
SYMS_UnitID UnitID = syms_spatial_map_1d_value_from_point(&Instance->UnitMap, VirtualOffset);
|
|
if (UnitID)
|
|
{
|
|
FSymsUnit* Unit = &Instance->Units[static_cast<uint32>(UnitID) - 1];
|
|
|
|
SYMS_U64 Value = syms_spatial_map_1d_value_from_point(&Unit->ProcMap, VirtualOffset);
|
|
if (Value)
|
|
{
|
|
SymsSymbol = reinterpret_cast<FSymsSymbol*>(Value);
|
|
|
|
SYMS_U64 SeqNumber = syms_spatial_map_1d_value_from_point(&Unit->LineMap, VirtualOffset);
|
|
if (SeqNumber)
|
|
{
|
|
SYMS_Line Line = syms_line_from_sequence_voff(&Unit->LineTable, SeqNumber, VirtualOffset);
|
|
if (Line.src_coord.file_id)
|
|
{
|
|
SYMS_String8 FileName = Unit->FileTable.strings[Line.src_coord.file_id - 1];
|
|
|
|
ANSICHAR SourceFile[MaxStringSize];
|
|
FCStringAnsi::Strncpy(SourceFile, reinterpret_cast<char*>(FileName.str), MaxStringSize);
|
|
SourceFile[MaxStringSize - 1] = 0;
|
|
SourceFilePersistent = StringAllocator.Store(ANSI_TO_TCHAR(SourceFile));
|
|
|
|
SourceFileLine = Line.src_coord.line;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SymsSymbol == nullptr)
|
|
{
|
|
// try lookup into stripped format symbols
|
|
SYMS_U64 Value = syms_spatial_map_1d_value_from_point(&Instance->StrippedMap, VirtualOffset);
|
|
if (Value)
|
|
{
|
|
SymsSymbol = &Instance->StrippedSymbols[Value - 1];
|
|
|
|
// use module name as filename
|
|
SourceFilePersistent = StringAllocator.Store(Entry->Module->Name);
|
|
}
|
|
}
|
|
|
|
// this includes skipping symbols without name (empty string)
|
|
if (!SymsSymbol || !SourceFilePersistent || (SymsSymbol && SymsSymbol->Name[0] == 0))
|
|
{
|
|
UpdateResolvedSymbol(Target,
|
|
ESymbolQueryResult::NotFound,
|
|
Entry->Module->Name,
|
|
GUnknownModuleTextSymsLib,
|
|
GUnknownModuleTextSymsLib,
|
|
0);
|
|
SymbolFilter.Update(Target);
|
|
return false;
|
|
}
|
|
|
|
ANSICHAR SymbolName[MaxStringSize];
|
|
#if USE_DBG_HELP_UNDECORATOR
|
|
UndecorateAndCopySymbolName(SymbolName, SymsSymbol->Name, MaxStringSize);
|
|
#else
|
|
FCStringAnsi::Strncpy(SymbolName, SymsSymbol->Name, MaxStringSize);
|
|
#endif
|
|
SymbolName[MaxStringSize - 1] = 0;
|
|
const TCHAR* SymbolNamePersistent = StringAllocator.Store(ANSI_TO_TCHAR(SymbolName));
|
|
|
|
// Store the strings and update the target data
|
|
UpdateResolvedSymbol(Target,
|
|
ESymbolQueryResult::OK,
|
|
Entry->Module->Name,
|
|
SymbolNamePersistent,
|
|
SourceFilePersistent,
|
|
static_cast<uint16>(SourceFileLine));
|
|
SymbolFilter.Update(Target);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FSymslibResolver::WaitForTasks()
|
|
{
|
|
uint32 OutstandingTasks = TasksInFlight.load(std::memory_order_acquire);
|
|
do
|
|
{
|
|
OutstandingTasks = TasksInFlight.load(std::memory_order_acquire);
|
|
FPlatformProcess::Sleep(0.0);
|
|
}
|
|
while (OutstandingTasks > 0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FSymslibResolver::UpdateSessionInfo() const
|
|
{
|
|
// Try to get session information.
|
|
// Depending on how module tracing and session info tracing is implemented on different platforms this may not
|
|
// be available at this point.
|
|
FAnalysisSessionReadScope _(Session);
|
|
const IDiagnosticsProvider* DiagnosticsProvider = ReadDiagnosticsProvider(Session);
|
|
if (DiagnosticsProvider && DiagnosticsProvider->IsSessionInfoAvailable())
|
|
{
|
|
const FSessionInfo Info = DiagnosticsProvider->GetSessionInfo();
|
|
Platform = Info.Platform;
|
|
AppName = Info.AppName;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
} // namespace TraceServices
|
|
|
|
#endif // UE_SYMSLIB_AVAILABLE
|