From 9665f7ac421a2a8c17e080a9ef4b7947bfa286a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gr=C3=A9goire?= Date: Sat, 11 Oct 2025 13:45:22 +0200 Subject: [PATCH 01/12] Move ImageEntry to header in prevision of sharing it with other CPP files in the future --- public/client/TracyCallstack.cpp | 6 ------ public/client/TracyCallstack.hpp | 12 ++++++++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index 840d1858e3..13de31ef54 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -124,12 +124,6 @@ namespace tracy class ImageCache { public: - struct ImageEntry - { - void* m_startAddress = nullptr; - void* m_endAddress = nullptr; - char* m_name = nullptr; - }; ImageCache() : m_images( 512 ) diff --git a/public/client/TracyCallstack.hpp b/public/client/TracyCallstack.hpp index 1d8cd654f1..4f5a3b82c5 100644 --- a/public/client/TracyCallstack.hpp +++ b/public/client/TracyCallstack.hpp @@ -5,6 +5,18 @@ #include "../common/TracyForceInline.hpp" #include "TracyCallstack.h" +namespace tracy +{ + +struct ImageEntry +{ + void* m_startAddress = nullptr; + void* m_endAddress = nullptr; + char* m_name = nullptr; +}; + +} + #ifndef TRACY_HAS_CALLSTACK namespace tracy From 12026dae5b6cd047e9ccca4317dc9b10437d488b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gr=C3=A9goire?= Date: Sat, 11 Oct 2025 14:10:18 +0200 Subject: [PATCH 02/12] Make ImageEntry m_startAddress/m_endAddress uint64_t as it will later be used in server, and thus not pointing to current process memory --- public/client/TracyCallstack.cpp | 14 +++++++------- public/client/TracyCallstack.hpp | 6 ++++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index 13de31ef54..aea58dd797 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -136,7 +136,7 @@ class ImageCache Clear(); } - const ImageEntry* GetImageForAddress( void* address ) + const ImageEntry* GetImageForAddress( uint64_t address ) { const ImageEntry* entry = GetImageForAddressImpl( address ); if( !entry ) @@ -156,12 +156,12 @@ class ImageCache { ImageCache* cache = reinterpret_cast( data ); - const auto startAddress = reinterpret_cast( info->dlpi_addr ); + const auto startAddress = static_cast( info->dlpi_addr ); if( cache->Contains( startAddress ) ) return 0; const uint32_t headerCount = info->dlpi_phnum; assert( headerCount > 0); - const auto endAddress = reinterpret_cast( info->dlpi_addr + + const auto endAddress = static_cast( info->dlpi_addr + info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz); ImageEntry* image = cache->m_images.push_next(); @@ -186,7 +186,7 @@ class ImageCache return 0; } - bool Contains( void* startAddress ) const + bool Contains( uint64_t startAddress ) const { return std::any_of( m_images.begin(), m_images.end(), [startAddress]( const ImageEntry& entry ) { return startAddress == entry.m_startAddress; } ); } @@ -236,10 +236,10 @@ class ImageCache m_haveMainImageName = true; } - const ImageEntry* GetImageForAddressImpl( void* address ) const + const ImageEntry* GetImageForAddressImpl( uint64_t address ) const { auto it = std::lower_bound( m_images.begin(), m_images.end(), address, - []( const ImageEntry& lhs, const void* rhs ) { return lhs.m_startAddress > rhs; } ); + []( const ImageEntry& lhs, const uint64_t rhs ) { return lhs.m_startAddress > rhs; } ); if( it != m_images.end() && address < it->m_endAddress ) { @@ -1273,7 +1273,7 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) uint64_t imageBaseAddress = 0x0; #ifdef TRACY_USE_IMAGE_CACHE - const auto* image = s_imageCache->GetImageForAddress((void*)ptr); + const auto* image = s_imageCache->GetImageForAddress( ptr ); if( image ) { imageName = image->m_name; diff --git a/public/client/TracyCallstack.hpp b/public/client/TracyCallstack.hpp index 4f5a3b82c5..69cdef81a8 100644 --- a/public/client/TracyCallstack.hpp +++ b/public/client/TracyCallstack.hpp @@ -1,6 +1,8 @@ #ifndef __TRACYCALLSTACK_HPP__ #define __TRACYCALLSTACK_HPP__ +#include + #include "../common/TracyApi.h" #include "../common/TracyForceInline.hpp" #include "TracyCallstack.h" @@ -10,8 +12,8 @@ namespace tracy struct ImageEntry { - void* m_startAddress = nullptr; - void* m_endAddress = nullptr; + uint64_t m_startAddress = 0; + uint64_t m_endAddress = 0; char* m_name = nullptr; }; From 385f72f9dc0e799f5375b6ab315b4dd549ba5b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gr=C3=A9goire?= Date: Sat, 11 Oct 2025 14:13:33 +0200 Subject: [PATCH 03/12] Rename ImageCache::Contains to ContainsImage The name was a bit misleading as it could be mistaken to mean "The cache contains the address" and not as "has an image with this start address". ie: that it could be mistaken to do GetImageForAddress( startAddress ) != nullptr. --- public/client/TracyCallstack.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index aea58dd797..e046001a09 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -157,7 +157,7 @@ class ImageCache ImageCache* cache = reinterpret_cast( data ); const auto startAddress = static_cast( info->dlpi_addr ); - if( cache->Contains( startAddress ) ) return 0; + if( cache->ContainsImage( startAddress ) ) return 0; const uint32_t headerCount = info->dlpi_phnum; assert( headerCount > 0); @@ -186,7 +186,7 @@ class ImageCache return 0; } - bool Contains( uint64_t startAddress ) const + bool ContainsImage( uint64_t startAddress ) const { return std::any_of( m_images.begin(), m_images.end(), [startAddress]( const ImageEntry& entry ) { return startAddress == entry.m_startAddress; } ); } From aa68c317808fa11729a7cdbae3981ccdcbab07e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gr=C3=A9goire?= Date: Sat, 11 Oct 2025 14:25:37 +0200 Subject: [PATCH 04/12] Add path to ImageEntry in prevision of replacing KernelDriver use + add DestroyImageEntry for ImageEntry cleanup --- public/client/TracyCallstack.cpp | 8 +++++++- public/client/TracyCallstack.hpp | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index e046001a09..54b59e1eb3 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -117,6 +117,12 @@ extern "C" const char* ___tracy_demangle( const char* mangled ) namespace tracy { +void DestroyImageEntry( ImageEntry& entry ) +{ + tracy_free( entry.m_path ); + tracy_free( entry.m_name ); +} + #ifdef TRACY_USE_IMAGE_CACHE // when we have access to dl_iterate_phdr(), we can build a cache of address ranges to image paths // so we can quickly determine which image an address falls into. @@ -252,7 +258,7 @@ class ImageCache { for( ImageEntry& entry : m_images ) { - tracy_free( entry.m_name ); + DestroyImageEntry( entry ); } m_images.clear(); diff --git a/public/client/TracyCallstack.hpp b/public/client/TracyCallstack.hpp index 69cdef81a8..7d8ed6e66c 100644 --- a/public/client/TracyCallstack.hpp +++ b/public/client/TracyCallstack.hpp @@ -15,6 +15,7 @@ struct ImageEntry uint64_t m_startAddress = 0; uint64_t m_endAddress = 0; char* m_name = nullptr; + char* m_path = nullptr; }; } From 0b814a25326fd76ee1ded8237c714ceb47d05893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gr=C3=A9goire?= Date: Sat, 11 Oct 2025 14:39:26 +0200 Subject: [PATCH 05/12] Split ImageCache to seperate platform specific so that it can be used on other platforms --- public/client/TracyCallstack.cpp | 92 +++++++++++++++++++------------- 1 file changed, 55 insertions(+), 37 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index 54b59e1eb3..c5c8003c3e 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -123,44 +123,85 @@ void DestroyImageEntry( ImageEntry& entry ) tracy_free( entry.m_name ); } +class ImageCache +{ +public: + + ImageCache( size_t imageCacheCapacity = 512 ) + : m_images( imageCacheCapacity ) + { + } + + ~ImageCache() + { + Clear(); + } + + const ImageEntry* GetImageForAddress( uint64_t address ) const + { + auto it = std::lower_bound( m_images.begin(), m_images.end(), address, + []( const ImageEntry& lhs, const uint64_t rhs ) { return lhs.m_startAddress > rhs; } ); + + if( it != m_images.end() && address < it->m_endAddress ) + { + return it; + } + return nullptr; + } + + void Clear() + { + for( ImageEntry& entry : m_images ) + { + DestroyImageEntry( entry ); + } + + m_images.clear(); + } + + bool ContainsImage( uint64_t startAddress ) const + { + return std::any_of( m_images.begin(), m_images.end(), [startAddress]( const ImageEntry& entry ) { return startAddress == entry.m_startAddress; } ); + } +protected: + tracy::FastVector m_images; +}; + #ifdef TRACY_USE_IMAGE_CACHE // when we have access to dl_iterate_phdr(), we can build a cache of address ranges to image paths // so we can quickly determine which image an address falls into. // We refresh this cache only when we hit an address that doesn't fall into any known range. -class ImageCache +class ImageCacheDlIteratePhdr : public ImageCache { public: - ImageCache() - : m_images( 512 ) + ImageCacheDlIteratePhdr() { Refresh(); } - ~ImageCache() + ~ImageCacheDlIteratePhdr() { - Clear(); } const ImageEntry* GetImageForAddress( uint64_t address ) { - const ImageEntry* entry = GetImageForAddressImpl( address ); + const ImageEntry* entry = ImageCache::GetImageForAddress( address ); if( !entry ) { Refresh(); - return GetImageForAddressImpl( address ); + return ImageCache::GetImageForAddress( address ); } return entry; } private: - tracy::FastVector m_images; bool m_updated = false; bool m_haveMainImageName = false; static int Callback( struct dl_phdr_info* info, size_t size, void* data ) { - ImageCache* cache = reinterpret_cast( data ); + ImageCacheDlIteratePhdr* cache = reinterpret_cast( data ); const auto startAddress = static_cast( info->dlpi_addr ); if( cache->ContainsImage( startAddress ) ) return 0; @@ -192,11 +233,6 @@ class ImageCache return 0; } - bool ContainsImage( uint64_t startAddress ) const - { - return std::any_of( m_images.begin(), m_images.end(), [startAddress]( const ImageEntry& entry ) { return startAddress == entry.m_startAddress; } ); - } - void Refresh() { m_updated = false; @@ -241,27 +277,9 @@ class ImageCache m_haveMainImageName = true; } - - const ImageEntry* GetImageForAddressImpl( uint64_t address ) const - { - auto it = std::lower_bound( m_images.begin(), m_images.end(), address, - []( const ImageEntry& lhs, const uint64_t rhs ) { return lhs.m_startAddress > rhs; } ); - - if( it != m_images.end() && address < it->m_endAddress ) - { - return it; - } - return nullptr; - } - void Clear() { - for( ImageEntry& entry : m_images ) - { - DestroyImageEntry( entry ); - } - - m_images.clear(); + ImageCache::Clear(); m_haveMainImageName = false; } }; @@ -784,7 +802,7 @@ int cb_num; CallstackEntry cb_data[MaxCbTrace]; int cb_fixup; #ifdef TRACY_USE_IMAGE_CACHE -static ImageCache* s_imageCache = nullptr; +static ImageCacheDlIteratePhdr* s_imageCache = nullptr; #endif //#ifdef TRACY_USE_IMAGE_CACHE #ifdef TRACY_DEBUGINFOD @@ -981,8 +999,8 @@ void InitCallstack() InitRpmalloc(); #ifdef TRACY_USE_IMAGE_CACHE - s_imageCache = (ImageCache*)tracy_malloc( sizeof( ImageCache ) ); - new(s_imageCache) ImageCache(); + s_imageCache = (ImageCacheDlIteratePhdr*)tracy_malloc( sizeof( ImageCacheDlIteratePhdr ) ); + new(s_imageCache) ImageCacheDlIteratePhdr(); #endif //#ifdef TRACY_USE_IMAGE_CACHE #ifndef TRACY_SYMBOL_OFFLINE_RESOLVE @@ -1080,7 +1098,7 @@ void EndCallstack() #ifdef TRACY_USE_IMAGE_CACHE if( s_imageCache ) { - s_imageCache->~ImageCache(); + s_imageCache->~ImageCacheDlIteratePhdr(); tracy_free( s_imageCache ); } #endif //#ifdef TRACY_USE_IMAGE_CACHE From 32f94a05cb163fd8481a000d865896f80b2837e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gr=C3=A9goire?= Date: Sat, 11 Oct 2025 14:48:08 +0200 Subject: [PATCH 06/12] Support adding new entries and keeping the image cache sorted Image cache will remain potentially unsorted until first access or `Sort` is called explicitely. --- public/client/TracyCallstack.cpp | 40 ++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index c5c8003c3e..aef6bef4d0 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -137,8 +137,18 @@ class ImageCache Clear(); } - const ImageEntry* GetImageForAddress( uint64_t address ) const + ImageEntry* AddEntry( const ImageEntry& entry ) { + if( m_sorted ) m_sorted = m_images.empty() || ( entry.m_startAddress < m_images.back().m_startAddress ); + ImageEntry* newEntry = m_images.push_next(); + *newEntry = entry; + return newEntry; + } + + const ImageEntry* GetImageForAddress( uint64_t address ) + { + Sort(); + auto it = std::lower_bound( m_images.begin(), m_images.end(), address, []( const ImageEntry& lhs, const uint64_t rhs ) { return lhs.m_startAddress > rhs; } ); @@ -148,6 +158,15 @@ class ImageCache } return nullptr; } + + void Sort() + { + if( m_sorted ) return; + + std::sort( m_images.begin(), m_images.end(), + []( const ImageEntry& lhs, const ImageEntry& rhs ) { return lhs.m_startAddress > rhs.m_startAddress; } ); + m_sorted = true; + } void Clear() { @@ -156,6 +175,7 @@ class ImageCache DestroyImageEntry( entry ); } + m_sorted = true; m_images.clear(); } @@ -165,6 +185,7 @@ class ImageCache } protected: tracy::FastVector m_images; + bool m_sorted = true; }; #ifdef TRACY_USE_IMAGE_CACHE @@ -211,23 +232,24 @@ class ImageCacheDlIteratePhdr : public ImageCache const auto endAddress = static_cast( info->dlpi_addr + info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz); - ImageEntry* image = cache->m_images.push_next(); - image->m_startAddress = startAddress; - image->m_endAddress = endAddress; + ImageEntry image{}; + image.m_startAddress = startAddress; + image.m_endAddress = endAddress; // the base executable name isn't provided when iterating with dl_iterate_phdr, // we will have to patch the executable image name outside this callback if( info->dlpi_name && info->dlpi_name[0] != '\0' ) { size_t sz = strlen( info->dlpi_name ) + 1; - image->m_name = (char*)tracy_malloc( sz ); - memcpy( image->m_name, info->dlpi_name, sz ); + image.m_name = (char*)tracy_malloc( sz ); + memcpy( image.m_name, info->dlpi_name, sz ); } else { - image->m_name = nullptr; + image.m_name = nullptr; } + cache->AddEntry( image ); cache->m_updated = true; return 0; @@ -240,9 +262,7 @@ class ImageCacheDlIteratePhdr : public ImageCache if( m_updated ) { - std::sort( m_images.begin(), m_images.end(), - []( const ImageEntry& lhs, const ImageEntry& rhs ) { return lhs.m_startAddress > rhs.m_startAddress; } ); - + Sort(); // patch the main executable image name here, as calling dl_* functions inside the dl_iterate_phdr callback might cause deadlocks UpdateMainImageName(); } From 3b5b32d3079c8ae9f218855004df394de236c47c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gr=C3=A9goire?= Date: Sat, 11 Oct 2025 14:55:23 +0200 Subject: [PATCH 07/12] Replace hardcoded string duplication by CopyStringFast This can be CopyStringFast since we allocated the cache on the same thread. --- public/client/TracyCallstack.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index aef6bef4d0..5634902e0d 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -238,16 +238,7 @@ class ImageCacheDlIteratePhdr : public ImageCache // the base executable name isn't provided when iterating with dl_iterate_phdr, // we will have to patch the executable image name outside this callback - if( info->dlpi_name && info->dlpi_name[0] != '\0' ) - { - size_t sz = strlen( info->dlpi_name ) + 1; - image.m_name = (char*)tracy_malloc( sz ); - memcpy( image.m_name, info->dlpi_name, sz ); - } - else - { - image.m_name = nullptr; - } + image.m_name = info->dlpi_name && info->dlpi_name[0] != '\0' ? CopyStringFast( info->dlpi_name ) : nullptr; cache->AddEntry( image ); cache->m_updated = true; From e1e9fb680d15198a7468220df7598da36157116f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gr=C3=A9goire?= Date: Sat, 11 Oct 2025 15:42:08 +0200 Subject: [PATCH 08/12] Rename TRACY_USE_IMAGE_CACHE to TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE Windows is already using a cache, and the only platforms that won't have one for now are those without dl_iterate_phdr. --- public/client/TracyCallstack.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index 5634902e0d..7a60b71900 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -110,7 +110,7 @@ extern "C" const char* ___tracy_demangle( const char* mangled ) #endif #if defined(TRACY_USE_LIBBACKTRACE) && TRACY_HAS_CALLSTACK != 4 // dl_iterate_phdr is required for the current image cache. Need to move it to libbacktrace? -# define TRACY_USE_IMAGE_CACHE +# define TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE # include #endif @@ -188,7 +188,7 @@ class ImageCache bool m_sorted = true; }; -#ifdef TRACY_USE_IMAGE_CACHE +#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE // when we have access to dl_iterate_phdr(), we can build a cache of address ranges to image paths // so we can quickly determine which image an address falls into. // We refresh this cache only when we hit an address that doesn't fall into any known range. @@ -294,7 +294,7 @@ class ImageCacheDlIteratePhdr : public ImageCache m_haveMainImageName = false; } }; -#endif //#ifdef TRACY_USE_IMAGE_CACHE +#endif //#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE // when "TRACY_SYMBOL_OFFLINE_RESOLVE" is set, instead of fully resolving symbols at runtime, // simply resolve the offset and image name (which will be enough the resolving to be done offline) @@ -812,9 +812,9 @@ struct backtrace_state* cb_bts = nullptr; int cb_num; CallstackEntry cb_data[MaxCbTrace]; int cb_fixup; -#ifdef TRACY_USE_IMAGE_CACHE +#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE static ImageCacheDlIteratePhdr* s_imageCache = nullptr; -#endif //#ifdef TRACY_USE_IMAGE_CACHE +#endif //#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE #ifdef TRACY_DEBUGINFOD debuginfod_client* s_debuginfod; @@ -1009,10 +1009,10 @@ void InitCallstack() { InitRpmalloc(); -#ifdef TRACY_USE_IMAGE_CACHE +#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE s_imageCache = (ImageCacheDlIteratePhdr*)tracy_malloc( sizeof( ImageCacheDlIteratePhdr ) ); new(s_imageCache) ImageCacheDlIteratePhdr(); -#endif //#ifdef TRACY_USE_IMAGE_CACHE +#endif //#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE #ifndef TRACY_SYMBOL_OFFLINE_RESOLVE s_shouldResolveSymbolsOffline = ShouldResolveSymbolsOffline(); @@ -1106,13 +1106,13 @@ debuginfod_client* GetDebuginfodClient() void EndCallstack() { -#ifdef TRACY_USE_IMAGE_CACHE +#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE if( s_imageCache ) { s_imageCache->~ImageCacheDlIteratePhdr(); tracy_free( s_imageCache ); } -#endif //#ifdef TRACY_USE_IMAGE_CACHE +#endif //#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE #ifndef TRACY_DEMANGLE ___tracy_free_demangle_buffer(); #endif @@ -1307,7 +1307,7 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) const char* imageName = nullptr; uint64_t imageBaseAddress = 0x0; -#ifdef TRACY_USE_IMAGE_CACHE +#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE const auto* image = s_imageCache->GetImageForAddress( ptr ); if( image ) { From 903cde50d979b0e33c5d3904933a153b5f3c101c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gr=C3=A9goire?= Date: Sat, 11 Oct 2025 18:01:03 +0200 Subject: [PATCH 09/12] Replace windows caches with ImageCache - Introduce both s_imageCache and s_krnlCache on all platforms, even if unused (will be reused later to unify platforms handling) - This means that what userland images that used to be unsorted are now sorted --- public/client/TracyCallstack.cpp | 175 ++++++++++++++++--------------- 1 file changed, 89 insertions(+), 86 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index 7a60b71900..4370887edf 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -294,8 +294,40 @@ class ImageCacheDlIteratePhdr : public ImageCache m_haveMainImageName = false; } }; +using UserlandImageCache = ImageCacheDlIteratePhdr; +#else +using UserlandImageCache = ImageCache; #endif //#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE +static UserlandImageCache* s_imageCache; +static ImageCache* s_krnlCache; + +void CreateImageCaches() +{ + assert( s_imageCache == nullptr && s_krnlCache == nullptr ); + s_imageCache = new ( tracy_malloc( sizeof( UserlandImageCache ) ) ) UserlandImageCache(); + s_krnlCache = new ( tracy_malloc( sizeof( ImageCache ) ) ) ImageCache(); +} + +void DestroyImageCaches() +{ + if( s_krnlCache != nullptr ) + { + s_krnlCache->~ImageCache(); + tracy_free( s_krnlCache ); + s_krnlCache = nullptr; + } + + if( s_imageCache != nullptr ) + { + s_imageCache->~UserlandImageCache(); + tracy_free( s_imageCache ); + s_imageCache = nullptr; + } + +} + + // when "TRACY_SYMBOL_OFFLINE_RESOLVE" is set, instead of fully resolving symbols at runtime, // simply resolve the offset and image name (which will be enough the resolving to be done offline) #ifdef TRACY_SYMBOL_OFFLINE_RESOLVE @@ -337,26 +369,6 @@ extern "C" } } -struct ModuleCache -{ - uint64_t start; - uint64_t end; - char* name; -}; - -static FastVector* s_modCache; - - -struct KernelDriver -{ - uint64_t addr; - const char* mod; - const char* path; -}; - -KernelDriver* s_krnlCache = nullptr; -size_t s_krnlCacheCnt; - void InitCallstackCritical() { ___tracy_RtlWalkFrameChainPtr = (___tracy_t_RtlWalkFrameChain)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "RtlWalkFrameChain" ); @@ -390,35 +402,47 @@ DWORD64 DbgHelpLoadSymbolsForModule( const char* imageName, uint64_t baseOfDll, return SymLoadModuleEx( GetCurrentProcess(), nullptr, imageName, nullptr, baseOfDll, bllSize, nullptr, 0 ); } -ModuleCache* LoadSymbolsForModuleAndCache( const char* imageName, uint32_t imageNameLength, uint64_t baseOfDll, uint32_t dllSize ) +char* FormatImageName( const char* imageName, uint32_t imageNameLength ) { - DbgHelpLoadSymbolsForModule( imageName, baseOfDll, dllSize ); - - ModuleCache* cachedModule = s_modCache->push_next(); - cachedModule->start = baseOfDll; - cachedModule->end = baseOfDll + dllSize; - // when doing offline symbol resolution, we must store the full path of the dll for the resolving to work if( s_shouldResolveSymbolsOffline ) - { - cachedModule->name = (char*)tracy_malloc_fast(imageNameLength + 1); - memcpy(cachedModule->name, imageName, imageNameLength); - cachedModule->name[imageNameLength] = '\0'; + { + char* alloc = (char*)tracy_malloc_fast( imageNameLength + 1 ); + memcpy( alloc, imageName, imageNameLength ); + alloc[imageNameLength] = '\0'; + return alloc; } else { - auto ptr = imageName + imageNameLength; - while (ptr > imageName && *ptr != '\\' && *ptr != '/') ptr--; - if (ptr > imageName) ptr++; + const char* ptr = imageName + imageNameLength; + while( ptr > imageName && *ptr != '\\' && *ptr != '/' ) ptr--; + if( ptr > imageName ) ptr++; const auto namelen = imageName + imageNameLength - ptr; - cachedModule->name = (char*)tracy_malloc_fast(namelen + 3); - cachedModule->name[0] = '['; - memcpy(cachedModule->name + 1, ptr, namelen); - cachedModule->name[namelen + 1] = ']'; - cachedModule->name[namelen + 2] = '\0'; + + char* alloc = (char*)tracy_malloc_fast( namelen + 3 ); + alloc[0] = '['; + memcpy( alloc + 1, ptr, namelen ); + alloc[namelen + 1] = ']'; + alloc[namelen + 2] = '\0'; + return alloc; } +} + +ImageEntry* CacheModuleInfo( const char* imagePath, uint32_t imageNameLength, uint64_t baseOfDll, uint32_t dllSize ) +{ + ImageEntry moduleEntry = {}; + moduleEntry.m_startAddress = baseOfDll; + moduleEntry.m_endAddress = baseOfDll + dllSize; + moduleEntry.m_path = CopyStringFast( imagePath, imageNameLength ); + moduleEntry.m_name = FormatImageName( imagePath, imageNameLength ); - return cachedModule; + return s_imageCache->AddEntry( moduleEntry ); +} + +ImageEntry* LoadSymbolsForModuleAndCache( const char* imagePath, uint32_t imageNameLength, uint64_t baseOfDll, uint32_t dllSize ) +{ + DbgHelpLoadSymbolsForModule( imagePath, baseOfDll, dllSize ); + return CacheModuleInfo( imagePath, imageNameLength, baseOfDll, dllSize ); } void InitCallstack() @@ -431,6 +455,8 @@ void InitCallstack() TracyDebug("TRACY: enabling offline symbol resolving!\n"); } + CreateImageCaches(); + DbgHelpInit(); #ifdef TRACY_DBGHELP_LOCK @@ -457,8 +483,6 @@ void InitCallstack() const auto windirlen = strlen( windir ); const auto sz = needed / sizeof( LPVOID ); - s_krnlCache = (KernelDriver*)tracy_malloc( sizeof(KernelDriver) * sz ); - int cnt = 0; for( size_t i=0; i", 2 ); - s_krnlCache[cnt] = KernelDriver { (uint64_t)dev[i], buf }; + + ImageEntry kernelDriver{}; + kernelDriver.m_startAddress = (uint64_t)dev[i]; + kernelDriver.m_endAddress = 0; + kernelDriver.m_name = buf; + kernelDriver.m_path = nullptr; const auto len = GetDeviceDriverFileNameA( dev[i], fn, sizeof( fn ) ); if( len != 0 ) @@ -485,24 +514,16 @@ void InitCallstack() } DbgHelpLoadSymbolsForModule( path, (DWORD64)dev[i], 0 ); - - const auto psz = strlen( path ); - auto pptr = (char*)tracy_malloc_fast( psz+1 ); - memcpy( pptr, path, psz ); - pptr[psz] = '\0'; - s_krnlCache[cnt].path = pptr; + + kernelDriver.m_path = CopyString( path ); } - cnt++; + s_krnlCache->AddEntry(kernelDriver); } } - s_krnlCacheCnt = cnt; - std::sort( s_krnlCache, s_krnlCache + s_krnlCacheCnt, []( const KernelDriver& lhs, const KernelDriver& rhs ) { return lhs.addr > rhs.addr; } ); + s_krnlCache->Sort(); } - s_modCache = (FastVector*)tracy_malloc( sizeof( FastVector ) ); - new(s_modCache) FastVector( 512 ); - HANDLE proc = GetCurrentProcess(); HMODULE mod[1024]; if( initTimeModuleLoad && EnumProcessModules( proc, mod, sizeof( mod ), &needed ) != 0 ) @@ -532,6 +553,7 @@ void InitCallstack() void EndCallstack() { + DestroyImageCaches(); } const char* DecodeCallstackPtrFast( uint64_t ptr ) @@ -568,9 +590,9 @@ const char* GetKernelModulePath( uint64_t addr ) { assert( addr >> 63 != 0 ); if( !s_krnlCache ) return nullptr; - auto it = std::lower_bound( s_krnlCache, s_krnlCache + s_krnlCacheCnt, addr, []( const KernelDriver& lhs, const uint64_t& rhs ) { return lhs.addr > rhs; } ); - if( it == s_krnlCache + s_krnlCacheCnt ) return nullptr; - return it->path; + const ImageEntry* imageEntry = s_krnlCache->GetImageForAddress( addr ); + if( imageEntry ) return imageEntry->m_path; + return nullptr; } struct ModuleNameAndBaseAddress @@ -583,24 +605,13 @@ ModuleNameAndBaseAddress GetModuleNameAndPrepareSymbols( uint64_t addr ) { if( ( addr >> 63 ) != 0 ) { - if( s_krnlCache ) - { - auto it = std::lower_bound( s_krnlCache, s_krnlCache + s_krnlCacheCnt, addr, []( const KernelDriver& lhs, const uint64_t& rhs ) { return lhs.addr > rhs; } ); - if( it != s_krnlCache + s_krnlCacheCnt ) - { - return ModuleNameAndBaseAddress{ it->mod, it->addr }; - } - } + const ImageEntry* entry = s_krnlCache->GetImageForAddress( addr ); + if( entry != nullptr ) return ModuleNameAndBaseAddress{ entry->m_name, entry->m_startAddress }; return ModuleNameAndBaseAddress{ "", addr }; } - for( auto& v : *s_modCache ) - { - if( addr >= v.start && addr < v.end ) - { - return ModuleNameAndBaseAddress{ v.name, v.start }; - } - } + const ImageEntry* entry = s_imageCache->GetImageForAddress( addr ); + if( entry != nullptr ) return ModuleNameAndBaseAddress{ entry->m_name, entry->m_startAddress }; HANDLE proc = GetCurrentProcess(); // Do not use FreeLibrary because we set the flag GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT @@ -622,8 +633,8 @@ ModuleNameAndBaseAddress GetModuleNameAndPrepareSymbols( uint64_t addr ) if( nameLength > 0 ) { // since this is the first time we encounter this module, load its symbols (needed for modules loaded after SymInitialize) - ModuleCache* cachedModule = LoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage ); - return ModuleNameAndBaseAddress{ cachedModule->name, cachedModule->start }; + ImageEntry* cachedModule = LoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage ); + return ModuleNameAndBaseAddress{ cachedModule->m_name, cachedModule->m_startAddress }; } } } @@ -812,9 +823,6 @@ struct backtrace_state* cb_bts = nullptr; int cb_num; CallstackEntry cb_data[MaxCbTrace]; int cb_fixup; -#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE -static ImageCacheDlIteratePhdr* s_imageCache = nullptr; -#endif //#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE #ifdef TRACY_DEBUGINFOD debuginfod_client* s_debuginfod; @@ -1010,8 +1018,7 @@ void InitCallstack() InitRpmalloc(); #ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE - s_imageCache = (ImageCacheDlIteratePhdr*)tracy_malloc( sizeof( ImageCacheDlIteratePhdr ) ); - new(s_imageCache) ImageCacheDlIteratePhdr(); + CreateImageCaches(); #endif //#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE #ifndef TRACY_SYMBOL_OFFLINE_RESOLVE @@ -1107,11 +1114,7 @@ debuginfod_client* GetDebuginfodClient() void EndCallstack() { #ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE - if( s_imageCache ) - { - s_imageCache->~ImageCacheDlIteratePhdr(); - tracy_free( s_imageCache ); - } + DestroyImageCaches(); #endif //#ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE #ifndef TRACY_DEMANGLE ___tracy_free_demangle_buffer(); @@ -1312,7 +1315,7 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) if( image ) { imageName = image->m_name; - imageBaseAddress = uint64_t(image->m_startAddress); + imageBaseAddress = uint64_t( image->m_startAddress ); } #else Dl_info dlinfo; From 2bdefc2aa3a865fb703f5539376932c16dad679a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gr=C3=A9goire?= Date: Sat, 11 Oct 2025 18:05:02 +0200 Subject: [PATCH 10/12] Move drivers and modules caching to their own functions --- public/client/TracyCallstack.cpp | 72 +++++++++++++++++++------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index 4370887edf..0b18ee1f3e 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -445,38 +445,11 @@ ImageEntry* LoadSymbolsForModuleAndCache( const char* imagePath, uint32_t imageN return CacheModuleInfo( imagePath, imageNameLength, baseOfDll, dllSize ); } -void InitCallstack() +static void CacheProcessDrivers() { -#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE - s_shouldResolveSymbolsOffline = ShouldResolveSymbolsOffline(); -#endif //#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE - if( s_shouldResolveSymbolsOffline ) - { - TracyDebug("TRACY: enabling offline symbol resolving!\n"); - } - - CreateImageCaches(); - - DbgHelpInit(); - -#ifdef TRACY_DBGHELP_LOCK - DBGHELP_LOCK; -#endif - - // use TRACY_NO_DBGHELP_INIT_LOAD=1 to disable preloading of driver - // and process module symbol loading at startup time - they will be loaded on demand later - // Sometimes this process can take a very long time and prevent resolving callstack frames - // symbols during that time. - const char* noInitLoadEnv = GetEnvVar( "TRACY_NO_DBGHELP_INIT_LOAD" ); - const bool initTimeModuleLoad = !( noInitLoadEnv && noInitLoadEnv[0] == '1' ); - if ( !initTimeModuleLoad ) - { - TracyDebug("TRACY: skipping init time dbghelper module load\n"); - } - DWORD needed; LPVOID dev[4096]; - if( initTimeModuleLoad && EnumDeviceDrivers( dev, sizeof(dev), &needed ) != 0 ) + if( EnumDeviceDrivers( dev, sizeof(dev), &needed ) != 0 ) { char windir[MAX_PATH]; if( !GetWindowsDirectoryA( windir, sizeof( windir ) ) ) memcpy( windir, "c:\\windows", 11 ); @@ -523,10 +496,14 @@ void InitCallstack() } s_krnlCache->Sort(); } +} +static void CacheProcessModules() +{ + DWORD needed; HANDLE proc = GetCurrentProcess(); HMODULE mod[1024]; - if( initTimeModuleLoad && EnumProcessModules( proc, mod, sizeof( mod ), &needed ) != 0 ) + if( EnumProcessModules( proc, mod, sizeof( mod ), &needed ) != 0 ) { const auto sz = needed / sizeof( HMODULE ); for( size_t i=0; i Date: Sat, 11 Oct 2025 18:08:55 +0200 Subject: [PATCH 11/12] `IsKernelAddress` helper --- public/client/TracyCallstack.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index 0b18ee1f3e..94af1962e9 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -117,6 +117,10 @@ extern "C" const char* ___tracy_demangle( const char* mangled ) namespace tracy { +static bool IsKernelAddress(uint64_t addr) { + return (addr >> 63) != 0; +} + void DestroyImageEntry( ImageEntry& entry ) { tracy_free( entry.m_path ); @@ -600,7 +604,7 @@ const char* DecodeCallstackPtrFast( uint64_t ptr ) const char* GetKernelModulePath( uint64_t addr ) { - assert( addr >> 63 != 0 ); + assert( IsKernelAddress( addr ) ); if( !s_krnlCache ) return nullptr; const ImageEntry* imageEntry = s_krnlCache->GetImageForAddress( addr ); if( imageEntry ) return imageEntry->m_path; @@ -615,7 +619,7 @@ struct ModuleNameAndBaseAddress ModuleNameAndBaseAddress GetModuleNameAndPrepareSymbols( uint64_t addr ) { - if( ( addr >> 63 ) != 0 ) + if( IsKernelAddress( addr ) ) { const ImageEntry* entry = s_krnlCache->GetImageForAddress( addr ); if( entry != nullptr ) return ModuleNameAndBaseAddress{ entry->m_name, entry->m_startAddress }; @@ -1317,7 +1321,7 @@ void GetSymbolForOfflineResolve(void* address, uint64_t imageBaseAddress, Callst CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) { InitRpmalloc(); - if( ptr >> 63 == 0 ) + if ( !IsKernelAddress( ptr ) ) { const char* imageName = nullptr; uint64_t imageBaseAddress = 0x0; From 8ccc76c66b26f3e4f13f613b5cae7f35363f2294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gr=C3=A9goire?= Date: Fri, 24 Oct 2025 13:07:29 +0200 Subject: [PATCH 12/12] Use CopyStringFast in FormatImageName --- public/client/TracyCallstack.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index 94af1962e9..7ab6b1c4c6 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -410,11 +410,8 @@ char* FormatImageName( const char* imageName, uint32_t imageNameLength ) { // when doing offline symbol resolution, we must store the full path of the dll for the resolving to work if( s_shouldResolveSymbolsOffline ) - { - char* alloc = (char*)tracy_malloc_fast( imageNameLength + 1 ); - memcpy( alloc, imageName, imageNameLength ); - alloc[imageNameLength] = '\0'; - return alloc; + { + return CopyStringFast( imageName, imageNameLength ); } else {