diff --git a/manual/tracy.tex b/manual/tracy.tex index 123ce80c..863eaaed 100644 --- a/manual/tracy.tex +++ b/manual/tracy.tex @@ -2041,6 +2041,20 @@ filesystem setup as the one used to run the tracy instrumented application). You can do path substitution with the \texttt{-p} option to perform any number of path substitions in order to use symbols located elsewhere. +By default symbol resolution is performed with the platform's native facility: the DbgHelp +library on Windows, and the \texttt{addr2line} tool found in \texttt{PATH} elsewhere. You can +override this with the \texttt{-a} option, passing the path to a custom +\texttt{addr2line}-compatible tool (for instance an \texttt{addr2line} from a cross-compilation +toolchain, or \texttt{llvm-addr2line}). The \texttt{-a} option works on all platforms, including +Windows, and takes precedence over the platform default. + +Extra arguments can be passed verbatim to the resolution tool with the \texttt{-A} option. Tracy +records callstack frame offsets relative to the image base, but \texttt{addr2line}-compatible +tools expect a full virtual address for images that have a non-zero preferred image base (such as +PE on Windows or Mach-O on Apple). For these, pass \texttt{-A "--relative-address"} so that +\texttt{llvm-addr2line} or \texttt{llvm-symbolizer} adds the image base back. ELF images need no +such adjustment. + \begin{bclogo}[ noborder=true, couleur=black!5, diff --git a/update/src/OfflineSymbolResolver.cpp b/update/src/OfflineSymbolResolver.cpp index 0e190d7d..30ea6b6a 100644 --- a/update/src/OfflineSymbolResolver.cpp +++ b/update/src/OfflineSymbolResolver.cpp @@ -11,6 +11,22 @@ #include "OfflineSymbolResolver.h" +bool ResolveSymbols( const std::string& addr2lineToolPath, const std::string& addr2lineArgs, + const std::string& imagePath, const FrameEntryList& inputEntryList, + SymbolEntryList& resolvedEntries ) +{ +#ifdef _WIN32 + // On Windows the default (no custom tool given) is the DbgHelp backend. + if( addr2lineToolPath.empty() ) + { + return ResolveSymbolsDbgHelp( imagePath, inputEntryList, resolvedEntries ); + } +#endif + // Everywhere else, and whenever a custom tool is given, use the addr2line-compatible backend. + // An empty path lets that backend fall back to the 'addr2line' found in PATH. + return ResolveSymbolsAddr2Line( addr2lineToolPath, addr2lineArgs, imagePath, inputEntryList, resolvedEntries ); +} + bool ApplyPathSubstitutions( std::string& path, const PathSubstitutionList& pathSubstitutionlist ) { for( const auto& substitution : pathSubstitutionlist ) @@ -31,7 +47,8 @@ tracy::StringIdx AddSymbolString( tracy::Worker& worker, const std::string& str return tracy::StringIdx( location.idx ); } -bool PatchSymbolsWithRegex( tracy::Worker& worker, const PathSubstitutionList& pathSubstitutionlist, bool verbose ) +bool PatchSymbolsWithRegex( tracy::Worker& worker, const PathSubstitutionList& pathSubstitutionlist, + const std::string& addr2lineToolPath, const std::string& addr2lineArgs, bool verbose ) { uint64_t callstackFrameCount = worker.GetCallstackFrameCount(); std::string relativeSoNameMatch = "[unresolved]"; @@ -91,7 +108,7 @@ bool PatchSymbolsWithRegex( tracy::Worker& worker, const PathSubstitutionList& p } SymbolEntryList resolvedEntries; - ResolveSymbols( imagePath, entries, resolvedEntries ); + ResolveSymbols( addr2lineToolPath, addr2lineArgs, imagePath, entries, resolvedEntries ); if( resolvedEntries.size() != entries.size() ) { @@ -131,7 +148,8 @@ bool PatchSymbolsWithRegex( tracy::Worker& worker, const PathSubstitutionList& p return true; } -void PatchSymbols( tracy::Worker& worker, const std::vector& pathSubstitutionsStrings, bool verbose ) +void PatchSymbols( tracy::Worker& worker, const std::vector& pathSubstitutionsStrings, + const std::string& addr2lineToolPath, const std::string& addr2lineArgs, bool verbose ) { std::cout << "Resolving and patching symbols..." << std::endl; @@ -160,7 +178,7 @@ void PatchSymbols( tracy::Worker& worker, const std::vector& pathSu } } - if ( !PatchSymbolsWithRegex(worker, pathSubstitutionList, verbose) ) + if ( !PatchSymbolsWithRegex(worker, pathSubstitutionList, addr2lineToolPath, addr2lineArgs, verbose) ) { std::cerr << "Failed to patch symbols" << std::endl; } diff --git a/update/src/OfflineSymbolResolver.h b/update/src/OfflineSymbolResolver.h index e3216eb5..65b61409 100644 --- a/update/src/OfflineSymbolResolver.h +++ b/update/src/OfflineSymbolResolver.h @@ -29,12 +29,35 @@ struct SymbolEntry using SymbolEntryList = std::vector; -bool ResolveSymbols( const std::string& imagePath, const FrameEntryList& inputEntryList, +// Dispatches to the appropriate backend depending on the platform and whether a custom +// addr2line-compatible tool was specified. When addr2lineToolPath is non-empty, the tool at +// that path is invoked (on any platform); otherwise the platform default is used (DbgHelp on +// Windows, the 'addr2line' found in PATH elsewhere). addr2lineArgs are extra arguments passed +// verbatim to the addr2line-compatible tool (e.g. "--relative-address"). +bool ResolveSymbols( const std::string& addr2lineToolPath, const std::string& addr2lineArgs, + const std::string& imagePath, const FrameEntryList& inputEntryList, SymbolEntryList& resolvedEntries ); -void PatchSymbols( tracy::Worker& worker, const std::vector& pathSubstitutionsStrings, bool verbose = false ); +// Backend invoking an addr2line-compatible tool. Available on all platforms. An empty +// addr2lineToolPath falls back to the 'addr2line' found in PATH. addr2lineArgs are inserted +// verbatim into the tool's command line. +bool ResolveSymbolsAddr2Line( const std::string& addr2lineToolPath, const std::string& addr2lineArgs, + const std::string& imagePath, const FrameEntryList& inputEntryList, + SymbolEntryList& resolvedEntries ); + +#ifdef _WIN32 +// Backend using the Windows DbgHelp library. +bool ResolveSymbolsDbgHelp( const std::string& imagePath, const FrameEntryList& inputEntryList, + SymbolEntryList& resolvedEntries ); +#endif + +void PatchSymbols( tracy::Worker& worker, const std::vector& pathSubstitutionsStrings, + const std::string& addr2lineToolPath = std::string(), + const std::string& addr2lineArgs = std::string(), bool verbose = false ); using PathSubstitutionList = std::vector >; -bool PatchSymbolsWithRegex( tracy::Worker& worker, const PathSubstitutionList& pathSubstituionlist, bool verbose = false ); +bool PatchSymbolsWithRegex( tracy::Worker& worker, const PathSubstitutionList& pathSubstituionlist, + const std::string& addr2lineToolPath = std::string(), + const std::string& addr2lineArgs = std::string(), bool verbose = false ); #endif // __SYMBOLRESOLVER_HPP__ \ No newline at end of file diff --git a/update/src/OfflineSymbolResolverAddr2Line.cpp b/update/src/OfflineSymbolResolverAddr2Line.cpp index e8809243..24a85c78 100644 --- a/update/src/OfflineSymbolResolverAddr2Line.cpp +++ b/update/src/OfflineSymbolResolverAddr2Line.cpp @@ -1,5 +1,3 @@ -#ifndef _WIN32 - #include "OfflineSymbolResolver.h" #include @@ -10,6 +8,11 @@ #include #include +#ifdef _WIN32 +# define popen _popen +# define pclose _pclose +#endif + std::string ExecShellCommand( const char* cmd ) { std::array buffer; @@ -29,23 +32,65 @@ std::string ExecShellCommand( const char* cmd ) class SymbolResolver { public: - SymbolResolver() + SymbolResolver( const std::string& addr2lineToolPath, const std::string& addr2lineArgs ) { + // Extra arguments are inserted verbatim into the tool invocation. Tracy records frame + // offsets as RVAs; for images with a non-zero preferred image base (PE, Mach-O) the user + // can pass "--relative-address" here so llvm-addr2line / llvm-symbolizer add the base back. + if( !addr2lineArgs.empty() ) + { + m_addr2LineArgs = " " + addr2lineArgs; + } + + if( !addr2lineToolPath.empty() ) + { + // If the value looks like a path (not a bare command name resolved via PATH), verify + // it exists so a wrong path fails with an actionable error instead of a cryptic shell one. + const bool looksLikePath = addr2lineToolPath.find( '/' ) != std::string::npos || + addr2lineToolPath.find( '\\' ) != std::string::npos; + if( looksLikePath && !std::ifstream( addr2lineToolPath ).good() ) + { + std::cerr << "Specified symbol resolution tool not found: '" << addr2lineToolPath + << "' (check the path passed to the '-a' option)" << std::endl; + return; + } + + m_addr2LinePath = addr2lineToolPath; + std::cout << "Using user-specified symbol resolution tool: '" << m_addr2LinePath.c_str() << "'" << std::endl; + return; + } + +#ifdef _WIN32 + std::cerr << "No symbol resolution tool specified (use the '-a' option to provide one)" << std::endl; +#else std::stringstream result( ExecShellCommand("which addr2line") ); std::getline(result, m_addr2LinePath); if( !m_addr2LinePath.length() ) { - std::cerr << "'addr2line' was not found in the system, please installed it" << std::endl; + std::cerr << "'addr2line' was not found in the system, please install it" << std::endl; } else { std::cout << "Using 'addr2line' found at: '" << m_addr2LinePath.c_str() << "'" << std::endl; } +#endif } static void escapeShellParam(std::string const& s, std::string& out) { +#ifdef _WIN32 + // cmd.exe / the CRT command parser do not understand POSIX backslash escapes, and + // backslashes are path separators on Windows. Wrap the parameter in double quotes + // (which handles spaces) and drop any embedded quotes, which cannot appear in a path. + out.reserve( s.size() + 2 ); + out.push_back( '"' ); + for( char c : s ) + { + if( c != '"' ) out.push_back( c ); + } + out.push_back( '"' ); +#else out.reserve( s.size() + 2 ); out.push_back( '"' ); for( unsigned char c : s ) @@ -73,13 +118,14 @@ public: } } out.push_back( '"' ); +#endif } bool ResolveSymbols( const std::string& imagePath, const FrameEntryList& inputEntryList, SymbolEntryList& resolvedEntries ) { if( !m_addr2LinePath.length() ) return false; - + std:: string escapedPath; escapeShellParam( imagePath, escapedPath ); @@ -93,14 +139,22 @@ public: // generate a single addr2line cmd line for all addresses in one invocation std::stringstream ss; - ss << m_addr2LinePath << " -C -f -e " << escapedPath << " -a "; + ss << m_addr2LinePath << " -C -f" << m_addr2LineArgs << " -e " << escapedPath << " -a "; for( ; entryIdx < batchEndIdx; entryIdx++ ) { const FrameEntry& entry = inputEntryList[entryIdx]; ss << " 0x" << std::hex << entry.symbolOffset; } - std::string resultStr = ExecShellCommand( ss.str().c_str() ); + std::string cmd = ss.str(); +#ifdef _WIN32 + // _popen runs the command through 'cmd.exe /c', which strips the outermost pair of + // quotes. Wrap the whole command so the quoting around the (possibly spaced) tool + // and image paths survives. + cmd = "\"" + cmd + "\""; +#endif + + std::string resultStr = ExecShellCommand( cmd.c_str() ); std::stringstream result( resultStr ); //printf("executing: '%s' got '%s'\n", ss.str().c_str(), result.str().c_str()); @@ -147,13 +201,13 @@ public: private: std::string m_addr2LinePath; + std::string m_addr2LineArgs; }; -bool ResolveSymbols( const std::string& imagePath, const FrameEntryList& inputEntryList, - SymbolEntryList& resolvedEntries ) +bool ResolveSymbolsAddr2Line( const std::string& addr2lineToolPath, const std::string& addr2lineArgs, + const std::string& imagePath, const FrameEntryList& inputEntryList, + SymbolEntryList& resolvedEntries ) { - static SymbolResolver symbolResolver; + static SymbolResolver symbolResolver( addr2lineToolPath, addr2lineArgs ); return symbolResolver.ResolveSymbols( imagePath, inputEntryList, resolvedEntries ); } - -#endif // #ifndef _WIN32 diff --git a/update/src/OfflineSymbolResolverDbgHelper.cpp b/update/src/OfflineSymbolResolverDbgHelper.cpp index de48b737..ce72a74f 100644 --- a/update/src/OfflineSymbolResolverDbgHelper.cpp +++ b/update/src/OfflineSymbolResolverDbgHelper.cpp @@ -122,8 +122,8 @@ private: char SymbolResolver::s_symbolResolutionBuffer[symbolResolutionBufferSize]; -bool ResolveSymbols( const std::string& imagePath, const FrameEntryList& inputEntryList, - SymbolEntryList& resolvedEntries ) +bool ResolveSymbolsDbgHelp( const std::string& imagePath, const FrameEntryList& inputEntryList, + SymbolEntryList& resolvedEntries ) { static SymbolResolver resolver; return resolver.ResolveSymbolsForModule( imagePath, inputEntryList, resolvedEntries ); diff --git a/update/src/update.cpp b/update/src/update.cpp index 8c41e1c2..cd4a5271 100644 --- a/update/src/update.cpp +++ b/update/src/update.cpp @@ -39,6 +39,10 @@ void Usage() printf( " -c: scan for source files missing in cache and add if found\n" ); printf( " -r: resolve symbols and patch callstack frames\n"); printf( " -p: substitute symbol resolution path with an alternative: \"REGEX_MATCH;REPLACEMENT\"\n"); + printf( " -a: path to a custom addr2line-compatible tool to use for symbol resolution\n"); + printf( " -A: extra arguments passed verbatim to the symbol resolution tool,\n"); + printf( " e.g. \"--relative-address\" for llvm-addr2line on PE/Mach-O images\n"); + printf( " -v: verbose output while resolving symbols\n"); printf( " -j: number of threads to use for compression (-1 to use all cores)\n" ); exit( 1 ); @@ -62,9 +66,12 @@ int main( int argc, char** argv ) bool cacheSource = false; bool resolveSymbols = false; std::vector pathSubstitutions; + std::string addr2lineToolPath; + std::string addr2lineArgs; + bool verboseSymbols = false; int c; - while( ( c = getopt( argc, argv, "4hez:ds:crp:j:" ) ) != -1 ) + while( ( c = getopt( argc, argv, "4hez:ds:crp:a:A:vj:" ) ) != -1 ) { switch( c ) { @@ -140,6 +147,15 @@ int main( int argc, char** argv ) case 'p': pathSubstitutions.push_back(optarg); break; + case 'a': + addr2lineToolPath = optarg; + break; + case 'A': + addr2lineArgs = optarg; + break; + case 'v': + verboseSymbols = true; + break; case 'j': streams = atoi( optarg ); break; @@ -181,7 +197,7 @@ int main( int argc, char** argv ) const auto t1 = std::chrono::high_resolution_clock::now(); if( cacheSource ) worker.CacheSourceFiles(); - if( resolveSymbols ) PatchSymbols( worker, pathSubstitutions ); + if( resolveSymbols ) PatchSymbols( worker, pathSubstitutions, addr2lineToolPath, addr2lineArgs, verboseSymbols ); auto w = std::unique_ptr( tracy::FileWrite::Open( output, clev, zstdLevel, streams ) ); if( !w )