Merge pull request #665 from tiago-rodrigues/trodrigues/offline_symbol_resolve

Add support for offline callstack symbol resolving
This commit is contained in:
Bartosz Taudul 2023-11-27 16:53:22 +01:00 committed by GitHub
commit af73dba73e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 737 additions and 74 deletions

View File

@ -85,6 +85,7 @@ set_option(TRACY_FIBERS "Enable fibers support" OFF)
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)
if(NOT TRACY_STATIC)
target_compile_definitions(TracyClient PRIVATE TRACY_EXPORTS)

View File

@ -1792,6 +1792,34 @@ At initilization time, tracy will attempt to preload symbols for device drivers
Inline frames retrieval on Windows can be multiple orders of magnitude slower than just performing essential symbol resolution. This manifests as profiler seemingly being stuck for a long time, having hundreds of thousands of query backlog entries queued, which are slowly trickling down. If your use case requires speed of operation rather than having call stacks with inline frames included, you may define the \texttt{TRACY\_NO\_CALLSTACK\_INLINES} macro, which will make the profiler stick to the basic but fast frame resolution mode.
\paragraph{Offline symbol resolution}
By default, tracy client resolves callstack symbols in a background thread at runtime.
This process requires that tracy client load symbols for the shared libraries
involved, which requires additial memory allocations, and potential impact runtime performance if a lot of symbol queries are involved.
As an alternative to to runtime symbol resolution, we can set the environment variable
\texttt{TRACY\_SYMBOL\_OFFLINE\_RESOLVE} to 1 and instead have tracy client only resolve
the minimal set of info required for offline resolution (a shared library path and an offset into that shared library).
The generated tracy capture will have callstack frames symbols showing \texttt{[unresolved]}.
The \texttt{update} tool can be used to load that capture, perform symbol resolution offline
(by passing \texttt{-r}) and writing out a new capture with symbols resolved.
By default \texttt{update} will use the original shared libraries paths that were recorded
in the capture (which assumes running in the same machine or a machine with identical
filesystem setup as the one used to run the tracy instrumented application).
You can do path substitution with the \texttt{-p} option to perform any number of path
substitions in order to use symbols located elsewhere.
\begin{bclogo}[
noborder=true,
couleur=black!5,
logo=\bcbombe
]{Important}
Beware that \texttt{update} will use any matching symbol file to the path it resolved to (no symbol version checking is done), so if the symbol file doesn't match the code that was used when doing the callstack capturing you will get incorrect results.
Also note that in the case of using offline symbol resolving, even after running the \texttt{update} tool to resolve symbols, the symbols statistics are not updated and will still report the unresolved symbols.
\end{bclogo}
\subsection{Lua support}
To profile Lua code using Tracy, include the \texttt{public/tracy/TracyLua.hpp} header file in your Lua wrapper and execute \texttt{tracy::LuaRegister(lua\_State*)} function to add instrumentation support.

View File

@ -93,6 +93,19 @@ extern "C" const char* ___tracy_demangle( const char* mangled )
namespace tracy
{
// 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
constexpr bool s_shouldResolveSymbolsOffline = true;
#else
static bool s_shouldResolveSymbolsOffline = false;
bool ShouldResolveSymbolsOffline()
{
const char* symbolOfflineResolve = GetEnvVar( "TRACY_SYMBOL_OFFLINE_RESOLVE" );
return (symbolOfflineResolve && symbolOfflineResolve[0] == '1');
}
#endif // #ifdef TRACY_SYMBOL_OFFLINE_RESOLVE
#if TRACY_HAS_CALLSTACK == 1
enum { MaxCbTrace = 64 };
@ -108,13 +121,13 @@ extern "C"
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 );
TRACY_API ___tracy_t_RtlWalkFrameChain ___tracy_RtlWalkFrameChain = 0;
t_SymAddrIncludeInlineTrace _SymAddrIncludeInlineTrace = 0;
t_SymQueryInlineTrace _SymQueryInlineTrace = 0;
t_SymFromInlineContext _SymFromInlineContext = 0;
t_SymGetLineFromInlineContext _SymGetLineFromInlineContext = 0;
}
TRACY_API ___tracy_t_RtlWalkFrameChain ___tracy_RtlWalkFrameChain = 0;
}
struct ModuleCache
{
@ -136,18 +149,19 @@ struct KernelDriver
KernelDriver* s_krnlCache = nullptr;
size_t s_krnlCacheCnt;
void InitCallstackCritical()
{
___tracy_RtlWalkFrameChain = (___tracy_t_RtlWalkFrameChain)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "RtlWalkFrameChain" );
}
void InitCallstack()
void DbgHelpInit()
{
_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" );
if( s_shouldResolveSymbolsOffline ) return;
_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");
#ifdef TRACY_DBGHELP_LOCK
DBGHELP_INIT;
@ -157,6 +171,64 @@ void InitCallstack()
SymInitialize( GetCurrentProcess(), nullptr, true );
SymSetOptions( SYMOPT_LOAD_LINES );
#ifdef TRACY_DBGHELP_LOCK
DBGHELP_UNLOCK;
#endif
}
DWORD64 DbgHelpLoadSymbolsForModule( const char* imageName, uint64_t baseOfDll, uint32_t bllSize )
{
if( s_shouldResolveSymbolsOffline ) return 0;
return SymLoadModuleEx( GetCurrentProcess(), nullptr, imageName, nullptr, baseOfDll, bllSize, nullptr, 0 );
}
ModuleCache* LoadSymbolsForModuleAndCache( const char* imageName, uint32_t imageNameLength, uint64_t baseOfDll, uint32_t dllSize )
{
DbgHelpLoadSymbolsForModule( imageName, baseOfDll, dllSize );
ModuleCache* cachedModule = s_modCache->push_next();
cachedModule->start = baseOfDll;
cachedModule->end = baseOfDll + dllSize;
// when doing offline symbol resolution, we must store the full path of the dll for the resolving to work
if( s_shouldResolveSymbolsOffline )
{
cachedModule->name = (char*)tracy_malloc_fast(imageNameLength + 1);
memcpy(cachedModule->name, imageName, imageNameLength);
cachedModule->name[imageNameLength] = '\0';
}
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()
{
#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE
s_shouldResolveSymbolsOffline = ShouldResolveSymbolsOffline();
#endif //#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE
if( s_shouldResolveSymbolsOffline )
{
TracyDebug("TRACY: enabling offline symbol resolving!\n");
}
DbgHelpInit();
#ifdef TRACY_DBGHELP_LOCK
DBGHELP_LOCK;
#endif
// use TRACY_NO_DBHELP_INIT_LOAD=1 to disable preloading of driver
// 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
@ -204,7 +276,7 @@ void InitCallstack()
path = full;
}
SymLoadModuleEx( GetCurrentProcess(), nullptr, path, nullptr, (DWORD64)dev[i], 0, nullptr, 0 );
DbgHelpLoadSymbolsForModule( path, (DWORD64)dev[i], 0 );
const auto psz = strlen( path );
auto pptr = (char*)tracy_malloc_fast( psz+1 );
@ -235,25 +307,12 @@ void InitCallstack()
{
const auto base = uint64_t( info.lpBaseOfDll );
char name[1024];
const auto res = GetModuleFileNameA( mod[i], name, 1021 );
if( res > 0 )
const auto nameLength = GetModuleFileNameA( mod[i], name, 1021 );
if( nameLength > 0 )
{
// This may be a new module loaded since our call to SymInitialize.
// Just in case, force DbgHelp to load its pdb !
SymLoadModuleEx(proc, NULL, name, NULL, (DWORD64)info.lpBaseOfDll, info.SizeOfImage, NULL, 0);
auto ptr = name + res;
while( ptr > name && *ptr != '\\' && *ptr != '/' ) ptr--;
if( ptr > name ) ptr++;
const auto namelen = name + res - ptr;
auto cache = s_modCache->push_next();
cache->start = base;
cache->end = base + info.SizeOfImage;
cache->name = (char*)tracy_malloc_fast( namelen+3 );
cache->name[0] = '[';
memcpy( cache->name+1, ptr, namelen );
cache->name[namelen+1] = ']';
cache->name[namelen+2] = '\0';
LoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage );
}
}
}
@ -270,6 +329,8 @@ void EndCallstack()
const char* DecodeCallstackPtrFast( uint64_t ptr )
{
if( s_shouldResolveSymbolsOffline ) return "[unresolved]";
static char ret[MaxNameSize];
const auto proc = GetCurrentProcess();
@ -305,7 +366,13 @@ const char* GetKernelModulePath( uint64_t addr )
return it->path;
}
static const char* GetModuleNameAndPrepareSymbols( uint64_t addr )
struct ModuleNameAndBaseAddress
{
const char* name;
uint64_t baseAddr;
};
ModuleNameAndBaseAddress GetModuleNameAndPrepareSymbols( uint64_t addr )
{
if( ( addr >> 63 ) != 0 )
{
@ -314,17 +381,17 @@ static const char* GetModuleNameAndPrepareSymbols( uint64_t addr )
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 it->mod;
return ModuleNameAndBaseAddress{ it->mod, it->addr };
}
}
return "<kernel>";
return ModuleNameAndBaseAddress{ "<kernel>", addr };
}
for( auto& v : *s_modCache )
{
if( addr >= v.start && addr < v.end )
{
return v.name;
return ModuleNameAndBaseAddress{ v.name, v.start };
}
}
@ -345,35 +412,33 @@ static const char* GetModuleNameAndPrepareSymbols( uint64_t addr )
if( addr >= base && addr < base + info.SizeOfImage )
{
char name[1024];
const auto res = GetModuleFileNameA( mod[i], name, 1021 );
if( res > 0 )
const auto nameLength = GetModuleFileNameA( mod[i], name, 1021 );
if( nameLength > 0 )
{
// since this is the first time we encounter this module, load its symbols (needed for modules loaded after SymInitialize)
SymLoadModuleEx(proc, NULL, name, NULL, (DWORD64)info.lpBaseOfDll, info.SizeOfImage, NULL, 0);
auto ptr = name + res;
while( ptr > name && *ptr != '\\' && *ptr != '/' ) ptr--;
if( ptr > name ) ptr++;
const auto namelen = name + res - ptr;
auto cache = s_modCache->push_next();
cache->start = base;
cache->end = base + info.SizeOfImage;
cache->name = (char*)tracy_malloc_fast( namelen+3 );
cache->name[0] = '[';
memcpy( cache->name+1, ptr, namelen );
cache->name[namelen+1] = ']';
cache->name[namelen+2] = '\0';
return cache->name;
ModuleCache* cachedModule = LoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage );
return ModuleNameAndBaseAddress{ cachedModule->name, cachedModule->start };
}
}
}
}
}
return "[unknown]";
return ModuleNameAndBaseAddress{ "[unknown]", 0x0 };
}
CallstackSymbolData DecodeSymbolAddress( uint64_t ptr )
{
CallstackSymbolData sym;
if( s_shouldResolveSymbolsOffline )
{
sym.file = "[unknown]";
sym.line = 0;
sym.needFree = false;
return sym;
}
IMAGEHLP_LINE64 line;
DWORD displacement = 0;
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
@ -401,15 +466,32 @@ CallstackSymbolData DecodeSymbolAddress( uint64_t ptr )
CallstackEntryData DecodeCallstackPtr( uint64_t ptr )
{
int write;
const auto proc = GetCurrentProcess();
InitRpmalloc();
#ifdef TRACY_DBGHELP_LOCK
DBGHELP_LOCK;
#endif
const auto moduleName = GetModuleNameAndPrepareSymbols(ptr);
InitRpmalloc();
const ModuleNameAndBaseAddress moduleNameAndAddress = GetModuleNameAndPrepareSymbols( ptr );
if( s_shouldResolveSymbolsOffline )
{
#ifdef TRACY_DBGHELP_LOCK
DBGHELP_UNLOCK;
#endif
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 };
}
int write;
const auto proc = GetCurrentProcess();
#if !defined TRACY_NO_CALLSTACK_INLINES
BOOL doInline = FALSE;
@ -459,7 +541,7 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr )
cb_data[write].line = line.LineNumber;
}
cb_data[write].name = symValid ? CopyStringFast( si->Name, si->NameLen ) : CopyStringFast( moduleName );
cb_data[write].name = symValid ? CopyStringFast( si->Name, si->NameLen ) : CopyStringFast( moduleNameAndAddress.name );
cb_data[write].file = CopyStringFast( filename );
if( symValid )
{
@ -492,7 +574,7 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr )
cb.line = line.LineNumber;
}
cb.name = symInlineValid ? CopyStringFast( si->Name, si->NameLen ) : CopyStringFast( moduleName );
cb.name = symInlineValid ? CopyStringFast( si->Name, si->NameLen ) : CopyStringFast( moduleNameAndAddress.name );
cb.file = CopyStringFast( filename );
if( symInlineValid )
{
@ -513,14 +595,15 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr )
DBGHELP_UNLOCK;
#endif
return { cb_data, uint8_t( cb_num ), moduleName };
return { cb_data, uint8_t( cb_num ), moduleNameAndAddress.name };
}
#elif TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6
enum { MaxCbTrace = 64 };
struct backtrace_state* cb_bts;
struct backtrace_state* cb_bts = nullptr;
int cb_num;
CallstackEntry cb_data[MaxCbTrace];
int cb_fixup;
@ -696,7 +779,19 @@ void InitCallstackCritical()
void InitCallstack()
{
cb_bts = backtrace_create_state( nullptr, 0, nullptr, nullptr );
#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE
s_shouldResolveSymbolsOffline = ShouldResolveSymbolsOffline();
#endif //#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE
if( s_shouldResolveSymbolsOffline )
{
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 );
}
#ifndef TRACY_DEMANGLE
___tracy_init_demangle_buffer();
#endif
@ -835,7 +930,15 @@ static void SymbolAddressErrorCb( void* data, const char* /*msg*/, int /*errnum*
CallstackSymbolData DecodeSymbolAddress( uint64_t ptr )
{
CallstackSymbolData sym;
backtrace_pcinfo( cb_bts, ptr, SymbolAddressDataCb, SymbolAddressErrorCb, &sym );
if( cb_bts )
{
backtrace_pcinfo( cb_bts, ptr, SymbolAddressDataCb, SymbolAddressErrorCb, &sym );
}
else
{
SymbolAddressErrorCb(&sym, nullptr, 0);
}
return sym;
}
@ -938,20 +1041,42 @@ 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)
{
// 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.symLen = 0x0;
cbEntry.file = CopyStringFast( "[unknown]" );
cbEntry.line = 0;
}
CallstackEntryData DecodeCallstackPtr( uint64_t ptr )
{
InitRpmalloc();
if( ptr >> 63 == 0 )
{
cb_num = 0;
backtrace_pcinfo( cb_bts, ptr, CallstackDataCb, CallstackErrorCb, nullptr );
assert( cb_num > 0 );
backtrace_syminfo( cb_bts, ptr, SymInfoCallback, SymInfoError, nullptr );
const char* symloc = nullptr;
Dl_info dlinfo;
if( dladdr( (void*)ptr, &dlinfo ) ) symloc = dlinfo.dli_fname;
if( dladdr( (void*)ptr, &dlinfo ) )
{
symloc = dlinfo.dli_fname;
}
if( s_shouldResolveSymbolsOffline )
{
cb_num = 1;
GetSymbolForOfflineResolve( (void*)ptr, dlinfo, cb_data[0] );
}
else
{
cb_num = 0;
backtrace_pcinfo( cb_bts, ptr, CallstackDataCb, CallstackErrorCb, nullptr );
assert( cb_num > 0 );
backtrace_syminfo( cb_bts, ptr, SymInfoCallback, SymInfoError, nullptr );
}
return { cb_data, uint8_t( cb_num ), symloc ? symloc : "[unknown]" };
}

View File

@ -553,11 +553,12 @@ Worker::Worker( const char* name, const char* program, const std::vector<ImportE
}
}
Worker::Worker( FileRead& f, EventType::Type eventMask, bool bgTasks )
Worker::Worker( FileRead& f, EventType::Type eventMask, bool bgTasks, bool allowStringModification)
: m_hasData( true )
, m_stream( nullptr )
, m_buffer( nullptr )
, m_inconsistentSamples( false )
, m_allowStringModification(allowStringModification)
{
auto loadStart = std::chrono::high_resolution_clock::now();
@ -707,7 +708,12 @@ Worker::Worker( FileRead& f, EventType::Type eventMask, bool bgTasks )
f.Read( sz );
m_data.stringMap.reserve( sz );
m_data.stringData.reserve_exact( sz, m_slab );
if( !m_allowStringModification )
{
m_data.stringData.reserve_exact( sz, m_slab );
}
for( uint64_t i=0; i<sz; i++ )
{
uint64_t ptr, ssz;
@ -716,7 +722,16 @@ Worker::Worker( FileRead& f, EventType::Type eventMask, bool bgTasks )
f.Read( dst, ssz );
dst[ssz] = '\0';
m_data.stringMap.emplace( charutil::StringKey { dst, size_t( ssz ) }, i );
m_data.stringData[i] = ( dst );
if( m_allowStringModification )
{
m_data.stringData.push_back( dst );
}
else
{
m_data.stringData[i] = ( dst );
}
pointerMap.emplace( ptr, dst );
}

View File

@ -446,7 +446,7 @@ public:
Worker( const char* addr, uint16_t port );
Worker( const char* name, const char* program, const std::vector<ImportEventTimeline>& timeline, const std::vector<ImportEventMessages>& messages, const std::vector<ImportEventPlots>& plots, const std::unordered_map<uint64_t, std::string>& threadNames );
Worker( FileRead& f, EventType::Type eventMask = EventType::All, bool bgTasks = true );
Worker( FileRead& f, EventType::Type eventMask = EventType::All, bool bgTasks = true, bool allowStringModification = false);
~Worker();
const std::string& GetAddr() const { return m_addr; }
@ -549,6 +549,8 @@ public:
StringIdx GetLocationForAddress( uint64_t address, uint32_t& line ) const;
const uint64_t* GetInlineSymbolList( uint64_t sym, uint32_t len );
unordered_flat_map<CallstackFrameId, CallstackFrameData*, CallstackFrameIdHash, CallstackFrameIdCompare>& GetCallstackFrameMap() { return m_data.callstackFrameMap; }
#ifndef TRACY_NO_STATISTICS
const VarArray<CallstackFrameId>& GetParentCallstack( uint32_t idx ) const { return *m_data.parentCallstackPayload[idx]; }
const CallstackFrameData* GetParentCallstackFrame( const CallstackFrameId& ptr ) const;
@ -665,6 +667,8 @@ public:
void CacheSourceFiles();
StringLocation StoreString(const char* str, size_t sz);
private:
void Network();
void Exec();
@ -892,7 +896,6 @@ private:
uint32_t GetSingleStringIdx();
uint32_t GetSecondStringIdx();
StringLocation StoreString( const char* str, size_t sz );
const ContextSwitch* const GetContextSwitchDataImpl( uint64_t thread );
void CacheSource( const StringRef& str, const StringIdx& image = StringIdx() );
@ -985,6 +988,7 @@ private:
bool m_combineSamples;
bool m_identifySamples = false;
bool m_inconsistentSamples;
bool m_allowStringModification = false;
short_ptr<GpuCtxData> m_gpuCtxMap[256];
uint32_t m_pendingCallstackId = 0;

View File

@ -60,7 +60,7 @@
<AdditionalIncludeDirectories>$(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\include;$(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\include\capstone;$(VcpkgManifestRoot)\vcpkg_installed\$(VcpkgTriplet)\$(VcpkgTriplet)\include\capstone;$(VcpkgRoot)\installed\$(VcpkgTriplet)\include\capstone</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>ws2_32.lib;capstone.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>ws2_32.lib;capstone.lib;dbghelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
<SubSystem>Console</SubSystem>
<AdditionalLibraryDirectories>$(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\debug\lib</AdditionalLibraryDirectories>
</Link>
@ -82,7 +82,7 @@
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>ws2_32.lib;capstone.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>ws2_32.lib;capstone.lib;dbghelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
<SubSystem>Console</SubSystem>
<AdditionalLibraryDirectories>$(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\lib</AdditionalLibraryDirectories>
</Link>
@ -130,6 +130,9 @@
<ClCompile Include="..\..\..\zstd\dictBuilder\divsufsort.c" />
<ClCompile Include="..\..\..\zstd\dictBuilder\fastcover.c" />
<ClCompile Include="..\..\..\zstd\dictBuilder\zdict.c" />
<ClCompile Include="..\..\src\OfflineSymbolResolver.cpp" />
<ClCompile Include="..\..\src\OfflineSymbolResolverAddr2Line.cpp" />
<ClCompile Include="..\..\src\OfflineSymbolResolverDbgHelper.cpp" />
<ClCompile Include="..\..\src\update.cpp" />
</ItemGroup>
<ItemGroup>
@ -200,6 +203,7 @@
<ClInclude Include="..\..\..\zstd\zdict.h" />
<ClInclude Include="..\..\..\zstd\zstd.h" />
<ClInclude Include="..\..\..\zstd\zstd_errors.h" />
<ClInclude Include="..\..\src\OfflineSymbolResolver.h" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\..\zstd\decompress\huf_decompress_amd64.S" />

View File

@ -159,6 +159,15 @@
<ClCompile Include="..\..\..\public\common\TracySystem.cpp">
<Filter>common</Filter>
</ClCompile>
<ClCompile Include="..\..\src\OfflineSymbolResolver.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="..\..\src\OfflineSymbolResolverAddr2Line.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="..\..\src\OfflineSymbolResolverDbgHelper.cpp">
<Filter>src</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\server\TracyCharUtil.hpp">
@ -362,6 +371,9 @@
<ClInclude Include="..\..\..\public\common\TracyYield.hpp">
<Filter>common</Filter>
</ClInclude>
<ClInclude Include="..\..\src\OfflineSymbolResolver.h">
<Filter>src</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\..\..\zstd\decompress\huf_decompress_amd64.S">

View File

@ -0,0 +1,171 @@
#include <fstream>
#include <iostream>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unordered_map>
#include "../../server/TracyWorker.hpp"
#include "../../zstd/zstd.h"
#include "OfflineSymbolResolver.h"
bool ApplyPathSubstitutions( std::string& path, const PathSubstitutionList& pathSubstitutionlist )
{
for( const auto& substitution : pathSubstitutionlist )
{
if( std::regex_match(path, substitution.first) )
{
path = std::regex_replace( path, substitution.first, substitution.second );
return true;
}
}
return false;
}
tracy::StringIdx AddSymbolString( tracy::Worker& worker, const std::string& str )
{
// TODO: use string hash map to reduce potential string duplication?
tracy::StringLocation location = worker.StoreString( str.c_str(), str.length() );
return tracy::StringIdx( location.idx );
}
bool PatchSymbolsWithRegex( tracy::Worker& worker, const PathSubstitutionList& pathSubstitutionlist, bool verbose )
{
uint64_t callstackFrameCount = worker.GetCallstackFrameCount();
std::string relativeSoNameMatch = "[unresolved]";
std::cout << "Found " << callstackFrameCount << " callstack frames. Batching into image groups..." << std::endl;
// batch the symbol queries by .so so we issue the least amount of requests
using FrameEntriesPerImageIdx = std::unordered_map<uint32_t, FrameEntryList>;
FrameEntriesPerImageIdx entriesPerImageIdx;
auto& callstackFrameMap = worker.GetCallstackFrameMap();
for( auto it = callstackFrameMap.begin(); it != callstackFrameMap.end(); ++it )
{
tracy::CallstackFrameData* frameDataPtr = it->second;
if( !frameDataPtr )
{
continue;
}
tracy::CallstackFrameData& frameData = *frameDataPtr;
const char* imageName = worker.GetString( frameData.imageName );
const uint32_t imageNameIdx = frameData.imageName.Idx();
FrameEntryList& entries = entriesPerImageIdx[imageNameIdx];
for( uint8_t f = 0; f < frameData.size; f++ )
{
tracy::CallstackFrame& frame = frameData.data[f];
// TODO: use a better way to identify symbols that are unresolved
const char* nameStr = worker.GetString(frame.name);
if( strncmp( nameStr, relativeSoNameMatch.c_str(), relativeSoNameMatch.length() ) == 0 )
{
// when doing offline resolving we pass the offset from the start of the shared library in the "symAddr"
const uint64_t decodedOffset = frame.symAddr;
entries.push_back( {&frame, decodedOffset} );
}
}
}
std::cout << "Batched into " << entriesPerImageIdx.size() << " unique image groups" << std::endl;
// FIXME: the resolving of symbols here can be slow and could be done in parallel per "image"
// - be careful with string allocation though as that would be not safe to do in parallel
for( FrameEntriesPerImageIdx::iterator imageIt = entriesPerImageIdx.begin(),
imageItEnd = entriesPerImageIdx.end(); imageIt != imageItEnd; ++imageIt )
{
tracy::StringIdx imageIdx( imageIt->first );
std::string imagePath = worker.GetString( imageIdx );
FrameEntryList& entries = imageIt->second;
if( !entries.size() ) continue;
std::cout << "Resolving " << entries.size() << " symbols for image: '"
<< imagePath << "'" << std::endl;
const bool substituted = ApplyPathSubstitutions( imagePath, pathSubstitutionlist );
if( substituted )
{
std::cout << "\tPath substituted to: '" << imagePath << "'" << std::endl;
}
SymbolEntryList resolvedEntries;
ResolveSymbols( imagePath, entries, resolvedEntries );
if( resolvedEntries.size() != entries.size() )
{
std::cerr << " failed to resolve all entries! (got: "
<< resolvedEntries.size() << ")" << std::endl;
continue;
}
// finally patch the string with the resolved symbol data
for ( size_t i = 0; i < resolvedEntries.size(); ++i )
{
FrameEntry& frameEntry = entries[i];
const SymbolEntry& symbolEntry = resolvedEntries[i];
tracy::CallstackFrame& frame = *frameEntry.frame;
if( !symbolEntry.name.length() ) continue;
if( verbose )
{
const char* nameStr = worker.GetString( frame.name );
std::cout << "patching '" << nameStr << "' of '" << imagePath
<< "' -> '" << symbolEntry.name << "'" << std::endl;
}
frame.name = AddSymbolString( worker, symbolEntry.name );
const char* newName = worker.GetString( frame.name );
if( symbolEntry.file.length() )
{
frame.file = AddSymbolString( worker, symbolEntry.file );
frame.line = symbolEntry.line;
}
}
}
return true;
}
void PatchSymbols( tracy::Worker& worker, const std::vector<std::string>& pathSubstitutionsStrings, bool verbose )
{
std::cout << "Resolving and patching symbols..." << std::endl;
PathSubstitutionList pathSubstitutionList;
for ( const std::string& pathSubst : pathSubstitutionsStrings )
{
std::size_t pos = pathSubst.find(';');
if ( pos == std::string::npos )
{
std::cerr << "Ignoring invalid path substitution: '" << pathSubst
<< " '(please separate the regex of the string to replace with a ';')" << std::endl;
continue;
}
try
{
std::regex reg(pathSubst.substr(0, pos));
std::string replacementStr(pathSubst.substr(pos + 1));
pathSubstitutionList.push_back(std::pair(reg, replacementStr));
}
catch ( std::exception& e )
{
std::cerr << "Ignoring invalid path substitution: '" << pathSubst
<< "' (" << e.what() << ")" << std::endl;
continue;
}
}
if ( !PatchSymbolsWithRegex(worker, pathSubstitutionList, verbose) )
{
std::cerr << "Failed to patch symbols" << std::endl;
}
}

View File

@ -0,0 +1,40 @@
#ifndef __SYMBOLRESOLVER_HPP__
#define __SYMBOLRESOLVER_HPP__
#include <string>
#include <vector>
#include <regex>
#include <cstdint>
namespace tracy
{
struct CallstackFrame;
class Worker;
}
struct FrameEntry
{
tracy::CallstackFrame* frame = nullptr;
uint64_t symbolOffset = 0;
};
using FrameEntryList = std::vector<FrameEntry>;
struct SymbolEntry
{
std::string name;
std::string file;
int line = 0;
};
using SymbolEntryList = std::vector<SymbolEntry>;
bool ResolveSymbols( const std::string& imagePath, const FrameEntryList& inputEntryList,
SymbolEntryList& resolvedEntries );
void PatchSymbols( tracy::Worker& worker, const std::vector<std::string>& pathSubstitutionsStrings, bool verbose = false );
using PathSubstitutionList = std::vector<std::pair<std::regex, std::string> >;
bool PatchSymbolsWithRegex( tracy::Worker& worker, const PathSubstitutionList& pathSubstituionlist, bool verbose = false );
#endif // __SYMBOLRESOLVER_HPP__

View File

@ -0,0 +1,114 @@
#ifndef _WIN32
#include "OfflineSymbolResolver.h"
#include <fstream>
#include <iostream>
#include <string>
#include <array>
#include <sstream>
#include <memory>
#include <stdio.h>
std::string ExecShellCommand( const char* cmd )
{
std::array<char, 128> buffer;
std::string result;
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
if( !pipe )
{
return "";
}
while( fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr )
{
result += buffer.data();
}
return result;
}
class SymbolResolver
{
public:
SymbolResolver()
{
std::stringstream result( ExecShellCommand("which addr2line") );
std::getline(result, m_addr2LinePath);
if( !m_addr2LinePath.length() )
{
std::cerr << "'addr2line' was not found in the system, please installed it" << std::endl;
}
else
{
std::cout << "Using 'addr2line' found at: '" << m_addr2LinePath.c_str() << "'" << std::endl;
}
}
bool ResolveSymbols( const std::string& imagePath, const FrameEntryList& inputEntryList,
SymbolEntryList& resolvedEntries )
{
if (!m_addr2LinePath.length()) return false;
// generate a single addr2line cmd line for all addresses in one invocation
std::stringstream ss;
ss << m_addr2LinePath << " -C -f -e " << imagePath << " -a ";
for ( const FrameEntry& entry : inputEntryList )
{
ss << " 0x" << std::hex << entry.symbolOffset;
}
std::string resultStr = ExecShellCommand( ss.str().c_str() );
std::stringstream result(resultStr);
//printf("executing: '%s' got '%s'\n", ss.str().c_str(), result.str().c_str());
// The output is 2 lines per entry with the following contents:
// hex_address_of_symbol
// symbol_name
// file:line
for( size_t i = 0; i < inputEntryList.size(); ++i )
{
const FrameEntry& inputEntry = inputEntryList[i];
SymbolEntry newEntry;
std::string addr;
std::getline( result, addr );
std::getline( result, newEntry.name );
if (newEntry.name == "??")
{
newEntry.name = "[unknown] + " + std::to_string(inputEntry.symbolOffset);
}
std::string fileLine;
std::getline(result, fileLine);
if ( fileLine != "??:?" )
{
size_t pos = fileLine.find_last_of(':');
if ( pos != std::string::npos )
{
newEntry.file = fileLine.substr( 0, pos );
std::string lineStr = fileLine.substr( pos + 1 );
char* after = nullptr;
newEntry.line = strtol( lineStr.c_str(), &after, 10 );
}
}
resolvedEntries.push_back( std::move(newEntry) );
}
return true;
}
private:
std::string m_addr2LinePath;
};
bool ResolveSymbols( const std::string& imagePath, const FrameEntryList& inputEntryList,
SymbolEntryList& resolvedEntries )
{
static SymbolResolver symbolResolver;
return symbolResolver.ResolveSymbols( imagePath, inputEntryList, resolvedEntries );
}
#endif // #ifndef _WIN32

View File

@ -0,0 +1,130 @@
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <dbghelp.h>
#include <cstdio>
#include <iostream>
#include <stdint.h>
#include <stdlib.h>
#include <windows.h>
#include <string>
#include "OfflineSymbolResolver.h"
class SymbolResolver
{
public:
SymbolResolver()
{
m_procHandle = GetCurrentProcess();
if( !SymInitialize(m_procHandle, NULL, FALSE) )
{
std::cerr << "SymInitialize() failed with: " << GetLastErrorString() << std::endl;
}
else
{
const DWORD symopts = SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES;
SymSetOptions( symopts );
m_dbgHelpInitialized = true;
}
}
~SymbolResolver()
{
SymCleanup( m_procHandle );
}
bool ResolveSymbolsForModule( const std::string& imagePath, const FrameEntryList& inputEntryList,
SymbolEntryList& resolvedEntries )
{
if( !m_dbgHelpInitialized ) return false;
ULONG64 moduleBase = SymLoadModuleEx( m_procHandle, NULL, imagePath.c_str(), NULL, 0, 0, NULL, 0 );
if( !moduleBase )
{
std::cerr << "SymLoadModuleEx() failed for module " << imagePath
<< ": " << GetLastErrorString() << std::endl;
return false;
}
for( size_t i = 0; i < inputEntryList.size(); ++i )
{
uint64_t offset = inputEntryList[i].symbolOffset;
DWORD64 address = moduleBase + offset;
SYMBOL_INFO* symbolInfo = (SYMBOL_INFO*)s_symbolResolutionBuffer;
symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFO);
symbolInfo->MaxNameLen = MAX_SYM_NAME;
SymbolEntry newEntry;
if( SymFromAddr( m_procHandle, address, NULL, symbolInfo ) )
{
newEntry.name = symbolInfo->Name;
//std::cout << "Resolved symbol to: '" << newEntry.name << "'" << std::endl;
}
else
{
newEntry.name = "[unknown] + " + std::to_string(offset);
}
IMAGEHLP_LINE lineInfo = { 0 };
lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
DWORD displaceMent = 0;
if ( SymGetLineFromAddr64( m_procHandle, address, &displaceMent, &lineInfo ) )
{
newEntry.file = lineInfo.FileName;
newEntry.line = int(lineInfo.LineNumber);
///std::cout << "\tline_file: " lineInfo.FileName << ":" << int(lineInfo.LineNumber) << std::endl;
}
resolvedEntries.push_back(std::move(newEntry));
}
SymUnloadModule64( m_procHandle, moduleBase );
return true;
}
private:
static const size_t symbolResolutionBufferSize = sizeof(SYMBOL_INFO) + MAX_SYM_NAME;
static char s_symbolResolutionBuffer[symbolResolutionBufferSize];
std::string GetLastErrorString()
{
DWORD error = GetLastError();
if (error == 0)
{
return "";
}
LPSTR messageBuffer = nullptr;
DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
size_t size = FormatMessageA( dwFlags, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&messageBuffer, 0, NULL );
std::string message(messageBuffer, size);
LocalFree(messageBuffer);
return message;
}
bool m_dbgHelpInitialized = false;
HANDLE m_procHandle = nullptr;
};
char SymbolResolver::s_symbolResolutionBuffer[symbolResolutionBufferSize];
bool ResolveSymbols( const std::string& imagePath, const FrameEntryList& inputEntryList,
SymbolEntryList& resolvedEntries )
{
static SymbolResolver resolver;
return resolver.ResolveSymbolsForModule( imagePath, inputEntryList, resolvedEntries );
}
#endif // #ifdef _WIN32

View File

@ -15,6 +15,8 @@
#include "../../zstd/zstd.h"
#include "../../getopt/getopt.h"
#include "OfflineSymbolResolver.h"
#ifdef __APPLE__
# define ftello64(x) ftello(x)
#elif defined _WIN32
@ -32,6 +34,9 @@ void Usage()
printf( " l: locks, m: messages, p: plots, M: memory, i: frame images\n" );
printf( " c: context switches, s: sampling data, C: symbol code, S: source cache\n" );
printf( " -c: scan for source files missing in cache and add if found\n" );
printf( " -r resolve symbols and patch callstack frames\n");
printf( " -p: substitute symbol resolution path with an alternative: \"REGEX_MATCH;REPLACEMENT\"\n");
exit( 1 );
}
@ -50,8 +55,11 @@ int main( int argc, char** argv )
int zstdLevel = 1;
bool buildDict = false;
bool cacheSource = false;
bool resolveSymbols = false;
std::vector<std::string> pathSubstitutions;
int c;
while( ( c = getopt( argc, argv, "hez:ds:c" ) ) != -1 )
while( ( c = getopt( argc, argv, "hez:ds:crp:" ) ) != -1 )
{
switch( c )
{
@ -118,12 +126,19 @@ int main( int argc, char** argv )
case 'c':
cacheSource = true;
break;
case 'r':
resolveSymbols = true;
break;
case 'p':
pathSubstitutions.push_back(optarg);
break;
default:
Usage();
break;
}
}
if( argc - optind != 2 ) Usage();
if (argc != optind + 2) Usage();
const char* input = argv[optind];
const char* output = argv[optind+1];
@ -144,7 +159,9 @@ int main( int argc, char** argv )
int inVer;
{
const auto t0 = std::chrono::high_resolution_clock::now();
tracy::Worker worker( *f, (tracy::EventType::Type)events, false );
const bool allowBgThreads = false;
const bool allowStringModification = resolveSymbols;
tracy::Worker worker( *f, (tracy::EventType::Type)events, allowBgThreads, allowStringModification);
#ifndef TRACY_NO_STATISTICS
while( !worker.AreSourceLocationZonesReady() ) std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
@ -152,6 +169,8 @@ int main( int argc, char** argv )
if( cacheSource ) worker.CacheSourceFiles();
if ( resolveSymbols ) PatchSymbols( worker, pathSubstitutions );
auto w = std::unique_ptr<tracy::FileWrite>( tracy::FileWrite::Open( output, clev, zstdLevel ) );
if( !w )
{