diff --git a/.gitignore b/.gitignore index e4f0cad38d..ddfe7e0a8f 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,5 @@ profiler/build/wasm/Tracy-debug.* profiler/build/wasm/embed.tracy examples/ToyPathTracer/Windows/TestCpu examples/ToyPathTracer/Windows/x64 +examples/ToyPathTracer/Windows/TestCpu.vcxproj.user *.user diff --git a/CMakeLists.txt b/CMakeLists.txt index 10e9b18cf2..9d977f0ab6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -188,17 +188,12 @@ set(client_includes ${TRACY_PUBLIC_DIR}/client/tracy_SPSCQueue.h ${TRACY_PUBLIC_DIR}/client/TracyKCore.hpp ${TRACY_PUBLIC_DIR}/client/TracyArmCpuTable.hpp - ${TRACY_PUBLIC_DIR}/client/TracyCallstack.h - ${TRACY_PUBLIC_DIR}/client/TracyCallstack.hpp ${TRACY_PUBLIC_DIR}/client/TracyCpuid.hpp - ${TRACY_PUBLIC_DIR}/client/TracyDebug.hpp ${TRACY_PUBLIC_DIR}/client/TracyDxt1.hpp - ${TRACY_PUBLIC_DIR}/client/TracyFastVector.hpp ${TRACY_PUBLIC_DIR}/client/TracyLock.hpp ${TRACY_PUBLIC_DIR}/client/TracyProfiler.hpp ${TRACY_PUBLIC_DIR}/client/TracyRingBuffer.hpp ${TRACY_PUBLIC_DIR}/client/TracyScoped.hpp - ${TRACY_PUBLIC_DIR}/client/TracyStringHelpers.hpp ${TRACY_PUBLIC_DIR}/client/TracySysPower.hpp ${TRACY_PUBLIC_DIR}/client/TracySysTime.hpp ${TRACY_PUBLIC_DIR}/client/TracySysTrace.hpp @@ -210,13 +205,18 @@ set(common_includes ${TRACY_PUBLIC_DIR}/common/TracyAlign.hpp ${TRACY_PUBLIC_DIR}/common/TracyAlloc.hpp ${TRACY_PUBLIC_DIR}/common/TracyApi.h + ${TRACY_PUBLIC_DIR}/common/TracyCallstack.h + ${TRACY_PUBLIC_DIR}/common/TracyCallstack.hpp ${TRACY_PUBLIC_DIR}/common/TracyColor.hpp + ${TRACY_PUBLIC_DIR}/common/TracyDebug.hpp + ${TRACY_PUBLIC_DIR}/common/TracyFastVector.hpp ${TRACY_PUBLIC_DIR}/common/TracyForceInline.hpp ${TRACY_PUBLIC_DIR}/common/TracyMutex.hpp ${TRACY_PUBLIC_DIR}/common/TracyProtocol.hpp ${TRACY_PUBLIC_DIR}/common/TracyQueue.hpp ${TRACY_PUBLIC_DIR}/common/TracySocket.hpp ${TRACY_PUBLIC_DIR}/common/TracyStackFrames.hpp + ${TRACY_PUBLIC_DIR}/common/TracyStringHelpers.hpp ${TRACY_PUBLIC_DIR}/common/TracySystem.hpp ${TRACY_PUBLIC_DIR}/common/TracyWinFamily.hpp ${TRACY_PUBLIC_DIR}/common/TracyYield.hpp) diff --git a/capture/src/capture.cpp b/capture/src/capture.cpp index 16293ef51f..b130bbb129 100644 --- a/capture/src/capture.cpp +++ b/capture/src/capture.cpp @@ -164,7 +164,10 @@ int main( int argc, char** argv ) printf( "Connecting to %s:%i...", address, port ); fflush( stdout ); - tracy::Worker worker( address, port, memoryLimit ); + + tracy::Worker::SymbolResolutionConfig symConfig{}; + symConfig.m_attemptResolutionByWorker = false; + tracy::Worker worker( address, port, memoryLimit, symConfig ); while( !worker.HasData() ) { const auto handshake = worker.GetHandshakeStatus(); diff --git a/cmake/server.cmake b/cmake/server.cmake index a76d1c1340..67dae697e7 100644 --- a/cmake/server.cmake +++ b/cmake/server.cmake @@ -3,6 +3,7 @@ set(TRACY_COMMON_DIR ${CMAKE_CURRENT_LIST_DIR}/../public/common) set(TRACY_COMMON_SOURCES tracy_lz4.cpp tracy_lz4hc.cpp + TracyCallstack.cpp TracySocket.cpp TracyStackFrames.cpp TracySystem.cpp diff --git a/csvexport/src/csvexport.cpp b/csvexport/src/csvexport.cpp index dae114053c..db35956b45 100644 --- a/csvexport/src/csvexport.cpp +++ b/csvexport/src/csvexport.cpp @@ -214,7 +214,9 @@ int main(int argc, char** argv) return 1; } - auto worker = tracy::Worker(*f); + tracy::Worker::SymbolResolutionConfig symConfig{}; + symConfig.m_attemptResolutionByWorker = false; + auto worker = tracy::Worker(*f, symConfig); if (args.unwrapMessages) { diff --git a/import/src/import-chrome.cpp b/import/src/import-chrome.cpp index 33db741e97..a4fdceaf8d 100644 --- a/import/src/import-chrome.cpp +++ b/import/src/import-chrome.cpp @@ -345,7 +345,9 @@ int main( int argc, char** argv ) return out; }; - tracy::Worker worker( getFilename(output), getFilename(input), timeline, messages, plots, threadNames ); + tracy::Worker::SymbolResolutionConfig symConfig{}; + symConfig.m_attemptResolutionByWorker = false; + tracy::Worker worker( getFilename(output), getFilename(input), timeline, messages, plots, threadNames, symConfig ); auto w = std::unique_ptr( tracy::FileWrite::Open( output, clev ) ); if( !w ) diff --git a/import/src/import-fuchsia.cpp b/import/src/import-fuchsia.cpp index 785979c198..3757c2e4ff 100644 --- a/import/src/import-fuchsia.cpp +++ b/import/src/import-fuchsia.cpp @@ -671,9 +671,11 @@ int main(int argc, char **argv) { out--; return out; }; - + + tracy::Worker::SymbolResolutionConfig symConfig{}; + symConfig.m_attemptResolutionByWorker = false; tracy::Worker worker(getFilename(output), getFilename(input), timeline, - messages, plots, std::move(dec.threadNames)); + messages, plots, std::move(dec.threadNames), symConfig); auto w = std::unique_ptr(tracy::FileWrite::Open(output, clev)); diff --git a/manual/tracy.tex b/manual/tracy.tex index ccebc0a288..065bbb56ac 100644 --- a/manual/tracy.tex +++ b/manual/tracy.tex @@ -1941,10 +1941,32 @@ \subsubsection{Debugging symbols} At initilization time, tracy will attempt to preload symbols for device drivers and process modules. As this process can be slow when a lot of pdbs are involved, you can set the \texttt{TRACY\_NO\_DBGHELP\_INIT\_LOAD} environment variable to "1" to disable this behavior and rely on-demand symbol loading. +\begin{bclogo}[ +noborder=true, +couleur=black!5, +logo=\bcbombe +]{Important} + +To be able to fully resolve kernel symbols, you might need to have matching versions of \texttt{dbghelp.dll} and \texttt{symsrv.dll} in your \texttt{PATH} or working directory and use symbol servers. Set the \texttt{\_NT\_SYMBOL\_PATH} environment variable to point to the symbol servers you want. For example: +\begin{lstlisting} +_NT\_SYMBOL\_PATH=srv*YourPdbCachePath*https://msdl.microsoft.com/download/symbols +\end{lstlisting} + +To get more information about this topic, check Microsoft's official docs: \url{https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/symbol-path}. +Note that downloading the kernel drivers symbols can be slow at the first execution of the program, or a Window update. +\end{bclogo} + \paragraph{Disabling resolution of inline frames} 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{Server symbol resolution} + +Instead of having the client do the resolution, you can configure the server to attempt the symbol resolution. +This is controlled by the profiler global settings in the "Symbol resolution" section. +If "Attempt resolution by profiler" is enabled, the server will attempt to resolve symbols locally. For this, the Tracy-Client will send informations about the executable images loaded by the process. If symbol resolution on the server fails, it will query the client for the information as a fallback mechanism. +However if "Prevent resolution by application" is enabled, Tracy-Client will not resolve any symbols and simply send the minimal images information. + \paragraph{Offline symbol resolution} By default, tracy client resolves callstack symbols in a background thread at runtime. diff --git a/meson.build b/meson.build index af6f220bd0..1a0a0773f1 100644 --- a/meson.build +++ b/meson.build @@ -146,17 +146,12 @@ client_includes = [ 'public/client/tracy_rpmalloc.hpp', 'public/client/tracy_SPSCQueue.h', 'public/client/TracyArmCpuTable.hpp', - 'public/client/TracyCallstack.h', - 'public/client/TracyCallstack.hpp', - 'public/client/TracyDebug.hpp', 'public/client/TracyDxt1.hpp', - 'public/client/TracyFastVector.hpp', 'public/client/TracyKCore.hpp', 'public/client/TracyLock.hpp', 'public/client/TracyProfiler.hpp', 'public/client/TracyRingBuffer.hpp', 'public/client/TracyScoped.hpp', - 'public/client/TracyStringHelpers.hpp', 'public/client/TracySysPower.hpp', 'public/client/TracySysTime.hpp', 'public/client/TracySysTrace.hpp', @@ -169,13 +164,18 @@ common_includes = [ 'public/common/TracyAlign.hpp', 'public/common/TracyAlloc.hpp', 'public/common/TracyApi.h', + 'public/common/TracyCallstack.h', + 'public/common/TracyCallstack.hpp', 'public/common/TracyColor.hpp', + 'public/common/TracyDebug.hpp', + 'public/common/TracyFastVector.hpp', 'public/common/TracyForceInline.hpp', 'public/common/TracyMutex.hpp', 'public/common/TracyProtocol.hpp', 'public/common/TracyQueue.hpp', 'public/common/TracySocket.hpp', 'public/common/TracyStackFrames.hpp', + 'public/common/TracyStringHelpers.hpp', 'public/common/TracySystem.hpp', 'public/common/TracyWinFamily.hpp', 'public/common/TracyYield.hpp' diff --git a/profiler/CMakeLists.txt b/profiler/CMakeLists.txt index d01731d320..dfaeada65c 100644 --- a/profiler/CMakeLists.txt +++ b/profiler/CMakeLists.txt @@ -20,7 +20,9 @@ project( if(SELF_PROFILE) add_definitions(-DTRACY_ENABLE) - add_compile_options(-g -O3 -fno-omit-frame-pointer) + if(NOT MSVC) + add_compile_options(-g -O3 -fno-omit-frame-pointer) + endif() endif() if(SANITIZE) diff --git a/profiler/src/main.cpp b/profiler/src/main.cpp index 7159ca31fb..9763f2c1c8 100644 --- a/profiler/src/main.cpp +++ b/profiler/src/main.cpp @@ -786,6 +786,18 @@ static void DrawContents() if( ImGui::Checkbox( "Enable Tracy Assist", &tracy::s_config.llm ) ) tracy::SaveConfig(); #endif + ImGui::TextUnformatted( "Symbol resolution" ); + ImGui::Indent(); + if( ImGui::Checkbox( "Attempt resolution by profiler", &tracy::s_config.symbolsAttemptResolutionByServer ) ) tracy::SaveConfig(); + ImGui::SameLine(); + tracy::DrawHelpMarker( "When enabled, the profiler will attempt to resolve symbols first before querying the application." ); + + if( ImGui::Checkbox( "Prevent resolution by application", &tracy::s_config.symbolsPreventResolutionByClient ) ) tracy::SaveConfig(); + ImGui::SameLine(); + tracy::DrawHelpMarker( "When enabled, the application will not attempt to resolve symbols at all. The profiler can do the resolution, or it will require to use the `update` tool." ); + + ImGui::Unindent(); + ImGui::PopStyleVar(); ImGui::TreePop(); } diff --git a/profiler/src/profiler/TracyConfig.cpp b/profiler/src/profiler/TracyConfig.cpp index aaeaf30825..621ac3ce99 100644 --- a/profiler/src/profiler/TracyConfig.cpp +++ b/profiler/src/profiler/TracyConfig.cpp @@ -41,6 +41,8 @@ void LoadConfig() if( v2 = ini_get( ini, "llm", "useragent" ); v2 ) s_config.llmUserAgent = v2; if( v2 = ini_get( ini, "llm", "searchIdentifier" ); v2 ) s_config.llmSearchIdentifier = v2; if( v2 = ini_get( ini, "llm", "searchApiKey" ); v2 ) s_config.llmSearchApiKey = v2; + if (ini_sget(ini, "symbols", "attemptResolutionByServer", "%d", &v)) s_config.symbolsAttemptResolutionByServer = (bool)v; + if (ini_sget(ini, "symbols", "preventResolutionByClient", "%d", &v)) s_config.symbolsPreventResolutionByClient = (bool)v; ini_free( ini ); } @@ -84,6 +86,10 @@ bool SaveConfig() fprintf( f, "searchIdentifier = %s\n", s_config.llmSearchIdentifier.c_str() ); fprintf( f, "searchApiKey = %s\n", s_config.llmSearchApiKey.c_str() ); + fprintf( f, "\n[symbols]\n" ); + fprintf( f, "attemptResolutionByServer = %i\n", (int)s_config.symbolsAttemptResolutionByServer); + fprintf( f, "preventResolutionByClient = %i\n", (int)s_config.symbolsPreventResolutionByClient); + fclose( f ); return true; } diff --git a/profiler/src/profiler/TracyConfig.hpp b/profiler/src/profiler/TracyConfig.hpp index dca00a6412..aebba0cd4a 100644 --- a/profiler/src/profiler/TracyConfig.hpp +++ b/profiler/src/profiler/TracyConfig.hpp @@ -35,6 +35,8 @@ struct Config std::string llmUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"; std::string llmSearchIdentifier; std::string llmSearchApiKey; + bool symbolsAttemptResolutionByServer = false; + bool symbolsPreventResolutionByClient = false; }; extern Config s_config; diff --git a/profiler/src/profiler/TracyImGui.hpp b/profiler/src/profiler/TracyImGui.hpp index c64f806a92..53745fe956 100644 --- a/profiler/src/profiler/TracyImGui.hpp +++ b/profiler/src/profiler/TracyImGui.hpp @@ -84,6 +84,22 @@ static constexpr const uint32_t AsmSyntaxColors[] = { ImGui::TextUnformatted( text ); } +[[maybe_unused]] static inline void TextCenteredWindow( const char* text ) +{ + const ImGuiStyle& imguiStyle = ImGui::GetStyle(); + + const ImVec2 size = ImGui::CalcTextSize(text) + ImVec2(imguiStyle.FramePadding.x * 2.0f, imguiStyle.FramePadding.y * 2.0f); + const ImVec2 avail = ImGui::GetContentRegionAvail(); + + const ImVec2 off = ImVec2( (avail.x - size.x ) * 0.5f, ( avail.y - size.y ) * 0.5f); + if( off.x > 0.0f ) + ImGui::SetCursorPosX( ImGui::GetCursorPosX() + off.x ); + if( off.y > 0.0f ) + ImGui::SetCursorPosY( ImGui::GetCursorPosY() + off.y ); + + ImGui::TextUnformatted( text ); +} + [[maybe_unused]] static inline bool ButtonCentered( const char* text ) { const auto tw = ImGui::CalcTextSize( text ).x + ImGui::GetStyle().FramePadding.x * 2; diff --git a/profiler/src/profiler/TracySourceView.cpp b/profiler/src/profiler/TracySourceView.cpp index c79948b8a6..a25478b9c1 100644 --- a/profiler/src/profiler/TracySourceView.cpp +++ b/profiler/src/profiler/TracySourceView.cpp @@ -1158,8 +1158,17 @@ void SourceView::RenderSymbolView( Worker& worker, View& view ) const auto shortenName = view.GetShortenName(); auto sym = worker.GetSymbolData( m_symAddr ); - assert( sym ); - ImGui::PushFont( g_fonts.normal, FontBig ); + + if( sym == nullptr ) + { + ImGui::PushFont( g_fonts.normal, FontNormal ); + TextCenteredWindow( ICON_FA_PERSON_DIGGING "Waiting for symbols resolution" ); + ImGui::PopFont(); + return; + } + + + ImGui::PushFont( g_fonts.normal, FontNormal ); ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) ); if( ButtonDisablable( " " ICON_FA_CARET_LEFT " ", m_historyCursor <= 1 ) ) { diff --git a/profiler/src/profiler/TracyView.cpp b/profiler/src/profiler/TracyView.cpp index 4ed9b1a61d..21e5216249 100644 --- a/profiler/src/profiler/TracyView.cpp +++ b/profiler/src/profiler/TracyView.cpp @@ -38,7 +38,7 @@ namespace tracy double s_time = 0; View::View( void(*cbMainThread)(const std::function&, bool), const char* addr, uint16_t port, SetTitleCallback stcb, SetScaleCallback sscb, AttentionCallback acb, AchievementsMgr* amgr ) - : m_worker( addr, port, s_config.memoryLimit == 0 ? -1 : ( s_config.memoryLimitPercent * tracy::GetPhysicalMemorySize() / 100 ) ) + : m_worker( addr, port, s_config.memoryLimit == 0 ? -1 : ( s_config.memoryLimitPercent * tracy::GetPhysicalMemorySize() / 100 ), SymbolResolutionConfigFromConfig( s_config ) ) , m_staticView( false ) , m_viewMode( ViewMode::LastFrames ) , m_viewModeHeuristicTry( true ) @@ -69,7 +69,7 @@ View::View( void(*cbMainThread)(const std::function&, bool), const char* } View::View( void(*cbMainThread)(const std::function&, bool), FileRead& f, SetTitleCallback stcb, SetScaleCallback sscb, AttentionCallback acb, AchievementsMgr* amgr ) - : m_worker( f ) + : m_worker( f, SymbolResolutionConfigFromConfig( s_config ) ) , m_filename( f.GetFilename() ) , m_staticView( true ) , m_viewMode( ViewMode::Paused ) @@ -913,6 +913,18 @@ bool View::DrawImpl() ImGui::SameLine(); ToggleButton( ICON_FA_FINGERPRINT " Info", m_showInfo ); ImGui::SameLine(); + + if( m_filename.empty() ) // Can't modify when loading from file + { + ImGui::SameLine(); + if (ImGui::Button( ICON_FA_USER_SECRET " Resolve Symbols")) + { + // TODO: Make it async ? + m_worker.ResolveSymbolLocally(); + } + + } + if( ImGui::Button( ICON_FA_SCREWDRIVER_WRENCH ) ) ImGui::OpenPopup( "ToolsPopup" ); if( ImGui::BeginPopup( "ToolsPopup" ) ) { diff --git a/profiler/src/profiler/TracyView.hpp b/profiler/src/profiler/TracyView.hpp index 5792b17f12..5c5f0a09f3 100644 --- a/profiler/src/profiler/TracyView.hpp +++ b/profiler/src/profiler/TracyView.hpp @@ -64,6 +64,7 @@ struct LockDraw; struct PlotDraw; struct FlameGraphContext; +static inline const Worker::SymbolResolutionConfig SymbolResolutionConfigFromConfig( const Config& config ) { return { config.symbolsAttemptResolutionByServer, config.symbolsPreventResolutionByClient }; } class View { @@ -148,6 +149,7 @@ class View const MessageData* GetMessageHighlight() const { return m_msgHighlight; } uint32_t GetLockInfoWindow() const { return m_lockInfoWindow; } + tracy_force_inline bool& Vis( const void* ptr ) { auto it = m_visMap.find( ptr ); diff --git a/profiler/src/profiler/TracyView_Compare.cpp b/profiler/src/profiler/TracyView_Compare.cpp index 4c09e8d3be..90dc21b2e3 100644 --- a/profiler/src/profiler/TracyView_Compare.cpp +++ b/profiler/src/profiler/TracyView_Compare.cpp @@ -230,7 +230,9 @@ void View::DrawCompare() m_compare.loadThread = std::thread( [this, f] { try { - m_compare.second = std::make_unique( *f, EventType::SourceCache ); + tracy::Worker::SymbolResolutionConfig symConfig{}; + symConfig.m_attemptResolutionByWorker = false; + m_compare.second = std::make_unique( *f, symConfig, EventType::SourceCache ); m_compare.userData = std::make_unique( m_compare.second->GetCaptureProgram().c_str(), m_compare.second->GetCaptureTime() ); m_compare.diffDirection = m_worker.GetCaptureTime() < m_compare.second->GetCaptureTime(); } diff --git a/public/TracyClient.cpp b/public/TracyClient.cpp index e9a018485c..11105eb32e 100644 --- a/public/TracyClient.cpp +++ b/public/TracyClient.cpp @@ -21,7 +21,7 @@ #include "common/tracy_lz4.cpp" #include "client/TracyProfiler.cpp" -#include "client/TracyCallstack.cpp" +#include "common/TracyCallstack.cpp" #include "client/TracySysPower.cpp" #include "client/TracySysTime.cpp" #include "client/TracySysTrace.cpp" @@ -35,28 +35,8 @@ #ifdef TRACY_ROCPROF # include "client/TracyRocprof.cpp" #endif - -#if defined(TRACY_HAS_CALLSTACK) -# if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 -# include "libbacktrace/alloc.cpp" -# include "libbacktrace/dwarf.cpp" -# include "libbacktrace/fileline.cpp" -# include "libbacktrace/mmapio.cpp" -# include "libbacktrace/posix.cpp" -# include "libbacktrace/sort.cpp" -# include "libbacktrace/state.cpp" -# if TRACY_HAS_CALLSTACK == 4 -# include "libbacktrace/macho.cpp" -# else -# include "libbacktrace/elf.cpp" -# endif -# include "common/TracyStackFrames.cpp" -# endif -#endif - #ifdef _MSC_VER # pragma comment(lib, "ws2_32.lib") -# pragma comment(lib, "dbghelp.lib") # pragma comment(lib, "advapi32.lib") # pragma comment(lib, "user32.lib") # pragma warning(pop) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp deleted file mode 100644 index bd3290604f..0000000000 --- a/public/client/TracyCallstack.cpp +++ /dev/null @@ -1,1420 +0,0 @@ -#include -#include -#include -#include -#include "TracyCallstack.hpp" -#include "TracyDebug.hpp" -#include "TracyFastVector.hpp" -#include "TracyStringHelpers.hpp" -#include "../common/TracyAlloc.hpp" -#include "../common/TracySystem.hpp" - - -#ifdef TRACY_HAS_CALLSTACK - -#if TRACY_HAS_CALLSTACK == 1 -# ifndef NOMINMAX -# define NOMINMAX -# endif -# include -# include -# include -# ifdef _MSC_VER -# pragma warning( push ) -# pragma warning( disable : 4091 ) -# endif -# include -# ifdef _MSC_VER -# pragma warning( pop ) -# endif -#elif TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 -# include "../libbacktrace/backtrace.hpp" -# include -# include -# include -# include -#elif TRACY_HAS_CALLSTACK == 5 -# include -# include -#endif - -#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 - -#if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 5 || TRACY_HAS_CALLSTACK == 6 -// 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 ); - -#ifndef TRACY_DEMANGLE -constexpr size_t ___tracy_demangle_buffer_len = 1024*1024; -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 ); -} - -extern "C" const char* ___tracy_demangle( const char* mangled ) -{ - if( !mangled || mangled[0] != '_' ) return nullptr; - if( strlen( mangled ) > ___tracy_demangle_buffer_len ) return nullptr; - int status; - size_t len = ___tracy_demangle_buffer_len; - return abi::__cxa_demangle( mangled, ___tracy_demangle_buffer, &len, &status ); -} -#endif -#endif - -#if TRACY_HAS_CALLSTACK == 3 -# define TRACY_USE_IMAGE_CACHE -# include -#endif - -namespace tracy -{ - -#ifdef TRACY_USE_IMAGE_CACHE -// when we have access to dl_iterate_phdr(), we can build a cache of address ranges to image paths -// so we can quickly determine which image an address falls into. -// We refresh this cache only when we hit an address that doesn't fall into any known range. -class ImageCache -{ -public: - struct ImageEntry - { - void* m_startAddress = nullptr; - void* m_endAddress = nullptr; - char* m_name = nullptr; - }; - - ImageCache() - : m_images( 512 ) - { - Refresh(); - } - - ~ImageCache() - { - Clear(); - } - - const ImageEntry* GetImageForAddress( void* address ) - { - const ImageEntry* entry = GetImageForAddressImpl( address ); - if( !entry ) - { - Refresh(); - return GetImageForAddressImpl( address ); - } - return entry; - } - -private: - tracy::FastVector m_images; - bool m_updated = false; - bool m_haveMainImageName = false; - - static int Callback( struct dl_phdr_info* info, size_t size, void* data ) - { - ImageCache* cache = reinterpret_cast( data ); - - const auto startAddress = reinterpret_cast( info->dlpi_addr ); - if( cache->Contains( startAddress ) ) return 0; - - const uint32_t headerCount = info->dlpi_phnum; - assert( headerCount > 0); - const auto endAddress = reinterpret_cast( info->dlpi_addr + - info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz); - - ImageEntry* image = cache->m_images.push_next(); - image->m_startAddress = startAddress; - image->m_endAddress = endAddress; - - // 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 - if( info->dlpi_name && info->dlpi_name[0] != '\0' ) - { - size_t sz = strlen( info->dlpi_name ) + 1; - image->m_name = (char*)tracy_malloc( sz ); - memcpy( image->m_name, info->dlpi_name, sz ); - } - else - { - image->m_name = nullptr; - } - - cache->m_updated = true; - - return 0; - } - - bool Contains( void* startAddress ) const - { - return std::any_of( m_images.begin(), m_images.end(), [startAddress]( const ImageEntry& entry ) { return startAddress == entry.m_startAddress; } ); - } - - void Refresh() - { - m_updated = false; - dl_iterate_phdr( Callback, this ); - - if( m_updated ) - { - std::sort( m_images.begin(), m_images.end(), - []( const ImageEntry& lhs, const ImageEntry& rhs ) { return lhs.m_startAddress > rhs.m_startAddress; } ); - - // patch the main executable image name here, as calling dl_* functions inside the dl_iterate_phdr callback might cause deadlocks - UpdateMainImageName(); - } - } - - 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; - } - - const ImageEntry* GetImageForAddressImpl( void* address ) const - { - auto it = std::lower_bound( m_images.begin(), m_images.end(), address, - []( const ImageEntry& lhs, const void* rhs ) { return lhs.m_startAddress > rhs; } ); - - if( it != m_images.end() && address < it->m_endAddress ) - { - return it; - } - return nullptr; - } - - void Clear() - { - for( ImageEntry& entry : m_images ) - { - tracy_free( entry.m_name ); - } - - m_images.clear(); - m_haveMainImageName = false; - } -}; -#endif //#ifdef TRACY_USE_IMAGE_CACHE - -// when "TRACY_SYMBOL_OFFLINE_RESOLVE" is set, instead of fully resolving symbols at runtime, -// simply resolve the offset and image name (which will be enough the resolving to be done offline) -#ifdef TRACY_SYMBOL_OFFLINE_RESOLVE -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 }; -enum { MaxNameSize = 8*1024 }; - -int cb_num; -CallstackEntry cb_data[MaxCbTrace]; - -extern "C" -{ - 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; - - typedef unsigned long (__stdcall *___tracy_t_RtlWalkFrameChain)( void**, unsigned long, unsigned long ); - ___tracy_t_RtlWalkFrameChain ___tracy_RtlWalkFrameChainPtr = nullptr; - TRACY_API unsigned long ___tracy_RtlWalkFrameChain( void** callers, unsigned long count, unsigned long flags) - { - return ___tracy_RtlWalkFrameChainPtr(callers, count, flags); - } -} - -struct ModuleCache -{ - uint64_t start; - uint64_t end; - char* name; -}; - -static FastVector* s_modCache; - - -struct KernelDriver -{ - uint64_t addr; - const char* mod; - const char* path; -}; - -KernelDriver* s_krnlCache = nullptr; -size_t s_krnlCacheCnt; - -void InitCallstackCritical() -{ - ___tracy_RtlWalkFrameChainPtr = (___tracy_t_RtlWalkFrameChain)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "RtlWalkFrameChain" ); -} - -void DbgHelpInit() -{ - 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; - DBGHELP_LOCK; -#endif - - 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_DBGHELP_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 - // symbols during that time. - const char* noInitLoadEnv = GetEnvVar( "TRACY_NO_DBGHELP_INIT_LOAD" ); - const bool initTimeModuleLoad = !( noInitLoadEnv && noInitLoadEnv[0] == '1' ); - if ( !initTimeModuleLoad ) - { - TracyDebug("TRACY: skipping init time dbghelper module load\n"); - } - - DWORD needed; - LPVOID dev[4096]; - if( initTimeModuleLoad && EnumDeviceDrivers( dev, sizeof(dev), &needed ) != 0 ) - { - char windir[MAX_PATH]; - if( !GetWindowsDirectoryA( windir, sizeof( windir ) ) ) memcpy( windir, "c:\\windows", 11 ); - const auto windirlen = strlen( windir ); - - const auto sz = needed / sizeof( LPVOID ); - s_krnlCache = (KernelDriver*)tracy_malloc( sizeof(KernelDriver) * sz ); - int cnt = 0; - for( size_t i=0; i", 2 ); - s_krnlCache[cnt] = KernelDriver { (uint64_t)dev[i], buf }; - - 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; - } - - DbgHelpLoadSymbolsForModule( path, (DWORD64)dev[i], 0 ); - - 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; - } - - cnt++; - } - } - s_krnlCacheCnt = cnt; - std::sort( s_krnlCache, s_krnlCache + s_krnlCacheCnt, []( const KernelDriver& lhs, const KernelDriver& rhs ) { return lhs.addr > rhs.addr; } ); - } - - s_modCache = (FastVector*)tracy_malloc( sizeof( FastVector ) ); - new(s_modCache) FastVector( 512 ); - - HANDLE proc = GetCurrentProcess(); - HMODULE mod[1024]; - if( initTimeModuleLoad && EnumProcessModules( proc, mod, sizeof( mod ), &needed ) != 0 ) - { - const auto sz = needed / sizeof( HMODULE ); - for( size_t i=0; i 0 ) - { - // This may be a new module loaded since our call to SymInitialize. - // Just in case, force DbgHelp to load its pdb ! - LoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage ); - } - } - } - } - -#ifdef TRACY_DBGHELP_LOCK - DBGHELP_UNLOCK; -#endif -} - -void EndCallstack() -{ -} - -const char* DecodeCallstackPtrFast( uint64_t ptr ) -{ - if( s_shouldResolveSymbolsOffline ) return "[unresolved]"; - - static char ret[MaxNameSize]; - const auto proc = GetCurrentProcess(); - - char buf[sizeof( SYMBOL_INFO ) + MaxNameSize]; - auto si = (SYMBOL_INFO*)buf; - si->SizeOfStruct = sizeof( SYMBOL_INFO ); - si->MaxNameLen = MaxNameSize; - -#ifdef TRACY_DBGHELP_LOCK - DBGHELP_LOCK; -#endif - if( SymFromAddr( proc, ptr, nullptr, si ) == 0 ) - { - *ret = '\0'; - } - else - { - memcpy( ret, si->Name, si->NameLen ); - ret[si->NameLen] = '\0'; - } -#ifdef TRACY_DBGHELP_LOCK - DBGHELP_UNLOCK; -#endif - return ret; -} - -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; -} - -struct ModuleNameAndBaseAddress -{ - const char* name; - uint64_t baseAddr; -}; - -ModuleNameAndBaseAddress GetModuleNameAndPrepareSymbols( uint64_t addr ) -{ - 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 ) - { - return ModuleNameAndBaseAddress{ it->mod, it->addr }; - } - } - return ModuleNameAndBaseAddress{ "", addr }; - } - - for( auto& v : *s_modCache ) - { - if( addr >= v.start && addr < v.end ) - { - return ModuleNameAndBaseAddress{ v.name, v.start }; - } - } - - HMODULE mod[1024]; - DWORD needed; - HANDLE proc = GetCurrentProcess(); - - InitRpmalloc(); - if( EnumProcessModules( proc, mod, sizeof( mod ), &needed ) != 0 ) - { - const auto sz = needed / sizeof( HMODULE ); - for( size_t i=0; i= base && addr < base + info.SizeOfImage ) - { - char name[1024]; - 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) - ModuleCache* cachedModule = LoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage ); - return ModuleNameAndBaseAddress{ cachedModule->name, cachedModule->start }; - } - } - } - } - } - - 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); -#ifdef TRACY_DBGHELP_LOCK - DBGHELP_LOCK; -#endif - const auto res = SymGetLineFromAddr64( GetCurrentProcess(), ptr, &displacement, &line ); - if( res == 0 || line.LineNumber >= 0xF00000 ) - { - sym.file = "[unknown]"; - sym.line = 0; - sym.needFree = false; - } - else - { - sym.file = CopyString( line.FileName ); - sym.line = line.LineNumber; - sym.needFree = true; - } -#ifdef TRACY_DBGHELP_LOCK - DBGHELP_UNLOCK; -#endif - return sym; -} - -CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) -{ -#ifdef TRACY_DBGHELP_LOCK - DBGHELP_LOCK; -#endif - - 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; - DWORD ctx = 0; - DWORD inlineNum = 0; - 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 ); - } - if( doInline ) - { - write = inlineNum; - cb_num = 1 + inlineNum; - } - else -#endif - { - write = 0; - cb_num = 1; - } - - char buf[sizeof( SYMBOL_INFO ) + MaxNameSize]; - auto si = (SYMBOL_INFO*)buf; - si->SizeOfStruct = sizeof( SYMBOL_INFO ); - si->MaxNameLen = MaxNameSize; - - const auto symValid = SymFromAddr( proc, ptr, nullptr, si ) != 0; - - IMAGEHLP_LINE64 line; - DWORD displacement = 0; - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - - { - const char* filename; - const auto res = SymGetLineFromAddr64( proc, ptr, &displacement, &line ); - if( res == 0 || line.LineNumber >= 0xF00000 ) - { - filename = "[unknown]"; - cb_data[write].line = 0; - } - else - { - filename = line.FileName; - cb_data[write].line = line.LineNumber; - } - - cb_data[write].name = symValid ? CopyStringFast( si->Name, si->NameLen ) : CopyStringFast( moduleNameAndAddress.name ); - cb_data[write].file = CopyStringFast( filename ); - 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; - } - } - -#if !defined TRACY_NO_CALLSTACK_INLINES - if( doInline ) - { - for( DWORD i=0; iName, si->NameLen ) : CopyStringFast( moduleNameAndAddress.name ); - cb.file = CopyStringFast( filename ); - if( symInlineValid ) - { - cb.symLen = si->Size; - cb.symAddr = si->Address; - } - else - { - cb.symLen = 0; - cb.symAddr = 0; - } - - ctx++; - } - } -#endif -#ifdef TRACY_DBGHELP_LOCK - DBGHELP_UNLOCK; -#endif - - 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 = nullptr; - -int cb_num; -CallstackEntry cb_data[MaxCbTrace]; -int cb_fixup; -#ifdef TRACY_USE_IMAGE_CACHE -static ImageCache* s_imageCache = nullptr; -#endif //#ifdef TRACY_USE_IMAGE_CACHE - -#ifdef TRACY_DEBUGINFOD -debuginfod_client* s_debuginfod; - -struct DebugInfo -{ - uint8_t* buildid; - size_t buildid_size; - char* filename; - int fd; -}; - -static FastVector* s_di_known; -#endif - -#ifdef __linux -struct KernelSymbol -{ - uint64_t addr; - uint32_t size; - 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; - tracy::FastVector tmpSym( 512 * 1024 ); - size_t linelen = 16 * 1024; // linelen must be big enough to prevent reallocs in getline() - auto linebuf = (char*)tracy_malloc( linelen ); - ssize_t sz; - size_t validCnt = 0; - 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++; - const bool valid = *ptr == 'T' || *ptr == 't'; - 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; - } - - char* strname = nullptr; - char* strmod = nullptr; - - if( valid ) - { - validCnt++; - - 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'; - } - } - - auto sym = tmpSym.push_next(); - sym->addr = addr; - sym->size = 0; - sym->name = strname; - sym->mod = strmod; - } - tracy_free_fast( linebuf ); - fclose( f ); - if( tmpSym.empty() ) return; - - std::sort( tmpSym.begin(), tmpSym.end(), []( const KernelSymbol& lhs, const KernelSymbol& rhs ) { return lhs.addr < rhs.addr; } ); - for( size_t i=0; i 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; -} - -void InitCallstackCritical() -{ -} - -void InitCallstack() -{ - InitRpmalloc(); - -#ifdef TRACY_USE_IMAGE_CACHE - s_imageCache = (ImageCache*)tracy_malloc( sizeof( ImageCache ) ); - new(s_imageCache) ImageCache(); -#endif //#ifdef TRACY_USE_IMAGE_CACHE - -#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE - s_shouldResolveSymbolsOffline = ShouldResolveSymbolsOffline(); -#endif //#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE - 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 - -#ifdef __linux - InitKernelSymbols(); -#endif -#ifdef TRACY_DEBUGINFOD - s_debuginfod = debuginfod_begin(); - s_di_known = (FastVector*)tracy_malloc( sizeof( FastVector ) ); - new (s_di_known) FastVector( 16 ); -#endif -} - -#ifdef TRACY_DEBUGINFOD -void ClearDebugInfoVector( FastVector& vec ) -{ - for( auto& v : vec ) - { - tracy_free( v.buildid ); - tracy_free( v.filename ); - if( v.fd >= 0 ) close( v.fd ); - } - vec.clear(); -} - -DebugInfo* FindDebugInfo( FastVector& 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; -} - -int GetDebugInfoDescriptor( const char* buildid_data, size_t buildid_size, const char* filename ) -{ - auto buildid = (uint8_t*)buildid_data; - auto it = FindDebugInfo( *s_di_known, buildid, buildid_size ); - if( it ) return it->fd >= 0 ? dup( it->fd ) : -1; - - int fd = debuginfod_find_debuginfo( s_debuginfod, buildid, buildid_size, nullptr ); - it = s_di_known->push_next(); - it->buildid_size = buildid_size; - it->buildid = (uint8_t*)tracy_malloc( buildid_size ); - memcpy( it->buildid, buildid, buildid_size ); - const auto fnsz = strlen( filename ) + 1; - it->filename = (char*)tracy_malloc( fnsz ); - memcpy( it->filename, filename, fnsz ); - it->fd = fd >= 0 ? fd : -1; - TracyDebug( "DebugInfo descriptor query: %i, fn: %s\n", fd, filename ); - return it->fd; -} - -const uint8_t* GetBuildIdForImage( const char* image, size_t& size ) -{ - assert( image ); - for( auto& v : *s_di_known ) - { - if( strcmp( image, v.filename ) == 0 ) - { - size = v.buildid_size; - return v.buildid; - } - } - return nullptr; -} - -debuginfod_client* GetDebuginfodClient() -{ - return s_debuginfod; -} -#endif - -void EndCallstack() -{ -#ifdef TRACY_USE_IMAGE_CACHE - if( s_imageCache ) - { - s_imageCache->~ImageCache(); - tracy_free( s_imageCache ); - } -#endif //#ifdef TRACY_USE_IMAGE_CACHE -#ifndef TRACY_DEMANGLE - ___tracy_free_demangle_buffer(); -#endif -#ifdef TRACY_DEBUGINFOD - ClearDebugInfoVector( *s_di_known ); - s_di_known->~FastVector(); - tracy_free( s_di_known ); - - debuginfod_end( s_debuginfod ); -#endif -} - -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; -} - -static int SymbolAddressDataCb( void* data, uintptr_t pc, uintptr_t lowaddr, const char* fn, int lineno, const char* function ) -{ - auto& sym = *(CallstackSymbolData*)data; - if( !fn ) - { - sym.file = "[unknown]"; - sym.line = 0; - sym.needFree = false; - } - else - { - sym.file = NormalizePath( fn ); - if( !sym.file ) sym.file = CopyString( fn ); - sym.line = lineno; - sym.needFree = true; - } - - return 1; -} - -static void SymbolAddressErrorCb( void* data, const char* /*msg*/, int /*errnum*/ ) -{ - auto& sym = *(CallstackSymbolData*)data; - sym.file = "[unknown]"; - sym.line = 0; - sym.needFree = false; -} - -CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) -{ - CallstackSymbolData sym; - if( cb_bts ) - { - backtrace_pcinfo( cb_bts, ptr, SymbolAddressDataCb, SymbolAddressErrorCb, &sym ); - } - else - { - SymbolAddressErrorCb(&sym, nullptr, 0); - } - - return sym; -} - -static int CallstackDataCb( void* /*data*/, uintptr_t pc, uintptr_t lowaddr, const char* fn, int lineno, const char* function ) -{ - cb_data[cb_num].symLen = 0; - cb_data[cb_num].symAddr = (uint64_t)lowaddr; - - if( !fn && !function ) - { - const char* symname = nullptr; - auto vptr = (void*)pc; - ptrdiff_t symoff = 0; - - Dl_info dlinfo; - if( dladdr( vptr, &dlinfo ) ) - { - symname = dlinfo.dli_sname; - symoff = (char*)pc - (char*)dlinfo.dli_saddr; - const char* demangled = ___tracy_demangle( symname ); - if( demangled ) symname = demangled; - } - - if( !symname ) symname = "[unknown]"; - - if( symoff == 0 ) - { - const auto len = std::min( strlen( symname ), std::numeric_limits::max() ); - cb_data[cb_num].name = CopyStringFast( symname, len ); - } - else - { - char buf[32]; - const auto offlen = sprintf( buf, " + %td", symoff ); - const auto namelen = std::min( strlen( symname ), std::numeric_limits::max() - offlen ); - auto name = (char*)tracy_malloc_fast( namelen + offlen + 1 ); - memcpy( name, symname, namelen ); - memcpy( name + namelen, buf, offlen ); - name[namelen + offlen] = '\0'; - cb_data[cb_num].name = name; - } - - cb_data[cb_num].file = CopyStringFast( "[unknown]" ); - cb_data[cb_num].line = 0; - } - else - { - if( !fn ) fn = "[unknown]"; - if( !function ) - { - function = "[unknown]"; - } - else - { - const char* demangled = ___tracy_demangle( function ); - if( demangled ) function = demangled; - } - - const auto len = std::min( strlen( function ), std::numeric_limits::max() ); - cb_data[cb_num].name = CopyStringFast( function, len ); - cb_data[cb_num].file = NormalizePath( fn ); - if( !cb_data[cb_num].file ) cb_data[cb_num].file = CopyStringFast( fn ); - cb_data[cb_num].line = lineno; - } - - if( ++cb_num >= MaxCbTrace ) - { - return 1; - } - else - { - return 0; - } -} - -static void CallstackErrorCb( void* /*data*/, const char* /*msg*/, int /*errnum*/ ) -{ - for( int i=0; i> 63 == 0 ) - { - const char* imageName = nullptr; - uint64_t imageBaseAddress = 0x0; - -#ifdef TRACY_USE_IMAGE_CACHE - const auto* image = s_imageCache->GetImageForAddress((void*)ptr); - if( image ) - { - imageName = image->m_name; - imageBaseAddress = uint64_t(image->m_startAddress); - } -#else - Dl_info dlinfo; - if( dladdr( (void*)ptr, &dlinfo ) ) - { - imageName = dlinfo.dli_fname; - imageBaseAddress = uint64_t( dlinfo.dli_fbase ); - } -#endif - - if( s_shouldResolveSymbolsOffline ) - { - cb_num = 1; - GetSymbolForOfflineResolve( (void*)ptr, imageBaseAddress, 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 ), imageName ? imageName : "[unknown]" }; - } -#ifdef __linux - else if( s_kernelSym ) - { - 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; } ); - if( it != s_kernelSym + s_kernelSymCnt ) - { - cb_data[0].name = CopyStringFast( it->name ); - cb_data[0].file = CopyStringFast( "" ); - cb_data[0].line = 0; - cb_data[0].symLen = it->size; - cb_data[0].symAddr = it->addr; - return { cb_data, 1, it->mod ? it->mod : "" }; - } - } -#endif - - cb_data[0].name = CopyStringFast( "[unknown]" ); - cb_data[0].file = CopyStringFast( "" ); - cb_data[0].line = 0; - cb_data[0].symLen = 0; - cb_data[0].symAddr = 0; - return { cb_data, 1, "" }; -} - -#elif TRACY_HAS_CALLSTACK == 5 - -void InitCallstackCritical() -{ -} - -void InitCallstack() -{ - ___tracy_init_demangle_buffer(); -} - -void EndCallstack() -{ - ___tracy_free_demangle_buffer(); -} - -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; -} - -CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) -{ - const char* symloc = nullptr; - Dl_info dlinfo; - if( dladdr( (void*)ptr, &dlinfo ) ) symloc = dlinfo.dli_fname; - if( !symloc ) symloc = "[unknown]"; - return CallstackSymbolData { symloc, 0, false, 0 }; -} - -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; - void* symaddr = nullptr; - - Dl_info dlinfo; - if( dladdr( vptr, &dlinfo ) ) - { - symloc = dlinfo.dli_fname; - symname = dlinfo.dli_sname; - symoff = (char*)ptr - (char*)dlinfo.dli_saddr; - symaddr = dlinfo.dli_saddr; - const char* demangled = ___tracy_demangle( symname ); - if( demangled ) symname = demangled; - } - - if( !symname ) symname = "[unknown]"; - if( !symloc ) symloc = "[unknown]"; - - if( symoff == 0 ) - { - const auto len = std::min( strlen( symname ), std::numeric_limits::max() ); - cb.name = CopyString( symname, len ); - } - else - { - char buf[32]; - const auto offlen = sprintf( buf, " + %td", symoff ); - const auto namelen = std::min( strlen( symname ), std::numeric_limits::max() - offlen ); - auto name = (char*)tracy_malloc( namelen + offlen + 1 ); - memcpy( name, symname, namelen ); - memcpy( name + namelen, buf, offlen ); - name[namelen + offlen] = '\0'; - cb.name = name; - } - - cb.file = CopyString( "[unknown]" ); - cb.symLen = 0; - cb.symAddr = (uint64_t)symaddr; - - return { &cb, 1, symloc }; -} - -#endif - -} - -#endif diff --git a/public/client/TracyKCore.cpp b/public/client/TracyKCore.cpp index 09d51d117a..b4d06fbd53 100644 --- a/public/client/TracyKCore.cpp +++ b/public/client/TracyKCore.cpp @@ -6,7 +6,7 @@ #include #include -#include "TracyDebug.hpp" +#include "../common/TracyDebug.hpp" #include "TracyKCore.hpp" #include "../common/TracyAlloc.hpp" diff --git a/public/client/TracyKCore.hpp b/public/client/TracyKCore.hpp index 437e172c23..15999c6c35 100644 --- a/public/client/TracyKCore.hpp +++ b/public/client/TracyKCore.hpp @@ -5,7 +5,7 @@ #include -#include "TracyFastVector.hpp" +#include "../common/TracyFastVector.hpp" namespace tracy { diff --git a/public/client/TracyOverride.cpp b/public/client/TracyOverride.cpp index 591508a7ff..871cbe031c 100644 --- a/public/client/TracyOverride.cpp +++ b/public/client/TracyOverride.cpp @@ -1,6 +1,6 @@ #ifdef TRACY_ENABLE # ifdef __linux__ -# include "TracyDebug.hpp" +# include "../common/TracyDebug.hpp" # ifdef TRACY_VERBOSE # include # include diff --git a/public/client/TracyProfiler.cpp b/public/client/TracyProfiler.cpp index e1b9d50c91..81987885dc 100644 --- a/public/client/TracyProfiler.cpp +++ b/public/client/TracyProfiler.cpp @@ -74,8 +74,8 @@ #include "../common/TracyYield.hpp" #include "../common/tracy_lz4.hpp" #include "tracy_rpmalloc.hpp" -#include "TracyCallstack.hpp" -#include "TracyDebug.hpp" +#include "../common/TracyCallstack.hpp" +#include "../common/TracyDebug.hpp" #include "TracyDxt1.hpp" #include "TracyScoped.hpp" #include "TracyProfiler.hpp" @@ -1971,6 +1971,15 @@ void Profiler::Worker() LZ4_resetStream( (LZ4_stream_t*)m_stream ); m_sock->Send( &welcome, sizeof( welcome ) ); + uint32_t serverFlags = 0; + if( m_sock->ReadRaw( &serverFlags, sizeof( serverFlags ), 2000 ) ) + { + if( serverFlags & ServerFlags::PreventSymbolResolution ) + { + PreventSymbolResolution(); + } + } + m_threadCtx = 0; m_refTimeSerial = 0; m_refTimeCtx = 0; @@ -2013,6 +2022,8 @@ void Profiler::Worker() } m_deferredLock.unlock(); #endif + // Send all known modules information. + SendCachedModulesInformation(); // Main communications loop int keepAlive = 0; @@ -2715,6 +2726,13 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) ++item; continue; } + case QueueType::ImageUpdate: + { + void* ptr = (void*)item->imageEntry.payload; + SendSingleDataPacket( ptr, item->imageEntry.payloadSize ); + tracy_free( ptr ); + break; + } default: assert( false ); break; @@ -3305,6 +3323,27 @@ void Profiler::SendLongString( uint64_t str, const char* ptr, size_t len, QueueT AppendDataUnsafe( ptr, l32 ); } +void Profiler::SendSingleDataPacket( void* ptr, size_t totalSize ) +{ + assert( totalSize <= std::numeric_limits::max() ); + + static_assert(sizeof(QueueHeader) + sizeof(QueueDataPacket) == QueueDataSize[(int)QueueType::DataPacket], "Size mismatch"); + + + NeedDataSize(QueueDataSize[(int)QueueType::DataPacket] + totalSize); + + QueueItem item; + tracy::MemWrite( &item.hdr.type, (int)QueueType::DataPacket ); + + uint16_t dataSize = uint16_t(totalSize); + tracy::MemWrite( &item.packet.packetSize, dataSize ); + + AppendDataUnsafe( &item, QueueDataSize[(int)QueueType::DataPacket] ); + AppendDataUnsafe( ptr, dataSize ); + + +} + void Profiler::SendSourceLocation( uint64_t ptr ) { auto srcloc = (const SourceLocationData*)ptr; @@ -3468,13 +3507,113 @@ void Profiler::QueueSourceCodeQuery( uint32_t id ) } #ifdef TRACY_HAS_CALLSTACK + +static void WriteDebugFieldToPacket( uint8_t** ptr, const ImageDebugInfo& imageDebugInfo ) +{ + MemWrite( *ptr, imageDebugInfo.debugFormat ); + *ptr += sizeof( imageDebugInfo.debugFormat ); + + const uint32_t dataSize = static_cast( imageDebugInfo.debugDataSize ); + + MemWrite( *ptr, dataSize ); + *ptr += sizeof( dataSize ); + + memcpy( *ptr, imageDebugInfo.debugData, dataSize ); + *ptr += dataSize; +} + + +static void SerializeImageEntry( const ImageEntry& imageEntry, void** outptr, size_t* outsize ) +{ + static constexpr int EndOfString = 1; + + const uint32_t moduleNameLength = ( imageEntry.name ? strlen( imageEntry.name ) : 0 ) + EndOfString; + const uint32_t modulePathLength = ( imageEntry.path ? strlen( imageEntry.path ) : 0 ) + EndOfString; + + const size_t baseModuleInfo = sizeof( imageEntry.start ) + sizeof( imageEntry.end ) + + sizeof( moduleNameLength ) + moduleNameLength + + sizeof( modulePathLength ) + modulePathLength + + sizeof( imageEntry.imageDebugInfo.debugFormat ); + + const uint32_t debugFormatSize = sizeof( uint32_t ) + + imageEntry.imageDebugInfo.debugDataSize; + + const size_t bufferSize = baseModuleInfo + debugFormatSize; + void* queueBuffer = tracy_malloc( bufferSize ); + + uint8_t* ptr = (uint8_t*)queueBuffer; + + MemWrite( ptr, imageEntry.start ); + ptr += sizeof( imageEntry.start ); + + MemWrite( ptr, imageEntry.end ); + ptr += sizeof( imageEntry.end ); + + MemWrite( ptr, moduleNameLength ); + ptr += sizeof( moduleNameLength ); + + memcpy( ptr, imageEntry.name ? imageEntry.name : "", moduleNameLength ); + ptr += moduleNameLength; + + MemWrite( ptr, modulePathLength ); + ptr += sizeof( modulePathLength ); + + memcpy( ptr, imageEntry.path ? imageEntry.path : "", modulePathLength ); + ptr += modulePathLength; + + WriteDebugFieldToPacket( &ptr, imageEntry.imageDebugInfo ); + + *outptr = queueBuffer; + *outsize = bufferSize; +} +void Profiler::SendImageInfo( const ImageEntry& imageEntry ) +{ + void* serializePtr = nullptr; + size_t size = 0; + SerializeImageEntry( imageEntry, &serializePtr, &size ); + + // First send the Data + SendSingleDataPacket( serializePtr, size ); + tracy_free( serializePtr ); + + // Then send that it received an image Update which will use the previous Data + QueueItem item; + tracy::MemWrite( &item.hdr.type, (int)QueueType::ImageUpdate ); + NeedDataSize( QueueDataSize[(int)QueueType::ImageUpdate] ); + + AppendData( &item, QueueDataSize[(int)QueueType::ImageUpdate] ); + +} + void Profiler::HandleSymbolQueueItem( const SymbolQueueItem& si ) { switch( si.type ) { case SymbolQueueItemType::CallstackFrame: { - const auto frameData = DecodeCallstackPtr( si.ptr ); + DecodeCallStackPtrStatus mask = DecodeCallStackPtrStatusFlags::SymbolMissing; + const auto frameData = DecodeCallstackPtr( si.ptr, &mask ); + + + if ( mask & DecodeCallStackPtrStatusFlags::NewModuleFound ) + { + + std::lock_guard mutexguard{ GetModuleCacheMutexForRead() }; + + const ImageEntry* entry = GetImageEntryFromPtr(si.ptr); + assert( entry != nullptr ); + + void* imageData = nullptr; + size_t dataSize = 0; + SerializeImageEntry( *entry, &imageData, &dataSize ); + + TracyLfqPrepare( QueueType::ImageUpdate ); + tracy::MemWrite( &item->imageEntry.payload, (uint64_t)imageData ); + tracy::MemWrite( &item->imageEntry.payloadSize, (uint64_t)dataSize ); + TracyLfqCommit; + } + + auto data = tracy_malloc_fast( sizeof( CallstackEntry ) * frameData.size ); memcpy( data, frameData.data, sizeof( CallstackEntry ) * frameData.size ); TracyLfqPrepare( QueueType::CallstackFrameSize ); @@ -4061,7 +4200,34 @@ void Profiler::ReportTopology() } tracy_free( cpuData ); -#endif +# endif +# endif +} + +void Profiler::SendCachedModulesInformation() +{ +#ifdef TRACY_HAS_CALLSTACK + // We are retrieving modules information from the Tracy Profiler thread + // But the Symbol Worker has ownership. Make sure it is not writing to the cache while we read it. + std::lock_guard mutexguard{ GetModuleCacheMutexForRead() }; + + const FastVector* kernelDrivers = GetKernelImageInfos(); + if( kernelDrivers ) + { + for ( auto& it : *kernelDrivers ) + { + SendImageInfo( it ); + } + } + + const FastVector* moduleCache = GetUserImageInfos(); + if( moduleCache ) + { + for ( auto& it : *moduleCache ) + { + SendImageInfo( it ); + } + } #endif } diff --git a/public/client/TracyProfiler.hpp b/public/client/TracyProfiler.hpp index e773f5ec0c..3a4fca9532 100644 --- a/public/client/TracyProfiler.hpp +++ b/public/client/TracyProfiler.hpp @@ -9,11 +9,11 @@ #include "tracy_concurrentqueue.h" #include "tracy_SPSCQueue.h" -#include "TracyCallstack.hpp" #include "TracyKCore.hpp" #include "TracySysPower.hpp" #include "TracySysTime.hpp" -#include "TracyFastVector.hpp" +#include "../common/TracyCallstack.hpp" +#include "../common/TracyFastVector.hpp" #include "../common/TracyQueue.hpp" #include "../common/TracyAlign.hpp" #include "../common/TracyAlloc.hpp" @@ -777,6 +777,7 @@ class Profiler void SendSecondString( const char* ptr ) { SendSecondString( ptr, strlen( ptr ) ); } void SendSecondString( const char* ptr, size_t len ); + void SendSingleDataPacket( void* ptr, size_t totalSize ); // Allocated source location data layout: // 2b payload size @@ -918,6 +919,12 @@ class Profiler void CalibrateDelay(); void ReportTopology(); + void SendCachedModulesInformation(); + +#ifdef TRACY_HAS_CALLSTACK + void SendImageInfo( const ImageEntry& moduleCacheEntry); +#endif + static tracy_force_inline void SendCallstackSerial( void* ptr ) { if( has_callstack() ) diff --git a/public/client/TracyRingBuffer.hpp b/public/client/TracyRingBuffer.hpp index e9100e2d8b..99bb06c3ef 100644 --- a/public/client/TracyRingBuffer.hpp +++ b/public/client/TracyRingBuffer.hpp @@ -8,7 +8,7 @@ #include #include -#include "TracyDebug.hpp" +#include "../common/TracyDebug.hpp" namespace tracy { diff --git a/public/client/TracyScoped.hpp b/public/client/TracyScoped.hpp index 7f9256d8c3..9f63bcef55 100644 --- a/public/client/TracyScoped.hpp +++ b/public/client/TracyScoped.hpp @@ -10,7 +10,7 @@ #include "../common/TracyAlign.hpp" #include "../common/TracyAlloc.hpp" #include "TracyProfiler.hpp" -#include "TracyCallstack.hpp" +#include "../common/TracyCallstack.hpp" namespace tracy { diff --git a/public/client/TracySysPower.cpp b/public/client/TracySysPower.cpp index 6ad1d64783..67749fcc5a 100644 --- a/public/client/TracySysPower.cpp +++ b/public/client/TracySysPower.cpp @@ -9,7 +9,7 @@ #include #include -#include "TracyDebug.hpp" +#include "../common/TracyDebug.hpp" #include "TracyProfiler.hpp" #include "../common/TracyAlloc.hpp" diff --git a/public/client/TracySysPower.hpp b/public/client/TracySysPower.hpp index 210123bce4..01db32724c 100644 --- a/public/client/TracySysPower.hpp +++ b/public/client/TracySysPower.hpp @@ -10,7 +10,7 @@ #include #include -#include "TracyFastVector.hpp" +#include "../common/TracyFastVector.hpp" namespace tracy { diff --git a/public/client/TracySysTrace.cpp b/public/client/TracySysTrace.cpp index 8e7f6139b6..3d341a110a 100644 --- a/public/client/TracySysTrace.cpp +++ b/public/client/TracySysTrace.cpp @@ -1,5 +1,5 @@ -#include "TracyDebug.hpp" -#include "TracyStringHelpers.hpp" +#include "../common/TracyDebug.hpp" +#include "../common/TracyStringHelpers.hpp" #include "TracySysTrace.hpp" #include "../common/TracySystem.hpp" diff --git a/public/common/TracyAlign.hpp b/public/common/TracyAlign.hpp index c3531ba0dd..6422f941a8 100644 --- a/public/common/TracyAlign.hpp +++ b/public/common/TracyAlign.hpp @@ -22,6 +22,12 @@ tracy_force_inline void MemWrite( void* ptr, T val ) memcpy( ptr, &val, sizeof( T ) ); } +template +inline T Align( T val, size_t align ) +{ + return (((val) + (T)(align) - 1) & ~(T)((align) - 1)); +} + } #endif diff --git a/public/common/TracyCallstack.cpp b/public/common/TracyCallstack.cpp new file mode 100644 index 0000000000..ab4bfae6f3 --- /dev/null +++ b/public/common/TracyCallstack.cpp @@ -0,0 +1,1966 @@ +#include +#include +#include +#include +#include // for memcpy +#include +#include +#include + +#include "TracyCallstack.hpp" +#include "TracyAlign.hpp" +#include "TracyDebug.hpp" +#include "TracyStringHelpers.hpp" +#include "TracyAlloc.hpp" +#include "TracySystem.hpp" +#include "TracyDebugModulesHeaderFile.hpp" + +#ifdef TRACY_HAS_CALLSTACK + +#if TRACY_HAS_CALLSTACK == 1 +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +# include +# include +# ifdef _MSC_VER +# pragma warning( push ) +# pragma warning( disable : 4091 ) +# endif +# include +# pragma comment( lib, "dbghelp.lib" ) +# ifdef _MSC_VER +# pragma warning( pop ) +# endif +#elif defined(TRACY_USE_LIBBACKTRACE) + +# include "../libbacktrace/backtrace.hpp" +# include +# include +# include +# include + +# include "../libbacktrace/alloc.cpp" +# include "../libbacktrace/dwarf.cpp" +# include "../libbacktrace/fileline.cpp" +# include "../libbacktrace/mmapio.cpp" +# include "../libbacktrace/posix.cpp" +# include "../libbacktrace/sort.cpp" +# include "../libbacktrace/state.cpp" +# if TRACY_HAS_CALLSTACK == 4 +# include "../libbacktrace/macho.cpp" +# else +# include "../libbacktrace/elf.cpp" +# endif +# include "TracyStackFrames.cpp" + +#elif TRACY_HAS_CALLSTACK == 5 +# include +# include +#endif + +#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 + +#if defined(TRACY_USE_LIBBACKTRACE) || TRACY_HAS_CALLSTACK == 5 +// 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 ); + +#ifndef TRACY_DEMANGLE +constexpr size_t ___tracy_demangle_buffer_len = 1024*1024; +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 ); +} + +extern "C" const char* ___tracy_demangle( const char* mangled ) +{ + if( !mangled || mangled[0] != '_' ) return nullptr; + if( strlen( mangled ) > ___tracy_demangle_buffer_len ) return nullptr; + int status; + size_t len = ___tracy_demangle_buffer_len; + return abi::__cxa_demangle( mangled, ___tracy_demangle_buffer, &len, &status ); +} +#endif +#endif + +#if defined(TRACY_USE_LIBBACKTRACE) && TRACY_HAS_CALLSTACK != 4 // dl_iterate_phdr is required for the current image cache. Need to move it to libbacktrace? +# define TRACY_USE_IMAGE_CACHE +# include +struct BuildIdNote { + ElfW(Nhdr) nhdr; + + char name[4]; + uint8_t build_id[0]; +}; +#endif + +namespace tracy +{ + + inline bool GetEnvBool( const char* environementVariableName, bool defaultValue ) + { + const char* v = GetEnvVar( environementVariableName ); + if (v) return v[0] == '1'; + else return defaultValue; + } + + // When "TRACY_SYMBOL_OFFLINE_RESOLVE" is set, symbols are not fully resolved at runtime. + // Instead, only the offset and image name are recorded, which is sufficient for offline resolution. + // If the server flag ServerFlags::PreventSymbolResolution is true, symbol resolution by the application is prevented, + // and resolution is handled exclusively by the server. +#ifdef TRACY_SYMBOL_OFFLINE_RESOLVE + static bool s_shouldResolveSymbolsOffline = true; +#else + static bool s_shouldResolveSymbolsOffline = false; +#endif // TRACY_SYMBOL_OFFLINE_RESOLVE + + void PreventSymbolResolution() { s_shouldResolveSymbolsOffline = true; } + + inline bool IsKernelAddress( uint64_t addr ) { + return (addr >> 63) != 0; + } + + void DestroyImageEntry( ImageEntry& entry ) + { + tracy_free_fast( entry.path ); + entry.path = nullptr; + tracy_free_fast( entry.name ); + entry.name = nullptr; + tracy_free( entry.imageDebugInfo.debugData ); + entry.imageDebugInfo.debugData = nullptr; + } + + class ImageCache + { + public: + ImageCache( size_t moduleCacheCapacity = 512 ) : m_modCache( moduleCacheCapacity ) + { + for (auto& it : m_modCache) + { + it.start = 0; + it.end = 0; + it.name = nullptr; + it.path = nullptr; + + it.imageDebugInfo.debugData = nullptr; + it.imageDebugInfo.debugDataSize = 0; + it.imageDebugInfo.debugFormat = ImageDebugFormatId::NoDebugFormat; + } + } + ~ImageCache() { Clear(); } + + ImageEntry* AddEntry( const ImageEntry& entry ) + { + m_sorted &= m_modCache.empty() ? true : (entry.start < m_modCache.back().start); + ImageEntry* newEntry = m_modCache.push_next(); + *newEntry = entry; + return newEntry; + } + + const ImageEntry* FindEntryFromAddr( uint64_t addr ) const + { + if( m_sorted ) + { + auto it = std::lower_bound( m_modCache.begin(), m_modCache.end(), addr, []( const ImageEntry& lhs, const uint64_t& rhs ) { return lhs.start > rhs; } ); + if( it != m_modCache.end() && ( addr < it->end || it->end == 0 ) ) + return &(*it); + } + else + { + auto it = std::find_if( m_modCache.begin(), m_modCache.end(), [addr]( const ImageEntry& module ) { return addr >= module.start && ( addr < module.end || module.end == 0 ); } ); + if( it != m_modCache.end() ) + return &(*it); + } + return nullptr; + } + + void Sort() + { + if( !m_sorted ) + { + std::sort( m_modCache.begin(), m_modCache.end(), []( const ImageEntry& lhs, const ImageEntry& rhs ) { return lhs.start > rhs.start; } ); + m_sorted = true; + } + } + + void Clear() + { + for( ImageEntry& cacheEntry : m_modCache ) + { + DestroyImageEntry( cacheEntry ); + } + m_modCache.clear(); + m_sorted = true; + } + + bool ContainsModule( uint64_t startAddress ) + { + const ImageEntry* moduleInfo = FindEntryFromAddr( startAddress ); + return moduleInfo && moduleInfo->start == startAddress; + } + + const FastVector& GetModuleData() const + { + return m_modCache; + } + protected: + FastVector m_modCache; + bool m_sorted = true; + }; + + + // The only threads that access the cache are the Symbol Worker and the Tracy Thread + // Only the symbol worker may write, but the Tracy Thread needs to be able to read the cache safely + // Since only the SymbolWorker is allowed to write the cache, it does not need to lock when reading. + static std::recursive_mutex s_cacheMutex; + std::recursive_mutex& GetModuleCacheMutexForRead() { + return s_cacheMutex; + } + +#if defined(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 ImageCacheLibbacktrace : public ImageCache +{ +public: + ImageCacheLibbacktrace() + : ImageCache() + { + Refresh(); + } + + ~ImageCacheLibbacktrace() + { + m_haveMainImageName = false; + } + + const ImageEntry* GetImageForAddress( uint64_t address ) + { + const ImageEntry* entry = FindEntryFromAddr( address ); + + if( !entry ) + { + Refresh(); + return FindEntryFromAddr( address ); + } + return entry; + } + +private: + bool m_updated = false; + bool m_haveMainImageName = false; + + // success return 0 + // failed return 1 + static int GetBuildIdNoteFromNote( ImageEntry* imageEntry, BuildIdNote* note, ptrdiff_t fileLen) + { + auto& nhdr = note->nhdr; + + while( fileLen >= sizeof(BuildIdNote) ) + { + if (nhdr.n_type == NT_GNU_BUILD_ID && + nhdr.n_descsz != 0 && + nhdr.n_namesz == 4 && + memcmp(note->name, "GNU", 4) == 0) + { + imageEntry->imageDebugInfo.debugFormat = ImageDebugFormatId::ElfDebugFormat; + imageEntry->imageDebugInfo.debugDataSize = nhdr.n_descsz; + imageEntry->imageDebugInfo.debugData = (uint8_t*)tracy_malloc(imageEntry->imageDebugInfo.debugDataSize); + memcpy(imageEntry->imageDebugInfo.debugData, ¬e->build_id[0], imageEntry->imageDebugInfo.debugDataSize); + + return 0; + } + + const size_t offset = sizeof(ElfW(Nhdr)) + + tracy::Align(note->nhdr.n_namesz, 4) + + tracy::Align(note->nhdr.n_descsz, 4); + + note = reinterpret_cast((char *)note + offset); + fileLen -= offset; + } + + return 1; + } + + static int Callback( struct dl_phdr_info* info, size_t size, void* data ) + { + ImageCacheLibbacktrace* cache = reinterpret_cast( data ); + + const uint64_t startAddress = reinterpret_cast( info->dlpi_addr ); + if( cache->ContainsModule( startAddress ) ) return 0; + + const uint32_t headerCount = info->dlpi_phnum; + assert( headerCount > 0); + const uint64_t endAddress = reinterpret_cast( info->dlpi_addr + + info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz); + + ImageEntry image{}; + image.start = startAddress; + image.end = endAddress; + + // 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 + image.name = info->dlpi_name && info->dlpi_name[0] != '\0' ? CopyStringFast(info->dlpi_name) : nullptr; + + + for (unsigned i = 0; i < info->dlpi_phnum; i++) + { + if (info->dlpi_phdr[i].p_type != PT_NOTE) + continue; + + void* raw = (void*)(info->dlpi_addr + + info->dlpi_phdr[i].p_vaddr); + + ptrdiff_t len = info->dlpi_phdr[i].p_filesz; + + if (GetBuildIdNoteFromNote(&image, static_cast(raw), len) == 0) + break; + + + } + + cache->AddEntry(image); + cache->m_updated = true; + + return 0; + } + + + + void Refresh() + { + m_updated = false; + dl_iterate_phdr( Callback, this ); + + if( m_updated ) + { + Sort(); + // patch the main executable image name here, as calling dl_* functions inside the dl_iterate_phdr callback might cause deadlocks + UpdateMainImageName(); + } + } + + void UpdateMainImageName() + { + if( m_haveMainImageName ) + { + return; + } + + for( ImageEntry& entry : m_modCache ) + { + if( entry.name == nullptr ) + { + Dl_info dlInfo; + if( dladdr( (void *)entry.start, &dlInfo ) ) + { + if( dlInfo.dli_fname ) + { + entry.name = CopyStringFast(dlInfo.dli_fname); + } + } + + // we only expect one entry to be null for the main executable entry + break; + } + } + + m_haveMainImageName = true; + } + + void Clear() + { + ImageCache::Clear(); + m_haveMainImageName = false; + } +}; +#endif // defined(TRACY_USE_IMAGE_CACHE) + + +#ifdef TRACY_USE_IMAGE_CACHE +typedef ImageCacheLibbacktrace UserlandImageCache; +#else +typedef ImageCache UserlandImageCache; +#endif //#ifdef TRACY_USE_IMAGE_CACHE + +static UserlandImageCache* s_imageCache = nullptr; +static ImageCache* s_krnlCache = nullptr; + +#ifdef __linux +static ImageCache* s_krnlSymbolsCache = nullptr; +#endif + +const ImageEntry* GetImageEntryFromPtr(uint64_t ptr) +{ + if(IsKernelAddress(ptr)) + { + return s_krnlCache->FindEntryFromAddr(ptr); + } + else return s_imageCache->FindEntryFromAddr(ptr); +} + +void CreateImageCaches() +{ + assert( s_imageCache == nullptr && s_krnlCache == nullptr ); + s_imageCache = new ( tracy_malloc( sizeof( UserlandImageCache ) ) ) UserlandImageCache(); + s_krnlCache = new ( tracy_malloc( sizeof( ImageCache ) ) ) ImageCache(); + + +} + +void DestroyImageCaches() +{ + if ( s_krnlCache != nullptr ) + { + s_krnlCache->~ImageCache(); + tracy_free( (void*)s_krnlCache ); + s_krnlCache = nullptr; + } + + if ( s_imageCache != nullptr ) + { + s_imageCache->~UserlandImageCache(); + tracy_free( s_imageCache ); + s_imageCache = nullptr; + } + +} + +const FastVector* GetUserImageInfos() +{ + return &s_imageCache->GetModuleData(); +} + +const FastVector* GetKernelImageInfos() +{ + return &s_krnlCache->GetModuleData(); +} + +char* FormatImageName(const char* imageName, uint32_t imageNameLength) +{ + const char* ptr = imageName + imageNameLength; + while( ptr > imageName && *ptr != '\\' && *ptr != '/' ) ptr--; + if( ptr > imageName ) ptr++; + const auto namelen = imageName + imageNameLength - ptr; + + char* alloc = (char*)tracy_malloc_fast( namelen + 3 ); + alloc[0] = '['; + memcpy( alloc + 1, ptr, namelen ); + alloc[namelen + 1] = ']'; + alloc[namelen + 2] = '\0'; + + return alloc; +} + +#if TRACY_HAS_CALLSTACK == 1 + +enum { MaxCbTrace = 64 }; +enum { MaxNameSize = 8*1024 }; + +int cb_num; +CallstackEntry cb_data[MaxCbTrace]; + +HANDLE s_DbgHelpSymHandle = 0; + +extern "C" +{ + 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; + + typedef unsigned long (__stdcall *___tracy_t_RtlWalkFrameChain)( void**, unsigned long, unsigned long ); + + ___tracy_t_RtlWalkFrameChain ___tracy_RtlWalkFrameChainPtr = nullptr; + + TRACY_API unsigned long ___tracy_RtlWalkFrameChain( void** callers, unsigned long count, unsigned long flags) + { + return ___tracy_RtlWalkFrameChainPtr(callers, count, flags); + } +} + +struct CV_INFO_PDB70 +{ + DWORD CvSignature; + GUID Signature; + DWORD Age; + BYTE PdbFileName[1]; +}; + +static constexpr DWORD CV_SIGNATURE_RSDS = 'SDSR'; // 'SDSR' + +bool GetModuleInfoFromPEHeaders( uint64_t baseOfDll, ImageDebugFormatId* debugFormat, uint8_t** debugInformationData, uint32_t* debugInformationSize ) +{ + // always true for loaded executables + static constexpr bool MappedAsImage = true; + + PVOID BaseAddress = (void*)baseOfDll; + + PIMAGE_NT_HEADERS header = ImageNtHeader( BaseAddress ); + + ULONG debugDirectoryCount = 0; + IMAGE_SECTION_HEADER* debugSectionHeader; + + PVOID debugSectionData = ImageDirectoryEntryToDataEx( BaseAddress, true, IMAGE_DIRECTORY_ENTRY_DEBUG, + &debugDirectoryCount, &debugSectionHeader ); + + if( debugSectionData == NULL ) + { + return false; + } + IMAGE_DEBUG_DIRECTORY* debugDirectory = static_cast( debugSectionData ); + + for( size_t i = 0; ( i * sizeof( IMAGE_DEBUG_DIRECTORY ) ) < debugDirectoryCount; i++ ) + { + + const IMAGE_DEBUG_DIRECTORY& curDebugDirectory = debugDirectory[i]; + + if( debugDirectory[i].Type != IMAGE_DEBUG_TYPE_CODEVIEW ) continue; + + CV_INFO_PDB70* pData = (CV_INFO_PDB70*)( uintptr_t( BaseAddress ) + + ( MappedAsImage + ? debugDirectory[i].AddressOfRawData + : debugDirectory[i].PointerToRawData) + ); + + if( pData->CvSignature != CV_SIGNATURE_RSDS ) continue; + + *debugFormat = ImageDebugFormatId::PdbDebugFormat; + + const size_t pdbFileLength = strlen( (const char*)pData->PdbFileName ); + const uint32_t debugFormatSize = sizeof( PEImageDebugData ) + pdbFileLength + 1; + *debugInformationData = (uint8_t*)tracy_malloc( debugFormatSize ); + *debugInformationSize = debugFormatSize; + + uint8_t* ptrToDebugPacket = reinterpret_cast( *debugInformationData ); + PEImageDebugData* ptrToWindowsDebugData = reinterpret_cast( ptrToDebugPacket ); + + // write minor major version + ptrToWindowsDebugData->majorVersion = curDebugDirectory.MajorVersion; + ptrToWindowsDebugData->minorVersion = curDebugDirectory.MinorVersion; + + ptrToWindowsDebugData->exeDataTimeStamp = curDebugDirectory.TimeDateStamp; + ptrToWindowsDebugData->cvInfo.Age = pData->Age; + ptrToWindowsDebugData->cvInfo.CvSignature = pData->CvSignature; + static_assert( sizeof( GUID ) == sizeof( pData->Signature ), "GUID size must match" ); + memcpy( &ptrToWindowsDebugData->cvInfo.Signature, &pData->Signature, sizeof( pData->Signature ) ); + memcpy( ptrToDebugPacket + sizeof( PEImageDebugData ), pData->PdbFileName, pdbFileLength + 1 ); + + return true; + } + return false; +} + +void GetModuleInfoFromDbgHelp( const char* imageName, ImageEntry* moduleEntry ) +{ + IMAGEHLP_MODULE64 moduleInfo{}; + moduleInfo.SizeOfStruct = sizeof( IMAGEHLP_MODULE64 ); + if( TRUE == SymGetModuleInfo64( s_DbgHelpSymHandle, moduleEntry->start, &moduleInfo ) ) + { + if( moduleInfo.SymType == SymDeferred ) // If symbol loading was deferred, force load it so that we can retrieve the debug informations + { + DWORD prevOptions = SymGetOptions(); + SymSetOptions( prevOptions & (~SYMOPT_DEFERRED_LOADS) ); + DWORD64 loadedAddr = SymLoadModuleEx( s_DbgHelpSymHandle, nullptr, imageName, nullptr, moduleEntry->start, moduleEntry->end ? (moduleEntry->end - moduleEntry->start) : 0, nullptr, 0 ); + SymSetOptions( prevOptions ); + if( !SymGetModuleInfo64( s_DbgHelpSymHandle, moduleEntry->start, &moduleInfo ) ) + { + return; + } + } + + // We now know the size of the module, which is needed for proper offline symbol resolution + // Otherwise DbgHelp may assume the module is of size 4096 (1page), leading to unresolved symbols. + if( moduleEntry->end == 0 && moduleInfo.ImageSize ) + { + moduleEntry->end = moduleEntry->start + moduleInfo.ImageSize; + } + + if( moduleInfo.CVSig != CV_SIGNATURE_RSDS ) // Do we have a pdb ? + return; + + ImageDebugInfo& debugInfo = moduleEntry->imageDebugInfo; + debugInfo.debugFormat = ImageDebugFormatId::PdbDebugFormat; + + const uint32_t pdbFileNameLen = static_cast( strlen( moduleInfo.CVData ) ); + debugInfo.debugDataSize = sizeof( PEImageDebugData ) + ( pdbFileNameLen + 1 ); + debugInfo.debugData = (uint8_t*)tracy_malloc( debugInfo.debugDataSize ); + PEImageDebugData* ptrToWindowsDebugData = reinterpret_cast( debugInfo.debugData ); + ptrToWindowsDebugData->majorVersion = 0; + ptrToWindowsDebugData->minorVersion = 0; + ptrToWindowsDebugData->exeDataTimeStamp = moduleInfo.TimeDateStamp; + + PdbInfo* pdbInfo = &ptrToWindowsDebugData->cvInfo; + + pdbInfo->Age = moduleInfo.PdbAge; + pdbInfo->CvSignature = moduleInfo.CVSig; + + static_assert( sizeof( pdbInfo->Signature ) == sizeof( moduleInfo.PdbSig70 ), "GUID size must match" ); + memcpy( &pdbInfo->Signature, &moduleInfo.PdbSig70, sizeof( moduleInfo.PdbSig70 ) ); + + char* pdbFileName = (char*)debugInfo.debugData + sizeof( PEImageDebugData ); + memcpy( pdbFileName, moduleInfo.CVData, pdbFileNameLen + 1 ); + } +} + +ImageEntry* CacheModuleInfo( const char* imagePath, uint32_t imageNameLength, uint64_t baseOfDll, uint32_t dllSize ) +{ + ImageEntry moduleEntry = {}; + moduleEntry.start = baseOfDll; + moduleEntry.end = baseOfDll + dllSize; + moduleEntry.path = CopyStringFast( imagePath, imageNameLength ); + moduleEntry.name = FormatImageName( imagePath, imageNameLength ); + + ImageDebugFormatId debugFormat = ImageDebugFormatId::NoDebugFormat; + uint8_t* debugData = nullptr; + uint32_t debugDataSize = 0; + + if( GetModuleInfoFromPEHeaders( moduleEntry.start, &debugFormat, &debugData, &debugDataSize ) ) + { + ImageDebugInfo& dmf = moduleEntry.imageDebugInfo; + + dmf.debugFormat = debugFormat; + dmf.debugData = debugData; + dmf.debugDataSize = debugDataSize; + } + + std::lock_guard mutexguard{ s_cacheMutex }; + return s_imageCache->AddEntry( moduleEntry ); +} + +ImageEntry* LoadSymbolsForModuleAndCache( const char* imagePath, uint32_t imageNameLength, uint64_t baseOfDll, uint32_t dllSize ) +{ + assert( s_DbgHelpSymHandle == GetCurrentProcess() ); // Only resolve with path if s_DbgHelpSymHandle is current process + SymLoadModuleEx( s_DbgHelpSymHandle, nullptr, imagePath, nullptr, baseOfDll, dllSize, nullptr, 0 ); + + return CacheModuleInfo( imagePath, imageNameLength, baseOfDll, dllSize ); +} + +struct ModuleNameAndBaseAddress +{ + const char* name; + uint64_t baseAddr; +}; + +ModuleNameAndBaseAddress TryToLoadModuleDebugInfoAndCache( uint64_t address ) +{ + InitRpmalloc(); + HMODULE mod[1024]; + DWORD needed; + HANDLE proc = GetCurrentProcess(); + + if( EnumProcessModules( proc, mod, sizeof( mod ), &needed ) != 0 ) + { + const auto sz = needed / sizeof(HMODULE); + for(size_t i=0; i= base && address < base + info.SizeOfImage ) + { + char name[1024]; + const auto nameLength = GetModuleFileNameA( mod[i], name, 1024 ); + if ( nameLength > 0 ) + { + // since this is the first time we encounter this module, load its symbols (needed for modules loaded after SymInitialize) + ImageEntry* ImageEntry = LoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage ); + return ModuleNameAndBaseAddress{ ImageEntry->name, ImageEntry->start }; + } + } + } + } + } + return { nullptr, 0 }; +} + +ModuleNameAndBaseAddress GetModuleNameAndPrepareSymbols( uint64_t addr, bool* failed ) +{ + if ( IsKernelAddress( addr ) ) + { + if ( s_krnlCache ) + { + const ImageEntry* entry = s_krnlCache->FindEntryFromAddr( addr ); + if ( entry ) + { + return ModuleNameAndBaseAddress{ entry->name, entry->start }; + } + } + return ModuleNameAndBaseAddress{ "", addr }; + } + + const ImageEntry* entry = s_imageCache->FindEntryFromAddr( addr ); + + if ( entry != nullptr ) + return ModuleNameAndBaseAddress{ entry->name, entry->start }; + + + *failed = true; + return ModuleNameAndBaseAddress{ "[unknown]", addr }; +} + + + + +void InitCallstackCritical() + +{ + ___tracy_RtlWalkFrameChainPtr = (___tracy_t_RtlWalkFrameChain)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "RtlWalkFrameChain" ); +} + +void DbgHelpInit( HANDLE symHandle, bool invadeProcess ) +{ + assert(s_DbgHelpSymHandle == 0); + s_DbgHelpSymHandle = symHandle; + + HMODULE DbgHelpHdl = GetModuleHandleA( "dbghelp.dll" ); + if ( !DbgHelpHdl ) { + TracyDebug("Couldn't load DbgHelp.dll\n"); + return; + } + + _SymAddrIncludeInlineTrace = (t_SymAddrIncludeInlineTrace)GetProcAddress( DbgHelpHdl, "SymAddrIncludeInlineTrace" ); + _SymQueryInlineTrace = (t_SymQueryInlineTrace)GetProcAddress( DbgHelpHdl, "SymQueryInlineTrace" ); + _SymFromInlineContext = (t_SymFromInlineContext)GetProcAddress( DbgHelpHdl, "SymFromInlineContext" ); + _SymGetLineFromInlineContext = (t_SymGetLineFromInlineContext)GetProcAddress( DbgHelpHdl, "SymGetLineFromInlineContext" ); + +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_INIT; + DBGHELP_LOCK; +#endif + + SymSetOptions( + SYMOPT_LOAD_LINES + | SYMOPT_DEFERRED_LOADS +#ifndef NDEBUG + | SYMOPT_DEBUG +#endif + ); + + if ( !SymInitialize( symHandle, nullptr, false ) ) + { + TracyDebug( "Failed to initalize DbgHelp %x\n", GetLastError() ); + } + +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif +} + + +static void CacheProcessDrivers() +{ + DWORD needed; + LPVOID dev[4096]; + if( EnumDeviceDrivers( dev, sizeof(dev), &needed ) != 0 ) + { + char windir[MAX_PATH]; + if( !GetWindowsDirectoryA( windir, sizeof( windir ) ) ) memcpy( windir, "c:\\windows", 11 ); + const auto windirlen = strlen( windir ); + + const auto sz = needed / sizeof(LPVOID); + int cnt = 0; + for( size_t i=0; i", 2 ); + ImageEntry kernelDriver{}; + + kernelDriver.start = (uint64_t)dev[i]; + kernelDriver.end = 0; // Should be filled by GetModuleInfoFromDbgHelp + kernelDriver.name = buf; + kernelDriver.path = nullptr; + kernelDriver.imageDebugInfo = {}; + + 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; + } + + kernelDriver.path = CopyStringFast( path ); + + if( SymLoadModuleEx( s_DbgHelpSymHandle, nullptr, path, nullptr, (DWORD64)dev[i], 0, nullptr, 0 ) ) + { + // Kernel drivers PE headers are not accessible from userland, use DbgHelp to retrieve debug info. + GetModuleInfoFromDbgHelp( path, &kernelDriver ); + // We no longer need it if we resolve symbols offline, unload it. + if( s_shouldResolveSymbolsOffline ) + { + SymUnloadModule64( s_DbgHelpSymHandle, (DWORD64)dev[i] ); + } + } + } + + s_krnlCache->AddEntry( kernelDriver ); + cnt++; + } + } + s_krnlCache->Sort(); + } +} + +static void CacheProcessModules() +{ + DWORD needed; + HANDLE proc = GetCurrentProcess(); + HMODULE mod[1024]; + if( EnumProcessModules( proc, mod, sizeof(mod), &needed ) != 0 ) + { + const auto sz = needed / sizeof(HMODULE); + for( size_t i=0; i 0 ) + { + // This may be a new module loaded since our call to SymInitialize. + // Just in case, force DbgHelp to load its pdb ! + LoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage ); + } + } + } + s_imageCache->Sort(); + } +} + +void InitCallstack() +{ +#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE + s_shouldResolveSymbolsOffline = GetEnvBool("TRACY_SYMBOL_OFFLINE_RESOLVE", s_shouldResolveSymbolsOffline); +#endif //#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE + if (s_shouldResolveSymbolsOffline) + { + TracyDebug("TRACY: enabling offline symbol resolving!\n"); + } + +#ifdef TRACY_ENABLE // Client or self-profiling server + // Use GetCurrentProcess() as this is used by default in most apps, but we should probably be using a fake handle to avoid collisions? + DbgHelpInit(GetCurrentProcess(), true /*Invade process, even though it should be unnecessary since we'll preload all modules and drivers*/); +#else + DbgHelpInit((HANDLE)42/*This is our fake DbgHelp Handle*/, false /* Don't invade the process, we're going to resolve symbols for another one*/); +#endif + + CreateImageCaches(); + +#ifdef TRACY_ENABLE // Client or self-profiling server +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_LOCK; +#endif + // use TRACY_NO_DBGHELP_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 + // symbols during that time. + const char* noInitLoadEnv = GetEnvVar("TRACY_NO_DBGHELP_INIT_LOAD"); + const bool initTimeModuleLoad = !(noInitLoadEnv && noInitLoadEnv[0] == '1'); + if ( !initTimeModuleLoad ) + { + TracyDebug("TRACY: skipping init time dbghelper module load\n"); + } + + std::lock_guard mutexguard{ s_cacheMutex }; + + if ( initTimeModuleLoad ) + { + CacheProcessDrivers(); + CacheProcessModules(); + } + +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif +#endif +} + +void EndCallstack() +{ + DestroyImageCaches(); + if ( s_DbgHelpSymHandle ) + { + SymCleanup(s_DbgHelpSymHandle); + s_DbgHelpSymHandle = 0; + } +} + +const char* DecodeCallstackPtrFast( uint64_t ptr ) +{ + if( s_shouldResolveSymbolsOffline ) return "[unresolved]"; + + static char ret[MaxNameSize]; + const auto proc = GetCurrentProcess(); + + char buf[sizeof( SYMBOL_INFO ) + MaxNameSize]; + auto si = (SYMBOL_INFO*)buf; + si->SizeOfStruct = sizeof( SYMBOL_INFO ); + si->MaxNameLen = MaxNameSize; + +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_LOCK; +#endif + if( SymFromAddr( proc, ptr, nullptr, si ) == 0 ) + { + *ret = '\0'; + } + else + { + memcpy( ret, si->Name, si->NameLen ); + ret[si->NameLen] = '\0'; + } +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif + return ret; +} + +const char* GetKernelModulePath( uint64_t addr ) +{ + assert( addr >> 63 != 0 ); + if( !s_krnlCache ) return nullptr; + + const ImageEntry* imageEntry = s_krnlCache->FindEntryFromAddr( addr ); + if( imageEntry ) return imageEntry->path; + return nullptr; +} + +bool LoadFromPdb( const char* moduleName, uint64_t baseAddress, uint64_t dllSize, ImageDebugFormatId debugFormat, const uint8_t* debugData, uint32_t debugDataSize ) +{ + assert( debugFormat == ImageDebugFormatId::PdbDebugFormat ); + + const uint32_t DataForDebugSize = static_cast( debugDataSize ); + assert( moduleName != nullptr ); + + const tracy::PEImageDebugData* windowsDebugData = (const tracy::PEImageDebugData*)( debugData ); + const size_t pdbPathLength = debugDataSize - sizeof( tracy::PEImageDebugData ); + const uint8_t* pdbPath = debugData + sizeof( tracy::PEImageDebugData ); + + static_assert( sizeof( PEImageDebugData ) == 32, "Structure changed or not properly packed" ); + + static_assert( offsetof( CV_INFO_PDB70, CvSignature ) == offsetof( PdbInfo, CvSignature ), "Mismatch with DbgHelp headers." ); + static_assert( offsetof( CV_INFO_PDB70, Signature ) == offsetof( PdbInfo, Signature ), "Mismatch with DbgHelp headers." ); + static_assert( offsetof( CV_INFO_PDB70, Age ) == offsetof( PdbInfo, Age ), "Mismatch with DbgHelp headers." ); + static_assert( offsetof( CV_INFO_PDB70, PdbFileName ) == sizeof( PdbInfo ), "Mismatch with DbgHelp headers." ); + + const uint32_t sizeOfPdbData = DataForDebugSize - offsetof( PEImageDebugData, cvInfo ); + + static constexpr auto mandatoryAlignment = 8; + + auto const debug_module_info_size = sizeof( IMAGE_DEBUG_DIRECTORY ) + sizeOfPdbData; + auto const debug_module_info_size_aligned = ( debug_module_info_size + mandatoryAlignment ) & ( ~uint64_t( mandatoryAlignment - 1 ) ); + + uint8_t* dataBuffer = static_cast( tracy_malloc( debug_module_info_size_aligned ) ); + IMAGE_DEBUG_DIRECTORY* info = reinterpret_cast( dataBuffer ); + + info->TimeDateStamp = windowsDebugData->exeDataTimeStamp; + info->Characteristics = 0; + info->MajorVersion = windowsDebugData->majorVersion; + info->MinorVersion = windowsDebugData->minorVersion; + info->Type = IMAGE_DEBUG_TYPE_CODEVIEW; + info->AddressOfRawData = 0; + info->PointerToRawData = sizeof( IMAGE_DEBUG_DIRECTORY ); + info->SizeOfData = sizeOfPdbData; + + memcpy( dataBuffer + info->PointerToRawData, &windowsDebugData->cvInfo, sizeOfPdbData ); + + MODLOAD_DATA module_load_info; + module_load_info.ssize = sizeof( module_load_info ); + module_load_info.ssig = DBHHEADER_DEBUGDIRS; + module_load_info.data = dataBuffer; + module_load_info.size = static_cast( debug_module_info_size_aligned ); + module_load_info.flags = 0; + + DWORD64 loaddedModule = SymLoadModuleEx( s_DbgHelpSymHandle, NULL, moduleName, NULL, baseAddress, + dllSize, &module_load_info, 0 ); + + tracy_free( dataBuffer ); + + + IMAGEHLP_MODULEW64 moduleInfoDebug{}; + moduleInfoDebug.SizeOfStruct = sizeof(IMAGEHLP_MODULEW64); + if( SymGetModuleInfoW64( s_DbgHelpSymHandle, loaddedModule, &moduleInfoDebug ) == TRUE ) + { + // Consider deferred to be failing too as it may fail later. We might want to handle failure in the UI. + if( moduleInfoDebug.SymType != SymNone && moduleInfoDebug.SymType != SymDeferred ) + { + return true; + } + } + return false; +} + +// Called from the profiler (server) only, we received data from the client. +void CacheImageAndLoadDebugInfo( ImageEntry& imageEntry, bool loadDebugInfo ) +{ + if ( IsKernelAddress( imageEntry.start ) ) + { + s_krnlCache->AddEntry( imageEntry ); + } + else + { + s_imageCache->AddEntry( imageEntry ); + } + + bool hasSymbolInfo = false; + if( imageEntry.imageDebugInfo.debugFormat == ImageDebugFormatId::PdbDebugFormat ) + { + char* nameFixed = nullptr; + if( IsKernelAddress( imageEntry.start ) ) + { + const size_t nameLen = strlen( imageEntry.name ); + if( nameLen > 3 && imageEntry.name[0] == '<' && imageEntry.name[nameLen - 1] == '>' ) + { + char* kernelName = CopyStringFast( imageEntry.name + 1 ); + kernelName[nameLen - 2] = 0; // replace > + nameFixed = kernelName; + } + } + hasSymbolInfo = LoadFromPdb( nameFixed ? nameFixed : imageEntry.name, imageEntry.start, imageEntry.end ? (imageEntry.end - imageEntry.start) : 0, + imageEntry.imageDebugInfo.debugFormat, imageEntry.imageDebugInfo.debugData, imageEntry.imageDebugInfo.debugDataSize ); + + if( nameFixed ) tracy_free_fast( nameFixed ); + } + + if ( !hasSymbolInfo ) + { + // TODO: load from path only if we can check we got the correct binary (check timestamp / guid ?) + // That would however require to disable deferred symbol loading or use FindExecutableImageEx + // SymLoadModuleEx(s_DbgHelpSymHandle, nullptr, imageEntry.name, nullptr, baseOfDll, dllSize, nullptr, 0); + } + +} + +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 ); +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_LOCK; +#endif + const auto res = SymGetLineFromAddr64( s_DbgHelpSymHandle, ptr, &displacement, &line ); + if( res == 0 || line.LineNumber >= 0xF00000 ) + { + sym.file = "[unknown]"; + sym.line = 0; + sym.needFree = false; + } + else + { + sym.file = CopyString( line.FileName ); + sym.line = line.LineNumber; + sym.needFree = true; + } +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif + return sym; +} + +static CallstackEntryData MakeUnresolvedCallstackEntryData( uint64_t ptr, ModuleNameAndBaseAddress moduleNameAndBaseAddress ) +{ + cb_data[0].symAddr = ptr - moduleNameAndBaseAddress.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, moduleNameAndBaseAddress.name }; +} + +CallstackEntryData DecodeCallstackPtr( uint64_t ptr, DecodeCallStackPtrStatus* _decodeCallStackPtrStatus ) +{ +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_LOCK; +#endif + + InitRpmalloc(); + + bool moduleNotFound = false; + ModuleNameAndBaseAddress moduleNameAndAddress = GetModuleNameAndPrepareSymbols( ptr, &moduleNotFound ); + + *_decodeCallStackPtrStatus = DecodeCallStackPtrStatusFlags::Success; + + if( moduleNotFound ) + { + // only the client can have a processHandle as s_DbgHelpSymHandle + if( s_DbgHelpSymHandle == GetCurrentProcess() ) + { + // We're on the client or self profiling, try to load a potentially new module. + moduleNameAndAddress = TryToLoadModuleDebugInfoAndCache( ptr ); + if (moduleNameAndAddress.baseAddr == 0) + { + // Failed to find the module information. + // Set base address to ptr so that cb_data[0].symAddr=0 + moduleNameAndAddress = { "[unknown]", ptr }; + } + else { + *_decodeCallStackPtrStatus |= DecodeCallStackPtrStatusFlags::NewModuleFound; + moduleNotFound = false; // We managed to load the module information + } + } + else + { + // We're on the server, it does not have a way to get information about the module + *_decodeCallStackPtrStatus |= DecodeCallStackPtrStatusFlags::ModuleMissing; + } + } + + if( s_shouldResolveSymbolsOffline || moduleNotFound ) + { +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif + + return MakeUnresolvedCallstackEntryData(ptr, moduleNameAndAddress); + } + + int write; + const auto proc = s_DbgHelpSymHandle; + +#if !defined TRACY_NO_CALLSTACK_INLINES + BOOL doInline = FALSE; + DWORD ctx = 0; + DWORD inlineNum = 0; + 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 ); + } + if( doInline ) + { + write = inlineNum; + cb_num = 1 + inlineNum; + } + else +#endif + { + write = 0; + cb_num = 1; + } + + char buf[sizeof( SYMBOL_INFO ) + MaxNameSize]; + auto si = (SYMBOL_INFO*)buf; + si->SizeOfStruct = sizeof( SYMBOL_INFO ); + si->MaxNameLen = MaxNameSize; + + + const auto symValid = SymFromAddr( proc, ptr, nullptr, si ) != 0; + if( symValid == FALSE) { + *_decodeCallStackPtrStatus |= DecodeCallStackPtrStatusFlags::SymbolMissing; + +#ifdef TRACY_VERBOSE + static bool doOnce = true; + if(doOnce) + { + if( GetModuleHandleA( "symsrv.dll" ) == NULL ) + { + TracyDebug( "symsrv.dll was not loaded. Symbol resolution may fail.\n" ); + } + doOnce = false; + } +#endif + return MakeUnresolvedCallstackEntryData(ptr, moduleNameAndAddress); + } + + + IMAGEHLP_LINE64 line; + DWORD displacement = 0; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + { + const char* filename; + const auto res = SymGetLineFromAddr64( proc, ptr, &displacement, &line ); + if( res == 0 || line.LineNumber >= 0xF00000 ) + { + filename = "[unknown]"; + cb_data[write].line = 0; + } + else + { + filename = line.FileName; + cb_data[write].line = line.LineNumber; + } + + cb_data[write].name = symValid ? CopyStringFast( si->Name, si->NameLen ) : CopyStringFast( moduleNameAndAddress.name ); + cb_data[write].file = CopyStringFast( filename ); + 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; + } + } + +#if !defined TRACY_NO_CALLSTACK_INLINES + if( doInline ) + { + for( DWORD i=0; iName, si->NameLen ) : CopyStringFast( moduleNameAndAddress.name ); + cb.file = CopyStringFast( filename ); + if( symInlineValid ) + { + cb.symLen = si->Size; + cb.symAddr = si->Address; + } + else + { + cb.symLen = 0; + cb.symAddr = 0; + } + + ctx++; + } + } +#endif +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif + + return { cb_data, uint8_t( cb_num ), moduleNameAndAddress.name }; +} + +#elif defined(TRACY_USE_LIBBACKTRACE) + +enum { MaxCbTrace = 64 }; + +struct backtrace_state* cb_bts = nullptr; + +int cb_num; +CallstackEntry cb_data[MaxCbTrace]; +int cb_fixup; + +void CacheImageAndLoadDebugInfo( ImageEntry& imageEntry, bool loadDebugInfo ) {} + +#ifdef TRACY_DEBUGINFOD +debuginfod_client* s_debuginfod; + +struct DebugInfo +{ + uint8_t* buildid; + size_t buildid_size; + char* filename; + int fd; +}; + + +static FastVector* s_di_known; +#endif + +#ifdef __linux + +static void InitKernelSymbols() +{ + if ( s_krnlSymbolsCache == nullptr ) + { + s_krnlSymbolsCache = new( tracy_malloc( sizeof( ImageCache ) ) ) ImageCache(); + } + + FILE* f = fopen( "/proc/kallsyms", "rb" ); + if( !f ) return; + tracy::FastVector tmpSym( 512 * 1024 ); + size_t linelen = 16 * 1024; // linelen must be big enough to prevent reallocs in getline() + auto linebuf = (char*)tracy_malloc( linelen ); + ssize_t sz; + size_t validCnt = 0; + 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++; + const bool valid = *ptr == 'T' || *ptr == 't'; + 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; + } + + char* strname = nullptr; + char* strmod = nullptr; + + if( valid ) + { + validCnt++; + + 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'; + } + } + + // Note: This uses the image cache as a symbol cache. + ImageEntry kernelSymbol{}; + kernelSymbol.start = addr; + kernelSymbol.end = 0; + kernelSymbol.name = strname; + kernelSymbol.path = strmod; + + s_krnlSymbolsCache->AddEntry( kernelSymbol ); + + } + tracy_free_fast( linebuf ); + fclose( f ); + + s_krnlSymbolsCache->Sort(); + + TracyDebug( "Loaded %zu kernel symbols (%zu code sections)\n", tmpSym.size(), validCnt ); +} +#endif + +char* NormalizePath( const char* path ) +{ + if( path[0] != '/' ) return nullptr; + + const char* ptr = path; + const char* end = path + strlen( path ); + + 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; +} + +void InitCallstackCritical() +{ +} + +void InitCallstack() +{ + InitRpmalloc(); + +#ifdef TRACY_USE_IMAGE_CACHE + CreateImageCaches(); +#endif //#ifdef TRACY_USE_IMAGE_CACHE + +#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE + s_shouldResolveSymbolsOffline = GetEnvBool( "TRACY_SYMBOL_OFFLINE_RESOLVE", s_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 + +#ifdef __linux + InitKernelSymbols(); +#endif +#ifdef TRACY_DEBUGINFOD + s_debuginfod = debuginfod_begin(); + s_di_known = (FastVector*)tracy_malloc( sizeof( FastVector ) ); + new (s_di_known) FastVector( 16 ); +#endif +} + +#ifdef TRACY_DEBUGINFOD +void ClearDebugInfoVector( FastVector& vec ) +{ + for( auto& v : vec ) + { + tracy_free( v.buildid ); + tracy_free( v.filename ); + if( v.fd >= 0 ) close( v.fd ); + } + vec.clear(); +} + +DebugInfo* FindDebugInfo( FastVector& 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; +} + +int GetDebugInfoDescriptor( const char* buildid_data, size_t buildid_size, const char* filename ) +{ + auto buildid = (uint8_t*)buildid_data; + auto it = FindDebugInfo( *s_di_known, buildid, buildid_size ); + if( it ) return it->fd >= 0 ? dup( it->fd ) : -1; + + int fd = debuginfod_find_debuginfo( s_debuginfod, buildid, buildid_size, nullptr ); + it = s_di_known->push_next(); + it->buildid_size = buildid_size; + it->buildid = (uint8_t*)tracy_malloc( buildid_size ); + memcpy( it->buildid, buildid, buildid_size ); + const auto fnsz = strlen( filename ) + 1; + it->filename = (char*)tracy_malloc( fnsz ); + memcpy( it->filename, filename, fnsz ); + it->fd = fd >= 0 ? fd : -1; + TracyDebug( "DebugInfo descriptor query: %i, fn: %s\n", fd, filename ); + return it->fd; +} + +const uint8_t* GetBuildIdForImage( const char* image, size_t& size ) +{ + assert( image ); + for( auto& v : *s_di_known ) + { + if( strcmp( image, v.filename ) == 0 ) + { + size = v.buildid_size; + return v.buildid; + } + } + return nullptr; +} + +debuginfod_client* GetDebuginfodClient() +{ + return s_debuginfod; +} +#endif + +void EndCallstack() +{ +#ifdef TRACY_USE_IMAGE_CACHE + DestroyImageCaches(); +#endif //#ifdef TRACY_USE_IMAGE_CACHE +#ifndef TRACY_DEMANGLE + ___tracy_free_demangle_buffer(); +#endif +#ifdef TRACY_DEBUGINFOD + ClearDebugInfoVector( *s_di_known ); + s_di_known->~FastVector(); + tracy_free( s_di_known ); + + debuginfod_end( s_debuginfod ); +#endif +} + +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; +} + +static int SymbolAddressDataCb( void* data, uintptr_t pc, uintptr_t lowaddr, const char* fn, int lineno, const char* function ) +{ + auto& sym = *(CallstackSymbolData*)data; + if( !fn ) + { + sym.file = "[unknown]"; + sym.line = 0; + sym.needFree = false; + } + else + { + sym.file = NormalizePath( fn ); + if( !sym.file ) sym.file = CopyString( fn ); + sym.line = lineno; + sym.needFree = true; + } + + return 1; +} + +static void SymbolAddressErrorCb( void* data, const char* /*msg*/, int /*errnum*/ ) +{ + auto& sym = *(CallstackSymbolData*)data; + sym.file = "[unknown]"; + sym.line = 0; + sym.needFree = false; +} + +CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) +{ + CallstackSymbolData sym; + if( cb_bts ) + { + backtrace_pcinfo( cb_bts, ptr, SymbolAddressDataCb, SymbolAddressErrorCb, &sym ); + } + else + { + SymbolAddressErrorCb(&sym, nullptr, 0); + } + + return sym; +} + +static int CallstackDataCb( void* /*data*/, uintptr_t pc, uintptr_t lowaddr, const char* fn, int lineno, const char* function ) +{ + cb_data[cb_num].symLen = 0; + cb_data[cb_num].symAddr = (uint64_t)lowaddr; + + if( !fn && !function ) + { + const char* symname = nullptr; + auto vptr = (void*)pc; + ptrdiff_t symoff = 0; + + Dl_info dlinfo; + if( dladdr( vptr, &dlinfo ) ) + { + symname = dlinfo.dli_sname; + symoff = (char*)pc - (char*)dlinfo.dli_saddr; + const char* demangled = ___tracy_demangle( symname ); + if( demangled ) symname = demangled; + } + + if( !symname ) symname = "[unknown]"; + + if( symoff == 0 ) + { + const auto len = std::min( strlen( symname ), std::numeric_limits::max() ); + cb_data[cb_num].name = CopyStringFast( symname, len ); + } + else + { + char buf[32]; + const auto offlen = sprintf( buf, " + %td", symoff ); + const auto namelen = std::min( strlen( symname ), std::numeric_limits::max() - offlen ); + auto name = (char*)tracy_malloc_fast( namelen + offlen + 1 ); + memcpy( name, symname, namelen ); + memcpy( name + namelen, buf, offlen ); + name[namelen + offlen] = '\0'; + cb_data[cb_num].name = name; + } + + cb_data[cb_num].file = CopyStringFast( "[unknown]" ); + cb_data[cb_num].line = 0; + } + else + { + if( !fn ) fn = "[unknown]"; + if( !function ) + { + function = "[unknown]"; + } + else + { + const char* demangled = ___tracy_demangle( function ); + if( demangled ) function = demangled; + } + + const auto len = std::min( strlen( function ), std::numeric_limits::max() ); + cb_data[cb_num].name = CopyStringFast( function, len ); + cb_data[cb_num].file = NormalizePath( fn ); + if( !cb_data[cb_num].file ) cb_data[cb_num].file = CopyStringFast( fn ); + cb_data[cb_num].line = lineno; + } + + if( ++cb_num >= MaxCbTrace ) + { + return 1; + } + else + { + return 0; + } +} + +static void CallstackErrorCb( void* /*data*/, const char* /*msg*/, int /*errnum*/ ) +{ + for( int i=0; i> 63 == 0 ) + { + const char* imageName = nullptr; + uint64_t imageBaseAddress = 0x0; + +#ifdef TRACY_USE_IMAGE_CACHE + const auto* image = s_imageCache->GetImageForAddress(ptr); + if( image ) + { + imageName = image->name; + imageBaseAddress = uint64_t(image->start); + } +#else + Dl_info dlinfo; + if( dladdr( (void*)ptr, &dlinfo ) ) + { + imageName = dlinfo.dli_fname; + imageBaseAddress = uint64_t( dlinfo.dli_fbase ); + } +#endif + + if( s_shouldResolveSymbolsOffline ) + { + cb_num = 1; + GetSymbolForOfflineResolve( (void*)ptr, imageBaseAddress, 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 ); + } + + if ( imageName ) + { + *_decodeCallStackPtrStatus = DecodeCallStackPtrStatusFlags::Success; + + } + else + { + + *_decodeCallStackPtrStatus |= DecodeCallStackPtrStatusFlags::SymbolMissing; + } + return { cb_data, uint8_t( cb_num ), imageName ? imageName : "[unknown]" }; + } +#ifdef __linux + else if( s_krnlSymbolsCache ) + { + // This is a symbol cache, kernel image cache is currently unused + const ImageEntry* symbolEntry = s_krnlSymbolsCache->FindEntryFromAddr((uint64_t)ptr); + if ( symbolEntry ) + { + cb_data[0].name = CopyStringFast( symbolEntry->name ); + cb_data[0].file = CopyStringFast( "" ); + cb_data[0].line = 0; + cb_data[0].symLen = symbolEntry->end - symbolEntry->start; + cb_data[0].symAddr = symbolEntry->start; + *_decodeCallStackPtrStatus = DecodeCallStackPtrStatusFlags::Success; + return { cb_data, 1, symbolEntry->path ? symbolEntry->path : "" }; + } + } +#endif +#endif + cb_data[0].name = CopyStringFast( "[unknown]" ); + cb_data[0].file = CopyStringFast( "" ); + cb_data[0].line = 0; + cb_data[0].symLen = 0; + cb_data[0].symAddr = 0; + *_decodeCallStackPtrStatus = DecodeCallStackPtrStatusFlags::Success; + return { cb_data, 1, "" }; +} + +#elif TRACY_HAS_CALLSTACK == 5 + +void CacheImageAndLoadDebugInfo( ImageEntry& imageEntry, bool loadDebugInfo ) {} + +void InitCallstackCritical() +{ +} + +void InitCallstack() +{ + ___tracy_init_demangle_buffer(); +} + +void EndCallstack() +{ + ___tracy_free_demangle_buffer(); +} + +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; +} + +CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) +{ + const char* symloc = nullptr; + Dl_info dlinfo; + if( dladdr( (void*)ptr, &dlinfo ) ) symloc = dlinfo.dli_fname; + if( !symloc ) symloc = "[unknown]"; + return CallstackSymbolData { symloc, 0, false, 0 }; +} + +CallstackEntryData DecodeCallstackPtr( uint64_t ptr, DecodeCallStackPtrStatus* _decodeCallStackPtrStatus ) +{ + static CallstackEntry cb; + cb.line = 0; + + const char* symname = nullptr; + const char* symloc = nullptr; + auto vptr = (void*)ptr; + ptrdiff_t symoff = 0; + void* symaddr = nullptr; + + Dl_info dlinfo; + if( dladdr( vptr, &dlinfo ) ) + { + symloc = dlinfo.dli_fname; + symname = dlinfo.dli_sname; + symoff = (char*)ptr - (char*)dlinfo.dli_saddr; + symaddr = dlinfo.dli_saddr; + const char* demangled = ___tracy_demangle( symname ); + if( demangled ) symname = demangled; + } + + if( !symname ) symname = "[unknown]"; + if( !symloc ) symloc = "[unknown]"; + + if( symoff == 0 ) + { + const auto len = std::min( strlen( symname ), std::numeric_limits::max() ); + cb.name = CopyString( symname, len ); + } + else + { + char buf[32]; + const auto offlen = sprintf( buf, " + %td", symoff ); + const auto namelen = std::min( strlen( symname ), std::numeric_limits::max() - offlen ); + auto name = (char*)tracy_malloc( namelen + offlen + 1 ); + memcpy( name, symname, namelen ); + memcpy( name + namelen, buf, offlen ); + name[namelen + offlen] = '\0'; + cb.name = name; + } + + cb.file = CopyString( "[unknown]" ); + cb.symLen = 0; + cb.symAddr = (uint64_t)symaddr; + + *_decodeCallStackPtrStatus = DecodeCallStackPtrStatus::Success; + return { &cb, 1, symloc }; +} + +#endif + +} + +#endif diff --git a/public/client/TracyCallstack.h b/public/common/TracyCallstack.h similarity index 83% rename from public/client/TracyCallstack.h rename to public/common/TracyCallstack.h index 1aca7292a4..2df15420c8 100644 --- a/public/client/TracyCallstack.h +++ b/public/common/TracyCallstack.h @@ -30,6 +30,10 @@ # define TRACY_HAS_CALLSTACK 6 # endif +#if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 +#define TRACY_USE_LIBBACKTRACE +#endif + #endif #endif diff --git a/public/client/TracyCallstack.hpp b/public/common/TracyCallstack.hpp similarity index 73% rename from public/client/TracyCallstack.hpp rename to public/common/TracyCallstack.hpp index 1d8cd654f1..669819598d 100644 --- a/public/client/TracyCallstack.hpp +++ b/public/common/TracyCallstack.hpp @@ -1,16 +1,62 @@ #ifndef __TRACYCALLSTACK_HPP__ #define __TRACYCALLSTACK_HPP__ +#include + #include "../common/TracyApi.h" #include "../common/TracyForceInline.hpp" #include "TracyCallstack.h" +namespace tracy +{ + +enum DecodeCallStackPtrStatusFlags : uint8_t +{ + Success = 0, + ModuleMissing = 1 << 0, + SymbolMissing = 1 << 1, + + ErrorMask = 0b11, + + NewModuleFound = 1 << 2, + + Count +}; + +using DecodeCallStackPtrStatus = uint8_t; +enum struct ImageDebugFormatId : uint8_t +{ + NoDebugFormat, + PdbDebugFormat, + GNUDebugFormat, + ElfDebugFormat +}; + +struct ImageDebugInfo +{ + ImageDebugFormatId debugFormat; + uint32_t debugDataSize; + uint8_t* debugData; +}; + +struct ImageEntry +{ + uint64_t start; + uint64_t end; + char* name; + char* path; + + ImageDebugInfo imageDebugInfo; +}; +} + #ifndef TRACY_HAS_CALLSTACK namespace tracy { static constexpr bool has_callstack() { return false; } static tracy_force_inline void* Callstack( int32_t /*depth*/ ) { return nullptr; } +inline void PreventSymbolResolution() { } } #else @@ -33,14 +79,17 @@ static tracy_force_inline void* Callstack( int32_t /*depth*/ ) { return nullptr; #include #include +#include #include "../common/TracyAlloc.hpp" +#include "../common/TracyFastVector.hpp" namespace tracy { static constexpr bool has_callstack() { return true; } + struct CallstackSymbolData { const char* file; @@ -55,7 +104,7 @@ struct CallstackEntry const char* file; uint32_t line; uint32_t symLen; - uint64_t symAddr; + uint64_t symAddr; // Relative address }; struct CallstackEntryData @@ -65,14 +114,26 @@ struct CallstackEntryData const char* imageName; }; + +void PreventSymbolResolution(); +std::recursive_mutex& GetModuleCacheMutexForRead(); +const ImageEntry* GetImageEntryFromPtr( uint64_t ptr ); + CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ); const char* DecodeCallstackPtrFast( uint64_t ptr ); -CallstackEntryData DecodeCallstackPtr( uint64_t ptr ); +CallstackEntryData DecodeCallstackPtr( uint64_t ptr , DecodeCallStackPtrStatus* _decodeCallStackPtrStatus ); + + void InitCallstack(); void InitCallstackCritical(); void EndCallstack(); const char* GetKernelModulePath( uint64_t addr ); +void CacheImageAndLoadDebugInfo( ImageEntry& imageEntry, bool loadDebugInfo ); +const FastVector* GetUserImageInfos(); +const FastVector* GetKernelImageInfos(); + + #ifdef TRACY_DEBUGINFOD const uint8_t* GetBuildIdForImage( const char* image, size_t& size ); debuginfod_client* GetDebuginfodClient(); diff --git a/public/client/TracyDebug.hpp b/public/common/TracyDebug.hpp similarity index 100% rename from public/client/TracyDebug.hpp rename to public/common/TracyDebug.hpp diff --git a/public/common/TracyDebugModulesHeaderFile.hpp b/public/common/TracyDebugModulesHeaderFile.hpp new file mode 100644 index 0000000000..91730c05fa --- /dev/null +++ b/public/common/TracyDebugModulesHeaderFile.hpp @@ -0,0 +1,27 @@ +#pragma once + + +namespace tracy +{ + +#pragma pack( push, 1 ) + +struct PdbInfo +{ + uint32_t CvSignature; + uint8_t Signature[16]; // GUID + uint32_t Age; +}; + +struct PEImageDebugData +{ + uint16_t majorVersion; + uint16_t minorVersion; + uint32_t exeDataTimeStamp; + PdbInfo cvInfo; +}; + +#pragma pack( pop ) + + +} diff --git a/public/client/TracyFastVector.hpp b/public/common/TracyFastVector.hpp similarity index 100% rename from public/client/TracyFastVector.hpp rename to public/common/TracyFastVector.hpp diff --git a/public/common/TracyProtocol.hpp b/public/common/TracyProtocol.hpp index ff38686f18..81ef6cc364 100644 --- a/public/common/TracyProtocol.hpp +++ b/public/common/TracyProtocol.hpp @@ -9,7 +9,7 @@ namespace tracy constexpr unsigned Lz4CompressBound( unsigned isize ) { return isize + ( isize / 255 ) + 16; } -enum : uint32_t { ProtocolVersion = 76 }; +enum : uint32_t { ProtocolVersion = 77 }; enum : uint16_t { BroadcastVersion = 3 }; using lz4sz_t = uint32_t; @@ -22,6 +22,7 @@ static_assert( TargetFrameSize * 2 >= 64 * 1024, "Not enough space for LZ4 strea enum { HandshakeShibbolethSize = 8 }; static const char HandshakeShibboleth[HandshakeShibbolethSize] = { 'T', 'r', 'a', 'c', 'y', 'P', 'r', 'f' }; + enum HandshakeStatus : uint8_t { HandshakePending, @@ -110,6 +111,13 @@ struct WelcomeMessage enum { WelcomeMessageSize = sizeof( WelcomeMessage ) }; +struct ServerFlags +{ + enum _t : uint32_t + { + PreventSymbolResolution = 1 << 0, + }; +}; struct OnDemandPayloadMessage { diff --git a/public/common/TracyQueue.hpp b/public/common/TracyQueue.hpp index 765c83c7d5..7f2528f358 100644 --- a/public/common/TracyQueue.hpp +++ b/public/common/TracyQueue.hpp @@ -69,6 +69,8 @@ enum class QueueType : uint8_t SourceCodeMetadata, FiberEnter, FiberLeave, + ImageUpdate, + DataPacket, Terminate, KeepAlive, ThreadContext, @@ -724,6 +726,20 @@ struct QueueHeader }; }; +static constexpr size_t MaxModule = 1024; + + +struct QueueImageEntry +{ + uint64_t payload; + uint64_t payloadSize; +}; + +struct QueueDataPacket +{ + uint16_t packetSize; +}; + struct QueueItem { QueueHeader hdr; @@ -816,6 +832,8 @@ struct QueueItem QueueFiberEnter fiberEnter; QueueFiberLeave fiberLeave; QueueGpuZoneAnnotation zoneAnnotation; + QueueImageEntry imageEntry; + QueueDataPacket packet; }; }; #pragma pack( pop ) @@ -885,6 +903,8 @@ static constexpr size_t QueueDataSize[] = { sizeof( QueueHeader ) + sizeof( QueueFiberEnter ), sizeof( QueueHeader ) + sizeof( QueueFiberLeave ), // above items must be first + sizeof( QueueHeader ) + sizeof( QueueImageEntry ), // image datas, + sizeof( QueueHeader ) + sizeof( QueueDataPacket ), // DataPacket sizeof( QueueHeader ), // terminate sizeof( QueueHeader ), // keep alive sizeof( QueueHeader ) + sizeof( QueueThreadContext ), diff --git a/public/client/TracyStringHelpers.hpp b/public/common/TracyStringHelpers.hpp similarity index 100% rename from public/client/TracyStringHelpers.hpp rename to public/common/TracyStringHelpers.hpp diff --git a/public/common/TracyVersion.hpp b/public/common/TracyVersion.hpp index 7d704c500d..81a71503e9 100644 --- a/public/common/TracyVersion.hpp +++ b/public/common/TracyVersion.hpp @@ -7,7 +7,7 @@ namespace Version { enum { Major = 0 }; enum { Minor = 12 }; -enum { Patch = 4 }; +enum { Patch = 5 }; } } diff --git a/public/libbacktrace/elf.cpp b/public/libbacktrace/elf.cpp index ffe8d7024b..6234821c20 100644 --- a/public/libbacktrace/elf.cpp +++ b/public/libbacktrace/elf.cpp @@ -47,7 +47,7 @@ POSSIBILITY OF SUCH DAMAGE. */ #include "backtrace.hpp" #include "internal.hpp" -#include "../client/TracyFastVector.hpp" +#include "../common/TracyFastVector.hpp" #include "../common/TracyAlloc.hpp" #ifndef S_ISLNK @@ -75,7 +75,7 @@ namespace tracy { #ifdef TRACY_DEBUGINFOD -int GetDebugInfoDescriptor( const char* buildid_data, size_t buildid_size ); +int GetDebugInfoDescriptor( const char* buildid_data, size_t buildid_size , const char* file_name ); #endif #if !defined(HAVE_DECL_STRNLEN) || !HAVE_DECL_STRNLEN diff --git a/public/tracy/TracyD3D11.hpp b/public/tracy/TracyD3D11.hpp index acab383169..2eae21db68 100644 --- a/public/tracy/TracyD3D11.hpp +++ b/public/tracy/TracyD3D11.hpp @@ -38,7 +38,7 @@ using TracyD3D11Ctx = void*; #include "Tracy.hpp" #include "../client/TracyProfiler.hpp" -#include "../client/TracyCallstack.hpp" +#include "../common/TracyCallstack.hpp" #include "../common/TracyYield.hpp" #include @@ -431,7 +431,7 @@ using TracyD3D11Ctx = tracy::D3D11Ctx*; #define TracyD3D11SrcLocSymbol TracyConcat(__tracy_gpu_d3d11_source_location,TracyLine) #define TracyD3D11SrcLocObject(name, color) static constexpr tracy::SourceLocationData TracyD3D11SrcLocSymbol { name, TracyFunction, TracyFile, (uint32_t)TracyLine, color }; -#if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK +#if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK && TRACY_CALLSTACK > 0 # define TracyD3D11Zone( ctx, name ) TracyD3D11NamedZoneS( ctx, TracyD3D11UnnamedZone, name, TRACY_CALLSTACK, true ) # define TracyD3D11ZoneC( ctx, name, color ) TracyD3D11NamedZoneCS( ctx, TracyD3D11UnnamedZone, name, color, TRACY_CALLSTACK, true ) # define TracyD3D11NamedZone( ctx, varname, name, active ) TracyD3D11SrcLocObject(name, 0); tracy::D3D11ZoneScope varname( ctx, &TracyD3D11SrcLocSymbol, TRACY_CALLSTACK, active ); diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index d36253d7cd..9f13e60765 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -34,7 +34,7 @@ using TracyD3D12Ctx = void*; #include "Tracy.hpp" #include "../client/TracyProfiler.hpp" -#include "../client/TracyCallstack.hpp" +#include "../common/TracyCallstack.hpp" #include #include diff --git a/public/tracy/TracyMetal.hmm b/public/tracy/TracyMetal.hmm index a4b4cb5216..98f909dbee 100644 --- a/public/tracy/TracyMetal.hmm +++ b/public/tracy/TracyMetal.hmm @@ -63,7 +63,7 @@ using TracyMetalCtx = void; #include "Tracy.hpp" #include "../client/TracyProfiler.hpp" -#include "../client/TracyCallstack.hpp" +#include "../common/TracyCallstack.hpp" #include "../common/TracyAlign.hpp" #include "../common/TracyAlloc.hpp" diff --git a/public/tracy/TracyOpenCL.hpp b/public/tracy/TracyOpenCL.hpp index ede5c4613b..33904b9746 100644 --- a/public/tracy/TracyOpenCL.hpp +++ b/public/tracy/TracyOpenCL.hpp @@ -40,7 +40,7 @@ using TracyCLCtx = void*; #include #include "Tracy.hpp" -#include "../client/TracyCallstack.hpp" +#include "../common/TracyCallstack.hpp" #include "../client/TracyProfiler.hpp" #include "../common/TracyAlloc.hpp" diff --git a/public/tracy/TracyOpenGL.hpp b/public/tracy/TracyOpenGL.hpp index 30abd4fd05..90a68ce264 100644 --- a/public/tracy/TracyOpenGL.hpp +++ b/public/tracy/TracyOpenGL.hpp @@ -37,7 +37,7 @@ class GpuCtxScope #include "Tracy.hpp" #include "../client/TracyProfiler.hpp" -#include "../client/TracyCallstack.hpp" +#include "../common/TracyCallstack.hpp" #include "../common/TracyAlign.hpp" #include "../common/TracyAlloc.hpp" diff --git a/public/tracy/TracyVulkan.hpp b/public/tracy/TracyVulkan.hpp index 429f299db5..9d090c52f3 100644 --- a/public/tracy/TracyVulkan.hpp +++ b/public/tracy/TracyVulkan.hpp @@ -41,7 +41,7 @@ using TracyVkCtx = void*; #include #include "Tracy.hpp" #include "../client/TracyProfiler.hpp" -#include "../client/TracyCallstack.hpp" +#include "../common/TracyCallstack.hpp" #include diff --git a/server/TracyWorker.cpp b/server/TracyWorker.cpp index e679b5062a..e4cf499957 100644 --- a/server/TracyWorker.cpp +++ b/server/TracyWorker.cpp @@ -26,11 +26,17 @@ #define ZDICT_STATIC_LINKING_ONLY #include + +#include "../public/common/TracyCallstack.hpp" +#include "../public/common/TracyFastVector.hpp" #include "../public/common/TracyProtocol.hpp" -#include "../public/common/TracySystem.hpp" -#include "../public/common/TracyYield.hpp" #include "../public/common/TracyStackFrames.hpp" +#include "../public/common/TracySystem.hpp" #include "../public/common/TracyVersion.hpp" +#include "../public/common/TracyYield.hpp" + + +#include "TracyAlign.hpp" #include "TracyFileRead.hpp" #include "TracyFileWrite.hpp" #include "TracyPrint.hpp" @@ -38,6 +44,11 @@ #include "TracyTaskDispatch.hpp" #include "TracyWorker.hpp" #include "tracy_pdqsort.h" +#include "TracyAlloc.hpp" + +#ifdef TRACY_ENABLE // The worker should only have TRACY_ENABLE when self profiling +#define TRACY_SELF_PROFILE +#endif namespace tracy { @@ -255,7 +266,7 @@ static bool IsQueryPrio( ServerQuery type ) LoadProgress Worker::s_loadProgress; -Worker::Worker( const char* addr, uint16_t port, int64_t memoryLimit ) +Worker::Worker( const char* addr, uint16_t port, int64_t memoryLimit, const SymbolResolutionConfig& symbolResConfig ) : m_addr( addr ) , m_port( port ) , m_hasData( false ) @@ -263,6 +274,7 @@ Worker::Worker( const char* addr, uint16_t port, int64_t memoryLimit ) , m_buffer( new char[TargetFrameSize*3 + 1] ) , m_bufferOffset( 0 ) , m_inconsistentSamples( false ) + , m_symbolConfig( symbolResConfig ) , m_pendingStrings( 0 ) , m_pendingThreads( 0 ) , m_pendingFibers( 0 ) @@ -276,6 +288,9 @@ Worker::Worker( const char* addr, uint16_t port, int64_t memoryLimit ) , m_traceVersion( CurrentVersion ) , m_loadTime( 0 ) { +#if defined(TRACY_HAS_CALLSTACK) && !defined( TRACY_SELF_PROFILE) // Self profiling will use the old path and query symbols + InitCallstack(); +#endif m_data.sourceLocationExpand.push_back( 0 ); m_data.localThreadCompress.InitZero(); m_data.callstackPayload.push_back( nullptr ); @@ -299,7 +314,7 @@ Worker::Worker( const char* addr, uint16_t port, int64_t memoryLimit ) m_threadNet = std::thread( [this] { SetThreadName( "Tracy Network" ); Network(); } ); } -Worker::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::Worker( const char* name, const char* program, const std::vector& timeline, const std::vector& messages, const std::vector& plots, const std::unordered_map& threadNames, const SymbolResolutionConfig& symbolResConfig ) : m_hasData( true ) , m_resolution( 0 ) , m_captureName( name ) @@ -312,9 +327,13 @@ Worker::Worker( const char* name, const char* program, const std::vectordebugFormat ); + + if( debugField->debugFormat == ImageDebugFormatId::NoDebugFormat ) + { + debugField->debugDataSize = 0; + debugField->debugData = nullptr; + return; + } + f.Read( debugField->debugDataSize ); + debugField->debugData = (uint8_t*)tracy_malloc( debugField->debugDataSize ); + f.Read( debugField->debugData, debugField->debugDataSize ); + }; + + auto DeserializeMallocedFastString = [&]( char** s ) + { + int sLength = -1; + f.Read( sLength ); + if (sLength == -1) + { + *s = nullptr; + return; + } + + *s = (char*)tracy_malloc_fast( sLength ); + + f.Read( *s, sLength ); + }; + + auto DeserializeImageEntries = [&]() + { + uint32_t moduleCount = 0; + f.Read( moduleCount ); + + for( size_t i=0; i= FileVersion( 0, 11, 3 ) ) + { + DeserializeImageEntries(); // UserLandModules + DeserializeImageEntries(); // KernelModules + } + + // Post read processing + s_loadProgress.total.store( 0, std::memory_order_relaxed ); m_loadTime = std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - loadStart ).count(); @@ -1942,6 +2023,9 @@ Worker::Worker( FileRead& f, EventType::Type eventMask, bool bgTasks, bool allow Worker::~Worker() { +#if defined(TRACY_HAS_CALLSTACK) && !defined( TRACY_SELF_PROFILE) // Self profiling will use the old path and query symbols + EndCallstack(); +#endif Shutdown(); if( m_threadNet.joinable() ) m_threadNet.join(); @@ -2679,7 +2763,8 @@ const SymbolStats* Worker::GetSymbolStats( uint64_t symAddr ) const } } -const unordered_flat_map* Worker::GetSymbolInstructionPointers( uint64_t symAddr ) const +const unordered_flat_map* +Worker::GetSymbolInstructionPointers( uint64_t symAddr ) const { assert( AreCallstackSamplesReady() ); auto it = m_data.instructionPointersMap.find( symAddr ); @@ -2752,6 +2837,7 @@ void Worker::Exec() m_sock.Send( HandshakeShibboleth, HandshakeShibbolethSize ); uint32_t protocolVersion = ProtocolVersion; m_sock.Send( &protocolVersion, sizeof( protocolVersion ) ); + HandshakeStatus handshake; if( !m_sock.Read( &handshake, sizeof( handshake ), 10, ShouldExit ) ) { @@ -2820,6 +2906,16 @@ void Worker::Exec() m_hostInfo = welcome.hostInfo; + uint32_t serverFlags = 0; + if( m_symbolConfig.m_preventResolutionByClient ) + { + // We could probably enable this automatically on windows when connected to localhost + // or based on some platform filter? For now, simply honor the config. + serverFlags |= ServerFlags::PreventSymbolResolution; + } + m_sock.Send( &serverFlags, sizeof(serverFlags) ); + + if( m_onDemand ) { OnDemandPayloadMessage onDemand; @@ -3138,6 +3234,15 @@ void Worker::DispatchFailure( const QueueItem& ev, const char*& ptr ) AddSecondString( ptr, sz ); ptr += sz; break; + case QueueType::DataPacket: + { + const QueueDataPacket* packet = reinterpret_cast( ptr ); + ptr += sizeof( QueueHeader ); + ptr += sizeof( QueueDataPacket ); + AddDataPacket( reinterpret_cast( ptr ), packet->packetSize ); + ptr += packet->packetSize; + } + break; default: ptr += QueueDataSize[ev.hdr.idx]; switch( ev.hdr.type ) @@ -3234,12 +3339,61 @@ void Worker::QueryDataTransfer( const void* ptr, size_t size ) void Worker::QueryCallstackFrame( uint64_t addr ) { - const auto packed = PackPointer( addr ); - if( m_data.callstackFrameMap.contains( packed ) ) return; + const auto packed = PackPointer(addr); + if (m_data.callstackFrameMap.contains(packed)) return; m_pendingCallstackFrames++; - m_data.callstackFrameMap.emplace( packed, nullptr ); - Query( ServerQueryCallstackFrame, addr ); + m_data.callstackFrameMap.emplace(packed, nullptr); + + DecodeCallStackPtrStatus status = DecodeCallStackPtrStatusFlags::SymbolMissing; +#if defined(TRACY_HAS_CALLSTACK) && !defined( TRACY_SELF_PROFILE) // Self profiling will use the old path and query symbols + if (m_symbolConfig.m_attemptResolutionByWorker) + { + + // TODO: offload to a worker thread + CallstackEntryData outCallStack = DecodeCallstackPtr(addr, &status); + if ((status & DecodeCallStackPtrStatusFlags::ErrorMask) == DecodeCallStackPtrStatusFlags::Success) + { + const uint32_t imageNameIdx = StoreString(outCallStack.imageName, strlen(outCallStack.imageName)).idx; + assert(outCallStack.size <= 255); + const QueueCallstackFrameSize queueCallstackFrameSize = + { + .ptr = addr, + .size = outCallStack.size, + }; + + ProcessCallstackFrameSize(queueCallstackFrameSize, imageNameIdx); + + for (size_t i = 0; i < queueCallstackFrameSize.size; i++) + { + const auto& frame = outCallStack.data[i]; + ProcessCallstackFrame(frame.line, frame.symAddr, frame.symLen, + StoreString(frame.name, strlen(frame.name)).idx, StoreString(frame.file, strlen(frame.file)).idx, true); + + // We copied it, now free the content + tracy_free_fast((void*)frame.name); + tracy_free_fast((void*)frame.file); + } + + assert(m_pendingCallstackSubframes == 0); + return; + } + + if (status & DecodeCallStackPtrStatusFlags::SymbolMissing) + { + tracy_free_fast((void*)outCallStack.data[0].file); + tracy_free_fast((void*)outCallStack.data[0].name); + } + + } +#endif + + if (status & DecodeCallStackPtrStatusFlags::ModuleMissing || status & DecodeCallStackPtrStatusFlags::SymbolMissing) + { + // TODO: actually only query module info, and then try to resolve again locally. + // Or we could just rely on the user triggering a new symbol resolution manually. + Query(ServerQueryCallstackFrame, addr); + } } bool Worker::DispatchProcess( const QueueItem& ev, const char*& ptr ) @@ -3343,6 +3497,15 @@ bool Worker::DispatchProcess( const QueueItem& ev, const char*& ptr ) AddSecondString( ptr, sz ); ptr += sz; return true; + case QueueType::DataPacket: + { + ptr += sizeof( QueueHeader ); + const QueueDataPacket* packet = reinterpret_cast( ptr ); + ptr += sizeof( QueueDataPacket ); + AddDataPacket( reinterpret_cast( ptr ), packet->packetSize ); + ptr += packet->packetSize; + return true; + } default: ptr += QueueDataSize[ev.hdr.idx]; return Process( ev ); @@ -3848,6 +4011,12 @@ void Worker::AddSecondString( const char* str, size_t sz ) m_pendingSecondString = StoreString( str, sz ); } +void Worker::AddDataPacket( const void* data, size_t size ) +{ + m_pendingDataPacket.data = static_cast( tracy_malloc( size ) ); + memcpy( m_pendingDataPacket.data, data, size ); +} + void Worker::AddExternalName( uint64_t ptr, const char* str, size_t sz ) { assert( m_pendingExternalNames > 0 ); @@ -4027,9 +4196,10 @@ void Worker::AddCallstackPayload( const char* _data, size_t _sz ) m_data.callstackMap.emplace( arr, idx ); m_data.callstackPayload.push_back( arr ); - for( auto& frame : *arr ) + + for ( CallstackFrameId frameId : *arr ) { - QueryCallstackFrame( GetCanonicalPointer( frame ) ); + QueryCallstackFrame( GetCanonicalPointer( frameId ) ); } } else @@ -4456,6 +4626,21 @@ uint32_t Worker::GetSecondStringIdx() return idx; } +void Worker::AccessPendingData( uint8_t** ptr, size_t* sz) +{ + assert( m_pendingDataPacket.data != nullptr ); + + *ptr = m_pendingDataPacket.data; + *sz = m_pendingDataPacket.dataSize; +} + +void Worker::FreePendingData() +{ + tracy_free( m_pendingDataPacket.data ); + m_pendingDataPacket.data = nullptr; + m_pendingDataPacket.dataSize = 0; +} + StringLocation Worker::StoreString( const char* str, size_t sz ) { StringLocation ret; @@ -4781,7 +4966,12 @@ bool Worker::Process( const QueueItem& ev ) break; case QueueType::FiberLeave: ProcessFiberLeave( ev.fiberLeave ); - break; + break; + case QueueType::ImageUpdate: + { + DispatchImageEntry(ev.imageEntry); + break; + } default: assert( false ); break; @@ -5087,6 +5277,55 @@ void Worker::SourceLocationOverflowFailure() m_failure = Failure::SourceLocationOverflow; } +void Worker::ResolveSymbolLocally() +{ +#if defined(TRACY_HAS_CALLSTACK) && !defined( TRACY_SELF_PROFILE) // Would be racy by design when self profiling, need to offload this to the Symbol Worker + const char * unresolvedStr = "[unresolved]"; + StringIdx unresolvedStrIdx = StoreString(unresolvedStr, strlen(unresolvedStr) ).idx; + + if(!m_symbolConfig.m_attemptResolutionByWorker) + { + // TODO: Load debug info. + } + + for (auto& it : m_data.callstackFrameMap) + { + CallstackFrameData& toResolveData = *it.second; + if(toResolveData.data[0].name.Idx() == unresolvedStrIdx.Idx()) + { + uint64_t symbolAddress = GetCanonicalPointer( it.first ); + DecodeCallStackPtrStatus status; + CallstackEntryData outCallStack = DecodeCallstackPtr( symbolAddress, &status ); + if ( ( status & DecodeCallStackPtrStatusFlags::ErrorMask ) == DecodeCallStackPtrStatusFlags::Success ) + { + assert( outCallStack.size <= 255 ); + assert( toResolveData.imageName.Idx() == StoreString( outCallStack.imageName, strlen(outCallStack.imageName)).idx); + if( toResolveData.size != outCallStack.size ) + { + // We're "leaking" the unresolved data until slab is reset + toResolveData.data = m_slab.Alloc( outCallStack.size ); + } + + for( size_t idx=0; idxsecond ) @@ -6583,18 +6820,28 @@ void Worker::ProcessCallstackFrameSize( const QueueCallstackFrameSize& ev ) m_callstackFrameStaging = m_slab.Alloc(); m_callstackFrameStaging->size = ev.size; m_callstackFrameStaging->data = m_slab.Alloc( ev.size ); - m_callstackFrameStaging->imageName = StringIdx( idx ); + m_callstackFrameStaging->imageName = StringIdx( imageNameIdx ); m_callstackFrameStagingPtr = ev.ptr; } } +void Worker::ProcessCallstackFrameSize( const QueueCallstackFrameSize& ev ) +{ + ProcessCallstackFrameSize( ev, GetSingleStringIdx() ); +} + void Worker::ProcessCallstackFrame( const QueueCallstackFrame& ev, bool querySymbols ) { - assert( m_pendingCallstackSubframes > 0 ); + const uint32_t nitidx = GetSingleStringIdx(); + const uint32_t fitidx = GetSecondStringIdx(); - const auto nitidx = GetSingleStringIdx(); - const auto fitidx = GetSecondStringIdx(); + ProcessCallstackFrame( ev.line, ev.symAddr, ev.symLen, nitidx, fitidx, querySymbols ); +} + +void Worker::ProcessCallstackFrame( uint32_t line, uint64_t symAddr, uint32_t symLen, uint32_t nitidx, uint32_t fitidx, bool querySymbols ) +{ + assert( m_pendingCallstackSubframes > 0 ); if( m_callstackFrameStaging ) { @@ -6625,13 +6872,18 @@ void Worker::ProcessCallstackFrame( const QueueCallstackFrame& ev, bool querySym const auto name = StringIdx( nitidx ); m_callstackFrameStaging->data[idx].name = name; m_callstackFrameStaging->data[idx].file = file; - m_callstackFrameStaging->data[idx].line = ev.line; - m_callstackFrameStaging->data[idx].symAddr = ev.symAddr; + m_callstackFrameStaging->data[idx].line = line; + m_callstackFrameStaging->data[idx].symAddr = symAddr; - if( querySymbols && ev.symAddr != 0 && m_data.symbolMap.find( ev.symAddr ) == m_data.symbolMap.end() && m_pendingSymbols.find( ev.symAddr ) == m_pendingSymbols.end() ) + if(querySymbols && symAddr!= 0 && m_data.symbolMap.find(symAddr) == m_data.symbolMap.end() && + m_pendingSymbols.find(symAddr) == m_pendingSymbols.end()) { - m_pendingSymbols.emplace( ev.symAddr, SymbolPending { name, m_callstackFrameStaging->imageName, file, ev.line, ev.symLen, idx < m_callstackFrameStaging->size - 1 } ); - Query( ServerQuerySymbol, ev.symAddr ); + m_pendingSymbols.emplace(symAddr, + SymbolPending{ name, m_callstackFrameStaging->imageName, file, line, symLen, + idx < m_callstackFrameStaging->size - 1 } ); + + + Query( ServerQuerySymbol, symAddr ); } StringRef ref( StringRef::Idx, fitidx ); @@ -6639,16 +6891,22 @@ void Worker::ProcessCallstackFrame( const QueueCallstackFrame& ev, bool querySym if( cit == m_checkedFileStrings.end() ) CacheSource( ref, m_callstackFrameStaging->imageName ); const auto frameId = PackPointer( m_callstackFrameStagingPtr ); + #ifndef TRACY_NO_STATISTICS + + auto it = m_data.pendingInstructionPointers.find( frameId ); if( it != m_data.pendingInstructionPointers.end() ) { - if( ev.symAddr != 0 ) + if( symAddr != 0 ) { - auto sit = m_data.instructionPointersMap.find( ev.symAddr ); + auto sit = m_data.instructionPointersMap.find( symAddr ); if( sit == m_data.instructionPointersMap.end() ) { - m_data.instructionPointersMap.emplace( ev.symAddr, unordered_flat_map { { it->first, it->second } } ); + m_data.instructionPointersMap.emplace( + symAddr, + unordered_flat_map{ + { it->first, it->second } } ); } else { @@ -6658,16 +6916,19 @@ void Worker::ProcessCallstackFrame( const QueueCallstackFrame& ev, bool querySym } m_data.pendingInstructionPointers.erase( it ); } + + auto pit = m_data.pendingSymbolSamples.find( frameId ); if( pit != m_data.pendingSymbolSamples.end() ) { - if( ev.symAddr != 0 ) + if( symAddr != 0 ) { - auto sit = m_data.symbolSamples.find( ev.symAddr ); + auto sit = m_data.symbolSamples.find( symAddr ); if( sit == m_data.symbolSamples.end() ) - { - pdqsort_branchless( pit->second.begin(), pit->second.end(), [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs.time.Val(); } ); - m_data.symbolSamples.emplace( ev.symAddr, std::move( pit->second ) ); + { + pdqsort_branchless( pit->second.begin(), pit->second.end(), []( const auto& lhs, const auto& rhs ) + { return lhs.time.Val() < rhs.time.Val(); } ); + m_data.symbolSamples.emplace( symAddr, std::move( pit->second ) ); } else { @@ -8511,6 +8772,57 @@ void Worker::Write( FileWrite& f, bool fiDict ) f.Write( &v.second.len, sizeof( v.second.len ) ); f.Write( v.second.data, v.second.len ); } + + +#ifdef TRACY_HAS_CALLSTACK + auto SerializeDebugModuleField = [&]( const ImageDebugInfo& debugField ) + { + f.Write( &debugField.debugFormat, sizeof( debugField.debugFormat ) ); + if( debugField.debugFormat == ImageDebugFormatId::NoDebugFormat ) + return; + + f.Write( &debugField.debugDataSize, sizeof( debugField.debugDataSize ) ); + f.Write( debugField.debugData, debugField.debugDataSize ); + }; + + auto SerializeString = [&]( const char* s ) + { + int sLength = s != nullptr ? strlen( s ) + 1 : -1; + f.Write( &sLength, sizeof( sLength ) ); + + if ( sLength == -1 ) + return; + + f.Write( s, sLength ); + }; + + auto SerializeImageEntries = [&]( const FastVector* imagesEntries ) + { + uint32_t moduleCount = static_cast( imagesEntries ? imagesEntries->size() : 0 ); + f.Write( &moduleCount, sizeof( moduleCount ) ); + + if( imagesEntries ) + { + for (const auto& m : *imagesEntries) + { + f.Write( &m.start, sizeof( m.start ) ); + f.Write( &m.end, sizeof( m.end ) ); + SerializeString( m.name ); + SerializeString( m.path ); + + SerializeDebugModuleField( m.imageDebugInfo ); + } + } + }; + + SerializeImageEntries( GetUserImageInfos() ); + SerializeImageEntries( GetKernelImageInfos() ); +#else + uint32_t moduleCount = 0; + f.Write( &moduleCount, sizeof( moduleCount ) ); // User images count + f.Write( &moduleCount, sizeof( moduleCount ) ); // Kernel images count +#endif + } void Worker::WriteTimeline( FileWrite& f, const Vector>& vec, int64_t& refTime ) @@ -8760,4 +9072,67 @@ void Worker::CacheSourceFiles() } } +void Worker::DispatchImageEntry( const QueueImageEntry& ev ) +{ + uint8_t* ptrToData = nullptr; + size_t s = 0; + AccessPendingData(&ptrToData, &s); + + + uint64_t baseAddress = MemRead(ptrToData); + ptrToData += sizeof(baseAddress); + + uint64_t end = MemRead(ptrToData); + ptrToData += sizeof(end); + + uint32_t moduleNameSize = MemRead(ptrToData); + ptrToData += sizeof(moduleNameSize); + + assert((char)(ptrToData[moduleNameSize - 1]) == '\0' && "missing end of string"); + char* moduleName = (char*)tracy_malloc_fast(moduleNameSize); + memcpy(moduleName, ptrToData, moduleNameSize); + ptrToData += moduleNameSize; + + uint32_t modulePathSize = MemRead(ptrToData); + ptrToData += sizeof(modulePathSize); + + assert((char)(ptrToData[modulePathSize - 1]) == '\0' && "missing end of string"); + char* modulePath = (char*)tracy_malloc_fast(modulePathSize); + memcpy(modulePath, ptrToData, modulePathSize); + ptrToData += modulePathSize; + + ImageDebugFormatId debugFormat = MemRead(ptrToData); + ptrToData += sizeof(ImageDebugFormatId); + + ImageEntry moduleCacheEntry = + { + .start = baseAddress, + .end = end, + .name = moduleName, + .path = modulePath, + }; + moduleCacheEntry.imageDebugInfo.debugFormat = debugFormat; + + if (debugFormat != ImageDebugFormatId::NoDebugFormat) + { + + uint32_t debugFormatSize = MemRead(ptrToData); + ptrToData += sizeof(debugFormatSize); + + uint8_t* debugData = (uint8_t*)tracy_malloc(debugFormatSize); + memcpy(debugData, ptrToData, debugFormatSize); + ptrToData += debugFormatSize; + + ImageDebugInfo& imageDebugInfo = moduleCacheEntry.imageDebugInfo; + + imageDebugInfo.debugData = debugData; + imageDebugInfo.debugDataSize = debugFormatSize; + + } +#ifdef TRACY_HAS_CALLSTACK + CacheImageAndLoadDebugInfo(moduleCacheEntry, m_symbolConfig.m_attemptResolutionByWorker); +#endif + FreePendingData(); +} + } diff --git a/server/TracyWorker.hpp b/server/TracyWorker.hpp index 05013e7b8a..66fa6ea093 100644 --- a/server/TracyWorker.hpp +++ b/server/TracyWorker.hpp @@ -17,6 +17,8 @@ #include "../public/common/TracyQueue.hpp" #include "../public/common/TracyProtocol.hpp" #include "../public/common/TracySocket.hpp" +#include "../public/common/TracyDebugModulesHeaderFile.hpp" + #include "tracy_robin_hood.h" #include "TracyEvent.hpp" #include "TracyShortPtr.hpp" @@ -285,6 +287,7 @@ class Worker StringDiscovery plots; Vector threads; Vector zoneExtra; + Vector modulesBaseAddress; MemData* memory; unordered_flat_map memNameMap; uint64_t zonesCnt = 0; @@ -431,6 +434,12 @@ class Worker const char* image; uint32_t csz; }; + + struct PendingDataPacket + { + size_t dataSize; + uint8_t* data; + }; public: enum class Failure @@ -453,9 +462,15 @@ class Worker NUM_FAILURES }; - Worker( const char* addr, uint16_t port, int64_t memoryLimit ); - 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, bool allowStringModification = false); + struct SymbolResolutionConfig + { + bool m_attemptResolutionByWorker; + bool m_preventResolutionByClient; + }; + + Worker(const char* addr, uint16_t port, int64_t memoryLimit, const SymbolResolutionConfig& symbolResConfig); + Worker( const char* name, const char* program, const std::vector& timeline, const std::vector& messages, const std::vector& plots, const std::unordered_map& threadNames, const SymbolResolutionConfig& symbolResConfig); + Worker(FileRead& f, const SymbolResolutionConfig& symbolResConfig, EventType::Type eventMask = EventType::All, bool bgTasks = true, bool allowStringModification = false); ~Worker(); const std::string& GetAddr() const { return m_addr; } @@ -552,6 +567,7 @@ class Worker const char* GetSymbolCode( uint64_t sym, uint32_t& len ) const; uint64_t GetSymbolForAddress( uint64_t address ); uint64_t GetSymbolForAddress( uint64_t address, uint32_t& offset ); + uint64_t GetInlineSymbolForAddress( uint64_t address ) const; bool HasInlineSymbolAddresses() const { return !m_data.codeSymbolMap.empty(); } StringIdx GetLocationForAddress( uint64_t address, uint32_t& line ) const; @@ -673,6 +689,7 @@ class Worker void DoPostponedInlineSymbols(); void DoPostponedWork(); void DoPostponedWorkAll(); + void ResolveSymbolLocally(); void CacheSourceFiles(); @@ -757,8 +774,11 @@ class Worker tracy_force_inline void ProcessCallstack(); tracy_force_inline void ProcessCallstackSample( const QueueCallstackSample& ev ); tracy_force_inline void ProcessCallstackSampleContextSwitch( const QueueCallstackSample& ev ); + tracy_force_inline void ProcessCallstackFrameSize( const QueueCallstackFrameSize& ev, uint32_t imageNameIdx ); tracy_force_inline void ProcessCallstackFrameSize( const QueueCallstackFrameSize& ev ); tracy_force_inline void ProcessCallstackFrame( const QueueCallstackFrame& ev, bool querySymbols ); + tracy_force_inline void ProcessCallstackFrame(uint32_t line, uint64_t symAddr, uint32_t symLen, uint32_t nitidx, uint32_t fitidx, bool querySymbols); + tracy_force_inline void ProcessSymbolInformation( const QueueSymbolInformation& ev ); tracy_force_inline void ProcessCrashReport( const QueueCrashReport& ev ); tracy_force_inline void ProcessSysTime( const QueueSysTime& ev ); @@ -780,6 +800,8 @@ class Worker tracy_force_inline void ProcessFiberEnter( const QueueFiberEnter& ev ); tracy_force_inline void ProcessFiberLeave( const QueueFiberLeave& ev ); + tracy_force_inline void DispatchImageEntry( const QueueImageEntry& ev); + tracy_force_inline ZoneEvent* AllocZoneEvent(); tracy_force_inline void ProcessZoneBeginImpl( ZoneEvent* zone, const QueueZoneBegin& ev ); tracy_force_inline void ProcessZoneBeginAllocSrcLocImpl( ZoneEvent* zone, const QueueZoneBeginLean& ev ); @@ -889,6 +911,7 @@ class Worker void AddSingleString( const char* str, size_t sz ); void AddSingleStringFailure( const char* str, size_t sz ); void AddSecondString( const char* str, size_t sz ); + void AddDataPacket( const void* data, size_t size ); void AddExternalName( uint64_t ptr, const char* str, size_t sz ); void AddExternalThreadName( uint64_t ptr, const char* str, size_t sz ); void AddFrameImageData( const char* data, size_t sz ); @@ -915,6 +938,10 @@ class Worker uint32_t GetSingleStringIdx(); uint32_t GetSecondStringIdx(); + + void AccessPendingData( uint8_t** ptr, size_t* sz ); + void FreePendingData(); + const ContextSwitch* const GetContextSwitchDataImpl( uint64_t thread ); void CacheSource( const StringRef& str, const StringIdx& image = StringIdx() ); @@ -1007,6 +1034,7 @@ class Worker bool m_identifySamples = false; bool m_inconsistentSamples; bool m_allowStringModification = false; + SymbolResolutionConfig m_symbolConfig; short_ptr m_gpuCtxMap[256]; uint32_t m_pendingCallstackId = 0; @@ -1021,6 +1049,7 @@ class Worker unordered_flat_set m_checkedFileStrings; StringLocation m_pendingSingleString = {}; StringLocation m_pendingSecondString = {}; + PendingDataPacket m_pendingDataPacket = {}; uint32_t m_pendingStrings; uint32_t m_pendingThreads; diff --git a/update/src/update.cpp b/update/src/update.cpp index a9cb625edd..0e29e976dd 100644 --- a/update/src/update.cpp +++ b/update/src/update.cpp @@ -170,7 +170,11 @@ int main( int argc, char** argv ) const auto t0 = std::chrono::high_resolution_clock::now(); const bool allowBgThreads = false; const bool allowStringModification = resolveSymbols; - tracy::Worker worker( *f, (tracy::EventType::Type)events, allowBgThreads, allowStringModification ); + tracy::Worker::SymbolResolutionConfig symConfig{}; + // TODO: Use the same mechanism everywhere + // Current offline resolution does not check if symbols match which can lead to invalid data. + symConfig.m_attemptResolutionByWorker = false; // resolveSymbols; + tracy::Worker worker( *f, symConfig, (tracy::EventType::Type)events, allowBgThreads, allowStringModification ); #ifndef TRACY_NO_STATISTICS while( !worker.AreSourceLocationZonesReady() ) std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );