// Copyright Epic Games, Inc. All Rights Reserved. #include "ModuleProvider.h" #include #include "Algo/Find.h" #include "Algo/Transform.h" #include "Common/CachedPagedArray.h" #include "Common/CachedStringStore.h" #include "Common/Utils.h" #include "Containers/Map.h" #include "GenericPlatform/GenericPlatformFile.h" #include "HAL/PlatformFileManager.h" #include "Misc/Paths.h" #include "Misc/PathViews.h" #include "TraceServices/Model/AnalysisCache.h" #include "TraceServices/Model/AnalysisSession.h" // Choose which symbol resolver to use. // If both are enabled and the RAD Syms library fails to initialize, it will fall back to DbgHelp (on Windows). #define USE_SYMSLIB 1 #define USE_DBGHELP 1 // Symbol files implementations #if USE_SYMSLIB #include "SymslibResolver.h" #endif #if USE_DBGHELP #include "DbgHelpResolver.h" #endif namespace TraceServices { ///////////////////////////////////////////////////////////////////// template class TModuleProvider : public IModuleAnalysisProvider { public: explicit TModuleProvider(IAnalysisSession& Session); virtual ~TModuleProvider(); //Query interface const FResolvedSymbol* GetSymbol(uint64 Address) override; uint32 GetNumModules() const override; void EnumerateModules(uint32 Start, TFunctionRef Callback) const override; FGraphEventRef LoadSymbolsForModuleUsingPath(uint64 Base, const TCHAR* Path) override; void EnumerateSymbolSearchPaths(TFunctionRef Callback) const override; void GetStats(FStats* OutStats) const override; //Analysis interface void OnModuleLoad(const FStringView& Module, uint64 Base, uint32 Size, const uint8* Checksum, uint32 ChecksumSize) override; void OnModuleUnload(uint64 Base) override; void OnAnalysisComplete() override; void SaveSymbolsToCache(IAnalysisCache& Cache); void LoadSymbolsFromCache(IAnalysisCache& Cache); private: uint32 GetNumCachedSymbolsFromModule(uint64 Base, uint32 Size); struct FSavedSymbol { uint64 Address; uint32 ModuleOffset; uint32 NameOffset; uint32 FileOffset; uint32 Line; }; mutable FRWLock ModulesLock; TPagedArray Modules; FRWLock SymbolsLock; // Persistently stored symbol strings FCachedStringStore Strings; // Efficient representation of symbols TPagedArray SymbolCache; // Lookup table to for symbols TMap SymbolCacheLookup; // Number of cached symbols that was loaded (used for stats) uint32 NumCachedSymbols; // Number of discovered symbols std::atomic SymbolsDiscovered; IAnalysisSession& Session; FString Platform; TUniquePtr Resolver; FGraphEventRef LoadSymbolsTask; bool LoadSymbolsAbort = false; }; ///////////////////////////////////////////////////////////////////// template TModuleProvider::TModuleProvider(IAnalysisSession& Session) : Modules(Session.GetLinearAllocator(), 128) , Strings(TEXT("ModuleProvider.Strings"), Session.GetCache()) , SymbolCache(Session.GetLinearAllocator(), 1024*1024) , NumCachedSymbols(0) , Session(Session) { Resolver = TUniquePtr(new SymbolResolverType(Session)); LoadSymbolsFromCache(Session.GetCache()); } ///////////////////////////////////////////////////////////////////// template TModuleProvider::~TModuleProvider() { if (LoadSymbolsTask) { LoadSymbolsAbort = true; LoadSymbolsTask->Wait(); } // Delete the resolver in order to flush all pending resolves Resolver.Reset(); SaveSymbolsToCache(Session.GetCache()); } ///////////////////////////////////////////////////////////////////// template const FResolvedSymbol* TModuleProvider::GetSymbol(uint64 Address) { { // Attempt to read from the cached symbols. FReadScopeLock _(SymbolsLock); if (const FResolvedSymbol** Entry = SymbolCacheLookup.Find(Address)) { return *Entry; } } FResolvedSymbol* ResolvedSymbol = nullptr; { FWriteScopeLock _(SymbolsLock); // Attempt again to read from the cached symbols. const FResolvedSymbol* CachedResolvedSymbol = SymbolCacheLookup.FindRef(Address); if (CachedResolvedSymbol) { return CachedResolvedSymbol; } // Add a pending entry to our cache. ResolvedSymbol = &SymbolCache.EmplaceBack(ESymbolQueryResult::Pending, nullptr, nullptr, nullptr, 0); SymbolCacheLookup.Add(Address, ResolvedSymbol); ++SymbolsDiscovered; } check(ResolvedSymbol); // If not in cache yet, queue it up in the resolver. Resolver->QueueSymbolResolve(Address, ResolvedSymbol); return ResolvedSymbol; } ///////////////////////////////////////////////////////////////////// template uint32 TModuleProvider::GetNumModules() const { FReadScopeLock _(ModulesLock); return Modules.Num(); } ///////////////////////////////////////////////////////////////////// template void TModuleProvider::EnumerateModules(uint32 Start, TFunctionRef Callback) const { FReadScopeLock _(ModulesLock); for (uint32 i = Start; i < Modules.Num(); ++i) { Callback(Modules[i]); } } ///////////////////////////////////////////////////////////////////// template FGraphEventRef TModuleProvider::LoadSymbolsForModuleUsingPath(uint64 Base, const TCHAR* Path) { FReadScopeLock _(ModulesLock); const FModule* Module = Algo::FindBy(Modules, Base, &FModule::Base); if (Module) { const FString FullPath = FPaths::ConvertRelativePathToFull(Path); if (Resolver && !FullPath.IsEmpty() && Module->Status.load() != EModuleStatus::Loaded) { // Setup a task to queue and watch the queued module. If it succeeds in resolving with the new path // re-resolve any cached symbols LoadSymbolsTask = FFunctionGraphTask::CreateAndDispatchWhenReady([this, Module, FullPath]() { auto ReloadModuleFn = [this] (const FModule* InModule, const TCHAR* InPath) { const uint32 DiscoveredSymbols = InModule->Stats.Discovered.load(); const uint64 ModuleBegin = InModule->Base; const uint64 ModuleEnd = InModule->Base + InModule->Size; auto ReresolveOnSuccess = [this, DiscoveredSymbols, ModuleBegin, ModuleEnd] (TArray>& OutSymbols) { OutSymbols.Reserve(DiscoveredSymbols); FReadScopeLock _(SymbolsLock); for (auto Pair : SymbolCacheLookup) { const uint64 Address = Pair.template Get<0>(); FResolvedSymbol* Symbol = const_cast(Pair.template Get<1>()); if (FMath::IsWithin(Address, ModuleBegin, ModuleEnd)) { OutSymbols.Add(TTuple(Address, Symbol)); } } }; Resolver->QueueModuleReload(InModule, InPath, ReresolveOnSuccess); // Wait for the resolver to do it's work while (InModule->Status.load() == EModuleStatus::Pending) { FPlatformProcess::Sleep(0.1f); } return InModule->Status.load(); }; UE_LOG(LogTraceServices, Display, TEXT("Queing symbol loading using path %s."), *FullPath); // Load the requested module const EModuleStatus Result = ReloadModuleFn(Module, *FullPath); if (Result == EModuleStatus::Loaded) { // Queue up any other failed module using the directory. IPlatformFile* PlatformFile = &FPlatformFileManager::Get().GetPlatformFile(); const FString Directory = PlatformFile->DirectoryExists(*FullPath) ? FullPath : FPaths::GetPath(FullPath); for (auto& OtherModule : Modules) { if (LoadSymbolsAbort) { return; } const EModuleStatus ModuleStatus = OtherModule.Status.load(); if (&OtherModule != Module && ModuleStatus >= EModuleStatus::FailedStatusStart) { ReloadModuleFn(&OtherModule, *Directory); } } } UE_LOG(LogTraceServices, Display, TEXT("Loading symbols for path %s complete."), *FullPath); }); return LoadSymbolsTask; } } return FGraphEventRef(); } ///////////////////////////////////////////////////////////////////// template void TModuleProvider::EnumerateSymbolSearchPaths(TFunctionRef Callback) const { Resolver->EnumerateSymbolSearchPaths(Callback); } ///////////////////////////////////////////////////////////////////// template void TModuleProvider::GetStats(FStats* OutStats) const { Resolver->GetStats(OutStats); OutStats->SymbolsDiscovered = SymbolsDiscovered.load(); // Add the cached symbols (the resolver doesn't know about them) OutStats->SymbolsResolved += NumCachedSymbols; } ///////////////////////////////////////////////////////////////////// template void TModuleProvider::OnModuleLoad(const FStringView& Module, uint64 Base, uint32 Size, const uint8* ImageId, uint32 ImageIdSize) { if (Module.Len() == 0) { return; } const FStringView NameTemp = FPathViews::GetCleanFilename(Module); const TCHAR* Name = Session.StoreString(NameTemp); const TCHAR* FullName = Session.StoreString(Module); FWriteScopeLock _(ModulesLock); FModule& NewModule = Modules.EmplaceBack(Name, FullName, Base, Size, EModuleStatus::Discovered); // The number of cached symbols for this module is equal to the number // of matching addresses in the cache. NewModule.Stats.Cached = GetNumCachedSymbolsFromModule(Base, Size); Resolver->QueueModuleLoad(ImageId, ImageIdSize, &NewModule); } ///////////////////////////////////////////////////////////////////// template void TModuleProvider::OnModuleUnload(uint64 Base) { //todo: Find entry, set bLoaded to false } ///////////////////////////////////////////////////////////////////// template void TModuleProvider::OnAnalysisComplete() { Resolver->OnAnalysisComplete(); } ///////////////////////////////////////////////////////////////////// template void TModuleProvider::SaveSymbolsToCache(IAnalysisCache& Cache) { // Create a temporary reverse lookup for symbol -> address TMap SymbolReverseLookup; SymbolReverseLookup.Reserve(SymbolCacheLookup.Num()); Algo::Transform(SymbolCacheLookup, SymbolReverseLookup, [](const TTuple& Pair) { return TTuple(Pair.Value, Pair.Key); }); // Save new symbols TCachedPagedArray SavedSymbols(TEXT("ModuleProvider.Symbols"), Cache); const uint32 NumPreviouslySavedSymbols = SavedSymbols.Num(); uint32 NumSavedSymbols = 0; for (uint32 SymbolIndex = SavedSymbols.Num(); SymbolIndex < SymbolCache.Num(); ++SymbolIndex) { const FResolvedSymbol& Symbol = SymbolCache[SymbolIndex]; if (Symbol.GetResult() != ESymbolQueryResult::OK) { continue; } const uint64* Address = SymbolReverseLookup.Find(&Symbol); const uint32 ModuleOffset = Strings.Store_GetOffset(Symbol.Module); const uint32 NameOffset = Strings.Store_GetOffset(Symbol.Name); const uint32 FileOffset = Strings.Store_GetOffset(Symbol.File); SavedSymbols.EmplaceBack(FSavedSymbol{*Address, ModuleOffset, NameOffset, FileOffset, Symbol.Line}); ++NumSavedSymbols; } UE_LOG(LogTraceServices, Display, TEXT("Added %d symbols to the %d previously saved symbols."), NumSavedSymbols, NumPreviouslySavedSymbols); } ///////////////////////////////////////////////////////////////////// template void TModuleProvider::LoadSymbolsFromCache(IAnalysisCache& Cache) { // Load saved symbols TCachedPagedArray SavedSymbols(TEXT("ModuleProvider.Symbols"), Cache); for (uint64 SymbolIndex = 0; SymbolIndex < SavedSymbols.Num(); ++SymbolIndex) { const FSavedSymbol& Symbol = SavedSymbols[SymbolIndex]; const TCHAR* Module = Strings.GetStringAtOffset(Symbol.ModuleOffset); const TCHAR* Name = Strings.GetStringAtOffset(Symbol.NameOffset); const TCHAR* File = Strings.GetStringAtOffset(Symbol.FileOffset); if (Module == nullptr || Name == nullptr || File == nullptr) { UE_LOG(LogTraceServices, Warning, TEXT("Found cached symbol (adress %llx) which referenced unknown string."), Symbol.Address); continue; } FResolvedSymbol& Resolved = SymbolCache.EmplaceBack(ESymbolQueryResult::OK, Module, Name, File, Symbol.Line); SymbolCacheLookup.Add(Symbol.Address, &Resolved); } NumCachedSymbols = SymbolCacheLookup.Num(); UE_LOG(LogTraceServices, Display, TEXT("Loaded %d symbols from cache."), SymbolCacheLookup.Num()); } ///////////////////////////////////////////////////////////////////// template uint32 TModuleProvider::GetNumCachedSymbolsFromModule(uint64 Base, uint32 Size) { uint32 Count(0); const uint64 Start = Base; const uint64 End = Base + Size; FWriteScopeLock _(SymbolsLock); for (auto& AddressSymbolPair : SymbolCacheLookup) { const uint64 Address = AddressSymbolPair.template Get<0>(); if (FMath::IsWithin(Address, Start, End)) { ++Count; } } return Count; } ///////////////////////////////////////////////////////////////////// IModuleAnalysisProvider* CreateModuleProvider(IAnalysisSession& InSession, const FAnsiStringView& InSymbolFormat) { IModuleAnalysisProvider* Provider(nullptr); #if PLATFORM_WINDOWS && USE_SYMSLIB if (!Provider && (InSymbolFormat.Equals("pdb") || InSymbolFormat.Equals("dwarf"))) { Provider = new TModuleProvider(InSession); } #endif // PLATFORM_WINDOWS && USE_SYMSLIB #if PLATFORM_WINDOWS && USE_DBGHELP if (!Provider && InSymbolFormat.Equals("pdb")) { Provider = new TModuleProvider(InSession); } #endif // PLATFORM_WINDOWS && USE_DBGHELP return Provider; } ///////////////////////////////////////////////////////////////////// FName GetModuleProviderName() { static FName Name(TEXT("ModuleProvider")); return Name; } ///////////////////////////////////////////////////////////////////// const IModuleProvider* ReadModuleProvider(const IAnalysisSession& Session) { return Session.ReadProvider(GetModuleProviderName()); } ///////////////////////////////////////////////////////////////////// } // namespace TraceServices #undef USE_SYMSLIB #undef USE_DBGHELP