diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index 7688eab2..01ded3e7 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -36,6 +36,10 @@ # include # include +# ifdef __linux__ +# include "TracyElf.hpp" +# endif + // Implementation files # include "../libbacktrace/alloc.cpp" # include "../libbacktrace/dwarf.cpp" @@ -332,6 +336,232 @@ void DestroyImageCaches() } +#ifdef __linux__ + +static constexpr uint32_t ExtPT_LOAD = 1; + +struct ExternalImageEntry +{ + uint64_t startAddress; + uint64_t endAddress; + uint64_t loadBias; + char* path; + backtrace_state* btState; + bool btAttempted; +}; + +static FastVector* s_extImages = nullptr; +static pid_t s_externalPid = 0; +static bool s_extImagesSorted = true; + +static uint64_t ReadElfMinLoadVaddr( const char* path ) +{ + int fd = open( path, O_RDONLY ); + if( fd < 0 ) return UINT64_MAX; + + elf_ehdr ehdr; + if( read( fd, &ehdr, sizeof( ehdr ) ) != sizeof( ehdr ) ) + { + close( fd ); + return UINT64_MAX; + } + + if( ehdr.e_ident[0] != 0x7f || ehdr.e_ident[1] != 'E' || + ehdr.e_ident[2] != 'L' || ehdr.e_ident[3] != 'F' ) + { + close( fd ); + return UINT64_MAX; + } + + if( ehdr.e_phoff == 0 || ehdr.e_phnum == 0 ) + { + close( fd ); + return UINT64_MAX; + } + + if( lseek( fd, ehdr.e_phoff, SEEK_SET ) == (off_t)-1 ) + { + close( fd ); + return UINT64_MAX; + } + + uint64_t minVaddr = UINT64_MAX; + for( uint16_t i = 0; i < ehdr.e_phnum; i++ ) + { + elf_phdr phdr; + if( read( fd, &phdr, sizeof( phdr ) ) != sizeof( phdr ) ) break; + if( phdr.p_type == ExtPT_LOAD ) minVaddr = std::min( minVaddr, phdr.p_vaddr ); + } + + close( fd ); + return minVaddr; +} + +static void ParseExternalProcMaps( pid_t pid ) +{ + char mapPath[64]; + snprintf( mapPath, sizeof( mapPath ), "/proc/%d/maps", (int)pid ); + FILE* f = fopen( mapPath, "r" ); + if( !f ) return; + + char line[1024]; + while( fgets( line, sizeof( line ), f ) ) + { + uint64_t start, end, offset; + uint32_t devMaj, devMin; + uint64_t inode; + char perms[8]; + int consumed = 0; + + if( sscanf( line, "%lx-%lx %7s %lx %x:%x %lu %n", &start, &end, perms, &offset, &devMaj, &devMin, &inode, &consumed ) < 7 ) continue; + if( !strchr( perms, 'x' ) ) continue; + + char* pathname = line + consumed; + while( *pathname == ' ' || *pathname == '\t' ) pathname++; + size_t plen = strlen( pathname ); + while( plen > 0 && ( pathname[plen-1] == '\n' || pathname[plen-1] == '\r' ) ) plen--; + 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; + + uint64_t minVaddr = ReadElfMinLoadVaddr( pathname ); + uint64_t loadBias; + if( minVaddr == UINT64_MAX ) + { + loadBias = start; + } + else + { + uint64_t pageSize = sysconf( _SC_PAGESIZE ); + uint64_t alignedVaddr = minVaddr & ~(pageSize - 1); + loadBias = start - alignedVaddr - offset; + } + + ExternalImageEntry entry = { + .startAddress = start, + .endAddress = end, + .loadBias = loadBias, + .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; + } + + 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; + } +} + +static const ExternalImageEntry* FindExternalImage( uint64_t address ) +{ + if( !s_extImages || s_extImages->empty() ) return nullptr; + + auto it = std::lower_bound( s_extImages->begin(), s_extImages->end(), address, + []( const ExternalImageEntry& e, uint64_t a ) { return e.startAddress > a; } ); + + if( it != s_extImages->end() && address >= it->startAddress && address < it->endAddress ) + { + return &*it; + } + return nullptr; +} + +static const ExternalImageEntry* FindExternalImageRefresh( uint64_t address ) +{ + auto entry = FindExternalImage( address ); + if( entry ) return entry; + + if( s_externalPid != 0 ) + { + ParseExternalProcMaps( s_externalPid ); + return FindExternalImage( address ); + } + return nullptr; +} + +static void ExternalBacktraceErrorCb( void* data, const char* msg, int errnum ) +{ +} + +static backtrace_state* GetExternalBtState( const ExternalImageEntry* entry ) +{ + auto* e = const_cast( entry ); + if( e->btAttempted ) return e->btState; + e->btAttempted = true; + e->btState = backtrace_create_state_for_file( e->path, 0, ExternalBacktraceErrorCb, nullptr ); + return e->btState; +} + +struct ExternalResolveData +{ + const char* name; + const char* file; + uint32_t line; + int count; +}; + +static int ExternalPcInfoCb( void* data, uintptr_t pc, uintptr_t lowaddr, const char* filename, int lineno, const char* function ) +{ + auto& rd = *(ExternalResolveData*)data; + + if( rd.count > 0 ) return 1; + rd.count++; + + if( function ) + { + const char* demangled = ___tracy_demangle( function ); + rd.name = demangled ? demangled : function; + } + else + { + rd.name = nullptr; + } + + rd.file = filename; + rd.line = lineno; + + return 0; +} + +struct ExternalSymInfoData +{ + const char* symname; + uintptr_t symval; + uintptr_t symsize; +}; + +static void ExternalSymInfoCb( void* data, uintptr_t pc, const char* symname, uintptr_t symval, uintptr_t symsize ) +{ + auto& sd = *(ExternalSymInfoData*)data; + sd.symname = symname; + sd.symval = symval; + sd.symsize = symsize; +} + +void InitExternalImageCache( pid_t pid ) +{ + s_externalPid = pid; + if( !s_extImages ) + { + s_extImages = (FastVector*)tracy_malloc( sizeof( FastVector ) ); + new (s_extImages) FastVector( 64 ); + } + ParseExternalProcMaps( pid ); +} + +#endif // __linux__ + + // 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 diff --git a/public/client/TracyCallstack.hpp b/public/client/TracyCallstack.hpp index 7d8ed6e6..8f2a0fde 100644 --- a/public/client/TracyCallstack.hpp +++ b/public/client/TracyCallstack.hpp @@ -88,6 +88,10 @@ void InitCallstackCritical(); void EndCallstack(); const char* GetKernelModulePath( uint64_t addr ); +#ifdef __linux__ +void InitExternalImageCache( pid_t pid ); +#endif + #ifdef TRACY_DEBUGINFOD const uint8_t* GetBuildIdForImage( const char* image, size_t& size ); debuginfod_client* GetDebuginfodClient();