You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
[Insights] Add symbol search paths and reload module triggers
* Implements functionality to search for symbols in a stack of search directories. Search directories can be added from configuration, environment variables or explicitly specified by user. * Exposes module information and ability to enumerate detected modules. * Adds functionality to attempt loading symbols for a module in a specific directory. #rb ionut.matasaru, martins.mozeiko #jira UE-137221 #preflight 61d56928932a02483cc346f7 #preflight 61d571e0ec35d1b940dad984 #ROBOMERGE-AUTHOR: johan.berg #ROBOMERGE-SOURCE: CL 18517208 in //UE5/Release-5.0/... via CL 18517221 #ROBOMERGE-BOT: STARSHIP (Release-Engine-Staging -> Release-Engine-Test) (v899-18417669) [CL 18517223 by johan berg in ue5-release-engine-test branch]
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "DbgHelpResolver.h"
|
||||
#include "Algo/Sort.h"
|
||||
#include "Algo/ForEach.h"
|
||||
#include "Containers/Queue.h"
|
||||
#include "Containers/StringView.h"
|
||||
#include "HAL/PlatformProcess.h"
|
||||
@@ -28,21 +29,37 @@ namespace TraceServices {
|
||||
|
||||
static const TCHAR* GUnknownModuleTextDbgHelp = TEXT("Unknown");
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
FDbgHelpResolver::FDbgHelpResolver(IAnalysisSession& InSession)
|
||||
: bRunWorkerThread(false)
|
||||
, bDrainThenStop(false)
|
||||
, bInitialized(false)
|
||||
, Session(InSession)
|
||||
{
|
||||
bInitialized = SetupSyms();
|
||||
// Setup search paths. The SearchPaths array is a priority stack, which
|
||||
// means paths are searched in reversed order.
|
||||
// 1. Any new paths entered by the user this session
|
||||
// 2. Path of the executable (if available)
|
||||
// 3. Paths from UE_INSIGHTS_SYMBOLPATH
|
||||
// 4. Paths from the user configuration file
|
||||
|
||||
if (bInitialized)
|
||||
// Paths from configuration
|
||||
FString SettingsIni;
|
||||
if (FConfigCacheIni::LoadGlobalIniFile(SettingsIni, TEXT("UnrealInsightsSettings")))
|
||||
{
|
||||
// Start the worker thread
|
||||
bRunWorkerThread = true;
|
||||
Thread = FRunnableThread::Create(this, TEXT("DbgHelpWorker"), 0, TPri_Normal);
|
||||
GConfig->GetArray(TEXT("Insights.MemoryProfiler"), TEXT("SymbolSearchPaths"), SymbolSearchPaths, SettingsIni);
|
||||
}
|
||||
|
||||
// Paths from environment
|
||||
FString SymbolPathEnvVar = FPlatformMisc::GetEnvironmentVariable(TEXT("UE_INSIGHTS_SYMBOL_PATH"));
|
||||
UE_LOG(LogDbgHelp, Log, TEXT("UE_INSIGHTS_SYMBOL_PATH: '%s'"), *SymbolPathEnvVar);
|
||||
FString SymbolPathPart;
|
||||
while (SymbolPathEnvVar.Split(TEXT(";"), &SymbolPathPart, &SymbolPathEnvVar))
|
||||
{
|
||||
SymbolSearchPaths.Emplace(SymbolPathPart);
|
||||
}
|
||||
|
||||
Start();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -56,17 +73,22 @@ FDbgHelpResolver::~FDbgHelpResolver()
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
void FDbgHelpResolver::QueueModuleLoad(const FStringView& ModulePath, uint64 Base, uint32 Size, const uint8* ImageId, uint32 ImageIdSize)
|
||||
void FDbgHelpResolver::QueueModuleLoad(const uint8* ImageId, uint32 ImageIdSize, FModule* Module)
|
||||
{
|
||||
check(Module != nullptr);
|
||||
|
||||
FScopeLock _(&ModulesCs);
|
||||
|
||||
const FStringView ModuleName = FPathViews::GetCleanFilename(ModulePath);
|
||||
|
||||
const FStringView ModuleName = FPathViews::GetCleanFilename(Module->FullName);
|
||||
|
||||
// Add module and sort list according to base address
|
||||
const int32 Index = LoadedModules.Add(FModuleEntry{ Base, Size, Session.StoreString(ModuleName), Session.StoreString(ModulePath), false });
|
||||
const int32 Index = LoadedModules.Add(FModuleEntry{
|
||||
Module->Base, Module->Size, Session.StoreString(ModuleName), Session.StoreString(Module->FullName),
|
||||
Module, TArray(ImageId, ImageIdSize)
|
||||
});
|
||||
|
||||
// Queue up module to have symbols loaded
|
||||
LoadSymbolsQueue.Enqueue(FQueuedModule{ Base, Size, LoadedModules[Index].Path });
|
||||
LoadSymbolsQueue.Enqueue(FQueuedModule{ Module, nullptr, LoadedModules[Index].ImageId});
|
||||
|
||||
// Sort list according to base address
|
||||
Algo::Sort(LoadedModules, [](const FModuleEntry& Lhs, const FModuleEntry& Rhs) { return Lhs.Base < Rhs.Base; });
|
||||
@@ -74,24 +96,61 @@ void FDbgHelpResolver::QueueModuleLoad(const FStringView& ModulePath, uint64 Bas
|
||||
++ModulesDiscovered;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
void FDbgHelpResolver::QueueModuleReload(const FModule* Module, const TCHAR* InPath, TFunction<void(SymbolArray&)> ResolveOnSuccess)
|
||||
{
|
||||
FScopeLock _(&ModulesCs);
|
||||
const uint64 ModuleBase = Module->Base;
|
||||
const FModuleEntry* Entry = LoadedModules.FindByPredicate([ModuleBase](const FModuleEntry& Entry) { return Entry.Base == ModuleBase; });
|
||||
if (Entry)
|
||||
{
|
||||
Entry->Module->Status.store(EModuleStatus::Pending);
|
||||
LoadSymbolsQueue.Enqueue(FQueuedModule{Module, Session.StoreString(InPath), TArrayView<const uint8>(Entry->ImageId)});
|
||||
}
|
||||
|
||||
SymbolArray SymbolsToResolve;
|
||||
ResolveOnSuccess(SymbolsToResolve);
|
||||
for(TTuple<uint64, FResolvedSymbol*> Pair : SymbolsToResolve)
|
||||
{
|
||||
QueueSymbolResolve(Pair.Get<0>(), Pair.Get<1>());
|
||||
}
|
||||
|
||||
if (!bRunWorkerThread && Thread)
|
||||
{
|
||||
// Restart the worker thread if it has stopped.
|
||||
Start();
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
void FDbgHelpResolver::QueueSymbolResolve(uint64 Address, FResolvedSymbol* Symbol)
|
||||
{
|
||||
++SymbolsDiscovered;
|
||||
ResolveQueue.Enqueue(FQueuedAddress{Address, Symbol});
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
void FDbgHelpResolver::GetStats(IModuleProvider::FStats* OutStats) const
|
||||
{
|
||||
FScopeLock _(&ModulesCs);
|
||||
FMemory::Memzero(*OutStats);
|
||||
for(const FModuleEntry& Entry : LoadedModules)
|
||||
{
|
||||
OutStats->SymbolsDiscovered += Entry.Module->Stats.Discovered.load();
|
||||
OutStats->SymbolsResolved += Entry.Module->Stats.Resolved.load();
|
||||
OutStats->SymbolsFailed += Entry.Module->Stats.Failed.load();
|
||||
}
|
||||
OutStats->ModulesDiscovered = ModulesDiscovered.load();
|
||||
OutStats->ModulesFailed = ModulesFailed.load();
|
||||
OutStats->ModulesLoaded = ModulesLoaded.load();
|
||||
OutStats->SymbolsDiscovered = SymbolsDiscovered.load();
|
||||
OutStats->SymbolsFailed = SymbolsFailed.load();
|
||||
OutStats->SymbolsResolved = SymbolsResolved.load();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
void FDbgHelpResolver::EnumerateSymbolSearchPaths(TFunctionRef<void(FStringView Path)> Callback) const
|
||||
{
|
||||
FScopeLock _(&SymbolSearchPathsLock);
|
||||
Algo::ForEach(SymbolSearchPaths, Callback);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
void FDbgHelpResolver::OnAnalysisComplete()
|
||||
{
|
||||
@@ -112,12 +171,13 @@ bool FDbgHelpResolver::SetupSyms()
|
||||
ULONG SymOpts = 0;
|
||||
SymOpts |= SYMOPT_LOAD_LINES;
|
||||
SymOpts |= SYMOPT_OMAP_FIND_NEAREST;
|
||||
SymOpts |= SYMOPT_DEFERRED_LOADS;
|
||||
//SymOpts |= SYMOPT_DEFERRED_LOADS;
|
||||
SymOpts |= SYMOPT_EXACT_SYMBOLS;
|
||||
SymOpts |= SYMOPT_IGNORE_NT_SYMPATH;
|
||||
SymOpts |= SYMOPT_UNDNAME;
|
||||
|
||||
SymSetOptions(SymOpts);
|
||||
|
||||
return SymInitialize((HANDLE)Handle, NULL, FALSE);
|
||||
}
|
||||
|
||||
@@ -133,7 +193,9 @@ void FDbgHelpResolver::FreeSyms() const
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
uint32 FDbgHelpResolver::Run()
|
||||
{
|
||||
while (bRunWorkerThread)
|
||||
const bool bInitialized = SetupSyms();
|
||||
|
||||
while (bInitialized && bRunWorkerThread)
|
||||
{
|
||||
// Prioritize queued module loads
|
||||
while (!LoadSymbolsQueue.IsEmpty() && bRunWorkerThread)
|
||||
@@ -141,7 +203,7 @@ uint32 FDbgHelpResolver::Run()
|
||||
FQueuedModule Item;
|
||||
if (LoadSymbolsQueue.Dequeue(Item))
|
||||
{
|
||||
LoadModuleSymbols(Item.Base, Item.Size, Item.ImagePath);
|
||||
LoadModuleSymbols(Item.Module, Item.Path, Item.ImageId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +232,14 @@ uint32 FDbgHelpResolver::Run()
|
||||
return 0;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
void FDbgHelpResolver::Start()
|
||||
{
|
||||
// Start the worker thread
|
||||
bRunWorkerThread = true;
|
||||
Thread = FRunnableThread::Create(this, TEXT("DbgHelpWorker"), 0, TPri_Normal);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
void FDbgHelpResolver::Stop()
|
||||
{
|
||||
@@ -191,15 +261,34 @@ void FDbgHelpResolver::ResolveSymbol(uint64 Address, FResolvedSymbol* Target)
|
||||
{
|
||||
check(Target);
|
||||
|
||||
const FModuleEntry* Module = GetModuleForAddress(Address);
|
||||
if (!Module)
|
||||
if (Target->Result.load() == ESymbolQueryResult::OK)
|
||||
{
|
||||
++SymbolsFailed;
|
||||
UpdateResolvedSymbol(Target, ESymbolQueryResult::NotLoaded, GUnknownModuleTextDbgHelp,
|
||||
return;
|
||||
}
|
||||
|
||||
const FModuleEntry* Entry = GetModuleForAddress(Address);
|
||||
if (!Entry)
|
||||
{
|
||||
++Entry->Module->Stats.Failed;
|
||||
UpdateResolvedSymbol(Target, ESymbolQueryResult::NotFound, GUnknownModuleTextDbgHelp,
|
||||
GUnknownModuleTextDbgHelp, GUnknownModuleTextDbgHelp, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const EModuleStatus ModuleStatus = Entry->Module->Status.load();
|
||||
if (ModuleStatus != EModuleStatus::Loaded)
|
||||
{
|
||||
const ESymbolQueryResult Result = ModuleStatus == EModuleStatus::VersionMismatch
|
||||
? ESymbolQueryResult::Mismatch
|
||||
: ESymbolQueryResult::NotLoaded;
|
||||
++Entry->Module->Stats.Failed;
|
||||
UpdateResolvedSymbol(Target, Result, GUnknownModuleTextDbgHelp,
|
||||
GUnknownModuleTextDbgHelp, GUnknownModuleTextDbgHelp, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
++Entry->Module->Stats.Discovered;
|
||||
|
||||
uint8 InfoBuffer[sizeof(SYMBOL_INFO) + (MaxNameLen * sizeof(char) + 1)];
|
||||
SYMBOL_INFO* Info = (SYMBOL_INFO*)InfoBuffer;
|
||||
Info->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
@@ -208,8 +297,8 @@ void FDbgHelpResolver::ResolveSymbol(uint64 Address, FResolvedSymbol* Target)
|
||||
// Find and build the symbol name
|
||||
if (!SymFromAddr((HANDLE)Handle, Address, NULL, Info))
|
||||
{
|
||||
++SymbolsFailed;
|
||||
UpdateResolvedSymbol(Target, ESymbolQueryResult::NotFound, Module->Name, GUnknownModuleTextDbgHelp,
|
||||
++Entry->Module->Stats.Failed;
|
||||
UpdateResolvedSymbol(Target, ESymbolQueryResult::NotFound, Entry->Name, GUnknownModuleTextDbgHelp,
|
||||
GUnknownModuleTextDbgHelp, 0);
|
||||
return;
|
||||
}
|
||||
@@ -223,42 +312,104 @@ void FDbgHelpResolver::ResolveSymbol(uint64 Address, FResolvedSymbol* Target)
|
||||
|
||||
if (!SymGetLineFromAddr((HANDLE)Handle, Address, &dwDisplacement, &Line))
|
||||
{
|
||||
++SymbolsFailed;
|
||||
UpdateResolvedSymbol(Target, ESymbolQueryResult::OK, Module->Name, SymbolNameStr, GUnknownModuleTextDbgHelp, 0);
|
||||
++Entry->Module->Stats.Failed;
|
||||
UpdateResolvedSymbol(Target, ESymbolQueryResult::OK, Entry->Name, SymbolNameStr, GUnknownModuleTextDbgHelp, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const TCHAR* SymbolFileStr = Session.StoreString(ANSI_TO_TCHAR(Line.FileName));
|
||||
|
||||
++SymbolsResolved;
|
||||
UpdateResolvedSymbol(Target, ESymbolQueryResult::OK, Module->Name, SymbolNameStr, SymbolFileStr, Line.LineNumber);
|
||||
++Entry->Module->Stats.Resolved;
|
||||
UpdateResolvedSymbol(Target, ESymbolQueryResult::OK, Entry->Name, SymbolNameStr, SymbolFileStr, Line.LineNumber);
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
bool FDbgHelpResolver::LoadModuleSymbols(uint64 Base, uint64 Size, const TCHAR* Path)
|
||||
void FDbgHelpResolver::LoadModuleSymbols(const FModule* Module, const TCHAR* Path, const TArrayView<const uint8> ImageId)
|
||||
{
|
||||
// Attempt to load symbols
|
||||
const DWORD64 LoadedBaseAddress = SymLoadModuleEx((HANDLE)Handle, NULL, TCHAR_TO_ANSI(Path), NULL, Base, Size, NULL, 0);
|
||||
const bool bSymbolsLoaded = Base == LoadedBaseAddress;
|
||||
check(Module);
|
||||
|
||||
if (!bSymbolsLoaded)
|
||||
const uint64 Base = Module->Base;
|
||||
const uint32 Size = Module->Size;
|
||||
|
||||
// Setup symbol search path
|
||||
{
|
||||
UE_LOG(LogDbgHelp, Warning, TEXT("Unable to load symbols for %s at 0x%016llx"), Path, Base);
|
||||
TAnsiStringBuilder<1024> UserSearchPath;
|
||||
UserSearchPath << Path << ";";
|
||||
Algo::ForEach(SymbolSearchPaths, [&UserSearchPath] (const FString& Path){ UserSearchPath.Appendf("%s;", TCHAR_TO_ANSI(*Path));});
|
||||
|
||||
if (!SymSetSearchPath((HANDLE)Handle, UserSearchPath.ToString()))
|
||||
{
|
||||
UE_LOG(LogDbgHelp, Warning, TEXT("Unable to set symbol search path to '%s'."), UserSearchPath.ToString());
|
||||
}
|
||||
TCHAR OutPath[1024];
|
||||
SymGetSearchPathW((HANDLE) Handle, OutPath, 1024);
|
||||
UE_LOG(LogDbgHelp, Display, TEXT("Search path: %s"), OutPath);
|
||||
}
|
||||
|
||||
// Attempt to load symbols
|
||||
const DWORD64 LoadedBaseAddress = SymLoadModuleEx((HANDLE)Handle, NULL, TCHAR_TO_ANSI(Module->Name), NULL, Base, Size, NULL, 0);
|
||||
const bool bModuleLoaded = Base == LoadedBaseAddress;
|
||||
bool bPdbLoaded = true;
|
||||
bool bPdbMatchesImage = true;
|
||||
IMAGEHLP_MODULE ModuleInfo;
|
||||
|
||||
if (bModuleLoaded)
|
||||
{
|
||||
ModuleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE);
|
||||
SymGetModuleInfo((HANDLE)Handle, Base, &ModuleInfo);
|
||||
|
||||
if (ModuleInfo.SymType != SymPdb)
|
||||
{
|
||||
bPdbLoaded = false;
|
||||
}
|
||||
// Check image checksum if it exists
|
||||
else if (!ImageId.IsEmpty())
|
||||
{
|
||||
// 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);
|
||||
const FGuid* ModuleGuid = (FGuid*) ImageId.GetData();
|
||||
const FGuid* PdbGuid = (FGuid*) &ModuleInfo.PdbSig70;
|
||||
bPdbMatchesImage = *ModuleGuid == *PdbGuid;
|
||||
}
|
||||
}
|
||||
|
||||
TStringBuilder<256> StatusMessage;
|
||||
EModuleStatus Status;
|
||||
if (!bModuleLoaded || !bPdbLoaded)
|
||||
{
|
||||
// Unload the module, otherwise any subsequent attempts to load module with another
|
||||
// path will fail.
|
||||
SymUnloadModule((HANDLE)Handle, Base);
|
||||
StatusMessage.Appendf(TEXT("Unable to load symbols for %s"), Path);
|
||||
Status = EModuleStatus::Failed;
|
||||
++ModulesFailed;
|
||||
}
|
||||
else if (!bPdbMatchesImage)
|
||||
{
|
||||
// Unload the module, otherwise any subsequent attempts to load module with another
|
||||
// path will fail.
|
||||
SymUnloadModule((HANDLE)Handle, Base);
|
||||
StatusMessage.Appendf(TEXT("Unable to load symbols for %s, pdb signature does not match."), Path);
|
||||
Status = EModuleStatus::VersionMismatch;
|
||||
++ModulesFailed;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogDbgHelp, Display, TEXT("Loaded symbols for %s at 0x%016llx."), Path, Base);
|
||||
StatusMessage.Appendf(TEXT("Loaded symbols for %s from %s."), Path, ANSI_TO_TCHAR(ModuleInfo.LoadedImageName));
|
||||
Status = EModuleStatus::Loaded;
|
||||
++ModulesLoaded;
|
||||
}
|
||||
|
||||
// Update the module entry with the result
|
||||
FScopeLock _(&ModulesCs);
|
||||
const int32 EntryIdx = Algo::BinarySearchBy(LoadedModules, Base, [](const FModuleEntry& Entry) { return Entry.Base; });
|
||||
check(EntryIdx >= 0 && EntryIdx < LoadedModules.Num());
|
||||
LoadedModules[EntryIdx].bSymbolsLoaded = bSymbolsLoaded;
|
||||
|
||||
return bSymbolsLoaded;
|
||||
check(EntryIdx != INDEX_NONE);
|
||||
const FModuleEntry& Entry = LoadedModules[EntryIdx];
|
||||
|
||||
// Make the status visible to the world
|
||||
Entry.Module->StatusMessage = Session.StoreString(StatusMessage.ToView());
|
||||
Entry.Module->Status.store(Status);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user