Add support for symbol path replacement using regex in tracy-edit

This commit is contained in:
Tiago Rodrigues 2023-11-17 21:07:25 -05:00 committed by trodrigues
parent fe0e5f3358
commit 0491cad49a
5 changed files with 128 additions and 62 deletions

View File

@ -11,6 +11,19 @@
#include "OfflineSymbolResolver.h" #include "OfflineSymbolResolver.h"
bool ApplyPathSubstitutions(std::string& path, const PathSubstitutionList& pathSubstituionlist)
{
for( const auto& substituion : pathSubstituionlist )
{
if( std::regex_match(path, substituion.first) )
{
path = std::regex_replace( path, substituion.first, substituion.second );
return true;
}
}
return false;
}
// TODO: use string hash map to reduce duplication or use some worker string internal hashing // TODO: use string hash map to reduce duplication or use some worker string internal hashing
tracy::StringIdx AddSymbolString(tracy::Worker& worker, const char* str) tracy::StringIdx AddSymbolString(tracy::Worker& worker, const char* str)
{ {
@ -18,7 +31,8 @@ tracy::StringIdx AddSymbolString(tracy::Worker& worker, const char* str)
return tracy::StringIdx( newStringIdx ); return tracy::StringIdx( newStringIdx );
} }
bool PatchSymbols(SymbolResolver* resolver, tracy::Worker& worker, bool verbose) bool PatchSymbols(SymbolResolver* resolver, tracy::Worker& worker,
const PathSubstitutionList& pathSubstituionlist, bool verbose)
{ {
if( !resolver ) if( !resolver )
{ {
@ -72,48 +86,57 @@ bool PatchSymbols(SymbolResolver* resolver, tracy::Worker& worker, bool verbose)
imageItEnd = entriesPerImageIdx.end(); imageIt != imageItEnd; ++imageIt ) imageItEnd = entriesPerImageIdx.end(); imageIt != imageItEnd; ++imageIt )
{ {
tracy::StringIdx imageIdx( imageIt->first ); tracy::StringIdx imageIdx( imageIt->first );
const char* imageName = worker.GetString( imageIdx ); std::string imagePath = worker.GetString( imageIdx );
FrameEntryList& entries = imageIt->second; FrameEntryList& entries = imageIt->second;
if (!entries.size())
std::cout << "Resolving " << entries.size() << " symbols for image: '" << imageName << "'" << std::endl;
if(!entries.size())
{ {
continue; continue;
} }
std::cout << "Resolving " << entries.size() << " symbols for image: '"
<< imagePath << "'" << std::endl;
const bool substituted = ApplyPathSubstitutions(imagePath, pathSubstituionlist);
if (substituted)
{
std::cout << "\tPath substituted to: '" << imagePath << "'" << std::endl;
}
SymbolEntryList resolvedEntries; SymbolEntryList resolvedEntries;
ResolveSymbols( resolver, imageName, entries, resolvedEntries ); ResolveSymbols( resolver, imagePath, entries, resolvedEntries );
if( resolvedEntries.size() != entries.size() ) if( resolvedEntries.size() != entries.size() )
{ {
std::cerr << "ERROR: failed to resolve all entries! (got: " << resolvedEntries.size() << ")" << std::endl; std::cerr << " failed to resolve all entries! (got: "
<< resolvedEntries.size() << ")" << std::endl;
continue; continue;
} }
// finally patch the string with the resolved symbol data // finally patch the string with the resolved symbol data
for (size_t i = 0; i < resolvedEntries.size(); ++i) for ( size_t i = 0; i < resolvedEntries.size(); ++i )
{ {
FrameEntry& frameEntry = entries[i]; FrameEntry& frameEntry = entries[i];
const SymbolEntry& symbolEntry = resolvedEntries[i]; const SymbolEntry& symbolEntry = resolvedEntries[i];
tracy::CallstackFrame& frame = *frameEntry.frame; tracy::CallstackFrame& frame = *frameEntry.frame;
if(!symbolEntry.name.length()) if (!symbolEntry.name.length())
continue;
if(verbose)
{ {
const char* nameStr = worker.GetString(frame.name); continue;
std::cout << "patching '" << nameStr << "' of '" << imageName << "' -> '" << symbolEntry.name << "'" << std::endl;
} }
frame.name = AddSymbolString(worker, symbolEntry.name.c_str()); if( verbose )
{
const char* nameStr = worker.GetString( frame.name );
std::cout << "patching '" << nameStr << "' of '" << imagePath
<< "' -> '" << symbolEntry.name << "'" << std::endl;
}
frame.name = AddSymbolString( worker, symbolEntry.name.c_str() );
const char* newName = worker.GetString(frame.name); const char* newName = worker.GetString(frame.name);
if(symbolEntry.file.length()) if( symbolEntry.file.length() )
{ {
frame.file = AddSymbolString(worker, symbolEntry.file.c_str()); frame.file = AddSymbolString( worker, symbolEntry.file.c_str() );
frame.line = symbolEntry.line; frame.line = symbolEntry.line;
} }
} }

View File

@ -3,6 +3,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <regex>
namespace tracy namespace tracy
{ {
@ -32,10 +33,13 @@ struct SymbolEntry
using SymbolEntryList = std::vector<SymbolEntry>; using SymbolEntryList = std::vector<SymbolEntry>;
bool ResolveSymbols(SymbolResolver* resolver, const char* imageName, bool ResolveSymbols(SymbolResolver* resolver, const std::string& imagePath,
const FrameEntryList& inputEntryList, const FrameEntryList& inputEntryList,
SymbolEntryList& resolvedEntries); SymbolEntryList& resolvedEntries);
bool PatchSymbols(SymbolResolver* resolver, tracy::Worker& worker, bool verbose = false); using PathSubstitutionList = std::vector<std::pair<std::regex, std::string> >;
bool PatchSymbols(SymbolResolver* resolver, tracy::Worker& worker,
const PathSubstitutionList& pathSubstituionlist, bool verbose = false);
#endif // __SYMBOLRESOLVER_HPP__ #endif // __SYMBOLRESOLVER_HPP__

View File

@ -33,18 +33,18 @@ public:
: m_addr2LinePath(addr2linePath) : m_addr2LinePath(addr2linePath)
{} {}
bool ResolveSymbols(const char* imageName, const FrameEntryList& inputEntryList, bool ResolveSymbols(const std::string& imagePath, const FrameEntryList& inputEntryList,
SymbolEntryList& resolvedEntries) SymbolEntryList& resolvedEntries)
{ {
// generate a single addr2line cmd line for all addresses in one invocation // generate a single addr2line cmd line for all addresses in one invocation
std::stringstream ss; std::stringstream ss;
ss << m_addr2LinePath << " -C -f -e " << imageName << " -a "; ss << m_addr2LinePath << " -C -f -e " << imagePath << " -a ";
for (const FrameEntry& entry : inputEntryList) for ( const FrameEntry& entry : inputEntryList )
{ {
ss << " 0x" << std::hex << entry.symbolOffset; ss << " 0x" << std::hex << entry.symbolOffset;
} }
std::string resultStr = ExecShellCommand(ss.str().c_str()); std::string resultStr = ExecShellCommand( ss.str().c_str() );
std::stringstream result(resultStr); std::stringstream result(resultStr);
//printf("executing: '%s' got '%s'\n", ss.str().c_str(), result.str().c_str()); //printf("executing: '%s' got '%s'\n", ss.str().c_str(), result.str().c_str());
@ -53,15 +53,15 @@ public:
// symbol_name // symbol_name
// file:line // file:line
for (size_t i = 0; i < inputEntryList.size(); ++i) for( size_t i = 0; i < inputEntryList.size(); ++i )
{ {
const FrameEntry& inputEntry = inputEntryList[i]; const FrameEntry& inputEntry = inputEntryList[i];
SymbolEntry newEntry; SymbolEntry newEntry;
std::string addr; std::string addr;
std::getline(result, addr); std::getline( result, addr );
std::getline(result, newEntry.name); std::getline( result, newEntry.name );
if (newEntry.name == "??") if (newEntry.name == "??")
{ {
newEntry.name = "[unknown] + " + std::to_string(inputEntry.symbolOffset); newEntry.name = "[unknown] + " + std::to_string(inputEntry.symbolOffset);
@ -69,19 +69,19 @@ public:
std::string fileLine; std::string fileLine;
std::getline(result, fileLine); std::getline(result, fileLine);
if (fileLine != "??:?") if ( fileLine != "??:?" )
{ {
size_t pos = fileLine.find_last_of(':'); size_t pos = fileLine.find_last_of(':');
if (pos != std::string::npos) if ( pos != std::string::npos )
{ {
newEntry.file = fileLine.substr(0, pos); newEntry.file = fileLine.substr( 0, pos );
std::string lineStr = fileLine.substr(pos + 1); std::string lineStr = fileLine.substr( pos + 1 );
char* after = nullptr; char* after = nullptr;
newEntry.line = strtol(lineStr.c_str(), &after, 10); newEntry.line = strtol( lineStr.c_str(), &after, 10 );
} }
} }
resolvedEntries.push_back(std::move(newEntry)); resolvedEntries.push_back( std::move(newEntry) );
} }
return true; return true;
@ -93,9 +93,9 @@ private:
SymbolResolver* CreateResolver() SymbolResolver* CreateResolver()
{ {
std::stringstream result(ExecShellCommand("which addr2line")); std::stringstream result( ExecShellCommand("which addr2line") );
std::string addr2LinePath; std::string addr2LinePath;
std::getline(result, addr2LinePath); std::getline( result, addr2LinePath );
if(!addr2LinePath.length()) if(!addr2LinePath.length())
{ {
@ -111,12 +111,12 @@ void DestroySymbolResolver(SymbolResolver* resolver)
delete resolver; delete resolver;
} }
bool ResolveSymbols(SymbolResolver* resolver, const char* imageName, bool ResolveSymbols(SymbolResolver* resolver, const std::string& imagePath,
const FrameEntryList& inputEntryList, SymbolEntryList& resolvedEntries) const FrameEntryList& inputEntryList, SymbolEntryList& resolvedEntries)
{ {
if (resolver) if (resolver)
{ {
return resolver->ResolveSymbols(imageName, inputEntryList, resolvedEntries); return resolver->ResolveSymbols( imagePath, inputEntryList, resolvedEntries );
} }
return false; return false;
} }

View File

@ -39,13 +39,13 @@ public:
SymCleanup( m_procHandle ); SymCleanup( m_procHandle );
} }
bool ResolveSymbolsForModule(const char* fileName, const FrameEntryList& inputEntryList, bool ResolveSymbolsForModule(const std::string& imagePath, const FrameEntryList& inputEntryList,
SymbolEntryList& resolvedEntries) SymbolEntryList& resolvedEntries)
{ {
ULONG64 moduleBase = SymLoadModuleEx( m_procHandle, NULL, fileName, NULL, 0, 0, NULL, 0 ); ULONG64 moduleBase = SymLoadModuleEx( m_procHandle, NULL, imagePath.c_str(), NULL, 0, 0, NULL, 0);
if (!moduleBase) if (!moduleBase)
{ {
std::cerr << "SymLoadModuleEx() failed for module " << fileName std::cerr << "SymLoadModuleEx() failed for module " << imagePath
<< ": " << GetLastErrorString() << std::endl; << ": " << GetLastErrorString() << std::endl;
return false; return false;
} }
@ -128,13 +128,13 @@ void DestroySymbolResolver(SymbolResolver* resolver)
delete resolver; delete resolver;
} }
bool ResolveSymbols(SymbolResolver* resolver, const char* imageName, bool ResolveSymbols(SymbolResolver* resolver, const std::string& imagePath,
const FrameEntryList& inputEntryList, const FrameEntryList& inputEntryList,
SymbolEntryList& resolvedEntries) SymbolEntryList& resolvedEntries)
{ {
if( resolver ) if( resolver )
{ {
return resolver->ResolveSymbolsForModule( imageName, inputEntryList, resolvedEntries ); return resolver->ResolveSymbolsForModule( imagePath, inputEntryList, resolvedEntries );
} }
return false; return false;
} }

View File

@ -4,6 +4,7 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <regex>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -22,6 +23,7 @@ struct Args
const char* outputTracyPath = nullptr; const char* outputTracyPath = nullptr;
bool verbose = false; bool verbose = false;
bool resolveSymbols = false; bool resolveSymbols = false;
std::vector<std::string> pathSubstitutions;
tracy::FileWrite::Compression compressionType = tracy::FileWrite::Compression::Zstd; tracy::FileWrite::Compression compressionType = tracy::FileWrite::Compression::Zstd;
int compressionLevel = 5; int compressionLevel = 5;
}; };
@ -30,13 +32,14 @@ void PrintUsageAndExit()
{ {
std::cerr << "Modify a tracy file" << std::endl; std::cerr << "Modify a tracy file" << std::endl;
std::cerr << "Usage:" << std::endl; std::cerr << "Usage:" << std::endl;
std::cerr << " extract [OPTION...] <input trace file> <output tracy file>" << std::endl; std::cerr << " tracy-edit [OPTION...] <input trace file> <output tracy file>" << std::endl;
std::cerr << std::endl; std::cerr << std::endl;
std::cerr << " -h, --help Print usage" << std::endl; std::cerr << " -h, --help Print usage" << std::endl;
std::cerr << " -v, --verbose Enable verbose logging" << std::endl; std::cerr << " -v, --verbose Enable verbose logging" << std::endl;
std::cerr << " -r, --resolveSymbols Resolve symbols and patch callstack frames" << std::endl; std::cerr << " -r, --resolveSymbols Resolve symbols and patch callstack frames" << std::endl;
std::cerr << " -c, --compression arg Compress output with the given compression algo" << std::endl; std::cerr << " -s, --substitutePath \"REGEX_MATCH;REPLACEMENT\" Substitute symbol resolution path with an alternative" << std::endl;
std::cerr << " -l, --compressesionLevel arg Level of compression" << std::endl; std::cerr << " -c, --compression Type Set the compression algorithm used for the output [Fast,Slow,Extreme,Zstd]" << std::endl;
std::cerr << " -l, --compressesionLevel Level Level of compression" << std::endl;
exit( 1 ); exit( 1 );
} }
@ -74,13 +77,14 @@ Args ParseArgs( int argc, char** argv )
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ "verbose", no_argument, NULL, 'v' }, { "verbose", no_argument, NULL, 'v' },
{ "resolveSymbols", no_argument, NULL, 'r' }, { "resolveSymbols", no_argument, NULL, 'r' },
{ "substitutePath", required_argument, NULL, 's' },
{ "compression", required_argument, NULL, 'c' }, { "compression", required_argument, NULL, 'c' },
{ "compressesionLevel", required_argument, NULL, 'l' }, { "compressesionLevel", required_argument, NULL, 'l' },
{ NULL, 0, NULL, 0 } { NULL, 0, NULL, 0 }
}; };
int c; int c;
while ( (c = getopt_long( argc, argv, "hvrc:l:", long_opts, NULL )) != -1 ) while ( (c = getopt_long( argc, argv, "hvrc:l:s:", long_opts, NULL )) != -1 )
{ {
switch (c) switch (c)
{ {
@ -96,6 +100,9 @@ Args ParseArgs( int argc, char** argv )
case 'c': case 'c':
args.compressionType = getCompressionFromString( optarg ); args.compressionType = getCompressionFromString( optarg );
break; break;
case 's':
args.pathSubstitutions.push_back( optarg );
break;
case 'l': case 'l':
args.compressionLevel = atoi( optarg ); args.compressionLevel = atoi( optarg );
break; break;
@ -116,6 +123,8 @@ Args ParseArgs( int argc, char** argv )
return args; return args;
} }
void PatchSymbols(tracy::Worker& worker, const Args& args);
int main( int argc, char** argv ) int main( int argc, char** argv )
{ {
#ifdef _WIN32 #ifdef _WIN32
@ -144,21 +153,9 @@ int main( int argc, char** argv )
std::cout << "Loaded." << std::endl; std::cout << "Loaded." << std::endl;
// attempt to resolve symbols only if requested
if(args.resolveSymbols) if(args.resolveSymbols)
{ {
std::cout << "Resolving and patching symbols..." << std::endl; PatchSymbols(worker, args);
SymbolResolver* resolver = CreateResolver();
if(!resolver)
{
std::cerr << "Failed to create symbol resolver - skipping resolving" << std::endl;
}
else
{
PatchSymbols(resolver, worker);
DestroySymbolResolver(resolver);
}
} }
// save out capture file with new compression options // save out capture file with new compression options
@ -179,3 +176,45 @@ int main( int argc, char** argv )
return 0; return 0;
} }
void PatchSymbols(tracy::Worker& worker, const Args& args)
{
std::cout << "Resolving and patching symbols..." << std::endl;
PathSubstitutionList pathSubstitutionList;
for (const std::string& pathSubst : args.pathSubstitutions)
{
std::size_t pos = pathSubst.find(';');
if (pos == std::string::npos)
{
std::cerr << "Ignoring invalid path substitution: '" << pathSubst
<< " '(please separate the regex of the string to replace with a ';')" << std::endl;
continue;
}
try
{
std::regex reg(pathSubst.substr(0, pos));
std::string replacementStr(pathSubst.substr(pos + 1));
pathSubstitutionList.push_back(std::pair(reg, replacementStr));
}
catch (std::exception& e)
{
std::cerr << "Ignoring invalid path substitution: '" << pathSubst
<< "' (" << e.what() << ")" << std::endl;
continue;
}
}
SymbolResolver* resolver = CreateResolver();
if (resolver)
{
PatchSymbols(resolver, worker, pathSubstitutionList, args.verbose);
DestroySymbolResolver(resolver);
}
else
{
std::cerr << "Failed to create symbol resolver - skipping resolving" << std::endl;
}
}