diff --git a/CMakeLists.txt b/CMakeLists.txt index 559f75e7..578351fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,7 @@ set_option(TRACY_NO_CRASH_HANDLER "Disable crash handling" OFF) set_option(TRACY_TIMER_FALLBACK "Use lower resolution timers" OFF) set_option(TRACE_CLIENT_LIBUNWIND_BACKTRACE "Use libunwind backtracing where supported" OFF) set_option(TRACY_SYMBOL_OFFLINE_RESOLVE "Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution" OFF) +set_option(TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT "Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation" OFF) if(NOT TRACY_STATIC) target_compile_definitions(TracyClient PRIVATE TRACY_EXPORTS) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index e346647e..1d31ecc9 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -90,9 +90,133 @@ extern "C" const char* ___tracy_demangle( const char* mangled ) #endif #endif +#if TRACY_HAS_CALLSTACK == 3 +# define TRACY_USE_IMAGE_CACHE +# include +#endif + namespace tracy { +#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 +{ +public: + struct ImageEntry + { + void* m_startAddress = nullptr; + void* m_endAddress = nullptr; + char* m_name = nullptr; + }; + + ImageCache() + { + m_images = (FastVector*)tracy_malloc( sizeof( FastVector ) ); + new(m_images) FastVector( 512 ); + + Refresh(); + } + + ~ImageCache() + { + Clear(); + + m_images->~FastVector(); + tracy_free( m_images ); + } + + const ImageEntry* GetImageForAddress( void* address ) + { + const ImageEntry* entry = GetImageForAddressImpl( address ); + if( !entry ) + { + Refresh(); + return GetImageForAddressImpl( address ); + } + return entry; + } + + +private: + tracy::FastVector* m_images; + + static int Callback( struct dl_phdr_info* info, size_t size, void* data ) + { + ImageCache* cache = reinterpret_cast( data ); + + ImageEntry* image = cache->m_images->push_next(); + image->m_startAddress = reinterpret_cast( info->dlpi_addr ); + const uint32_t headerCount = info->dlpi_phnum; + assert( headerCount > 0); + image->m_endAddress = reinterpret_cast( info->dlpi_addr + + info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz); + + const char* imageName = nullptr; + // the base executable name isn't provided when iterating with dl_iterate_phdr, get it with dladdr() + if( info->dlpi_name && info->dlpi_name[0] != '\0' ) + { + imageName = info->dlpi_name; + } + else + { + Dl_info dlInfo; + if( dladdr( (void *)info->dlpi_addr, &dlInfo ) ) + { + imageName = dlInfo.dli_fname; + } + } + + if(imageName != nullptr) + { + size_t sz = strlen( imageName ) + 1; + image->m_name = (char*)tracy_malloc( sz ); + memcpy( (void*)image->m_name, imageName, sz ); + } + else + { + image->m_name = nullptr; + } + + return 0; + } + + void Refresh() + { + Clear(); + + dl_iterate_phdr( Callback, this ); + + std::sort( m_images->begin(), m_images->end(), + []( const ImageEntry& lhs, const ImageEntry& rhs ) { return lhs.m_startAddress > rhs.m_startAddress; } ); + } + + const ImageEntry* GetImageForAddressImpl( void* 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; } ); + + if( it != m_images->end() && address < it->m_endAddress ) + { + return it; + } + return nullptr; + } + + void Clear() + { + for( ImageEntry& entry : *m_images ) + { + tracy_free( entry.m_name ); + } + + m_images->clear(); + } +}; +#endif //#ifdef TRACY_USE_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) #ifdef TRACY_SYMBOL_OFFLINE_RESOLVE @@ -607,6 +731,9 @@ struct backtrace_state* cb_bts = nullptr; int cb_num; CallstackEntry cb_data[MaxCbTrace]; int cb_fixup; +#ifdef TRACY_USE_IMAGE_CACHE +static ImageCache* s_imageCache = nullptr; +#endif //#ifdef TRACY_USE_IMAGE_CACHE #ifdef TRACY_DEBUGINFOD debuginfod_client* s_debuginfod; @@ -779,6 +906,13 @@ void InitCallstackCritical() void InitCallstack() { + InitRpmalloc(); + +#ifdef TRACY_USE_IMAGE_CACHE + s_imageCache = (ImageCache*)tracy_malloc( sizeof( ImageCache ) ); + new(s_imageCache) ImageCache(); +#endif //#ifdef TRACY_USE_IMAGE_CACHE + #ifndef TRACY_SYMBOL_OFFLINE_RESOLVE s_shouldResolveSymbolsOffline = ShouldResolveSymbolsOffline(); #endif //#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE @@ -869,6 +1003,13 @@ debuginfod_client* GetDebuginfodClient() void EndCallstack() { +#ifdef TRACY_USE_IMAGE_CACHE + if( s_imageCache ) + { + s_imageCache->~ImageCache(); + tracy_free( s_imageCache ); + } +#endif //#ifdef TRACY_USE_IMAGE_CACHE #ifndef TRACY_DEMANGLE ___tracy_free_demangle_buffer(); #endif @@ -1041,12 +1182,12 @@ void SymInfoError( void* /*data*/, const char* /*msg*/, int /*errnum*/ ) cb_data[cb_num-1].symAddr = 0; } -void GetSymbolForOfflineResolve(void* address, Dl_info& dlinfo, CallstackEntry& cbEntry) +void GetSymbolForOfflineResolve(void* address, uint64_t imageBaseAddress, CallstackEntry& cbEntry) { // tagged with a string that we can identify as an unresolved symbol cbEntry.name = CopyStringFast( "[unresolved]" ); // set .so relative offset so it can be resolved offline - cbEntry.symAddr = (uint64_t)address - (uint64_t)(dlinfo.dli_fbase); + cbEntry.symAddr = (uint64_t)address - imageBaseAddress; cbEntry.symLen = 0x0; cbEntry.file = CopyStringFast( "[unknown]" ); cbEntry.line = 0; @@ -1057,17 +1198,29 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) InitRpmalloc(); if( ptr >> 63 == 0 ) { - const char* symloc = nullptr; + const char* imageName = nullptr; + uint64_t imageBaseAddress = 0x0; + +#ifdef TRACY_USE_IMAGE_CACHE + const auto* image = s_imageCache->GetImageForAddress((void*)ptr); + if( image ) + { + imageName = image->m_name; + imageBaseAddress = uint64_t(image->m_startAddress); + } +#else Dl_info dlinfo; if( dladdr( (void*)ptr, &dlinfo ) ) { - symloc = dlinfo.dli_fname; + imageName = dlinfo.dli_fname; + imageBaseAddress = uint64_t( dlinfo.dli_fbase ); } +#endif if( s_shouldResolveSymbolsOffline ) { cb_num = 1; - GetSymbolForOfflineResolve( (void*)ptr, dlinfo, cb_data[0] ); + GetSymbolForOfflineResolve( (void*)ptr, imageBaseAddress, cb_data[0] ); } else { @@ -1078,7 +1231,7 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) backtrace_syminfo( cb_bts, ptr, SymInfoCallback, SymInfoError, nullptr ); } - return { cb_data, uint8_t( cb_num ), symloc ? symloc : "[unknown]" }; + return { cb_data, uint8_t( cb_num ), imageName ? imageName : "[unknown]" }; } #ifdef __linux else if( s_kernelSym ) diff --git a/public/libbacktrace/dwarf.cpp b/public/libbacktrace/dwarf.cpp index f3899cbc..b6d681aa 100644 --- a/public/libbacktrace/dwarf.cpp +++ b/public/libbacktrace/dwarf.cpp @@ -4251,6 +4251,19 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, } } +bool dwarf_fileline_dwarf_lookup_pc_in_all_entries(struct backtrace_state *state, uintptr_t pc, + backtrace_full_callback callback, backtrace_error_callback error_callback, void *data, + int& found, int ret) +{ + for (struct dwarf_data* ddata = (struct dwarf_data *)state->fileline_data; + ddata != NULL; + ddata = ddata->next) + { + ret = dwarf_lookup_pc(state, ddata, pc, callback, error_callback, data, &found); + if (ret != 0 || found) return true; + } + return false; +} /* Return the file/line information for a PC using the DWARF mapping we built earlier. */ @@ -4262,20 +4275,30 @@ dwarf_fileline (struct backtrace_state *state, uintptr_t pc, { struct dwarf_data *ddata; int found; - int ret; + int ret = 0; if (!state->threaded) + { + if (dwarf_fileline_dwarf_lookup_pc_in_all_entries(state, pc, callback, error_callback, data, found, ret)) { - for (ddata = (struct dwarf_data *) state->fileline_data; - ddata != NULL; - ddata = ddata->next) - { - ret = dwarf_lookup_pc (state, ddata, pc, callback, error_callback, - data, &found); - if (ret != 0 || found) - return ret; - } + return ret; } + + // if we failed to obtain an entry in range, it can mean that the address map has been changed and new entries + // have been loaded in the meantime. Request a refresh and try again. + if (state->request_known_address_ranges_refresh_fn) + { + int new_range_count = state->request_known_address_ranges_refresh_fn(state, pc); + if (new_range_count > 0) + { + if (dwarf_fileline_dwarf_lookup_pc_in_all_entries(state, pc, callback, error_callback, data, found, ret)) + { + return ret; + } + } + } + + } else { struct dwarf_data **pp; diff --git a/public/libbacktrace/elf.cpp b/public/libbacktrace/elf.cpp index c65bc4e7..22a4ba20 100644 --- a/public/libbacktrace/elf.cpp +++ b/public/libbacktrace/elf.cpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. */ #include #include #include +#include #ifdef HAVE_DL_ITERATE_PHDR #include @@ -7350,13 +7351,37 @@ struct PhdrIterate { char* dlpi_name; ElfW(Addr) dlpi_addr; + ElfW(Addr) dlpi_end_addr; }; FastVector s_phdrData(16); +struct ElfAddrRange +{ + ElfW(Addr) dlpi_addr; + ElfW(Addr) dlpi_end_addr; +}; +FastVector s_sortedKnownElfRanges(16); + +static int address_in_known_elf_ranges(uintptr_t pc) +{ + auto it = std::lower_bound( s_sortedKnownElfRanges.begin(), s_sortedKnownElfRanges.end(), pc, + []( const ElfAddrRange& lhs, const uintptr_t rhs ) { return uintptr_t(lhs.dlpi_addr) > rhs; } ); + if( it != s_sortedKnownElfRanges.end() && pc <= it->dlpi_end_addr ) + { + return true; + } + return false; +} + static int phdr_callback_mock (struct dl_phdr_info *info, size_t size ATTRIBUTE_UNUSED, void *pdata) { + if( address_in_known_elf_ranges(info->dlpi_addr) ) + { + return 0; + } + auto ptr = s_phdrData.push_next(); if (info->dlpi_name) { @@ -7366,6 +7391,12 @@ phdr_callback_mock (struct dl_phdr_info *info, size_t size ATTRIBUTE_UNUSED, } else ptr->dlpi_name = nullptr; ptr->dlpi_addr = info->dlpi_addr; + + // calculate the end address as well, so we can quickly determine if a PC is within the range of this image + ptr->dlpi_end_addr = uintptr_t(info->dlpi_addr) + (info->dlpi_phnum ? uintptr_t( + info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + + info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz) : 0); + return 0; } @@ -7422,6 +7453,66 @@ phdr_callback (struct PhdrIterate *info, void *pdata) return 0; } +static int elf_iterate_phdr_and_add_new_files(phdr_data *pd) +{ + assert(s_phdrData.empty()); + // dl_iterate_phdr, will only add entries for elf files loaded in a previouly unseen range + dl_iterate_phdr(phdr_callback_mock, nullptr); + + if(s_phdrData.size() == 0) + { + return 0; + } + + uint32_t headersAdded = 0; + for (auto &v : s_phdrData) + { + phdr_callback(&v, (void *)pd); + + auto newEntry = s_sortedKnownElfRanges.push_next(); + newEntry->dlpi_addr = v.dlpi_addr; + newEntry->dlpi_end_addr = v.dlpi_end_addr; + + tracy_free(v.dlpi_name); + + headersAdded++; + } + + s_phdrData.clear(); + + std::sort( s_sortedKnownElfRanges.begin(), s_sortedKnownElfRanges.end(), + []( const ElfAddrRange& lhs, const ElfAddrRange& rhs ) { return lhs.dlpi_addr > rhs.dlpi_addr; } ); + + return headersAdded; +} + +#ifdef TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT +/* Request an elf entry update if the pc passed in is not in any of the known elf ranges. +This could mean that new images were dlopened and we need to add those new elf entries */ +static int elf_refresh_address_ranges_if_needed(struct backtrace_state *state, uintptr_t pc) +{ + if ( address_in_known_elf_ranges(pc) ) + { + return 0; + } + + struct phdr_data pd; + int found_sym = 0; + int found_dwarf = 0; + fileline fileline_fn = nullptr; + pd.state = state; + pd.error_callback = nullptr; + pd.data = nullptr; + pd.fileline_fn = &fileline_fn; + pd.found_sym = &found_sym; + pd.found_dwarf = &found_dwarf; + pd.exe_filename = nullptr; + pd.exe_descriptor = -1; + + return elf_iterate_phdr_and_add_new_files(&pd); +} +#endif //#ifdef TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT + /* Initialize the backtrace data we need from an ELF executable. At the ELF level, all we need to do is find the debug info sections. */ @@ -7452,14 +7543,7 @@ backtrace_initialize (struct backtrace_state *state, const char *filename, pd.exe_filename = filename; pd.exe_descriptor = ret < 0 ? descriptor : -1; - assert (s_phdrData.empty()); - dl_iterate_phdr (phdr_callback_mock, nullptr); - for (auto& v : s_phdrData) - { - phdr_callback (&v, (void *) &pd); - tracy_free (v.dlpi_name); - } - s_phdrData.clear(); + elf_iterate_phdr_and_add_new_files(&pd); if (!state->threaded) { @@ -7485,6 +7569,13 @@ backtrace_initialize (struct backtrace_state *state, const char *filename, if (*fileline_fn == NULL || *fileline_fn == elf_nodebug) *fileline_fn = elf_fileline_fn; + // install an address range refresh callback so we can cope with dynamically loaded elf files +#ifdef TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT + state->request_known_address_ranges_refresh_fn = elf_refresh_address_ranges_if_needed; +#else + state->request_known_address_ranges_refresh_fn = NULL; +#endif + return 1; } diff --git a/public/libbacktrace/internal.hpp b/public/libbacktrace/internal.hpp index f871844b..fea298fa 100644 --- a/public/libbacktrace/internal.hpp +++ b/public/libbacktrace/internal.hpp @@ -133,6 +133,11 @@ typedef void (*syminfo) (struct backtrace_state *state, uintptr_t pc, backtrace_syminfo_callback callback, backtrace_error_callback error_callback, void *data); +/* The type of the function that will trigger an known address range refresh + (if pc passed in is for an address whichs lies ourtisde of known ranges) */ +typedef int (*request_known_address_ranges_refresh)(struct backtrace_state *state, + uintptr_t pc); + /* What the backtrace state pointer points to. */ struct backtrace_state @@ -159,6 +164,8 @@ struct backtrace_state int lock_alloc; /* The freelist when using mmap. */ struct backtrace_freelist_struct *freelist; + /* Trigger an known address range refresh */ + request_known_address_ranges_refresh request_known_address_ranges_refresh_fn; }; /* Open a file for reading. Returns -1 on error. If DOES_NOT_EXIST