From 2a40f6f990986837abb4389801cc2227d5ab4bb5 Mon Sep 17 00:00:00 2001 From: Bartosz Taudul Date: Sun, 30 Aug 2026 16:14:18 +0200 Subject: [PATCH] Converge the external image list on refresh. The list only ever added entries, so a mapping the target re-mapped or unloaded stayed in the list and could shadow the new mapping or keep serving an already-unloaded library. Rebuild it from the target's current maps on each refresh, carrying over the cached backtrace state for unchanged mappings and dropping entries the current maps no longer confirm. The dropped entries' paths are abandoned rather than freed: queued callstack items still carry the path pointer, which the worker thread reads at its own pace. Strip the kernel's " (deleted)" suffix so unlinked images converge to their canonical path. --- public/client/TracyCallstack.cpp | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index 125ff767..994ad943 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -353,7 +353,6 @@ void DestroyImageCaches() # include # include - static constexpr uint32_t ExtPT_LOAD = 1; struct ExternalImageEntry @@ -361,6 +360,7 @@ struct ExternalImageEntry uint64_t startAddress; uint64_t endAddress; uint64_t loadBias; + uint64_t mapsOffset; char* path; backtrace_state* btState; bool btAttempted; @@ -370,7 +370,6 @@ static FastVector* s_extImages = nullptr; static pid_t s_externalTargetPid = 0; static char s_externalTargetName[64] = {}; static uint64_t s_externalTargetExeMtime = 0; -static bool s_extImagesSorted = true; // Wall-clock second of the last /proc//maps re-parse. Used to rate-limit // refreshes so addresses that never resolve (JIT, vDSO, stack) do not trigger // a full re-parse on every symbolization. @@ -485,6 +484,8 @@ static void ParseExternalProcMaps( pid_t pid ) FILE* f = fopen( mapPath, "r" ); if( !f ) return; + FastVector fresh( 64 ); + char line[1024]; while( fgets( line, sizeof( line ), f ) ) { @@ -501,10 +502,21 @@ static void ParseExternalProcMaps( pid_t pid ) while( *pathname == ' ' || *pathname == '\t' ) pathname++; size_t plen = strlen( pathname ); while( plen > 0 && ( pathname[plen-1] == '\n' || pathname[plen-1] == '\r' ) ) plen--; + if( plen >= 10 && strncmp( pathname + plen - 10, " (deleted)", 10 ) == 0 ) plen -= 10; pathname[plen] = '\0'; if( plen == 0 || pathname[0] != '/' ) continue; - if( std::find_if( s_extImages->begin(), s_extImages->end(), [start]( const ExternalImageEntry& e ) { return e.startAddress == start; } ) != s_extImages->end() ) continue; + + // list is sorted by start address + auto it = std::lower_bound( s_extImages->begin(), s_extImages->end(), start, + []( const ExternalImageEntry& e, uint64_t a ) { return e.startAddress > a; } ); + if( it != s_extImages->end() && it->startAddress == start + && it->endAddress == end && it->mapsOffset == offset + && strcmp( it->path, pathname ) == 0 ) + { + fresh.push_next()[0] = *it; + continue; + } uint64_t pageSize = sysconf( _SC_PAGESIZE ); uint64_t loadBias = ReadElfSegmentLoadBias( pathname, start, end, offset, pageSize ); @@ -518,24 +530,20 @@ static void ParseExternalProcMaps( pid_t pid ) .startAddress = start, .endAddress = end, .loadBias = loadBias, + .mapsOffset = offset, .path = (char*)tracy_malloc( plen + 1 ), .btState = nullptr, .btAttempted = false }; memcpy( entry.path, pathname, plen + 1 ); - - s_extImagesSorted = false; - s_extImages->push_next()[0] = entry; + fresh.push_next()[0] = entry; } fclose( f ); - if( !s_extImagesSorted ) - { - std::sort( s_extImages->begin(), s_extImages->end(), - []( const ExternalImageEntry& a, const ExternalImageEntry& b ) { return a.startAddress > b.startAddress; } ); - s_extImagesSorted = true; - } + std::sort( fresh.begin(), fresh.end(), + []( const ExternalImageEntry& a, const ExternalImageEntry& b ) { return a.startAddress > b.startAddress; } ); + s_extImages->swap( fresh ); } static const ExternalImageEntry* FindExternalImage( uint64_t address )