diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index bef85431..8860cbe2 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -93,6 +93,14 @@ extern "C" const char* ___tracy_demangle( const char* mangled ) namespace tracy { +// when "TRACY_SYMBOL_OFFLINE_RESOLVE" is set to "1", instead of fully resolving symbols at runtime, +// simply resolve the offset and image name (which will be enough the resolving to be done offline) +bool getDoOfflineSymbolResolve() +{ + const char* symbolOfflineResolve = GetEnvVar( "TRACY_SYMBOL_OFFLINE_RESOLVE" ); + return (symbolOfflineResolve && symbolOfflineResolve[0] == '1'); +} + #if TRACY_HAS_CALLSTACK == 1 enum { MaxCbTrace = 64 }; @@ -135,19 +143,24 @@ struct KernelDriver KernelDriver* s_krnlCache = nullptr; size_t s_krnlCacheCnt; - +bool s_doOfflineSymbolResolve = false; 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_doOfflineSymbolResolve ) + { + 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 +170,65 @@ void InitCallstack() SymInitialize( GetCurrentProcess(), nullptr, true ); SymSetOptions( SYMOPT_LOAD_LINES ); +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif +} + +DWORD64 dbgHelpLoadSymbolsForModule(PCSTR imageName, DWORD64 baseOfDll, DWORD bllSize) +{ + if( !s_doOfflineSymbolResolve ) + { + return SymLoadModuleEx( GetCurrentProcess(), nullptr, imageName, nullptr, baseOfDll, bllSize, nullptr, 0 ); + } + return 0x0; +} + +ModuleCache* dbgHelpLoadSymbolsForModuleAndCache(PCSTR imageName, DWORD imageNameLength, DWORD64 baseOfDll, DWORD 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_doOfflineSymbolResolve ) + { + cachedModule->name = (char*)tracy_malloc_fast(imageNameLength + 2); + memcpy(cachedModule->name, imageName, imageNameLength); + cachedModule->name[imageNameLength + 1] = '\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() +{ + s_doOfflineSymbolResolve = getDoOfflineSymbolResolve(); + if( s_doOfflineSymbolResolve ) + { + 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'; + dbgHelpLoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage ); } } } @@ -270,6 +329,11 @@ void EndCallstack() const char* DecodeCallstackPtrFast( uint64_t ptr ) { + if( s_doOfflineSymbolResolve ) + { + return "[unresolved]"; + } + static char ret[MaxNameSize]; const auto proc = GetCurrentProcess(); @@ -305,7 +369,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 +384,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 ""; + return ModuleNameAndBaseAddress{ "", addr }; } for( auto& v : *s_modCache ) { if( addr >= v.start && addr < v.end ) { - return v.name; + return ModuleNameAndBaseAddress{ v.name, v.start }; } } @@ -345,35 +415,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 = dbgHelpLoadSymbolsForModuleAndCache( 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_doOfflineSymbolResolve ) + { + sym.file = "[unknown]"; + sym.line = 0; + sym.needFree = false; + return sym; + } + IMAGEHLP_LINE64 line; DWORD displacement = 0; line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); @@ -401,16 +469,29 @@ CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) { + InitRpmalloc(); + + const ModuleNameAndBaseAddress moduleNameAndAddress = GetModuleNameAndPrepareSymbols( ptr ); + + if( s_doOfflineSymbolResolve ) + { + 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(); - InitRpmalloc(); #ifdef TRACY_DBGHELP_LOCK DBGHELP_LOCK; #endif - const auto moduleName = GetModuleNameAndPrepareSymbols(ptr); - #if !defined TRACY_NO_CALLSTACK_INLINES BOOL doInline = FALSE; DWORD ctx = 0; @@ -459,7 +540,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 +573,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,7 +594,7 @@ 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 @@ -696,7 +777,16 @@ void InitCallstackCritical() void InitCallstack() { - cb_bts = backtrace_create_state( nullptr, 0, nullptr, nullptr ); + if( getDoOfflineSymbolResolve() ) + { + 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 +925,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 +1036,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(cb_bts) + { + cb_num = 0; + backtrace_pcinfo( cb_bts, ptr, CallstackDataCb, CallstackErrorCb, nullptr ); + assert( cb_num > 0 ); + + backtrace_syminfo( cb_bts, ptr, SymInfoCallback, SymInfoError, nullptr ); + } + else + { + cb_num = 1; + getSymbolForOfflineResolve( (void*)ptr, dlinfo, cb_data[0] ); + } return { cb_data, uint8_t( cb_num ), symloc ? symloc : "[unknown]" }; } diff --git a/server/TracyWorker.cpp b/server/TracyWorker.cpp index 90dac69c..36acf354 100644 --- a/server/TracyWorker.cpp +++ b/server/TracyWorker.cpp @@ -553,11 +553,12 @@ Worker::Worker( const char* name, const char* program, const std::vector( sz+1 ); + memcpy( ptr, newString, sz ); + ptr[sz] = '\0'; + uint32_t idx = m_data.stringData.size(); + m_data.stringMap.emplace( charutil::StringKey { ptr, sz }, idx ); + m_data.stringData.push_back( ptr ); + return idx; +} + static const char* BadExternalThreadNames[] = { "ntdll.dll", "???", diff --git a/server/TracyWorker.hpp b/server/TracyWorker.hpp index 6ef503c9..081f8be9 100644 --- a/server/TracyWorker.hpp +++ b/server/TracyWorker.hpp @@ -446,7 +446,7 @@ public: Worker( const char* addr, uint16_t port ); Worker( const char* name, const char* program, const std::vector& timeline, const std::vector& messages, const std::vector& plots, const std::unordered_map& 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& GetCallstackFrameMap() { return m_data.callstackFrameMap; } + #ifndef TRACY_NO_STATISTICS const VarArray& GetParentCallstack( uint32_t idx ) const { return *m_data.parentCallstackPayload[idx]; } const CallstackFrameData* GetParentCallstackFrame( const CallstackFrameId& ptr ) const; @@ -665,6 +667,8 @@ public: void CacheSourceFiles(); + uint32_t AddNewString(const char* newString); + private: void Network(); void Exec(); @@ -984,6 +988,7 @@ private: bool m_combineSamples; bool m_identifySamples = false; bool m_inconsistentSamples; + bool m_allowStringModification = false; short_ptr m_gpuCtxMap[256]; uint32_t m_pendingCallstackId = 0; diff --git a/tracy-edit/build/unix/Makefile b/tracy-edit/build/unix/Makefile new file mode 100644 index 00000000..035eda1b --- /dev/null +++ b/tracy-edit/build/unix/Makefile @@ -0,0 +1,16 @@ +all: release + +debug: + @+make -f debug.mk all + +release: + @+make -f release.mk all + +clean: + @+make -f build.mk clean + +db: clean + @bear -- $(MAKE) -f debug.mk all + @mv -f compile_commands.json ../../../ + +.PHONY: all clean debug release db diff --git a/tracy-edit/build/unix/build.mk b/tracy-edit/build/unix/build.mk new file mode 100644 index 00000000..dffcb6cc --- /dev/null +++ b/tracy-edit/build/unix/build.mk @@ -0,0 +1,13 @@ +CFLAGS += +CXXFLAGS := $(CFLAGS) -std=gnu++17 +DEFINES += -DTRACY_NO_STATISTICS +INCLUDES := -I../../../capstone/include/capstone +LIBS += -lcapstone -lpthread +LDFLAGS := -L../../../capstone +PROJECT := tracy-edit +IMAGE := $(PROJECT)-$(BUILD) + +FILTER := +include ../../../common/src-from-vcxproj.mk + +include ../../../common/unix.mk diff --git a/tracy-edit/build/unix/debug.mk b/tracy-edit/build/unix/debug.mk new file mode 100644 index 00000000..a4ec6b6a --- /dev/null +++ b/tracy-edit/build/unix/debug.mk @@ -0,0 +1,6 @@ +CFLAGS := -g3 -Wall +DEFINES := -DDEBUG +BUILD := debug + +include ../../../common/unix-debug.mk +include build.mk diff --git a/tracy-edit/build/unix/release.mk b/tracy-edit/build/unix/release.mk new file mode 100644 index 00000000..ccf07661 --- /dev/null +++ b/tracy-edit/build/unix/release.mk @@ -0,0 +1,9 @@ +CFLAGS := -O3 +ifndef TRACY_NO_LTO +CFLAGS += -flto +endif +DEFINES := -DNDEBUG +BUILD := release + +include ../../../common/unix-release.mk +include build.mk diff --git a/tracy-edit/build/win32/tracy-edit.sln b/tracy-edit/build/win32/tracy-edit.sln new file mode 100644 index 00000000..9b606fbc --- /dev/null +++ b/tracy-edit/build/win32/tracy-edit.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30907.101 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tracy-edit", "tracy-edit.vcxproj", "{447D58BF-94CD-4469-BB90-549C05D03E00}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {447D58BF-94CD-4469-BB90-549C05D03E00}.Debug|x64.ActiveCfg = Debug|x64 + {447D58BF-94CD-4469-BB90-549C05D03E00}.Debug|x64.Build.0 = Debug|x64 + {447D58BF-94CD-4469-BB90-549C05D03E00}.Release|x64.ActiveCfg = Release|x64 + {447D58BF-94CD-4469-BB90-549C05D03E00}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3E51386C-43EA-44AC-9F24-AFAFE4D63ADE} + EndGlobalSection +EndGlobal diff --git a/tracy-edit/build/win32/tracy-edit.vcxproj b/tracy-edit/build/win32/tracy-edit.vcxproj new file mode 100644 index 00000000..2fb58ced --- /dev/null +++ b/tracy-edit/build/win32/tracy-edit.vcxproj @@ -0,0 +1,210 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {447D58BF-94CD-4469-BB90-549C05D03E00} + tracy-edit + 10.0.17763.0 + x64-windows-static + + + + Application + true + v143 + MultiByte + + + Application + false + v143 + true + MultiByte + + + + + + + + + + + + + + + + true + + + + Level3 + Disabled + true + true + true + TRACY_NO_STATISTICS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32_LEAN_AND_MEAN;NOMINMAX;_USE_MATH_DEFINES;%(PreprocessorDefinitions) + AdvancedVectorExtensions2 + stdcpplatest + $(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\include;$(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\include\capstone;$(VcpkgManifestRoot)\vcpkg_installed\$(VcpkgTriplet)\$(VcpkgTriplet)\include\capstone;$(VcpkgRoot)\installed\$(VcpkgTriplet)\include\capstone + + + ws2_32.lib;capstone.lib;dbghelp.lib;%(AdditionalDependencies) + Console + $(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\debug\lib + + + + + Level3 + MaxSpeed + true + true + true + true + true + TRACY_NO_STATISTICS;NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32_LEAN_AND_MEAN;NOMINMAX;_USE_MATH_DEFINES;%(PreprocessorDefinitions) + AdvancedVectorExtensions2 + stdcpplatest + $(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\include;$(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\include\capstone;$(VcpkgManifestRoot)\vcpkg_installed\$(VcpkgTriplet)\$(VcpkgTriplet)\include\capstone;$(VcpkgRoot)\installed\$(VcpkgTriplet)\include\capstone + + + true + true + ws2_32.lib;capstone.lib;dbghelp.lib;%(AdditionalDependencies) + Console + $(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\lib + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tracy-edit/build/win32/tracy-edit.vcxproj.filters b/tracy-edit/build/win32/tracy-edit.vcxproj.filters new file mode 100644 index 00000000..8b77d3ab --- /dev/null +++ b/tracy-edit/build/win32/tracy-edit.vcxproj.filters @@ -0,0 +1,366 @@ + + + + + {729c80ee-4d26-4a5e-8f1f-6c075783eb56} + + + {cf23ef7b-7694-4154-830b-00cf053350ea} + + + {e39d3623-47cd-4752-8da9-3ea324f964c1} + + + {9ec18988-3ab7-4c05-a9d0-46c0a68037de} + + + {5ee9ba63-2914-4027-997e-e743a294bba6} + + + {a166d032-7be0-4d07-9f85-a8199cc1ec7c} + + + {438fff23-197c-4b6f-91f0-74f8b3878571} + + + {e5c7021a-e0e4-45c2-b461-e806bc036d5f} + + + + + server + + + server + + + src + + + server + + + server + + + server + + + server + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\decompress + + + zstd\decompress + + + zstd\decompress + + + zstd\decompress + + + zstd\dictBuilder + + + zstd\dictBuilder + + + zstd\dictBuilder + + + zstd\dictBuilder + + + common + + + common + + + common + + + common + + + common + + + src + + + src + + + src + + + + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + zstd + + + zstd + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\decompress + + + zstd\decompress + + + zstd\decompress + + + zstd + + + zstd\dictBuilder + + + zstd\dictBuilder + + + zstd\common + + + zstd\compress + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + + + zstd\decompress + + + \ No newline at end of file diff --git a/tracy-edit/src/OfflineSymbolResolver.cpp b/tracy-edit/src/OfflineSymbolResolver.cpp new file mode 100644 index 00000000..5f0e6630 --- /dev/null +++ b/tracy-edit/src/OfflineSymbolResolver.cpp @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "../../server/TracyWorker.hpp" +#include "../../zstd/zstd.h" + +#include "OfflineSymbolResolver.h" + +// TODO: use string hash map to reduce duplication or use some worker string internal hashing +tracy::StringIdx AddSymbolString(tracy::Worker& worker, const char* str) +{ + uint32_t newStringIdx = worker.AddNewString( str ); + return tracy::StringIdx( newStringIdx ); +} + +bool PatchSymbols(SymbolResolver* resolver, tracy::Worker& worker, bool verbose) +{ + if( !resolver ) + { + return false; + } + + 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; + 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 ); + const char* imageName = worker.GetString( imageIdx ); + + FrameEntryList& entries = imageIt->second; + + std::cout << "Resolving " << entries.size() << " symbols for image: '" << imageName << "'" << std::endl; + + if(!entries.size()) + { + continue; + } + + SymbolEntryList resolvedEntries; + ResolveSymbols( resolver, imageName, entries, resolvedEntries ); + + if( resolvedEntries.size() != entries.size() ) + { + std::cerr << "ERROR: 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 '" << imageName << "' -> '" << symbolEntry.name << "'" << std::endl; + } + + frame.name = AddSymbolString(worker, symbolEntry.name.c_str()); + const char* newName = worker.GetString(frame.name); + + if(symbolEntry.file.length()) + { + frame.file = AddSymbolString(worker, symbolEntry.file.c_str()); + frame.line = symbolEntry.line; + } + } + } + + return true; +} diff --git a/tracy-edit/src/OfflineSymbolResolver.h b/tracy-edit/src/OfflineSymbolResolver.h new file mode 100644 index 00000000..584e9179 --- /dev/null +++ b/tracy-edit/src/OfflineSymbolResolver.h @@ -0,0 +1,41 @@ +#ifndef __SYMBOLRESOLVER_HPP__ +#define __SYMBOLRESOLVER_HPP__ + +#include +#include + +namespace tracy +{ + struct CallstackFrame; + class Worker; +} + +class SymbolResolver; + +SymbolResolver* CreateResolver(); +void DestroySymbolResolver(SymbolResolver* resolver); + +struct FrameEntry +{ + tracy::CallstackFrame* frame = nullptr; + uint64_t symbolOffset = 0; +}; + +using FrameEntryList = std::vector; + +struct SymbolEntry +{ + std::string name; + std::string file; + int line = 0; +}; + +using SymbolEntryList = std::vector; + +bool ResolveSymbols(SymbolResolver* resolver, const char* imageName, + const FrameEntryList& inputEntryList, + SymbolEntryList& resolvedEntries); + +bool PatchSymbols(SymbolResolver* resolver, tracy::Worker& worker, bool verbose = false); + +#endif // __SYMBOLRESOLVER_HPP__ \ No newline at end of file diff --git a/tracy-edit/src/OfflineSymbolResolverAddr2Line.cpp b/tracy-edit/src/OfflineSymbolResolverAddr2Line.cpp new file mode 100644 index 00000000..cb467f51 --- /dev/null +++ b/tracy-edit/src/OfflineSymbolResolverAddr2Line.cpp @@ -0,0 +1,124 @@ +#ifdef __linux + +#include "OfflineSymbolResolver.h" + +#include +#include +#include +#include +#include +#include +#include + +std::string ExecShellCommand(const char* cmd) +{ + std::array buffer; + std::string result; + std::unique_ptr 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(const std::string& addr2linePath) + : m_addr2LinePath(addr2linePath) + {} + + bool ResolveSymbols(const char* imageName, const FrameEntryList& inputEntryList, + SymbolEntryList& resolvedEntries) + { + // generate a single addr2line cmd line for all addresses in one invocation + std::stringstream ss; + ss << m_addr2LinePath << " -C -f -e " << imageName << " -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; +}; + +SymbolResolver* CreateResolver() +{ + std::stringstream result(ExecShellCommand("which addr2line")); + std::string addr2LinePath; + std::getline(result, addr2LinePath); + + if(!addr2LinePath.length()) + { + std::cerr << "'addr2line' was not found in the system, please installed it" << std::endl; + return nullptr; + } + std::cout << "Using 'addr2line' found at: '" << addr2LinePath.c_str() << "'" << std::endl; + return new SymbolResolver{addr2LinePath}; +} + +void DestroySymbolResolver(SymbolResolver* resolver) +{ + delete resolver; +} + +bool ResolveSymbols(SymbolResolver* resolver, const char* imageName, + const FrameEntryList& inputEntryList, SymbolEntryList& resolvedEntries) +{ + if (resolver) + { + return resolver->ResolveSymbols(imageName, inputEntryList, resolvedEntries); + } + return false; +} + +#endif // #ifdef __linux \ No newline at end of file diff --git a/tracy-edit/src/OfflineSymbolResolverDbgHelper.cpp b/tracy-edit/src/OfflineSymbolResolverDbgHelper.cpp new file mode 100644 index 00000000..40b96cd3 --- /dev/null +++ b/tracy-edit/src/OfflineSymbolResolverDbgHelper.cpp @@ -0,0 +1,142 @@ +#ifdef _WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +#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_DEBUG | SYMOPT_LOAD_LINES; + SymSetOptions( symopts ); + } + } + + ~SymbolResolver() + { + SymCleanup( m_procHandle ); + } + + bool ResolveSymbolsForModule(const char* fileName, const FrameEntryList& inputEntryList, + SymbolEntryList& resolvedEntries) + { + ULONG64 moduleBase = SymLoadModuleEx( m_procHandle, NULL, fileName, NULL, 0, 0, NULL, 0 ); + if (!moduleBase) + { + std::cerr << "SymLoadModuleEx() failed for module " << fileName + << ": " << 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; + } + + HANDLE m_procHandle = nullptr; +}; + +char SymbolResolver::s_symbolResolutionBuffer[symbolResolutionBufferSize]; + + +SymbolResolver* CreateResolver() +{ + SymbolResolver* resolver = new SymbolResolver(); + return resolver; +} + +void DestroySymbolResolver(SymbolResolver* resolver) +{ + delete resolver; +} + +bool ResolveSymbols(SymbolResolver* resolver, const char* imageName, + const FrameEntryList& inputEntryList, + SymbolEntryList& resolvedEntries) +{ + if( resolver ) + { + return resolver->ResolveSymbolsForModule( imageName, inputEntryList, resolvedEntries ); + } + return false; +} + +#endif // #ifdef _WIN32 \ No newline at end of file diff --git a/tracy-edit/src/tracy-edit.cpp b/tracy-edit/src/tracy-edit.cpp new file mode 100644 index 00000000..dcf8a251 --- /dev/null +++ b/tracy-edit/src/tracy-edit.cpp @@ -0,0 +1,181 @@ +#ifdef _WIN32 +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include "../../server/TracyFileRead.hpp" +#include "../../server/TracyFileWrite.hpp" +#include "../../server/TracyWorker.hpp" +#include "../../getopt/getopt.h" + +#include "OfflineSymbolResolver.h" + +struct Args +{ + const char* inputTracyPath = nullptr; + const char* outputTracyPath = nullptr; + bool verbose = false; + bool resolveSymbols = false; + tracy::FileWrite::Compression compressionType = tracy::FileWrite::Compression::Zstd; + int compressionLevel = 5; +}; + +void PrintUsageAndExit() +{ + std::cerr << "Modify a tracy file" << std::endl; + std::cerr << "Usage:" << std::endl; + std::cerr << " extract [OPTION...] " << std::endl; + std::cerr << std::endl; + std::cerr << " -h, --help Print usage" << std::endl; + std::cerr << " -v, --verbose Enable verbose logging" << std::endl; + std::cerr << " -r, --resolveSymbols Resolve symbols and patch callstack frames" << std::endl; + std::cerr << " -c, --compression arg Compress output with the given compression algo" << std::endl; + std::cerr << " -l, --compressesionLevel arg Level of compression" << std::endl; + exit( 1 ); +} + +static const char* compressionTypeStr[] +{ + "Fast", + "Slow", + "Extreme", + "Zstd" +}; +static_assert( uint32_t(tracy::FileWrite::Compression::Zstd)+1 == sizeof(compressionTypeStr)/sizeof(compressionTypeStr[0])); +tracy::FileWrite::Compression getCompressionFromString(const char* str) +{ + for( uint32_t i = 0; i < sizeof(compressionTypeStr)/sizeof(compressionTypeStr[0]); ++i ) + { + if( strcmp( compressionTypeStr[i], str ) == 0 ) + { + return tracy::FileWrite::Compression( i ); + } + } + return tracy::FileWrite::Compression::Zstd; +} + +Args ParseArgs( int argc, char** argv ) +{ + if ( argc < 3 ) + { + PrintUsageAndExit(); + } + + Args args; + + struct option long_opts[] = + { + { "help", no_argument, NULL, 'h' }, + { "verbose", no_argument, NULL, 'v' }, + { "resolveSymbols", no_argument, NULL, 'r' }, + { "compression", required_argument, NULL, 'c' }, + { "compressesionLevel", required_argument, NULL, 'l' }, + { NULL, 0, NULL, 0 } + }; + + int c; + while ( (c = getopt_long( argc, argv, "hvrc:l:", long_opts, NULL )) != -1 ) + { + switch (c) + { + case 'h': + PrintUsageAndExit(); + break; + case 'v': + args.verbose = true; + break; + case 'r': + args.resolveSymbols = true; + break; + case 'c': + args.compressionType = getCompressionFromString( optarg ); + break; + case 'l': + args.compressionLevel = atoi( optarg ); + break; + default: + PrintUsageAndExit(); + break; + } + } + + if (argc != optind + 2) + { + PrintUsageAndExit(); + } + + args.inputTracyPath = argv[optind + 0]; + args.outputTracyPath = argv[optind + 1]; + + return args; +} + +int main( int argc, char** argv ) +{ +#ifdef _WIN32 + if( !AttachConsole( ATTACH_PARENT_PROCESS ) ) + { + AllocConsole(); + SetConsoleMode( GetStdHandle( STD_OUTPUT_HANDLE ), 0x07 ); + } +#endif // #ifdef _WIN32 + + Args args = ParseArgs( argc, argv ); + + // load input tracy file + auto f = std::unique_ptr(tracy::FileRead::Open( args.inputTracyPath )); + if (!f) + { + std::cerr << "Could not open file: " << args.inputTracyPath; + return 1; + } + + std::cout << "Reading ..." << std::endl; + + const bool allowBgThreads = false; + bool allowStringModification = true; + tracy::Worker worker( *f, tracy::EventType::All, allowBgThreads, allowStringModification ); + + std::cout << "Loaded." << std::endl; + + // attempt to resolve symbols only if requested + if(args.resolveSymbols) + { + std::cout << "Resolving and patching symbols..." << std::endl; + + SymbolResolver* resolver = CreateResolver(); + if(!resolver) + { + std::cerr << "Failed to create symbol resolver - skipping resolving" << std::endl; + } + else + { + PatchSymbols(resolver, worker); + DestroySymbolResolver(resolver); + } + } + + // save out capture file with new compression options + std::cout << "Saving (using '" << compressionTypeStr[uint32_t(args.compressionType)] + << "', level: " << args.compressionLevel << ") ..." << std::endl; + + auto w = std::unique_ptr( + tracy::FileWrite::Open( args.outputTracyPath, args.compressionType, args.compressionLevel) ); + if( !w ) + { + std::cerr << "Cannot open output file: '" << args.outputTracyPath << "'" << std::endl; + exit( 1 ); + } + + worker.Write( *w, false ); + + std::cout << "Cleanup..." << std::endl; + + return 0; +}