diff --git a/tracy-edit/src/OfflineSymbolResolver.cpp b/tracy-edit/src/OfflineSymbolResolver.cpp index 5f0e6630..3788e8b8 100644 --- a/tracy-edit/src/OfflineSymbolResolver.cpp +++ b/tracy-edit/src/OfflineSymbolResolver.cpp @@ -11,6 +11,19 @@ #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 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 ); } -bool PatchSymbols(SymbolResolver* resolver, tracy::Worker& worker, bool verbose) +bool PatchSymbols(SymbolResolver* resolver, tracy::Worker& worker, + const PathSubstitutionList& pathSubstituionlist, bool verbose) { if( !resolver ) { @@ -72,48 +86,57 @@ bool PatchSymbols(SymbolResolver* resolver, tracy::Worker& worker, bool verbose) imageItEnd = entriesPerImageIdx.end(); imageIt != imageItEnd; ++imageIt ) { tracy::StringIdx imageIdx( imageIt->first ); - const char* imageName = worker.GetString( imageIdx ); + std::string imagePath = worker.GetString( imageIdx ); FrameEntryList& entries = imageIt->second; - - std::cout << "Resolving " << entries.size() << " symbols for image: '" << imageName << "'" << std::endl; - - if(!entries.size()) + if (!entries.size()) { 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; - ResolveSymbols( resolver, imageName, entries, resolvedEntries ); + ResolveSymbols( resolver, imagePath, entries, resolvedEntries ); 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; } // 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]; const SymbolEntry& symbolEntry = resolvedEntries[i]; tracy::CallstackFrame& frame = *frameEntry.frame; - if(!symbolEntry.name.length()) - continue; - - if(verbose) + if (!symbolEntry.name.length()) { - const char* nameStr = worker.GetString(frame.name); - std::cout << "patching '" << nameStr << "' of '" << imageName << "' -> '" << symbolEntry.name << "'" << std::endl; + continue; } - 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); - 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; } } diff --git a/tracy-edit/src/OfflineSymbolResolver.h b/tracy-edit/src/OfflineSymbolResolver.h index 584e9179..26d483a2 100644 --- a/tracy-edit/src/OfflineSymbolResolver.h +++ b/tracy-edit/src/OfflineSymbolResolver.h @@ -3,6 +3,7 @@ #include #include +#include namespace tracy { @@ -32,10 +33,13 @@ struct SymbolEntry using SymbolEntryList = std::vector; -bool ResolveSymbols(SymbolResolver* resolver, const char* imageName, +bool ResolveSymbols(SymbolResolver* resolver, const std::string& imagePath, const FrameEntryList& inputEntryList, SymbolEntryList& resolvedEntries); -bool PatchSymbols(SymbolResolver* resolver, tracy::Worker& worker, bool verbose = false); +using PathSubstitutionList = std::vector >; + +bool PatchSymbols(SymbolResolver* resolver, tracy::Worker& worker, + const PathSubstitutionList& pathSubstituionlist, bool verbose = false); #endif // __SYMBOLRESOLVER_HPP__ \ No newline at end of file diff --git a/tracy-edit/src/OfflineSymbolResolverAddr2Line.cpp b/tracy-edit/src/OfflineSymbolResolverAddr2Line.cpp index 5e93e242..b35b65b5 100644 --- a/tracy-edit/src/OfflineSymbolResolverAddr2Line.cpp +++ b/tracy-edit/src/OfflineSymbolResolverAddr2Line.cpp @@ -33,18 +33,18 @@ public: : m_addr2LinePath(addr2linePath) {} - bool ResolveSymbols(const char* imageName, const FrameEntryList& inputEntryList, + bool ResolveSymbols(const std::string& imagePath, const FrameEntryList& inputEntryList, SymbolEntryList& resolvedEntries) { // generate a single addr2line cmd line for all addresses in one invocation std::stringstream ss; - ss << m_addr2LinePath << " -C -f -e " << imageName << " -a "; - for (const FrameEntry& entry : inputEntryList) + ss << m_addr2LinePath << " -C -f -e " << imagePath << " -a "; + for ( const FrameEntry& entry : inputEntryList ) { 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); //printf("executing: '%s' got '%s'\n", ss.str().c_str(), result.str().c_str()); @@ -53,15 +53,15 @@ public: // symbol_name // 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]; SymbolEntry newEntry; std::string addr; - std::getline(result, addr); - std::getline(result, newEntry.name); + std::getline( result, addr ); + std::getline( result, newEntry.name ); if (newEntry.name == "??") { newEntry.name = "[unknown] + " + std::to_string(inputEntry.symbolOffset); @@ -69,19 +69,19 @@ public: std::string fileLine; std::getline(result, fileLine); - if (fileLine != "??:?") + if ( fileLine != "??:?" ) { size_t pos = fileLine.find_last_of(':'); - if (pos != std::string::npos) + if ( pos != std::string::npos ) { - newEntry.file = fileLine.substr(0, pos); - std::string lineStr = fileLine.substr(pos + 1); + newEntry.file = fileLine.substr( 0, pos ); + std::string lineStr = fileLine.substr( pos + 1 ); 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; @@ -93,9 +93,9 @@ private: SymbolResolver* CreateResolver() { - std::stringstream result(ExecShellCommand("which addr2line")); + std::stringstream result( ExecShellCommand("which addr2line") ); std::string addr2LinePath; - std::getline(result, addr2LinePath); + std::getline( result, addr2LinePath ); if(!addr2LinePath.length()) { @@ -111,12 +111,12 @@ void DestroySymbolResolver(SymbolResolver* resolver) delete resolver; } -bool ResolveSymbols(SymbolResolver* resolver, const char* imageName, +bool ResolveSymbols(SymbolResolver* resolver, const std::string& imagePath, const FrameEntryList& inputEntryList, SymbolEntryList& resolvedEntries) { if (resolver) { - return resolver->ResolveSymbols(imageName, inputEntryList, resolvedEntries); + return resolver->ResolveSymbols( imagePath, inputEntryList, resolvedEntries ); } return false; } diff --git a/tracy-edit/src/OfflineSymbolResolverDbgHelper.cpp b/tracy-edit/src/OfflineSymbolResolverDbgHelper.cpp index 40b96cd3..7f5b7c04 100644 --- a/tracy-edit/src/OfflineSymbolResolverDbgHelper.cpp +++ b/tracy-edit/src/OfflineSymbolResolverDbgHelper.cpp @@ -39,13 +39,13 @@ public: SymCleanup( m_procHandle ); } - bool ResolveSymbolsForModule(const char* fileName, const FrameEntryList& inputEntryList, + bool ResolveSymbolsForModule(const std::string& imagePath, const FrameEntryList& inputEntryList, 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) { - std::cerr << "SymLoadModuleEx() failed for module " << fileName + std::cerr << "SymLoadModuleEx() failed for module " << imagePath << ": " << GetLastErrorString() << std::endl; return false; } @@ -128,13 +128,13 @@ void DestroySymbolResolver(SymbolResolver* resolver) delete resolver; } -bool ResolveSymbols(SymbolResolver* resolver, const char* imageName, +bool ResolveSymbols(SymbolResolver* resolver, const std::string& imagePath, const FrameEntryList& inputEntryList, SymbolEntryList& resolvedEntries) { if( resolver ) { - return resolver->ResolveSymbolsForModule( imageName, inputEntryList, resolvedEntries ); + return resolver->ResolveSymbolsForModule( imagePath, inputEntryList, resolvedEntries ); } return false; } diff --git a/tracy-edit/src/tracy-edit.cpp b/tracy-edit/src/tracy-edit.cpp index dcf8a251..de2e07a2 100644 --- a/tracy-edit/src/tracy-edit.cpp +++ b/tracy-edit/src/tracy-edit.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -22,6 +23,7 @@ struct Args const char* outputTracyPath = nullptr; bool verbose = false; bool resolveSymbols = false; + std::vector pathSubstitutions; tracy::FileWrite::Compression compressionType = tracy::FileWrite::Compression::Zstd; int compressionLevel = 5; }; @@ -30,13 +32,14 @@ void PrintUsageAndExit() { std::cerr << "Modify a tracy file" << std::endl; std::cerr << "Usage:" << std::endl; - std::cerr << " extract [OPTION...] " << std::endl; + std::cerr << " tracy-edit [OPTION...] " << std::endl; std::cerr << std::endl; - std::cerr << " -h, --help Print usage" << std::endl; - std::cerr << " -v, --verbose Enable verbose logging" << std::endl; - std::cerr << " -r, --resolveSymbols Resolve symbols and patch callstack frames" << std::endl; - std::cerr << " -c, --compression arg Compress output with the given compression algo" << std::endl; - std::cerr << " -l, --compressesionLevel arg Level of compression" << std::endl; + std::cerr << " -h, --help Print usage" << std::endl; + std::cerr << " -v, --verbose Enable verbose logging" << std::endl; + std::cerr << " -r, --resolveSymbols Resolve symbols and patch callstack frames" << std::endl; + std::cerr << " -s, --substitutePath \"REGEX_MATCH;REPLACEMENT\" Substitute symbol resolution path with an alternative" << 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 ); } @@ -74,13 +77,14 @@ Args ParseArgs( int argc, char** argv ) { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "resolveSymbols", no_argument, NULL, 'r' }, + { "substitutePath", required_argument, NULL, 's' }, { "compression", required_argument, NULL, 'c' }, { "compressesionLevel", required_argument, NULL, 'l' }, { NULL, 0, NULL, 0 } }; int c; - while ( (c = getopt_long( argc, argv, "hvrc:l:", long_opts, NULL )) != -1 ) + while ( (c = getopt_long( argc, argv, "hvrc:l:s:", long_opts, NULL )) != -1 ) { switch (c) { @@ -96,6 +100,9 @@ Args ParseArgs( int argc, char** argv ) case 'c': args.compressionType = getCompressionFromString( optarg ); break; + case 's': + args.pathSubstitutions.push_back( optarg ); + break; case 'l': args.compressionLevel = atoi( optarg ); break; @@ -116,6 +123,8 @@ Args ParseArgs( int argc, char** argv ) return args; } +void PatchSymbols(tracy::Worker& worker, const Args& args); + int main( int argc, char** argv ) { #ifdef _WIN32 @@ -144,21 +153,9 @@ int main( int argc, char** argv ) std::cout << "Loaded." << std::endl; - // attempt to resolve symbols only if requested if(args.resolveSymbols) { - std::cout << "Resolving and patching symbols..." << std::endl; - - SymbolResolver* resolver = CreateResolver(); - if(!resolver) - { - std::cerr << "Failed to create symbol resolver - skipping resolving" << std::endl; - } - else - { - PatchSymbols(resolver, worker); - DestroySymbolResolver(resolver); - } + PatchSymbols(worker, args); } // save out capture file with new compression options @@ -179,3 +176,45 @@ int main( int argc, char** argv ) 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; + } +}