2022-07-19 23:21:43 +00:00
|
|
|
#include <limits>
|
2020-03-28 20:27:19 +00:00
|
|
|
#include <new>
|
2018-06-30 14:00:51 +00:00
|
|
|
#include <stdio.h>
|
2019-11-05 19:09:40 +00:00
|
|
|
#include <string.h>
|
2018-06-18 23:17:19 +00:00
|
|
|
#include "TracyCallstack.hpp"
|
2023-12-20 12:16:51 +00:00
|
|
|
#include "TracyDebug.hpp"
|
2020-02-24 20:54:19 +00:00
|
|
|
#include "TracyFastVector.hpp"
|
2021-10-22 19:53:46 +00:00
|
|
|
#include "TracyStringHelpers.hpp"
|
2020-03-28 20:27:19 +00:00
|
|
|
#include "../common/TracyAlloc.hpp"
|
2023-12-20 12:16:51 +00:00
|
|
|
#include "../common/TracySystem.hpp"
|
|
|
|
|
2018-06-18 23:17:19 +00:00
|
|
|
|
|
|
|
#ifdef TRACY_HAS_CALLSTACK
|
|
|
|
|
2018-07-14 09:08:17 +00:00
|
|
|
#if TRACY_HAS_CALLSTACK == 1
|
2019-10-10 18:29:06 +00:00
|
|
|
# ifndef NOMINMAX
|
|
|
|
# define NOMINMAX
|
|
|
|
# endif
|
2018-06-20 19:01:25 +00:00
|
|
|
# include <windows.h>
|
2020-02-22 20:06:32 +00:00
|
|
|
# include <psapi.h>
|
2021-06-15 19:04:16 +00:00
|
|
|
# include <algorithm>
|
2018-12-28 23:42:38 +00:00
|
|
|
# ifdef _MSC_VER
|
|
|
|
# pragma warning( push )
|
|
|
|
# pragma warning( disable : 4091 )
|
|
|
|
# endif
|
2018-06-20 19:01:25 +00:00
|
|
|
# include <dbghelp.h>
|
2018-12-28 23:42:38 +00:00
|
|
|
# ifdef _MSC_VER
|
|
|
|
# pragma warning( pop )
|
|
|
|
# endif
|
2019-11-29 11:04:47 +00:00
|
|
|
#elif TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6
|
2019-01-20 15:55:59 +00:00
|
|
|
# include "../libbacktrace/backtrace.hpp"
|
2021-06-10 23:31:02 +00:00
|
|
|
# include <algorithm>
|
2018-06-20 20:54:42 +00:00
|
|
|
# include <dlfcn.h>
|
2018-06-20 21:01:00 +00:00
|
|
|
# include <cxxabi.h>
|
2021-06-10 23:31:02 +00:00
|
|
|
# include <stdlib.h>
|
2019-11-29 11:04:47 +00:00
|
|
|
#elif TRACY_HAS_CALLSTACK == 5
|
2019-05-22 12:14:30 +00:00
|
|
|
# include <dlfcn.h>
|
|
|
|
# include <cxxabi.h>
|
2018-06-18 23:17:19 +00:00
|
|
|
#endif
|
|
|
|
|
2020-10-28 19:04:37 +00:00
|
|
|
#ifdef TRACY_DBGHELP_LOCK
|
|
|
|
# include "TracyProfiler.hpp"
|
|
|
|
|
|
|
|
# define DBGHELP_INIT TracyConcat( TRACY_DBGHELP_LOCK, Init() )
|
|
|
|
# define DBGHELP_LOCK TracyConcat( TRACY_DBGHELP_LOCK, Lock() );
|
|
|
|
# define DBGHELP_UNLOCK TracyConcat( TRACY_DBGHELP_LOCK, Unlock() );
|
|
|
|
|
|
|
|
extern "C"
|
|
|
|
{
|
|
|
|
void DBGHELP_INIT;
|
|
|
|
void DBGHELP_LOCK;
|
|
|
|
void DBGHELP_UNLOCK;
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
2022-03-11 14:40:10 +00:00
|
|
|
#if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 5 || TRACY_HAS_CALLSTACK == 6
|
2022-07-19 23:21:43 +00:00
|
|
|
// If you want to use your own demangling functionality (e.g. for another language),
|
|
|
|
// define TRACY_DEMANGLE and provide your own implementation of the __tracy_demangle
|
|
|
|
// function. The input parameter is a function name. The demangle function must
|
|
|
|
// identify whether this name is mangled, and fail if it is not. Failure is indicated
|
|
|
|
// by returning nullptr. If demangling succeeds, a pointer to the C string containing
|
|
|
|
// demangled function must be returned. The demangling function is responsible for
|
|
|
|
// managing memory for this string. It is expected that it will be internally reused.
|
|
|
|
// When a call to ___tracy_demangle is made, previous contents of the string memory
|
|
|
|
// do not need to be preserved. Function may return string of any length, but the
|
|
|
|
// profiler can choose to truncate it.
|
|
|
|
extern "C" const char* ___tracy_demangle( const char* mangled );
|
2022-03-11 14:40:10 +00:00
|
|
|
|
|
|
|
#ifndef TRACY_DEMANGLE
|
2023-12-12 18:09:41 +00:00
|
|
|
constexpr size_t ___tracy_demangle_buffer_len = 1024*1024;
|
2022-07-23 10:34:35 +00:00
|
|
|
char* ___tracy_demangle_buffer;
|
|
|
|
|
|
|
|
void ___tracy_init_demangle_buffer()
|
|
|
|
{
|
|
|
|
___tracy_demangle_buffer = (char*)tracy::tracy_malloc( ___tracy_demangle_buffer_len );
|
|
|
|
}
|
|
|
|
|
|
|
|
void ___tracy_free_demangle_buffer()
|
|
|
|
{
|
|
|
|
tracy::tracy_free( ___tracy_demangle_buffer );
|
|
|
|
}
|
|
|
|
|
2022-07-19 23:21:43 +00:00
|
|
|
extern "C" const char* ___tracy_demangle( const char* mangled )
|
2022-03-11 14:40:10 +00:00
|
|
|
{
|
2022-07-19 23:21:43 +00:00
|
|
|
if( !mangled || mangled[0] != '_' ) return nullptr;
|
2022-07-23 10:37:00 +00:00
|
|
|
if( strlen( mangled ) > ___tracy_demangle_buffer_len ) return nullptr;
|
2022-03-11 14:40:10 +00:00
|
|
|
int status;
|
2022-07-23 10:34:35 +00:00
|
|
|
size_t len = ___tracy_demangle_buffer_len;
|
|
|
|
return abi::__cxa_demangle( mangled, ___tracy_demangle_buffer, &len, &status );
|
2022-03-11 14:40:10 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
2023-12-06 02:01:12 +00:00
|
|
|
#if TRACY_HAS_CALLSTACK == 3
|
2023-11-29 18:23:58 +00:00
|
|
|
# define TRACY_USE_IMAGE_CACHE
|
|
|
|
# include <link.h>
|
|
|
|
#endif
|
|
|
|
|
2023-12-03 14:23:03 +00:00
|
|
|
namespace tracy
|
|
|
|
{
|
|
|
|
|
2023-11-29 18:23:58 +00:00
|
|
|
#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
|
|
|
|
{
|
2023-12-10 01:12:53 +00:00
|
|
|
void* m_startAddress = nullptr;
|
|
|
|
void* m_endAddress = nullptr;
|
|
|
|
char* m_name = nullptr;
|
2023-11-29 18:23:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
ImageCache()
|
2023-12-12 18:27:53 +00:00
|
|
|
: m_images( 512 )
|
2023-11-29 18:23:58 +00:00
|
|
|
{
|
|
|
|
Refresh();
|
|
|
|
}
|
2023-12-09 15:40:07 +00:00
|
|
|
|
|
|
|
~ImageCache()
|
2023-11-29 18:23:58 +00:00
|
|
|
{
|
2023-12-10 01:12:53 +00:00
|
|
|
Clear();
|
2023-12-09 15:40:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const ImageEntry* GetImageForAddress( void* address )
|
|
|
|
{
|
|
|
|
const ImageEntry* entry = GetImageForAddressImpl( address );
|
2023-11-29 18:23:58 +00:00
|
|
|
if( !entry )
|
|
|
|
{
|
|
|
|
Refresh();
|
2023-12-09 15:40:07 +00:00
|
|
|
return GetImageForAddressImpl( address );
|
2023-11-29 18:23:58 +00:00
|
|
|
}
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2023-12-12 18:27:53 +00:00
|
|
|
tracy::FastVector<ImageEntry> m_images;
|
2024-05-03 21:05:59 +00:00
|
|
|
bool m_updated = false;
|
|
|
|
bool m_haveMainImageName = false;
|
2023-11-29 18:23:58 +00:00
|
|
|
|
2023-12-12 18:09:41 +00:00
|
|
|
static int Callback( struct dl_phdr_info* info, size_t size, void* data )
|
2023-11-29 18:23:58 +00:00
|
|
|
{
|
|
|
|
ImageCache* cache = reinterpret_cast<ImageCache*>( data );
|
|
|
|
|
2023-12-12 18:23:27 +00:00
|
|
|
const auto startAddress = reinterpret_cast<void*>( info->dlpi_addr );
|
|
|
|
if( cache->Contains( startAddress ) ) return 0;
|
|
|
|
|
2023-11-29 18:23:58 +00:00
|
|
|
const uint32_t headerCount = info->dlpi_phnum;
|
|
|
|
assert( headerCount > 0);
|
2023-12-12 18:23:27 +00:00
|
|
|
const auto endAddress = reinterpret_cast<void*>( info->dlpi_addr +
|
2023-11-29 18:23:58 +00:00
|
|
|
info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz);
|
|
|
|
|
2023-12-12 18:27:53 +00:00
|
|
|
ImageEntry* image = cache->m_images.push_next();
|
2023-12-12 18:23:27 +00:00
|
|
|
image->m_startAddress = startAddress;
|
|
|
|
image->m_endAddress = endAddress;
|
|
|
|
|
2024-05-03 21:05:59 +00:00
|
|
|
// the base executable name isn't provided when iterating with dl_iterate_phdr,
|
|
|
|
// we will have to patch the executable image name outside this callback
|
2023-11-29 18:23:58 +00:00
|
|
|
if( info->dlpi_name && info->dlpi_name[0] != '\0' )
|
|
|
|
{
|
2024-05-03 21:05:59 +00:00
|
|
|
size_t sz = strlen( info->dlpi_name ) + 1;
|
2023-12-10 01:12:53 +00:00
|
|
|
image->m_name = (char*)tracy_malloc( sz );
|
2024-05-03 21:05:59 +00:00
|
|
|
memcpy( image->m_name, info->dlpi_name, sz );
|
2023-12-10 01:12:53 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
image->m_name = nullptr;
|
2023-11-29 18:23:58 +00:00
|
|
|
}
|
|
|
|
|
2023-12-12 18:23:27 +00:00
|
|
|
cache->m_updated = true;
|
|
|
|
|
2023-11-29 18:23:58 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-12-12 18:23:27 +00:00
|
|
|
bool Contains( void* startAddress ) const
|
2023-11-29 18:23:58 +00:00
|
|
|
{
|
2023-12-12 18:27:53 +00:00
|
|
|
return std::any_of( m_images.begin(), m_images.end(), [startAddress]( const ImageEntry& entry ) { return startAddress == entry.m_startAddress; } );
|
2023-12-12 18:23:27 +00:00
|
|
|
}
|
2023-11-29 18:23:58 +00:00
|
|
|
|
2023-12-12 18:23:27 +00:00
|
|
|
void Refresh()
|
|
|
|
{
|
|
|
|
m_updated = false;
|
2023-11-29 18:23:58 +00:00
|
|
|
dl_iterate_phdr( Callback, this );
|
2023-12-12 18:09:41 +00:00
|
|
|
|
2023-12-12 18:23:27 +00:00
|
|
|
if( m_updated )
|
|
|
|
{
|
2023-12-12 18:27:53 +00:00
|
|
|
std::sort( m_images.begin(), m_images.end(),
|
2023-12-12 18:23:27 +00:00
|
|
|
[]( const ImageEntry& lhs, const ImageEntry& rhs ) { return lhs.m_startAddress > rhs.m_startAddress; } );
|
2024-05-03 21:05:59 +00:00
|
|
|
|
|
|
|
// patch the main executable image name here, as calling dl_* functions inside the dl_iterate_phdr callback might cause deadlocks
|
|
|
|
UpdateMainImageName();
|
2023-12-12 18:23:27 +00:00
|
|
|
}
|
2023-11-29 18:23:58 +00:00
|
|
|
}
|
|
|
|
|
2024-05-03 21:05:59 +00:00
|
|
|
void UpdateMainImageName()
|
|
|
|
{
|
|
|
|
if( m_haveMainImageName )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for( ImageEntry& entry : m_images )
|
|
|
|
{
|
|
|
|
if( entry.m_name == nullptr )
|
|
|
|
{
|
|
|
|
Dl_info dlInfo;
|
|
|
|
if( dladdr( (void *)entry.m_startAddress, &dlInfo ) )
|
|
|
|
{
|
|
|
|
if( dlInfo.dli_fname )
|
|
|
|
{
|
|
|
|
size_t sz = strlen( dlInfo.dli_fname ) + 1;
|
|
|
|
entry.m_name = (char*)tracy_malloc( sz );
|
|
|
|
memcpy( entry.m_name, dlInfo.dli_fname, sz );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// we only expect one entry to be null for the main executable entry
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_haveMainImageName = true;
|
|
|
|
}
|
|
|
|
|
2023-12-12 18:09:41 +00:00
|
|
|
const ImageEntry* GetImageForAddressImpl( void* address ) const
|
2023-11-29 18:23:58 +00:00
|
|
|
{
|
2023-12-12 18:27:53 +00:00
|
|
|
auto it = std::lower_bound( m_images.begin(), m_images.end(), address,
|
2023-11-29 18:23:58 +00:00
|
|
|
[]( const ImageEntry& lhs, const void* rhs ) { return lhs.m_startAddress > rhs; } );
|
|
|
|
|
2023-12-12 18:27:53 +00:00
|
|
|
if( it != m_images.end() && address < it->m_endAddress )
|
2023-11-29 18:23:58 +00:00
|
|
|
{
|
2023-12-03 14:36:54 +00:00
|
|
|
return it;
|
2023-11-29 18:23:58 +00:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
2023-12-10 01:12:53 +00:00
|
|
|
|
|
|
|
void Clear()
|
|
|
|
{
|
2023-12-12 18:27:53 +00:00
|
|
|
for( ImageEntry& entry : m_images )
|
2023-12-10 01:12:53 +00:00
|
|
|
{
|
|
|
|
tracy_free( entry.m_name );
|
|
|
|
}
|
|
|
|
|
2023-12-12 18:27:53 +00:00
|
|
|
m_images.clear();
|
2024-05-03 21:05:59 +00:00
|
|
|
m_haveMainImageName = false;
|
2023-12-10 01:12:53 +00:00
|
|
|
}
|
2023-11-29 18:23:58 +00:00
|
|
|
};
|
|
|
|
#endif //#ifdef TRACY_USE_IMAGE_CACHE
|
|
|
|
|
2023-11-18 21:27:26 +00:00
|
|
|
// when "TRACY_SYMBOL_OFFLINE_RESOLVE" is set, instead of fully resolving symbols at runtime,
|
2023-11-17 20:48:17 +00:00
|
|
|
// simply resolve the offset and image name (which will be enough the resolving to be done offline)
|
2023-11-18 21:27:26 +00:00
|
|
|
#ifdef TRACY_SYMBOL_OFFLINE_RESOLVE
|
2023-11-19 14:31:42 +00:00
|
|
|
constexpr bool s_shouldResolveSymbolsOffline = true;
|
2023-11-18 21:27:26 +00:00
|
|
|
#else
|
2023-11-19 14:31:42 +00:00
|
|
|
static bool s_shouldResolveSymbolsOffline = false;
|
2023-11-18 20:03:12 +00:00
|
|
|
bool ShouldResolveSymbolsOffline()
|
2023-11-17 20:48:17 +00:00
|
|
|
{
|
|
|
|
const char* symbolOfflineResolve = GetEnvVar( "TRACY_SYMBOL_OFFLINE_RESOLVE" );
|
|
|
|
return (symbolOfflineResolve && symbolOfflineResolve[0] == '1');
|
|
|
|
}
|
2023-11-18 21:27:26 +00:00
|
|
|
#endif // #ifdef TRACY_SYMBOL_OFFLINE_RESOLVE
|
2023-11-17 20:48:17 +00:00
|
|
|
|
2018-07-14 09:08:17 +00:00
|
|
|
#if TRACY_HAS_CALLSTACK == 1
|
2018-06-20 19:45:27 +00:00
|
|
|
|
2022-10-04 20:16:20 +00:00
|
|
|
enum { MaxCbTrace = 64 };
|
2020-02-26 21:32:09 +00:00
|
|
|
enum { MaxNameSize = 8*1024 };
|
2019-01-26 22:50:58 +00:00
|
|
|
|
|
|
|
int cb_num;
|
|
|
|
CallstackEntry cb_data[MaxCbTrace];
|
|
|
|
|
2020-01-25 15:49:29 +00:00
|
|
|
extern "C"
|
|
|
|
{
|
2021-06-16 23:34:45 +00:00
|
|
|
typedef DWORD (__stdcall *t_SymAddrIncludeInlineTrace)( HANDLE hProcess, DWORD64 Address );
|
|
|
|
typedef BOOL (__stdcall *t_SymQueryInlineTrace)( HANDLE hProcess, DWORD64 StartAddress, DWORD StartContext, DWORD64 StartRetAddress, DWORD64 CurAddress, LPDWORD CurContext, LPDWORD CurFrameIndex );
|
|
|
|
typedef BOOL (__stdcall *t_SymFromInlineContext)( HANDLE hProcess, DWORD64 Address, ULONG InlineContext, PDWORD64 Displacement, PSYMBOL_INFO Symbol );
|
|
|
|
typedef BOOL (__stdcall *t_SymGetLineFromInlineContext)( HANDLE hProcess, DWORD64 qwAddr, ULONG InlineContext, DWORD64 qwModuleBaseAddress, PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line64 );
|
|
|
|
|
|
|
|
t_SymAddrIncludeInlineTrace _SymAddrIncludeInlineTrace = 0;
|
|
|
|
t_SymQueryInlineTrace _SymQueryInlineTrace = 0;
|
|
|
|
t_SymFromInlineContext _SymFromInlineContext = 0;
|
|
|
|
t_SymGetLineFromInlineContext _SymGetLineFromInlineContext = 0;
|
2018-06-23 00:07:47 +00:00
|
|
|
|
2023-11-18 21:27:26 +00:00
|
|
|
TRACY_API ___tracy_t_RtlWalkFrameChain ___tracy_RtlWalkFrameChain = 0;
|
|
|
|
}
|
2021-10-07 21:28:40 +00:00
|
|
|
|
2020-02-22 20:32:10 +00:00
|
|
|
struct ModuleCache
|
|
|
|
{
|
|
|
|
uint64_t start;
|
|
|
|
uint64_t end;
|
|
|
|
char* name;
|
|
|
|
};
|
|
|
|
|
2020-02-24 20:54:19 +00:00
|
|
|
static FastVector<ModuleCache>* s_modCache;
|
2021-06-15 19:04:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
struct KernelDriver
|
|
|
|
{
|
|
|
|
uint64_t addr;
|
|
|
|
const char* mod;
|
2021-11-25 22:27:05 +00:00
|
|
|
const char* path;
|
2021-06-15 19:04:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
KernelDriver* s_krnlCache = nullptr;
|
|
|
|
size_t s_krnlCacheCnt;
|
2020-02-22 20:32:10 +00:00
|
|
|
|
2022-08-16 11:55:46 +00:00
|
|
|
void InitCallstackCritical()
|
2018-06-18 23:17:19 +00:00
|
|
|
{
|
2022-01-01 16:33:39 +00:00
|
|
|
___tracy_RtlWalkFrameChain = (___tracy_t_RtlWalkFrameChain)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "RtlWalkFrameChain" );
|
2022-08-16 11:55:46 +00:00
|
|
|
}
|
|
|
|
|
2023-11-18 20:03:12 +00:00
|
|
|
void DbgHelpInit()
|
2022-08-16 11:55:46 +00:00
|
|
|
{
|
2023-11-19 14:31:42 +00:00
|
|
|
if( s_shouldResolveSymbolsOffline ) return;
|
2023-11-17 20:48:17 +00:00
|
|
|
|
|
|
|
_SymAddrIncludeInlineTrace = (t_SymAddrIncludeInlineTrace)GetProcAddress(GetModuleHandleA("dbghelp.dll"), "SymAddrIncludeInlineTrace");
|
|
|
|
_SymQueryInlineTrace = (t_SymQueryInlineTrace)GetProcAddress(GetModuleHandleA("dbghelp.dll"), "SymQueryInlineTrace");
|
|
|
|
_SymFromInlineContext = (t_SymFromInlineContext)GetProcAddress(GetModuleHandleA("dbghelp.dll"), "SymFromInlineContext");
|
|
|
|
_SymGetLineFromInlineContext = (t_SymGetLineFromInlineContext)GetProcAddress(GetModuleHandleA("dbghelp.dll"), "SymGetLineFromInlineContext");
|
2020-04-07 19:35:37 +00:00
|
|
|
|
2023-11-19 14:31:42 +00:00
|
|
|
#ifdef TRACY_DBGHELP_LOCK
|
2020-10-28 19:04:37 +00:00
|
|
|
DBGHELP_INIT;
|
|
|
|
DBGHELP_LOCK;
|
2023-11-19 14:31:42 +00:00
|
|
|
#endif
|
2020-10-28 19:04:37 +00:00
|
|
|
|
2018-06-18 23:17:19 +00:00
|
|
|
SymInitialize( GetCurrentProcess(), nullptr, true );
|
|
|
|
SymSetOptions( SYMOPT_LOAD_LINES );
|
2020-02-22 20:32:18 +00:00
|
|
|
|
2023-11-19 14:31:42 +00:00
|
|
|
#ifdef TRACY_DBGHELP_LOCK
|
2023-11-17 20:48:17 +00:00
|
|
|
DBGHELP_UNLOCK;
|
2023-11-19 14:31:42 +00:00
|
|
|
#endif
|
2023-11-17 20:48:17 +00:00
|
|
|
}
|
|
|
|
|
2023-11-18 20:03:12 +00:00
|
|
|
DWORD64 DbgHelpLoadSymbolsForModule( const char* imageName, uint64_t baseOfDll, uint32_t bllSize )
|
2023-11-17 20:48:17 +00:00
|
|
|
{
|
2023-11-19 14:31:42 +00:00
|
|
|
if( s_shouldResolveSymbolsOffline ) return 0;
|
2023-11-18 20:03:12 +00:00
|
|
|
return SymLoadModuleEx( GetCurrentProcess(), nullptr, imageName, nullptr, baseOfDll, bllSize, nullptr, 0 );
|
2023-11-17 20:48:17 +00:00
|
|
|
}
|
|
|
|
|
2023-11-18 21:27:26 +00:00
|
|
|
ModuleCache* LoadSymbolsForModuleAndCache( const char* imageName, uint32_t imageNameLength, uint64_t baseOfDll, uint32_t dllSize )
|
2023-11-17 20:48:17 +00:00
|
|
|
{
|
2023-11-18 20:03:12 +00:00
|
|
|
DbgHelpLoadSymbolsForModule( imageName, baseOfDll, dllSize );
|
2023-11-17 20:48:17 +00:00
|
|
|
|
|
|
|
ModuleCache* cachedModule = s_modCache->push_next();
|
|
|
|
cachedModule->start = baseOfDll;
|
|
|
|
cachedModule->end = baseOfDll + dllSize;
|
|
|
|
|
2023-12-12 18:09:41 +00:00
|
|
|
// when doing offline symbol resolution, we must store the full path of the dll for the resolving to work
|
2023-11-19 14:31:42 +00:00
|
|
|
if( s_shouldResolveSymbolsOffline )
|
2023-11-17 20:48:17 +00:00
|
|
|
{
|
2023-11-18 20:03:12 +00:00
|
|
|
cachedModule->name = (char*)tracy_malloc_fast(imageNameLength + 1);
|
2023-11-17 20:48:17 +00:00
|
|
|
memcpy(cachedModule->name, imageName, imageNameLength);
|
2023-11-18 20:03:12 +00:00
|
|
|
cachedModule->name[imageNameLength] = '\0';
|
2023-11-17 20:48:17 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto ptr = imageName + imageNameLength;
|
|
|
|
while (ptr > imageName && *ptr != '\\' && *ptr != '/') ptr--;
|
|
|
|
if (ptr > imageName) ptr++;
|
|
|
|
const auto namelen = imageName + imageNameLength - ptr;
|
|
|
|
cachedModule->name = (char*)tracy_malloc_fast(namelen + 3);
|
|
|
|
cachedModule->name[0] = '[';
|
|
|
|
memcpy(cachedModule->name + 1, ptr, namelen);
|
|
|
|
cachedModule->name[namelen + 1] = ']';
|
|
|
|
cachedModule->name[namelen + 2] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
return cachedModule;
|
|
|
|
}
|
|
|
|
|
|
|
|
void InitCallstack()
|
|
|
|
{
|
2023-11-18 21:27:26 +00:00
|
|
|
#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE
|
2023-11-19 14:31:42 +00:00
|
|
|
s_shouldResolveSymbolsOffline = ShouldResolveSymbolsOffline();
|
|
|
|
#endif //#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE
|
|
|
|
if( s_shouldResolveSymbolsOffline )
|
2023-11-17 20:48:17 +00:00
|
|
|
{
|
|
|
|
TracyDebug("TRACY: enabling offline symbol resolving!\n");
|
|
|
|
}
|
|
|
|
|
2023-11-18 20:03:12 +00:00
|
|
|
DbgHelpInit();
|
2023-11-17 20:48:17 +00:00
|
|
|
|
2023-11-19 14:31:42 +00:00
|
|
|
#ifdef TRACY_DBGHELP_LOCK
|
2023-11-17 20:48:17 +00:00
|
|
|
DBGHELP_LOCK;
|
2023-11-19 14:31:42 +00:00
|
|
|
#endif
|
2023-11-17 20:48:17 +00:00
|
|
|
|
2023-12-20 12:14:06 +00:00
|
|
|
// use TRACY_NO_DBGHELP_INIT_LOAD=1 to disable preloading of driver
|
2023-11-10 22:02:03 +00:00
|
|
|
// and process module symbol loading at startup time - they will be loaded on demand later
|
|
|
|
// Sometimes this process can take a very long time and prevent resolving callstack frames
|
|
|
|
// symbols during that time.
|
2023-12-20 12:14:06 +00:00
|
|
|
const char* noInitLoadEnv = GetEnvVar( "TRACY_NO_DBGHELP_INIT_LOAD" );
|
2023-11-13 18:43:03 +00:00
|
|
|
const bool initTimeModuleLoad = !( noInitLoadEnv && noInitLoadEnv[0] == '1' );
|
|
|
|
if ( !initTimeModuleLoad )
|
2023-11-10 22:02:03 +00:00
|
|
|
{
|
2023-11-13 18:43:03 +00:00
|
|
|
TracyDebug("TRACY: skipping init time dbghelper module load\n");
|
2023-11-10 22:02:03 +00:00
|
|
|
}
|
|
|
|
|
2020-02-22 20:32:18 +00:00
|
|
|
DWORD needed;
|
2021-06-15 19:04:16 +00:00
|
|
|
LPVOID dev[4096];
|
2023-11-13 18:43:03 +00:00
|
|
|
if( initTimeModuleLoad && EnumDeviceDrivers( dev, sizeof(dev), &needed ) != 0 )
|
2021-06-15 19:04:16 +00:00
|
|
|
{
|
2021-06-15 19:28:02 +00:00
|
|
|
char windir[MAX_PATH];
|
|
|
|
if( !GetWindowsDirectoryA( windir, sizeof( windir ) ) ) memcpy( windir, "c:\\windows", 11 );
|
|
|
|
const auto windirlen = strlen( windir );
|
|
|
|
|
2021-06-15 19:04:16 +00:00
|
|
|
const auto sz = needed / sizeof( LPVOID );
|
|
|
|
s_krnlCache = (KernelDriver*)tracy_malloc( sizeof(KernelDriver) * sz );
|
|
|
|
int cnt = 0;
|
|
|
|
for( size_t i=0; i<sz; i++ )
|
|
|
|
{
|
|
|
|
char fn[MAX_PATH];
|
|
|
|
const auto len = GetDeviceDriverBaseNameA( dev[i], fn, sizeof( fn ) );
|
|
|
|
if( len != 0 )
|
|
|
|
{
|
|
|
|
auto buf = (char*)tracy_malloc_fast( len+3 );
|
|
|
|
buf[0] = '<';
|
|
|
|
memcpy( buf+1, fn, len );
|
|
|
|
memcpy( buf+len+1, ">", 2 );
|
2021-11-25 22:27:05 +00:00
|
|
|
s_krnlCache[cnt] = KernelDriver { (uint64_t)dev[i], buf };
|
2021-06-15 19:28:02 +00:00
|
|
|
|
|
|
|
const auto len = GetDeviceDriverFileNameA( dev[i], fn, sizeof( fn ) );
|
|
|
|
if( len != 0 )
|
|
|
|
{
|
|
|
|
char full[MAX_PATH];
|
|
|
|
char* path = fn;
|
|
|
|
|
|
|
|
if( memcmp( fn, "\\SystemRoot\\", 12 ) == 0 )
|
|
|
|
{
|
|
|
|
memcpy( full, windir, windirlen );
|
|
|
|
strcpy( full + windirlen, fn + 11 );
|
|
|
|
path = full;
|
|
|
|
}
|
|
|
|
|
2023-11-18 20:03:12 +00:00
|
|
|
DbgHelpLoadSymbolsForModule( path, (DWORD64)dev[i], 0 );
|
2021-11-25 22:27:05 +00:00
|
|
|
|
|
|
|
const auto psz = strlen( path );
|
|
|
|
auto pptr = (char*)tracy_malloc_fast( psz+1 );
|
|
|
|
memcpy( pptr, path, psz );
|
|
|
|
pptr[psz] = '\0';
|
|
|
|
s_krnlCache[cnt].path = pptr;
|
2021-06-15 19:28:02 +00:00
|
|
|
}
|
2021-11-25 22:27:05 +00:00
|
|
|
|
|
|
|
cnt++;
|
2021-06-15 19:04:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
s_krnlCacheCnt = cnt;
|
|
|
|
std::sort( s_krnlCache, s_krnlCache + s_krnlCacheCnt, []( const KernelDriver& lhs, const KernelDriver& rhs ) { return lhs.addr > rhs.addr; } );
|
|
|
|
}
|
2020-02-22 20:32:18 +00:00
|
|
|
|
2020-03-28 20:27:19 +00:00
|
|
|
s_modCache = (FastVector<ModuleCache>*)tracy_malloc( sizeof( FastVector<ModuleCache> ) );
|
|
|
|
new(s_modCache) FastVector<ModuleCache>( 512 );
|
2020-02-24 20:54:19 +00:00
|
|
|
|
2021-06-15 19:04:16 +00:00
|
|
|
HANDLE proc = GetCurrentProcess();
|
|
|
|
HMODULE mod[1024];
|
2023-11-13 18:43:03 +00:00
|
|
|
if( initTimeModuleLoad && EnumProcessModules( proc, mod, sizeof( mod ), &needed ) != 0 )
|
2020-02-22 20:32:18 +00:00
|
|
|
{
|
|
|
|
const auto sz = needed / sizeof( HMODULE );
|
|
|
|
for( size_t i=0; i<sz; i++ )
|
|
|
|
{
|
|
|
|
MODULEINFO info;
|
|
|
|
if( GetModuleInformation( proc, mod[i], &info, sizeof( info ) ) != 0 )
|
|
|
|
{
|
|
|
|
char name[1024];
|
2023-11-17 20:48:17 +00:00
|
|
|
const auto nameLength = GetModuleFileNameA( mod[i], name, 1021 );
|
|
|
|
if( nameLength > 0 )
|
2020-02-22 20:32:18 +00:00
|
|
|
{
|
2023-02-14 14:32:37 +00:00
|
|
|
// This may be a new module loaded since our call to SymInitialize.
|
|
|
|
// Just in case, force DbgHelp to load its pdb !
|
2023-11-18 21:27:26 +00:00
|
|
|
LoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage );
|
2020-02-22 20:32:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-10-28 19:04:37 +00:00
|
|
|
|
2023-11-19 14:31:42 +00:00
|
|
|
#ifdef TRACY_DBGHELP_LOCK
|
2020-10-28 19:04:37 +00:00
|
|
|
DBGHELP_UNLOCK;
|
2023-11-19 14:31:42 +00:00
|
|
|
#endif
|
2018-06-18 23:17:19 +00:00
|
|
|
}
|
|
|
|
|
2022-04-27 15:49:28 +00:00
|
|
|
void EndCallstack()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-03-05 01:34:35 +00:00
|
|
|
const char* DecodeCallstackPtrFast( uint64_t ptr )
|
|
|
|
{
|
2023-11-19 14:31:42 +00:00
|
|
|
if( s_shouldResolveSymbolsOffline ) return "[unresolved]";
|
2023-11-17 20:48:17 +00:00
|
|
|
|
2020-02-26 21:30:11 +00:00
|
|
|
static char ret[MaxNameSize];
|
2019-03-05 01:34:35 +00:00
|
|
|
const auto proc = GetCurrentProcess();
|
|
|
|
|
2020-02-26 21:30:11 +00:00
|
|
|
char buf[sizeof( SYMBOL_INFO ) + MaxNameSize];
|
2019-03-05 01:34:35 +00:00
|
|
|
auto si = (SYMBOL_INFO*)buf;
|
|
|
|
si->SizeOfStruct = sizeof( SYMBOL_INFO );
|
2020-02-26 21:30:11 +00:00
|
|
|
si->MaxNameLen = MaxNameSize;
|
2019-03-05 01:34:35 +00:00
|
|
|
|
2023-11-19 14:31:42 +00:00
|
|
|
#ifdef TRACY_DBGHELP_LOCK
|
2020-10-28 19:04:37 +00:00
|
|
|
DBGHELP_LOCK;
|
2023-11-19 14:31:42 +00:00
|
|
|
#endif
|
2019-03-05 01:34:35 +00:00
|
|
|
if( SymFromAddr( proc, ptr, nullptr, si ) == 0 )
|
|
|
|
{
|
|
|
|
*ret = '\0';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
memcpy( ret, si->Name, si->NameLen );
|
|
|
|
ret[si->NameLen] = '\0';
|
|
|
|
}
|
2023-11-19 14:31:42 +00:00
|
|
|
#ifdef TRACY_DBGHELP_LOCK
|
2020-10-28 19:04:37 +00:00
|
|
|
DBGHELP_UNLOCK;
|
2023-11-19 14:31:42 +00:00
|
|
|
#endif
|
2019-03-05 01:34:35 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-11-25 22:27:20 +00:00
|
|
|
const char* GetKernelModulePath( uint64_t addr )
|
|
|
|
{
|
|
|
|
assert( addr >> 63 != 0 );
|
|
|
|
if( !s_krnlCache ) return nullptr;
|
|
|
|
auto it = std::lower_bound( s_krnlCache, s_krnlCache + s_krnlCacheCnt, addr, []( const KernelDriver& lhs, const uint64_t& rhs ) { return lhs.addr > rhs; } );
|
|
|
|
if( it == s_krnlCache + s_krnlCacheCnt ) return nullptr;
|
|
|
|
return it->path;
|
|
|
|
}
|
|
|
|
|
2023-11-17 20:48:17 +00:00
|
|
|
struct ModuleNameAndBaseAddress
|
|
|
|
{
|
|
|
|
const char* name;
|
|
|
|
uint64_t baseAddr;
|
|
|
|
};
|
|
|
|
|
|
|
|
ModuleNameAndBaseAddress GetModuleNameAndPrepareSymbols( uint64_t addr )
|
2020-02-22 20:06:32 +00:00
|
|
|
{
|
2021-06-15 19:08:09 +00:00
|
|
|
if( ( addr >> 63 ) != 0 )
|
|
|
|
{
|
|
|
|
if( s_krnlCache )
|
|
|
|
{
|
|
|
|
auto it = std::lower_bound( s_krnlCache, s_krnlCache + s_krnlCacheCnt, addr, []( const KernelDriver& lhs, const uint64_t& rhs ) { return lhs.addr > rhs; } );
|
|
|
|
if( it != s_krnlCache + s_krnlCacheCnt )
|
|
|
|
{
|
2023-11-17 20:48:17 +00:00
|
|
|
return ModuleNameAndBaseAddress{ it->mod, it->addr };
|
2021-06-15 19:08:09 +00:00
|
|
|
}
|
|
|
|
}
|
2023-11-17 20:48:17 +00:00
|
|
|
return ModuleNameAndBaseAddress{ "<kernel>", addr };
|
2021-06-15 19:08:09 +00:00
|
|
|
}
|
2020-02-24 22:04:10 +00:00
|
|
|
|
2020-02-24 20:54:19 +00:00
|
|
|
for( auto& v : *s_modCache )
|
2020-02-22 20:32:10 +00:00
|
|
|
{
|
2020-02-24 20:54:19 +00:00
|
|
|
if( addr >= v.start && addr < v.end )
|
2020-02-22 20:32:10 +00:00
|
|
|
{
|
2023-11-17 20:48:17 +00:00
|
|
|
return ModuleNameAndBaseAddress{ v.name, v.start };
|
2020-02-22 20:32:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-22 20:06:32 +00:00
|
|
|
HMODULE mod[1024];
|
|
|
|
DWORD needed;
|
|
|
|
HANDLE proc = GetCurrentProcess();
|
|
|
|
|
2021-06-09 23:48:11 +00:00
|
|
|
InitRpmalloc();
|
2020-02-22 20:06:32 +00:00
|
|
|
if( EnumProcessModules( proc, mod, sizeof( mod ), &needed ) != 0 )
|
|
|
|
{
|
|
|
|
const auto sz = needed / sizeof( HMODULE );
|
|
|
|
for( size_t i=0; i<sz; i++ )
|
|
|
|
{
|
|
|
|
MODULEINFO info;
|
|
|
|
if( GetModuleInformation( proc, mod[i], &info, sizeof( info ) ) != 0 )
|
|
|
|
{
|
|
|
|
const auto base = uint64_t( info.lpBaseOfDll );
|
|
|
|
if( addr >= base && addr < base + info.SizeOfImage )
|
|
|
|
{
|
|
|
|
char name[1024];
|
2023-11-17 20:48:17 +00:00
|
|
|
const auto nameLength = GetModuleFileNameA( mod[i], name, 1021 );
|
|
|
|
if( nameLength > 0 )
|
2020-02-22 20:06:32 +00:00
|
|
|
{
|
2021-12-08 20:30:06 +00:00
|
|
|
// since this is the first time we encounter this module, load its symbols (needed for modules loaded after SymInitialize)
|
2023-11-18 21:27:26 +00:00
|
|
|
ModuleCache* cachedModule = LoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage );
|
2023-11-17 20:48:17 +00:00
|
|
|
return ModuleNameAndBaseAddress{ cachedModule->name, cachedModule->start };
|
2020-02-22 20:06:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-11-17 20:48:17 +00:00
|
|
|
|
|
|
|
return ModuleNameAndBaseAddress{ "[unknown]", 0x0 };
|
2020-02-22 20:06:32 +00:00
|
|
|
}
|
|
|
|
|
2020-08-20 15:38:29 +00:00
|
|
|
CallstackSymbolData DecodeSymbolAddress( uint64_t ptr )
|
2020-02-26 21:32:42 +00:00
|
|
|
{
|
2020-08-20 15:38:29 +00:00
|
|
|
CallstackSymbolData sym;
|
2023-11-17 20:48:17 +00:00
|
|
|
|
2023-11-19 14:31:42 +00:00
|
|
|
if( s_shouldResolveSymbolsOffline )
|
2023-11-17 20:48:17 +00:00
|
|
|
{
|
|
|
|
sym.file = "[unknown]";
|
|
|
|
sym.line = 0;
|
|
|
|
sym.needFree = false;
|
|
|
|
return sym;
|
|
|
|
}
|
|
|
|
|
2020-02-26 21:32:42 +00:00
|
|
|
IMAGEHLP_LINE64 line;
|
|
|
|
DWORD displacement = 0;
|
|
|
|
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
2023-11-19 14:31:42 +00:00
|
|
|
#ifdef TRACY_DBGHELP_LOCK
|
2020-10-28 19:04:37 +00:00
|
|
|
DBGHELP_LOCK;
|
2023-11-19 14:31:42 +00:00
|
|
|
#endif
|
2020-10-28 19:04:37 +00:00
|
|
|
const auto res = SymGetLineFromAddr64( GetCurrentProcess(), ptr, &displacement, &line );
|
2021-11-20 14:56:41 +00:00
|
|
|
if( res == 0 || line.LineNumber >= 0xF00000 )
|
2020-02-26 21:32:42 +00:00
|
|
|
{
|
2020-02-27 12:17:26 +00:00
|
|
|
sym.file = "[unknown]";
|
2020-02-26 21:32:42 +00:00
|
|
|
sym.line = 0;
|
2021-10-20 23:30:17 +00:00
|
|
|
sym.needFree = false;
|
2020-02-26 21:32:42 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-10-20 23:30:17 +00:00
|
|
|
sym.file = CopyString( line.FileName );
|
2020-02-26 21:32:42 +00:00
|
|
|
sym.line = line.LineNumber;
|
2021-10-20 23:30:17 +00:00
|
|
|
sym.needFree = true;
|
2020-02-26 21:32:42 +00:00
|
|
|
}
|
2023-11-19 14:31:42 +00:00
|
|
|
#ifdef TRACY_DBGHELP_LOCK
|
2021-10-20 23:31:03 +00:00
|
|
|
DBGHELP_UNLOCK;
|
2023-11-19 14:31:42 +00:00
|
|
|
#endif
|
2020-02-26 21:32:42 +00:00
|
|
|
return sym;
|
|
|
|
}
|
|
|
|
|
2019-01-20 18:11:48 +00:00
|
|
|
CallstackEntryData DecodeCallstackPtr( uint64_t ptr )
|
2018-06-18 23:17:19 +00:00
|
|
|
{
|
2023-11-24 21:32:45 +00:00
|
|
|
#ifdef TRACY_DBGHELP_LOCK
|
|
|
|
DBGHELP_LOCK;
|
|
|
|
#endif
|
|
|
|
|
2023-11-17 20:48:17 +00:00
|
|
|
InitRpmalloc();
|
|
|
|
|
|
|
|
const ModuleNameAndBaseAddress moduleNameAndAddress = GetModuleNameAndPrepareSymbols( ptr );
|
|
|
|
|
2023-11-19 14:31:42 +00:00
|
|
|
if( s_shouldResolveSymbolsOffline )
|
2023-11-17 20:48:17 +00:00
|
|
|
{
|
2023-11-24 21:32:45 +00:00
|
|
|
#ifdef TRACY_DBGHELP_LOCK
|
|
|
|
DBGHELP_UNLOCK;
|
|
|
|
#endif
|
|
|
|
|
2023-11-17 20:48:17 +00:00
|
|
|
cb_data[0].symAddr = ptr - moduleNameAndAddress.baseAddr;
|
|
|
|
cb_data[0].symLen = 0;
|
|
|
|
|
|
|
|
cb_data[0].name = CopyStringFast("[unresolved]");
|
|
|
|
cb_data[0].file = CopyStringFast("[unknown]");
|
|
|
|
cb_data[0].line = 0;
|
|
|
|
|
|
|
|
return { cb_data, 1, moduleNameAndAddress.name };
|
|
|
|
}
|
|
|
|
|
2019-02-02 12:58:17 +00:00
|
|
|
int write;
|
2018-06-18 23:17:19 +00:00
|
|
|
const auto proc = GetCurrentProcess();
|
2021-06-09 23:48:11 +00:00
|
|
|
|
2023-11-19 14:31:42 +00:00
|
|
|
#if !defined TRACY_NO_CALLSTACK_INLINES
|
2019-01-26 22:50:58 +00:00
|
|
|
BOOL doInline = FALSE;
|
2021-06-16 23:34:45 +00:00
|
|
|
DWORD ctx = 0;
|
2021-06-20 06:26:03 +00:00
|
|
|
DWORD inlineNum = 0;
|
2021-06-16 23:34:45 +00:00
|
|
|
if( _SymAddrIncludeInlineTrace )
|
|
|
|
{
|
|
|
|
inlineNum = _SymAddrIncludeInlineTrace( proc, ptr );
|
|
|
|
if( inlineNum > MaxCbTrace - 1 ) inlineNum = MaxCbTrace - 1;
|
|
|
|
DWORD idx;
|
|
|
|
if( inlineNum != 0 ) doInline = _SymQueryInlineTrace( proc, ptr, 0, ptr, ptr, &ctx, &idx );
|
|
|
|
}
|
2019-01-26 22:50:58 +00:00
|
|
|
if( doInline )
|
|
|
|
{
|
|
|
|
write = inlineNum;
|
|
|
|
cb_num = 1 + inlineNum;
|
|
|
|
}
|
|
|
|
else
|
2023-11-19 14:31:42 +00:00
|
|
|
#endif
|
2019-01-26 22:50:58 +00:00
|
|
|
{
|
|
|
|
write = 0;
|
|
|
|
cb_num = 1;
|
|
|
|
}
|
2018-06-18 23:17:19 +00:00
|
|
|
|
2020-02-26 21:30:11 +00:00
|
|
|
char buf[sizeof( SYMBOL_INFO ) + MaxNameSize];
|
2018-06-18 23:17:19 +00:00
|
|
|
auto si = (SYMBOL_INFO*)buf;
|
|
|
|
si->SizeOfStruct = sizeof( SYMBOL_INFO );
|
2020-02-26 21:30:11 +00:00
|
|
|
si->MaxNameLen = MaxNameSize;
|
2018-06-18 23:17:19 +00:00
|
|
|
|
2020-02-25 22:03:40 +00:00
|
|
|
const auto symValid = SymFromAddr( proc, ptr, nullptr, si ) != 0;
|
2018-06-18 23:17:19 +00:00
|
|
|
|
|
|
|
IMAGEHLP_LINE64 line;
|
2018-06-20 19:01:25 +00:00
|
|
|
DWORD displacement = 0;
|
2019-02-20 15:28:27 +00:00
|
|
|
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
|
|
|
|
2018-06-19 23:05:44 +00:00
|
|
|
{
|
2019-02-20 15:28:27 +00:00
|
|
|
const char* filename;
|
2021-11-20 14:56:41 +00:00
|
|
|
const auto res = SymGetLineFromAddr64( proc, ptr, &displacement, &line );
|
|
|
|
if( res == 0 || line.LineNumber >= 0xF00000 )
|
2019-02-20 15:28:27 +00:00
|
|
|
{
|
|
|
|
filename = "[unknown]";
|
|
|
|
cb_data[write].line = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
filename = line.FileName;
|
|
|
|
cb_data[write].line = line.LineNumber;
|
|
|
|
}
|
|
|
|
|
2023-11-17 20:48:17 +00:00
|
|
|
cb_data[write].name = symValid ? CopyStringFast( si->Name, si->NameLen ) : CopyStringFast( moduleNameAndAddress.name );
|
2021-06-09 23:48:11 +00:00
|
|
|
cb_data[write].file = CopyStringFast( filename );
|
2020-03-25 17:05:00 +00:00
|
|
|
if( symValid )
|
|
|
|
{
|
|
|
|
cb_data[write].symLen = si->Size;
|
|
|
|
cb_data[write].symAddr = si->Address;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
cb_data[write].symLen = 0;
|
|
|
|
cb_data[write].symAddr = 0;
|
|
|
|
}
|
2019-02-20 15:28:27 +00:00
|
|
|
}
|
2019-01-26 22:50:58 +00:00
|
|
|
|
2023-11-19 14:31:42 +00:00
|
|
|
#if !defined TRACY_NO_CALLSTACK_INLINES
|
2019-01-26 22:50:58 +00:00
|
|
|
if( doInline )
|
|
|
|
{
|
|
|
|
for( DWORD i=0; i<inlineNum; i++ )
|
|
|
|
{
|
|
|
|
auto& cb = cb_data[i];
|
2021-06-16 23:34:45 +00:00
|
|
|
const auto symInlineValid = _SymFromInlineContext( proc, ptr, ctx, nullptr, si ) != 0;
|
2019-02-20 15:28:27 +00:00
|
|
|
const char* filename;
|
2021-06-16 23:34:45 +00:00
|
|
|
if( _SymGetLineFromInlineContext( proc, ptr, ctx, 0, &displacement, &line ) == 0 )
|
2019-01-26 22:50:58 +00:00
|
|
|
{
|
|
|
|
filename = "[unknown]";
|
|
|
|
cb.line = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
filename = line.FileName;
|
|
|
|
cb.line = line.LineNumber;
|
|
|
|
}
|
|
|
|
|
2023-11-17 20:48:17 +00:00
|
|
|
cb.name = symInlineValid ? CopyStringFast( si->Name, si->NameLen ) : CopyStringFast( moduleNameAndAddress.name );
|
2021-06-09 23:48:11 +00:00
|
|
|
cb.file = CopyStringFast( filename );
|
2020-03-25 17:05:00 +00:00
|
|
|
if( symInlineValid )
|
|
|
|
{
|
|
|
|
cb.symLen = si->Size;
|
|
|
|
cb.symAddr = si->Address;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
cb.symLen = 0;
|
|
|
|
cb.symAddr = 0;
|
|
|
|
}
|
2020-02-25 22:03:40 +00:00
|
|
|
|
2019-01-26 22:50:58 +00:00
|
|
|
ctx++;
|
|
|
|
}
|
|
|
|
}
|
2023-11-19 14:31:42 +00:00
|
|
|
#endif
|
|
|
|
#ifdef TRACY_DBGHELP_LOCK
|
2020-10-28 19:04:37 +00:00
|
|
|
DBGHELP_UNLOCK;
|
2023-11-19 14:31:42 +00:00
|
|
|
#endif
|
2018-06-19 23:26:05 +00:00
|
|
|
|
2023-11-17 20:48:17 +00:00
|
|
|
return { cb_data, uint8_t( cb_num ), moduleNameAndAddress.name };
|
2018-06-18 23:17:19 +00:00
|
|
|
}
|
|
|
|
|
2019-11-29 11:04:47 +00:00
|
|
|
#elif TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6
|
2018-06-21 15:07:21 +00:00
|
|
|
|
2022-10-04 20:16:20 +00:00
|
|
|
enum { MaxCbTrace = 64 };
|
2018-06-21 15:07:21 +00:00
|
|
|
|
2023-11-18 21:27:26 +00:00
|
|
|
struct backtrace_state* cb_bts = nullptr;
|
|
|
|
|
2019-01-20 15:55:59 +00:00
|
|
|
int cb_num;
|
|
|
|
CallstackEntry cb_data[MaxCbTrace];
|
2020-02-26 01:25:45 +00:00
|
|
|
int cb_fixup;
|
2023-11-29 18:23:58 +00:00
|
|
|
#ifdef TRACY_USE_IMAGE_CACHE
|
|
|
|
static ImageCache* s_imageCache = nullptr;
|
|
|
|
#endif //#ifdef TRACY_USE_IMAGE_CACHE
|
2018-06-21 15:07:21 +00:00
|
|
|
|
2022-04-27 16:28:40 +00:00
|
|
|
#ifdef TRACY_DEBUGINFOD
|
|
|
|
debuginfod_client* s_debuginfod;
|
2022-04-30 18:07:47 +00:00
|
|
|
|
|
|
|
struct DebugInfo
|
|
|
|
{
|
|
|
|
uint8_t* buildid;
|
|
|
|
size_t buildid_size;
|
2022-05-01 10:43:15 +00:00
|
|
|
char* filename;
|
2022-04-30 18:07:47 +00:00
|
|
|
int fd;
|
|
|
|
};
|
|
|
|
|
2024-02-26 22:03:37 +00:00
|
|
|
static FastVector<DebugInfo>* s_di_known;
|
2022-04-27 16:28:40 +00:00
|
|
|
#endif
|
|
|
|
|
2021-06-10 23:31:02 +00:00
|
|
|
#ifdef __linux
|
|
|
|
struct KernelSymbol
|
|
|
|
{
|
|
|
|
uint64_t addr;
|
2024-05-25 14:27:13 +00:00
|
|
|
uint32_t size;
|
2021-06-10 23:31:02 +00:00
|
|
|
const char* name;
|
|
|
|
const char* mod;
|
|
|
|
};
|
|
|
|
|
|
|
|
KernelSymbol* s_kernelSym = nullptr;
|
|
|
|
size_t s_kernelSymCnt;
|
|
|
|
|
|
|
|
static void InitKernelSymbols()
|
|
|
|
{
|
|
|
|
FILE* f = fopen( "/proc/kallsyms", "rb" );
|
|
|
|
if( !f ) return;
|
2024-05-25 13:42:13 +00:00
|
|
|
tracy::FastVector<KernelSymbol> tmpSym( 512 * 1024 );
|
2021-06-10 23:31:02 +00:00
|
|
|
size_t linelen = 16 * 1024; // linelen must be big enough to prevent reallocs in getline()
|
|
|
|
auto linebuf = (char*)tracy_malloc( linelen );
|
|
|
|
ssize_t sz;
|
2024-05-25 14:27:13 +00:00
|
|
|
size_t validCnt = 0;
|
2021-06-10 23:31:02 +00:00
|
|
|
while( ( sz = getline( &linebuf, &linelen, f ) ) != -1 )
|
|
|
|
{
|
|
|
|
auto ptr = linebuf;
|
|
|
|
uint64_t addr = 0;
|
|
|
|
while( *ptr != ' ' )
|
|
|
|
{
|
|
|
|
auto v = *ptr;
|
|
|
|
if( v >= '0' && v <= '9' )
|
|
|
|
{
|
|
|
|
v -= '0';
|
|
|
|
}
|
|
|
|
else if( v >= 'a' && v <= 'f' )
|
|
|
|
{
|
|
|
|
v -= 'a';
|
|
|
|
v += 10;
|
|
|
|
}
|
|
|
|
else if( v >= 'A' && v <= 'F' )
|
|
|
|
{
|
|
|
|
v -= 'A';
|
|
|
|
v += 10;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
assert( false );
|
|
|
|
}
|
|
|
|
assert( ( v & ~0xF ) == 0 );
|
|
|
|
addr <<= 4;
|
|
|
|
addr |= v;
|
|
|
|
ptr++;
|
|
|
|
}
|
|
|
|
if( addr == 0 ) continue;
|
|
|
|
ptr++;
|
2024-05-25 14:06:36 +00:00
|
|
|
const bool valid = *ptr == 'T' || *ptr == 't';
|
2021-06-10 23:31:02 +00:00
|
|
|
ptr += 2;
|
|
|
|
const auto namestart = ptr;
|
|
|
|
while( *ptr != '\t' && *ptr != '\n' ) ptr++;
|
|
|
|
const auto nameend = ptr;
|
|
|
|
const char* modstart = nullptr;
|
|
|
|
const char* modend;
|
|
|
|
if( *ptr == '\t' )
|
|
|
|
{
|
|
|
|
ptr += 2;
|
|
|
|
modstart = ptr;
|
|
|
|
while( *ptr != ']' ) ptr++;
|
|
|
|
modend = ptr;
|
|
|
|
}
|
|
|
|
|
2024-05-25 14:06:36 +00:00
|
|
|
char* strname = nullptr;
|
2021-06-10 23:31:02 +00:00
|
|
|
char* strmod = nullptr;
|
2024-05-25 14:06:36 +00:00
|
|
|
|
|
|
|
if( valid )
|
2021-06-10 23:31:02 +00:00
|
|
|
{
|
2024-05-25 14:27:13 +00:00
|
|
|
validCnt++;
|
|
|
|
|
2024-05-25 14:06:36 +00:00
|
|
|
strname = (char*)tracy_malloc_fast( nameend - namestart + 1 );
|
|
|
|
memcpy( strname, namestart, nameend - namestart );
|
|
|
|
strname[nameend-namestart] = '\0';
|
|
|
|
|
|
|
|
if( modstart )
|
|
|
|
{
|
|
|
|
strmod = (char*)tracy_malloc_fast( modend - modstart + 1 );
|
|
|
|
memcpy( strmod, modstart, modend - modstart );
|
|
|
|
strmod[modend-modstart] = '\0';
|
|
|
|
}
|
2021-06-10 23:31:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
auto sym = tmpSym.push_next();
|
|
|
|
sym->addr = addr;
|
2024-05-25 14:27:13 +00:00
|
|
|
sym->size = 0;
|
2021-06-10 23:31:02 +00:00
|
|
|
sym->name = strname;
|
|
|
|
sym->mod = strmod;
|
|
|
|
}
|
|
|
|
tracy_free_fast( linebuf );
|
|
|
|
fclose( f );
|
|
|
|
if( tmpSym.empty() ) return;
|
|
|
|
|
2024-05-25 14:27:13 +00:00
|
|
|
std::sort( tmpSym.begin(), tmpSym.end(), []( const KernelSymbol& lhs, const KernelSymbol& rhs ) { return lhs.addr < rhs.addr; } );
|
|
|
|
for( size_t i=0; i<tmpSym.size()-1; i++ )
|
|
|
|
{
|
|
|
|
if( tmpSym[i].name ) tmpSym[i].size = tmpSym[i+1].addr - tmpSym[i].addr;
|
|
|
|
}
|
|
|
|
|
|
|
|
s_kernelSymCnt = validCnt;
|
|
|
|
s_kernelSym = (KernelSymbol*)tracy_malloc_fast( sizeof( KernelSymbol ) * validCnt );
|
|
|
|
auto dst = s_kernelSym;
|
|
|
|
for( auto& v : tmpSym )
|
|
|
|
{
|
|
|
|
if( v.name ) *dst++ = v;
|
|
|
|
}
|
|
|
|
assert( dst == s_kernelSym + validCnt );
|
|
|
|
|
|
|
|
TracyDebug( "Loaded %zu kernel symbols (%zu code sections)\n", tmpSym.size(), validCnt );
|
2021-06-10 23:31:02 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2022-09-01 23:23:14 +00:00
|
|
|
char* NormalizePath( const char* path )
|
|
|
|
{
|
|
|
|
if( path[0] != '/' ) return nullptr;
|
|
|
|
|
|
|
|
const char* ptr = path;
|
|
|
|
const char* end = path;
|
|
|
|
while( *end ) end++;
|
|
|
|
|
|
|
|
char* res = (char*)tracy_malloc( end - ptr + 1 );
|
|
|
|
size_t rsz = 0;
|
|
|
|
|
|
|
|
while( ptr < end )
|
|
|
|
{
|
|
|
|
const char* next = ptr;
|
|
|
|
while( next < end && *next != '/' ) next++;
|
|
|
|
size_t lsz = next - ptr;
|
|
|
|
switch( lsz )
|
|
|
|
{
|
|
|
|
case 2:
|
|
|
|
if( memcmp( ptr, "..", 2 ) == 0 )
|
|
|
|
{
|
|
|
|
const char* back = res + rsz - 1;
|
|
|
|
while( back > res && *back != '/' ) back--;
|
|
|
|
rsz = back - res;
|
|
|
|
ptr = next + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
if( *ptr == '.' )
|
|
|
|
{
|
|
|
|
ptr = next + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0:
|
|
|
|
ptr = next + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if( rsz != 1 ) res[rsz++] = '/';
|
|
|
|
memcpy( res+rsz, ptr, lsz );
|
|
|
|
rsz += lsz;
|
|
|
|
ptr = next + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( rsz == 0 )
|
|
|
|
{
|
|
|
|
memcpy( res, "/", 2 );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
res[rsz] = '\0';
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2022-08-16 11:55:46 +00:00
|
|
|
void InitCallstackCritical()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-01-20 15:55:59 +00:00
|
|
|
void InitCallstack()
|
|
|
|
{
|
2023-11-29 18:23:58 +00:00
|
|
|
InitRpmalloc();
|
|
|
|
|
|
|
|
#ifdef TRACY_USE_IMAGE_CACHE
|
2023-12-09 15:40:07 +00:00
|
|
|
s_imageCache = (ImageCache*)tracy_malloc( sizeof( ImageCache ) );
|
2023-11-29 18:23:58 +00:00
|
|
|
new(s_imageCache) ImageCache();
|
|
|
|
#endif //#ifdef TRACY_USE_IMAGE_CACHE
|
2023-12-12 18:09:41 +00:00
|
|
|
|
2023-11-18 21:27:26 +00:00
|
|
|
#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE
|
2023-11-19 14:31:42 +00:00
|
|
|
s_shouldResolveSymbolsOffline = ShouldResolveSymbolsOffline();
|
|
|
|
#endif //#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE
|
|
|
|
if( s_shouldResolveSymbolsOffline )
|
2023-11-17 20:48:17 +00:00
|
|
|
{
|
|
|
|
cb_bts = nullptr; // disable use of libbacktrace calls
|
|
|
|
TracyDebug("TRACY: enabling offline symbol resolving!\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
cb_bts = backtrace_create_state( nullptr, 0, nullptr, nullptr );
|
|
|
|
}
|
|
|
|
|
2023-09-05 23:28:58 +00:00
|
|
|
#ifndef TRACY_DEMANGLE
|
2022-07-23 10:34:35 +00:00
|
|
|
___tracy_init_demangle_buffer();
|
2023-09-05 23:28:58 +00:00
|
|
|
#endif
|
2021-06-10 23:31:02 +00:00
|
|
|
|
|
|
|
#ifdef __linux
|
|
|
|
InitKernelSymbols();
|
|
|
|
#endif
|
2022-04-27 16:28:40 +00:00
|
|
|
#ifdef TRACY_DEBUGINFOD
|
|
|
|
s_debuginfod = debuginfod_begin();
|
2024-02-26 22:03:37 +00:00
|
|
|
s_di_known = (FastVector<DebugInfo>*)tracy_malloc( sizeof( FastVector<DebugInfo> ) );
|
|
|
|
new (s_di_known) FastVector<DebugInfo>( 16 );
|
2022-04-27 16:28:40 +00:00
|
|
|
#endif
|
2018-06-21 15:07:21 +00:00
|
|
|
}
|
|
|
|
|
2022-04-30 18:07:47 +00:00
|
|
|
#ifdef TRACY_DEBUGINFOD
|
|
|
|
void ClearDebugInfoVector( FastVector<DebugInfo>& vec )
|
|
|
|
{
|
|
|
|
for( auto& v : vec )
|
|
|
|
{
|
|
|
|
tracy_free( v.buildid );
|
2022-05-01 10:43:15 +00:00
|
|
|
tracy_free( v.filename );
|
2022-04-30 18:07:47 +00:00
|
|
|
if( v.fd >= 0 ) close( v.fd );
|
|
|
|
}
|
|
|
|
vec.clear();
|
|
|
|
}
|
2022-04-30 19:35:14 +00:00
|
|
|
|
|
|
|
DebugInfo* FindDebugInfo( FastVector<DebugInfo>& vec, const uint8_t* buildid_data, size_t buildid_size )
|
|
|
|
{
|
|
|
|
for( auto& v : vec )
|
|
|
|
{
|
|
|
|
if( v.buildid_size == buildid_size && memcmp( v.buildid, buildid_data, buildid_size ) == 0 )
|
|
|
|
{
|
|
|
|
return &v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-05-01 10:43:15 +00:00
|
|
|
int GetDebugInfoDescriptor( const char* buildid_data, size_t buildid_size, const char* filename )
|
2022-04-30 19:35:14 +00:00
|
|
|
{
|
|
|
|
auto buildid = (uint8_t*)buildid_data;
|
2024-02-26 22:03:37 +00:00
|
|
|
auto it = FindDebugInfo( *s_di_known, buildid, buildid_size );
|
2022-04-30 19:35:14 +00:00
|
|
|
if( it ) return it->fd >= 0 ? dup( it->fd ) : -1;
|
|
|
|
|
2022-04-30 21:22:54 +00:00
|
|
|
int fd = debuginfod_find_debuginfo( s_debuginfod, buildid, buildid_size, nullptr );
|
2024-02-26 22:03:37 +00:00
|
|
|
it = s_di_known->push_next();
|
2022-04-30 21:22:54 +00:00
|
|
|
it->buildid_size = buildid_size;
|
|
|
|
it->buildid = (uint8_t*)tracy_malloc( buildid_size );
|
|
|
|
memcpy( it->buildid, buildid, buildid_size );
|
2022-05-01 10:43:15 +00:00
|
|
|
const auto fnsz = strlen( filename ) + 1;
|
|
|
|
it->filename = (char*)tracy_malloc( fnsz );
|
|
|
|
memcpy( it->filename, filename, fnsz );
|
2022-04-30 21:22:54 +00:00
|
|
|
it->fd = fd >= 0 ? fd : -1;
|
2022-08-16 13:22:28 +00:00
|
|
|
TracyDebug( "DebugInfo descriptor query: %i, fn: %s\n", fd, filename );
|
2022-04-30 21:22:54 +00:00
|
|
|
return it->fd;
|
2022-04-30 19:35:14 +00:00
|
|
|
}
|
2022-05-01 12:50:50 +00:00
|
|
|
|
|
|
|
const uint8_t* GetBuildIdForImage( const char* image, size_t& size )
|
|
|
|
{
|
|
|
|
assert( image );
|
2024-02-26 22:03:37 +00:00
|
|
|
for( auto& v : *s_di_known )
|
2022-05-01 12:50:50 +00:00
|
|
|
{
|
|
|
|
if( strcmp( image, v.filename ) == 0 )
|
|
|
|
{
|
|
|
|
size = v.buildid_size;
|
|
|
|
return v.buildid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
2022-05-01 13:05:23 +00:00
|
|
|
|
|
|
|
debuginfod_client* GetDebuginfodClient()
|
|
|
|
{
|
|
|
|
return s_debuginfod;
|
|
|
|
}
|
2022-04-30 18:07:47 +00:00
|
|
|
#endif
|
|
|
|
|
2022-04-27 15:49:28 +00:00
|
|
|
void EndCallstack()
|
|
|
|
{
|
2023-11-29 18:23:58 +00:00
|
|
|
#ifdef TRACY_USE_IMAGE_CACHE
|
2023-12-09 15:40:07 +00:00
|
|
|
if( s_imageCache )
|
|
|
|
{
|
|
|
|
s_imageCache->~ImageCache();
|
|
|
|
tracy_free( s_imageCache );
|
|
|
|
}
|
2023-11-29 18:23:58 +00:00
|
|
|
#endif //#ifdef TRACY_USE_IMAGE_CACHE
|
2023-09-05 23:28:58 +00:00
|
|
|
#ifndef TRACY_DEMANGLE
|
2022-07-23 10:34:35 +00:00
|
|
|
___tracy_free_demangle_buffer();
|
2023-09-05 23:28:58 +00:00
|
|
|
#endif
|
2022-04-27 16:28:40 +00:00
|
|
|
#ifdef TRACY_DEBUGINFOD
|
2024-02-26 22:03:37 +00:00
|
|
|
ClearDebugInfoVector( *s_di_known );
|
|
|
|
s_di_known->~FastVector<DebugInfo>();
|
|
|
|
tracy_free( s_di_known );
|
|
|
|
|
2022-04-27 16:28:40 +00:00
|
|
|
debuginfod_end( s_debuginfod );
|
|
|
|
#endif
|
2022-04-27 15:49:28 +00:00
|
|
|
}
|
|
|
|
|
Use dladdr, not libbacktrace in fast callstack decode path.
DecodeCallstackPtrFast() may be called outside the symbol processing thread,
for example in the crash handler. Using the less-capable dladdr functionality
doesn't have a big impact here. Callstack decoding in this context is used to
remove the uninteresting top part of the callstack, so that the callstack ends
at the crashing function, and not in the crash handler. Even if this
functionality would be impacted by this change, the damage done is close to
none.
The other alternative is to use locking each time a libbacktrace is to be
used, which does not seem to be worthy to do, considering that the problem
only occurs in a very rare code path.
NB everything was working when it was first implemented, because back then the
callstack decoding was still performed on the main thread, and not on a
separate, dedicated one.
2022-10-08 11:22:56 +00:00
|
|
|
const char* DecodeCallstackPtrFast( uint64_t ptr )
|
2019-03-05 01:34:35 +00:00
|
|
|
{
|
Use dladdr, not libbacktrace in fast callstack decode path.
DecodeCallstackPtrFast() may be called outside the symbol processing thread,
for example in the crash handler. Using the less-capable dladdr functionality
doesn't have a big impact here. Callstack decoding in this context is used to
remove the uninteresting top part of the callstack, so that the callstack ends
at the crashing function, and not in the crash handler. Even if this
functionality would be impacted by this change, the damage done is close to
none.
The other alternative is to use locking each time a libbacktrace is to be
used, which does not seem to be worthy to do, considering that the problem
only occurs in a very rare code path.
NB everything was working when it was first implemented, because back then the
callstack decoding was still performed on the main thread, and not on a
separate, dedicated one.
2022-10-08 11:22:56 +00:00
|
|
|
static char ret[1024];
|
|
|
|
auto vptr = (void*)ptr;
|
|
|
|
const char* symname = nullptr;
|
|
|
|
Dl_info dlinfo;
|
|
|
|
if( dladdr( vptr, &dlinfo ) && dlinfo.dli_sname )
|
2019-03-05 17:56:39 +00:00
|
|
|
{
|
Use dladdr, not libbacktrace in fast callstack decode path.
DecodeCallstackPtrFast() may be called outside the symbol processing thread,
for example in the crash handler. Using the less-capable dladdr functionality
doesn't have a big impact here. Callstack decoding in this context is used to
remove the uninteresting top part of the callstack, so that the callstack ends
at the crashing function, and not in the crash handler. Even if this
functionality would be impacted by this change, the damage done is close to
none.
The other alternative is to use locking each time a libbacktrace is to be
used, which does not seem to be worthy to do, considering that the problem
only occurs in a very rare code path.
NB everything was working when it was first implemented, because back then the
callstack decoding was still performed on the main thread, and not on a
separate, dedicated one.
2022-10-08 11:22:56 +00:00
|
|
|
symname = dlinfo.dli_sname;
|
|
|
|
}
|
|
|
|
if( symname )
|
|
|
|
{
|
|
|
|
strcpy( ret, symname );
|
2019-03-05 17:56:39 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Use dladdr, not libbacktrace in fast callstack decode path.
DecodeCallstackPtrFast() may be called outside the symbol processing thread,
for example in the crash handler. Using the less-capable dladdr functionality
doesn't have a big impact here. Callstack decoding in this context is used to
remove the uninteresting top part of the callstack, so that the callstack ends
at the crashing function, and not in the crash handler. Even if this
functionality would be impacted by this change, the damage done is close to
none.
The other alternative is to use locking each time a libbacktrace is to be
used, which does not seem to be worthy to do, considering that the problem
only occurs in a very rare code path.
NB everything was working when it was first implemented, because back then the
callstack decoding was still performed on the main thread, and not on a
separate, dedicated one.
2022-10-08 11:22:56 +00:00
|
|
|
*ret = '\0';
|
2019-03-05 17:56:39 +00:00
|
|
|
}
|
2019-03-05 01:34:35 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-05-30 13:38:59 +00:00
|
|
|
static int SymbolAddressDataCb( void* data, uintptr_t pc, uintptr_t lowaddr, const char* fn, int lineno, const char* function )
|
2020-02-26 22:38:04 +00:00
|
|
|
{
|
2020-08-20 15:38:29 +00:00
|
|
|
auto& sym = *(CallstackSymbolData*)data;
|
2020-02-27 11:58:01 +00:00
|
|
|
if( !fn )
|
2020-02-26 22:38:04 +00:00
|
|
|
{
|
2020-08-18 18:34:11 +00:00
|
|
|
sym.file = "[unknown]";
|
2020-02-26 22:38:04 +00:00
|
|
|
sym.line = 0;
|
2020-02-27 12:17:26 +00:00
|
|
|
sym.needFree = false;
|
2020-02-26 22:38:04 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-09-01 23:23:29 +00:00
|
|
|
sym.file = NormalizePath( fn );
|
|
|
|
if( !sym.file ) sym.file = CopyString( fn );
|
2020-02-26 22:38:04 +00:00
|
|
|
sym.line = lineno;
|
2020-02-27 12:17:26 +00:00
|
|
|
sym.needFree = true;
|
2020-02-26 22:38:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void SymbolAddressErrorCb( void* data, const char* /*msg*/, int /*errnum*/ )
|
|
|
|
{
|
2020-08-20 15:38:29 +00:00
|
|
|
auto& sym = *(CallstackSymbolData*)data;
|
2020-06-04 15:46:39 +00:00
|
|
|
sym.file = "[unknown]";
|
|
|
|
sym.line = 0;
|
|
|
|
sym.needFree = false;
|
2020-02-26 22:38:04 +00:00
|
|
|
}
|
|
|
|
|
2020-08-20 15:38:29 +00:00
|
|
|
CallstackSymbolData DecodeSymbolAddress( uint64_t ptr )
|
2020-02-26 22:38:04 +00:00
|
|
|
{
|
2020-08-20 15:38:29 +00:00
|
|
|
CallstackSymbolData sym;
|
2023-11-17 20:48:17 +00:00
|
|
|
if( cb_bts )
|
|
|
|
{
|
|
|
|
backtrace_pcinfo( cb_bts, ptr, SymbolAddressDataCb, SymbolAddressErrorCb, &sym );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SymbolAddressErrorCb(&sym, nullptr, 0);
|
|
|
|
}
|
|
|
|
|
2020-02-26 22:38:04 +00:00
|
|
|
return sym;
|
|
|
|
}
|
|
|
|
|
2020-05-30 13:38:59 +00:00
|
|
|
static int CallstackDataCb( void* /*data*/, uintptr_t pc, uintptr_t lowaddr, const char* fn, int lineno, const char* function )
|
2019-01-20 15:55:59 +00:00
|
|
|
{
|
2020-03-25 17:05:00 +00:00
|
|
|
cb_data[cb_num].symLen = 0;
|
2020-05-30 13:38:59 +00:00
|
|
|
cb_data[cb_num].symAddr = (uint64_t)lowaddr;
|
2020-02-26 01:25:45 +00:00
|
|
|
|
2019-01-20 15:55:59 +00:00
|
|
|
if( !fn && !function )
|
2018-06-20 19:45:27 +00:00
|
|
|
{
|
2019-01-20 15:55:59 +00:00
|
|
|
const char* symname = nullptr;
|
|
|
|
auto vptr = (void*)pc;
|
|
|
|
ptrdiff_t symoff = 0;
|
2018-06-20 21:01:00 +00:00
|
|
|
|
2019-01-20 15:55:59 +00:00
|
|
|
Dl_info dlinfo;
|
|
|
|
if( dladdr( vptr, &dlinfo ) )
|
2018-06-20 21:01:00 +00:00
|
|
|
{
|
2019-01-20 15:55:59 +00:00
|
|
|
symname = dlinfo.dli_sname;
|
|
|
|
symoff = (char*)pc - (char*)dlinfo.dli_saddr;
|
2022-07-19 23:21:43 +00:00
|
|
|
const char* demangled = ___tracy_demangle( symname );
|
|
|
|
if( demangled ) symname = demangled;
|
2018-06-20 21:01:00 +00:00
|
|
|
}
|
2018-06-20 20:54:42 +00:00
|
|
|
|
2019-01-20 15:55:59 +00:00
|
|
|
if( !symname ) symname = "[unknown]";
|
|
|
|
|
|
|
|
if( symoff == 0 )
|
2018-06-20 20:54:42 +00:00
|
|
|
{
|
2022-07-19 23:21:43 +00:00
|
|
|
const auto len = std::min<size_t>( strlen( symname ), std::numeric_limits<uint16_t>::max() );
|
|
|
|
cb_data[cb_num].name = CopyStringFast( symname, len );
|
2018-06-20 20:54:42 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-01-20 15:55:59 +00:00
|
|
|
char buf[32];
|
|
|
|
const auto offlen = sprintf( buf, " + %td", symoff );
|
2022-07-19 23:21:43 +00:00
|
|
|
const auto namelen = std::min<size_t>( strlen( symname ), std::numeric_limits<uint16_t>::max() - offlen );
|
2021-06-09 23:48:11 +00:00
|
|
|
auto name = (char*)tracy_malloc_fast( namelen + offlen + 1 );
|
2019-01-20 15:55:59 +00:00
|
|
|
memcpy( name, symname, namelen );
|
|
|
|
memcpy( name + namelen, buf, offlen );
|
|
|
|
name[namelen + offlen] = '\0';
|
|
|
|
cb_data[cb_num].name = name;
|
2018-06-20 20:54:42 +00:00
|
|
|
}
|
2019-01-20 15:55:59 +00:00
|
|
|
|
2021-06-09 23:48:11 +00:00
|
|
|
cb_data[cb_num].file = CopyStringFast( "[unknown]" );
|
2019-01-20 15:55:59 +00:00
|
|
|
cb_data[cb_num].line = 0;
|
2018-06-20 20:54:42 +00:00
|
|
|
}
|
2019-01-20 15:55:59 +00:00
|
|
|
else
|
2018-06-20 20:54:42 +00:00
|
|
|
{
|
2019-01-20 15:55:59 +00:00
|
|
|
if( !fn ) fn = "[unknown]";
|
|
|
|
if( !function )
|
|
|
|
{
|
|
|
|
function = "[unknown]";
|
|
|
|
}
|
2022-07-19 23:21:43 +00:00
|
|
|
else
|
2019-01-20 15:55:59 +00:00
|
|
|
{
|
2022-07-19 23:21:43 +00:00
|
|
|
const char* demangled = ___tracy_demangle( function );
|
|
|
|
if( demangled ) function = demangled;
|
2019-01-20 15:55:59 +00:00
|
|
|
}
|
|
|
|
|
2022-07-19 23:21:43 +00:00
|
|
|
const auto len = std::min<size_t>( strlen( function ), std::numeric_limits<uint16_t>::max() );
|
|
|
|
cb_data[cb_num].name = CopyStringFast( function, len );
|
2022-09-01 23:23:29 +00:00
|
|
|
cb_data[cb_num].file = NormalizePath( fn );
|
|
|
|
if( !cb_data[cb_num].file ) cb_data[cb_num].file = CopyStringFast( fn );
|
2019-01-20 15:55:59 +00:00
|
|
|
cb_data[cb_num].line = lineno;
|
2018-06-20 19:45:27 +00:00
|
|
|
}
|
|
|
|
|
2019-01-20 15:55:59 +00:00
|
|
|
if( ++cb_num >= MaxCbTrace )
|
2018-06-21 11:10:48 +00:00
|
|
|
{
|
2019-01-20 15:55:59 +00:00
|
|
|
return 1;
|
2018-06-21 11:10:48 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-01-20 15:55:59 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-20 15:28:27 +00:00
|
|
|
static void CallstackErrorCb( void* /*data*/, const char* /*msg*/, int /*errnum*/ )
|
2019-01-20 15:55:59 +00:00
|
|
|
{
|
|
|
|
for( int i=0; i<cb_num; i++ )
|
|
|
|
{
|
2021-06-09 23:48:11 +00:00
|
|
|
tracy_free_fast( (void*)cb_data[i].name );
|
|
|
|
tracy_free_fast( (void*)cb_data[i].file );
|
2018-06-21 11:10:48 +00:00
|
|
|
}
|
2018-06-20 19:45:27 +00:00
|
|
|
|
2021-06-09 23:48:11 +00:00
|
|
|
cb_data[0].name = CopyStringFast( "[error]" );
|
|
|
|
cb_data[0].file = CopyStringFast( "[error]" );
|
2019-01-20 15:55:59 +00:00
|
|
|
cb_data[0].line = 0;
|
2018-06-20 19:45:27 +00:00
|
|
|
|
2019-01-20 15:55:59 +00:00
|
|
|
cb_num = 1;
|
|
|
|
}
|
2018-06-20 20:18:12 +00:00
|
|
|
|
2020-02-26 01:25:45 +00:00
|
|
|
void SymInfoCallback( void* /*data*/, uintptr_t pc, const char* symname, uintptr_t symval, uintptr_t symsize )
|
|
|
|
{
|
2020-05-30 13:38:59 +00:00
|
|
|
cb_data[cb_num-1].symLen = (uint32_t)symsize;
|
|
|
|
cb_data[cb_num-1].symAddr = (uint64_t)symval;
|
2020-02-26 01:25:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SymInfoError( void* /*data*/, const char* /*msg*/, int /*errnum*/ )
|
|
|
|
{
|
2020-05-30 13:38:59 +00:00
|
|
|
cb_data[cb_num-1].symLen = 0;
|
|
|
|
cb_data[cb_num-1].symAddr = 0;
|
2020-02-26 01:25:45 +00:00
|
|
|
}
|
|
|
|
|
2023-11-29 18:23:58 +00:00
|
|
|
void GetSymbolForOfflineResolve(void* address, uint64_t imageBaseAddress, CallstackEntry& cbEntry)
|
2023-11-17 20:48:17 +00:00
|
|
|
{
|
|
|
|
// 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
|
2023-11-29 18:23:58 +00:00
|
|
|
cbEntry.symAddr = (uint64_t)address - imageBaseAddress;
|
2023-11-17 20:48:17 +00:00
|
|
|
cbEntry.symLen = 0x0;
|
|
|
|
cbEntry.file = CopyStringFast( "[unknown]" );
|
|
|
|
cbEntry.line = 0;
|
|
|
|
}
|
|
|
|
|
2019-01-20 18:11:48 +00:00
|
|
|
CallstackEntryData DecodeCallstackPtr( uint64_t ptr )
|
2019-01-20 15:55:59 +00:00
|
|
|
{
|
2021-06-09 23:48:11 +00:00
|
|
|
InitRpmalloc();
|
2021-06-12 13:27:35 +00:00
|
|
|
if( ptr >> 63 == 0 )
|
|
|
|
{
|
2023-11-29 18:23:58 +00:00
|
|
|
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
|
2021-06-12 13:27:35 +00:00
|
|
|
Dl_info dlinfo;
|
2023-11-17 20:48:17 +00:00
|
|
|
if( dladdr( (void*)ptr, &dlinfo ) )
|
|
|
|
{
|
2023-11-29 18:23:58 +00:00
|
|
|
imageName = dlinfo.dli_fname;
|
|
|
|
imageBaseAddress = uint64_t( dlinfo.dli_fbase );
|
2023-11-17 20:48:17 +00:00
|
|
|
}
|
2023-11-29 18:23:58 +00:00
|
|
|
#endif
|
2023-11-17 20:48:17 +00:00
|
|
|
|
2023-11-24 21:32:45 +00:00
|
|
|
if( s_shouldResolveSymbolsOffline )
|
|
|
|
{
|
|
|
|
cb_num = 1;
|
2023-11-29 18:23:58 +00:00
|
|
|
GetSymbolForOfflineResolve( (void*)ptr, imageBaseAddress, cb_data[0] );
|
2023-11-24 21:32:45 +00:00
|
|
|
}
|
|
|
|
else
|
2023-11-17 20:48:17 +00:00
|
|
|
{
|
|
|
|
cb_num = 0;
|
|
|
|
backtrace_pcinfo( cb_bts, ptr, CallstackDataCb, CallstackErrorCb, nullptr );
|
|
|
|
assert( cb_num > 0 );
|
|
|
|
|
|
|
|
backtrace_syminfo( cb_bts, ptr, SymInfoCallback, SymInfoError, nullptr );
|
|
|
|
}
|
2020-02-26 01:25:45 +00:00
|
|
|
|
2023-11-29 18:23:58 +00:00
|
|
|
return { cb_data, uint8_t( cb_num ), imageName ? imageName : "[unknown]" };
|
2021-06-12 13:27:35 +00:00
|
|
|
}
|
2021-06-20 17:35:05 +00:00
|
|
|
#ifdef __linux
|
2021-06-12 13:27:35 +00:00
|
|
|
else if( s_kernelSym )
|
|
|
|
{
|
2024-05-25 14:27:13 +00:00
|
|
|
auto it = std::lower_bound( s_kernelSym, s_kernelSym + s_kernelSymCnt, ptr, []( const KernelSymbol& lhs, const uint64_t& rhs ) { return lhs.addr + lhs.size < rhs; } );
|
2021-06-12 13:27:35 +00:00
|
|
|
if( it != s_kernelSym + s_kernelSymCnt )
|
|
|
|
{
|
|
|
|
cb_data[0].name = CopyStringFast( it->name );
|
|
|
|
cb_data[0].file = CopyStringFast( "<kernel>" );
|
|
|
|
cb_data[0].line = 0;
|
2024-05-26 18:48:55 +00:00
|
|
|
cb_data[0].symLen = it->size;
|
2021-06-12 13:27:35 +00:00
|
|
|
cb_data[0].symAddr = it->addr;
|
|
|
|
return { cb_data, 1, it->mod ? it->mod : "<kernel>" };
|
|
|
|
}
|
|
|
|
}
|
2021-06-20 17:35:05 +00:00
|
|
|
#endif
|
2020-02-26 01:11:51 +00:00
|
|
|
|
2021-06-12 13:27:35 +00:00
|
|
|
cb_data[0].name = CopyStringFast( "[unknown]" );
|
|
|
|
cb_data[0].file = CopyStringFast( "<kernel>" );
|
|
|
|
cb_data[0].line = 0;
|
|
|
|
cb_data[0].symLen = 0;
|
|
|
|
cb_data[0].symAddr = 0;
|
|
|
|
return { cb_data, 1, "<kernel>" };
|
2018-06-20 19:45:27 +00:00
|
|
|
}
|
|
|
|
|
2019-05-22 12:14:30 +00:00
|
|
|
#elif TRACY_HAS_CALLSTACK == 5
|
|
|
|
|
2022-08-16 11:55:46 +00:00
|
|
|
void InitCallstackCritical()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-05-22 12:14:30 +00:00
|
|
|
void InitCallstack()
|
|
|
|
{
|
2022-07-23 10:34:35 +00:00
|
|
|
___tracy_init_demangle_buffer();
|
2019-05-22 12:14:30 +00:00
|
|
|
}
|
|
|
|
|
2022-04-27 15:49:28 +00:00
|
|
|
void EndCallstack()
|
|
|
|
{
|
2022-07-30 20:02:25 +00:00
|
|
|
___tracy_free_demangle_buffer();
|
2022-04-27 15:49:28 +00:00
|
|
|
}
|
|
|
|
|
2019-05-22 12:14:30 +00:00
|
|
|
const char* DecodeCallstackPtrFast( uint64_t ptr )
|
|
|
|
{
|
|
|
|
static char ret[1024];
|
|
|
|
auto vptr = (void*)ptr;
|
|
|
|
const char* symname = nullptr;
|
|
|
|
Dl_info dlinfo;
|
|
|
|
if( dladdr( vptr, &dlinfo ) && dlinfo.dli_sname )
|
|
|
|
{
|
|
|
|
symname = dlinfo.dli_sname;
|
|
|
|
}
|
|
|
|
if( symname )
|
|
|
|
{
|
|
|
|
strcpy( ret, symname );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*ret = '\0';
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-08-20 15:38:29 +00:00
|
|
|
CallstackSymbolData DecodeSymbolAddress( uint64_t ptr )
|
2020-02-26 22:24:18 +00:00
|
|
|
{
|
|
|
|
const char* symloc = nullptr;
|
2020-02-27 12:28:45 +00:00
|
|
|
Dl_info dlinfo;
|
2020-02-27 11:58:01 +00:00
|
|
|
if( dladdr( (void*)ptr, &dlinfo ) ) symloc = dlinfo.dli_fname;
|
|
|
|
if( !symloc ) symloc = "[unknown]";
|
2021-06-19 17:02:08 +00:00
|
|
|
return CallstackSymbolData { symloc, 0, false, 0 };
|
2020-02-26 22:24:18 +00:00
|
|
|
}
|
|
|
|
|
2019-05-22 12:14:30 +00:00
|
|
|
CallstackEntryData DecodeCallstackPtr( uint64_t ptr )
|
|
|
|
{
|
|
|
|
static CallstackEntry cb;
|
|
|
|
cb.line = 0;
|
|
|
|
|
|
|
|
const char* symname = nullptr;
|
|
|
|
const char* symloc = nullptr;
|
|
|
|
auto vptr = (void*)ptr;
|
|
|
|
ptrdiff_t symoff = 0;
|
2020-02-26 01:04:29 +00:00
|
|
|
void* symaddr = nullptr;
|
2019-05-22 12:14:30 +00:00
|
|
|
|
|
|
|
Dl_info dlinfo;
|
|
|
|
if( dladdr( vptr, &dlinfo ) )
|
|
|
|
{
|
|
|
|
symloc = dlinfo.dli_fname;
|
|
|
|
symname = dlinfo.dli_sname;
|
|
|
|
symoff = (char*)ptr - (char*)dlinfo.dli_saddr;
|
2020-02-26 01:04:29 +00:00
|
|
|
symaddr = dlinfo.dli_saddr;
|
2022-07-19 23:21:43 +00:00
|
|
|
const char* demangled = ___tracy_demangle( symname );
|
|
|
|
if( demangled ) symname = demangled;
|
2019-05-22 12:14:30 +00:00
|
|
|
}
|
|
|
|
|
2020-02-27 12:40:41 +00:00
|
|
|
if( !symname ) symname = "[unknown]";
|
|
|
|
if( !symloc ) symloc = "[unknown]";
|
2019-05-22 12:14:30 +00:00
|
|
|
|
|
|
|
if( symoff == 0 )
|
|
|
|
{
|
2022-07-19 23:21:43 +00:00
|
|
|
const auto len = std::min<size_t>( strlen( symname ), std::numeric_limits<uint16_t>::max() );
|
|
|
|
cb.name = CopyString( symname, len );
|
2019-05-22 12:14:30 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char buf[32];
|
|
|
|
const auto offlen = sprintf( buf, " + %td", symoff );
|
2022-07-19 23:21:43 +00:00
|
|
|
const auto namelen = std::min<size_t>( strlen( symname ), std::numeric_limits<uint16_t>::max() - offlen );
|
2019-05-22 12:14:30 +00:00
|
|
|
auto name = (char*)tracy_malloc( namelen + offlen + 1 );
|
|
|
|
memcpy( name, symname, namelen );
|
|
|
|
memcpy( name + namelen, buf, offlen );
|
|
|
|
name[namelen + offlen] = '\0';
|
|
|
|
cb.name = name;
|
|
|
|
}
|
|
|
|
|
2020-08-18 18:34:11 +00:00
|
|
|
cb.file = CopyString( "[unknown]" );
|
2020-03-25 17:05:00 +00:00
|
|
|
cb.symLen = 0;
|
2020-02-26 01:04:29 +00:00
|
|
|
cb.symAddr = (uint64_t)symaddr;
|
|
|
|
|
2020-02-27 12:45:39 +00:00
|
|
|
return { &cb, 1, symloc };
|
2019-05-22 12:14:30 +00:00
|
|
|
}
|
|
|
|
|
2018-06-20 19:45:27 +00:00
|
|
|
#endif
|
|
|
|
|
2018-06-18 23:17:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|