mirror of
https://github.com/wolfpld/tracy.git
synced 2026-06-08 00:23:47 +00:00
Allow offline symbol resolution with any addr2line-compatible tool
The addr2line backend of tracy-update now builds on every platform, including Windows, and can be pointed at any addr2line-compatible executable: - `-a`: path to a custom symbol resolution tool (e.g. `llvm-addr2line` or a cross-compilation toolchain's `addr2line`). Works on all platforms and takes precedence over the platform default (DbgHelp on Windows, the `addr2line` found in `PATH` elsewhere). Path-like values are validated up front so a wrong path fails with an actionable message instead of a cryptic, localized shell error. - `-A`: extra arguments passed verbatim to the tool, e.g. `--relative-address` so `llvm-addr2line`/`llvm-symbolizer` accept the image-relative offsets Tracy records for images with a non-zero preferred base (PE, Mach-O). - `-v`: verbose output while patching symbols.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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<std::string>& pathSubstitutionsStrings, bool verbose )
|
||||
void PatchSymbols( tracy::Worker& worker, const std::vector<std::string>& 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<std::string>& pathSu
|
||||
}
|
||||
}
|
||||
|
||||
if ( !PatchSymbolsWithRegex(worker, pathSubstitutionList, verbose) )
|
||||
if ( !PatchSymbolsWithRegex(worker, pathSubstitutionList, addr2lineToolPath, addr2lineArgs, verbose) )
|
||||
{
|
||||
std::cerr << "Failed to patch symbols" << std::endl;
|
||||
}
|
||||
|
||||
@@ -29,12 +29,35 @@ struct SymbolEntry
|
||||
|
||||
using SymbolEntryList = std::vector<SymbolEntry>;
|
||||
|
||||
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<std::string>& 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<std::string>& pathSubstitutionsStrings,
|
||||
const std::string& addr2lineToolPath = std::string(),
|
||||
const std::string& addr2lineArgs = std::string(), bool verbose = false );
|
||||
|
||||
using PathSubstitutionList = std::vector<std::pair<std::regex, std::string> >;
|
||||
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__
|
||||
@@ -1,5 +1,3 @@
|
||||
#ifndef _WIN32
|
||||
|
||||
#include "OfflineSymbolResolver.h"
|
||||
|
||||
#include <fstream>
|
||||
@@ -10,6 +8,11 @@
|
||||
#include <memory>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# define popen _popen
|
||||
# define pclose _pclose
|
||||
#endif
|
||||
|
||||
std::string ExecShellCommand( const char* cmd )
|
||||
{
|
||||
std::array<char, 128> 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
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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<std::string> 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>( tracy::FileWrite::Open( output, clev, zstdLevel, streams ) );
|
||||
if( !w )
|
||||
|
||||
Reference in New Issue
Block a user