1
0
mirror of https://github.com/wolfpld/tracy synced 2025-04-29 12:23:53 +00:00

Add image cache to avoid calling dladdr() when doing offline symbol resolution. This cache can also be used in the runtime symbol resolution case to detect we should recreate "backtrace_state" when new images have been loaded

This commit is contained in:
Tiago Rodrigues 2023-11-29 13:23:58 -05:00
parent af73dba73e
commit 132419dba3
2 changed files with 158 additions and 6 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(TRACY_TIMER_FALLBACK "Use lower resolution timers" OFF)
set_option(TRACE_CLIENT_LIBUNWIND_BACKTRACE "Use libunwind backtracing where supported" 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_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_ENABLE_IMAGE_CACHE "On glibc platforms, when doing offline symbol resolution, use an cache to determine the image path and offset instead of dladdr()" OFF)
if(NOT TRACY_STATIC) if(NOT TRACY_STATIC)
target_compile_definitions(TracyClient PRIVATE TRACY_EXPORTS) target_compile_definitions(TracyClient PRIVATE TRACY_EXPORTS)

View File

@ -90,6 +90,124 @@ extern "C" const char* ___tracy_demangle( const char* mangled )
#endif #endif
#endif #endif
#if defined( TRACY_ENABLE_IMAGE_CACHE) && (TRACY_HAS_CALLSTACK == 3)
# define TRACY_USE_IMAGE_CACHE
# include <link.h>
#endif
#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
{
ImageEntry( void* startAddress, void* endAddress, const char* name )
: m_startAddress( startAddress ), m_endAddress( endAddress ), m_name( name ) {}
void* m_startAddress;
void* m_endAddress;
const char* m_name;
};
ImageCache()
{
m_images = (tracy::FastVector<ImageEntry>*)tracy::tracy_malloc( sizeof( tracy::FastVector<ImageEntry> ) );
new(m_images) tracy::FastVector<ImageEntry>( 512 );
Refresh();
}
const ImageEntry* GetImageForAddress( void* address )
{
const ImageEntry* entry = GetImageEntryForAddress( address );
if( !entry )
{
//printf("* addr not found: %p (%d entries) refreshing (m_numberOfRefreshes: %d)\n",
// address, m_images->size(), m_numberOfRefreshes);
Refresh();
return GetImageEntryForAddress( address );
}
return entry;
}
typedef void (*RefreshCallback)();
void SetOnRefreshCallback( RefreshCallback callback )
{
m_onRefreshCallback = callback;
}
private:
tracy::FastVector<ImageEntry>* m_images;
const char* m_imageName = nullptr;
RefreshCallback m_onRefreshCallback;
uint32_t m_numberOfRefreshes = 0;
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);
// the base executable name isn't provided when iterating with dl_iterate_phdr,
// so we must get it in an alternative way and cache it
if( info->dlpi_name && info->dlpi_name[0] != '\0' )
{
image->m_name = info->dlpi_name;
}
else
{
if( !cache->m_imageName )
{
Dl_info dlInfo;
if( dladdr( (void *)info->dlpi_addr, &dlInfo ) )
{
cache->m_imageName = dlInfo.dli_fname;
}
}
image->m_name = cache->m_imageName;
}
//printf("\tdl_iterate_phdr:'%s', start:%p, end:%p (headerCount: %d)'\n",
// image->m_name, image->m_startAddress, image->m_endAddress, headerCount);
return 0;
}
void Refresh()
{
m_images->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; } );
if( m_onRefreshCallback ) m_onRefreshCallback();
m_numberOfRefreshes++;
}
const ImageEntry* GetImageEntryForAddress( 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;
}
};
#endif //#ifdef TRACY_USE_IMAGE_CACHE
namespace tracy namespace tracy
{ {
@ -607,6 +725,9 @@ struct backtrace_state* cb_bts = nullptr;
int cb_num; int cb_num;
CallstackEntry cb_data[MaxCbTrace]; CallstackEntry cb_data[MaxCbTrace];
int cb_fixup; int cb_fixup;
#ifdef TRACY_USE_IMAGE_CACHE
static ImageCache* s_imageCache = nullptr;
#endif //#ifdef TRACY_USE_IMAGE_CACHE
#ifdef TRACY_DEBUGINFOD #ifdef TRACY_DEBUGINFOD
debuginfod_client* s_debuginfod; debuginfod_client* s_debuginfod;
@ -779,6 +900,13 @@ void InitCallstackCritical()
void InitCallstack() void InitCallstack()
{ {
InitRpmalloc();
#ifdef TRACY_USE_IMAGE_CACHE
s_imageCache = (ImageCache*)tracy::tracy_malloc( sizeof( ImageCache ) );
new(s_imageCache) ImageCache();
#endif //#ifdef TRACY_USE_IMAGE_CACHE
#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE #ifndef TRACY_SYMBOL_OFFLINE_RESOLVE
s_shouldResolveSymbolsOffline = ShouldResolveSymbolsOffline(); s_shouldResolveSymbolsOffline = ShouldResolveSymbolsOffline();
#endif //#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE #endif //#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE
@ -789,7 +917,15 @@ void InitCallstack()
} }
else else
{ {
#ifdef TRACY_USE_IMAGE_CACHE
s_imageCache->SetOnRefreshCallback([]()
{
// FIXME: there is no backtrace_destroy_state(), so we are forced to leak...
cb_bts = backtrace_create_state( nullptr, 0, nullptr, nullptr );
});
#else
cb_bts = backtrace_create_state( nullptr, 0, nullptr, nullptr ); cb_bts = backtrace_create_state( nullptr, 0, nullptr, nullptr );
#endif // #ifdef TRACY_USE_IMAGE_CACHE
} }
#ifndef TRACY_DEMANGLE #ifndef TRACY_DEMANGLE
@ -869,6 +1005,9 @@ debuginfod_client* GetDebuginfodClient()
void EndCallstack() void EndCallstack()
{ {
#ifdef TRACY_USE_IMAGE_CACHE
tracy::tracy_free(s_imageCache);
#endif //#ifdef TRACY_USE_IMAGE_CACHE
#ifndef TRACY_DEMANGLE #ifndef TRACY_DEMANGLE
___tracy_free_demangle_buffer(); ___tracy_free_demangle_buffer();
#endif #endif
@ -1041,12 +1180,12 @@ void SymInfoError( void* /*data*/, const char* /*msg*/, int /*errnum*/ )
cb_data[cb_num-1].symAddr = 0; 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 // tagged with a string that we can identify as an unresolved symbol
cbEntry.name = CopyStringFast( "[unresolved]" ); cbEntry.name = CopyStringFast( "[unresolved]" );
// set .so relative offset so it can be resolved offline // 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.symLen = 0x0;
cbEntry.file = CopyStringFast( "[unknown]" ); cbEntry.file = CopyStringFast( "[unknown]" );
cbEntry.line = 0; cbEntry.line = 0;
@ -1057,17 +1196,29 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr )
InitRpmalloc(); InitRpmalloc();
if( ptr >> 63 == 0 ) 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; Dl_info dlinfo;
if( dladdr( (void*)ptr, &dlinfo ) ) if( dladdr( (void*)ptr, &dlinfo ) )
{ {
symloc = dlinfo.dli_fname; imageName = dlinfo.dli_fname;
imageBaseAddress = uint64_t( dlinfo.dli_fbase );
} }
#endif
if( s_shouldResolveSymbolsOffline ) if( s_shouldResolveSymbolsOffline )
{ {
cb_num = 1; cb_num = 1;
GetSymbolForOfflineResolve( (void*)ptr, dlinfo, cb_data[0] ); GetSymbolForOfflineResolve( (void*)ptr, imageBaseAddress, cb_data[0] );
} }
else else
{ {
@ -1078,7 +1229,7 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr )
backtrace_syminfo( cb_bts, ptr, SymInfoCallback, SymInfoError, nullptr ); 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 #ifdef __linux
else if( s_kernelSym ) else if( s_kernelSym )