Merge pull request #674 from tiago-rodrigues/trodrigues/offline_symbol_resolve_and_imagecache2

Add image cache to avoid calling dladdr() and add libbacktrace elf image list refresh
This commit is contained in:
Bartosz Taudul 2023-12-11 20:31:14 +01:00 committed by GitHub
commit 9bc014b183
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 299 additions and 24 deletions

View File

@ -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)

View File

@ -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 <link.h>
#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<ImageEntry>*)tracy_malloc( sizeof( FastVector<ImageEntry> ) );
new(m_images) FastVector<ImageEntry>( 512 );
Refresh();
}
~ImageCache()
{
Clear();
m_images->~FastVector<ImageEntry>();
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<ImageEntry>* m_images;
static int Callback( struct dl_phdr_info* info, size_t size, void* data )
{
ImageCache* cache = reinterpret_cast<ImageCache*>( data );
ImageEntry* image = cache->m_images->push_next();
image->m_startAddress = reinterpret_cast<void*>( info->dlpi_addr );
const uint32_t headerCount = info->dlpi_phnum;
assert( headerCount > 0);
image->m_endAddress = reinterpret_cast<void*>( 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 )

View File

@ -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,19 +4275,29 @@ dwarf_fileline (struct backtrace_state *state, uintptr_t pc,
{
struct dwarf_data *ddata;
int found;
int ret;
int ret = 0;
if (!state->threaded)
{
for (ddata = (struct dwarf_data *) state->fileline_data;
ddata != NULL;
ddata = ddata->next)
if (dwarf_fileline_dwarf_lookup_pc_in_all_entries(state, pc, callback, error_callback, data, found, ret))
{
ret = dwarf_lookup_pc (state, ddata, pc, callback, error_callback,
data, &found);
if (ret != 0 || found)
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
{

View File

@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <algorithm>
#ifdef HAVE_DL_ITERATE_PHDR
#include <link.h>
@ -7350,13 +7351,37 @@ struct PhdrIterate
{
char* dlpi_name;
ElfW(Addr) dlpi_addr;
ElfW(Addr) dlpi_end_addr;
};
FastVector<PhdrIterate> s_phdrData(16);
struct ElfAddrRange
{
ElfW(Addr) dlpi_addr;
ElfW(Addr) dlpi_end_addr;
};
FastVector<ElfAddrRange> 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;
}

View File

@ -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