#include "common/StackTrace.h" #include #include #include #include #include #include #include #include #include #include #include #include #define perr std::cerr // Detect the OS // clang-format off #if defined( WIN32 ) || defined( _WIN32 ) || defined( WIN64 ) || defined( _WIN64 ) || defined( _MSC_VER ) #define USE_WINDOWS #define NOMINMAX #elif defined( __APPLE__ ) #define USE_MAC #define USE_NM #elif defined( __linux ) || defined( __linux__ ) || defined( __unix ) || defined( __posix ) #define USE_LINUX #define USE_NM #else #error Unknown OS #endif // clang-format on // Include system dependent headers // clang-format off // Detect the OS and include system dependent headers #ifdef USE_WINDOWS #include #include #include #include #include #include #include #include #pragma comment( lib, "version.lib" ) // for "VerQueryValue" #else #include #include #include #include #include #include #include #endif #ifdef USE_MAC #include #include #include #include #endif // clang-format on #ifdef __GNUC__ #define USE_ABI #include #endif #ifndef NULL_USE #define NULL_USE( variable ) \ do { \ if ( 0 ) { \ char *temp = (char *) &variable; \ temp++; \ } \ } while ( 0 ) #endif // Set the callstack signal #ifdef SIGRTMIN #define CALLSTACK_SIG SIGRTMIN + 4 #else #define CALLSTACK_SIG SIGUSR1 #define SIGRTMIN SIGUSR1 #define SIGRTMAX SIGUSR1 #endif // Helper thread static std::shared_ptr globalMonitorThread; // Utility to break a string by a newline static inline std::vector breakString( const std::string &str ) { std::vector strvec; size_t i1 = 0; size_t i2 = std::min( str.find( '\n', i1 ), str.length() ); while ( i1 < str.length() ) { strvec.push_back( str.substr( i1, i2 - i1 ) ); i1 = i2 + 1; i2 = std::min( str.find( '\n', i1 ), str.length() ); } return strvec; } // Function to replace all instances of a string with another static inline void strrep( std::string &str, const std::string &s, const std::string &r ) { size_t i = 0; while ( i < str.length() ) { i = str.find( s, i ); if ( i == std::string::npos ) { break; } str.replace( i, s.length(), r ); i += r.length(); } } // Utility to strip the path from a filename static inline std::string stripPath( const std::string &filename ) { if ( filename.empty() ) return std::string(); int i = 0; for ( i = (int) filename.size() - 1; i >= 0 && filename[i] != 47 && filename[i] != 92; i-- ) { } i = std::max( 0, i + 1 ); return filename.substr( i ); } // Inline function to subtract two addresses returning the absolute difference static inline void *subtractAddress( void *a, void *b ) { return reinterpret_cast( std::abs( reinterpret_cast( a ) - reinterpret_cast( b ) ) ); } #ifdef USE_WINDOWS static BOOL __stdcall readProcMem( HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead ) { SIZE_T st; BOOL bRet = ReadProcessMemory( hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, &st ); *lpNumberOfBytesRead = (DWORD) st; return bRet; } static inline std::string getCurrentDirectory() { char temp[1024] = { 0 }; GetCurrentDirectoryA( sizeof( temp ), temp ); return temp; } namespace StackTrace { BOOL GetModuleListTH32( HANDLE hProcess, DWORD pid ); BOOL GetModuleListPSAPI( HANDLE hProcess ); DWORD LoadModule( HANDLE hProcess, LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size ); void LoadModules(); }; // namespace StackTrace #endif // Functions to copy data static inline char *copy_in( size_t N, const void *data, char *ptr ) { memcpy( ptr, data, N ); return ptr + N; } static inline const char *copy_out( size_t N, void *data, const char *ptr ) { memcpy( data, ptr, N ); return ptr + N; } /**************************************************************************** * Utility to call system command and return output * ****************************************************************************/ #ifdef USE_WINDOWS #define popen _popen #define pclose _pclose #endif std::string StackTrace::exec( const std::string &cmd, int &code ) { signal( SIGCHLD, SIG_DFL ); // Clear child exited FILE *pipe = popen( cmd.c_str(), "r" ); if ( pipe == nullptr ) return std::string(); std::string result = ""; result.reserve( 1024 ); while ( !feof( pipe ) ) { char buffer[257]; buffer[256] = 0; if ( fgets( buffer, 128, pipe ) != nullptr ) result += buffer; } auto status = pclose( pipe ); code = WEXITSTATUS( status ); return result; } /**************************************************************************** * stack_info * ****************************************************************************/ void StackTrace::stack_info::clear() { address = nullptr; address2 = nullptr; object.clear(); function.clear(); filename.clear(); line = -1; } bool StackTrace::stack_info::operator==( const StackTrace::stack_info &rhs ) const { if ( address == rhs.address ) return true; if ( address2 == rhs.address2 && object == rhs.object ) return true; return false; } bool StackTrace::stack_info::operator!=( const StackTrace::stack_info &rhs ) const { return !operator==( rhs ); } int StackTrace::stack_info::getAddressWidth() const { auto addr = reinterpret_cast( address ); if ( addr <= 0xFFFF ) return 4; if ( addr <= 0xFFFFFFFF ) return 8; if ( addr <= 0xFFFFFFFFFFFF ) return 12; return 16; } std::string StackTrace::stack_info::print( int widthAddress, int widthObject, int widthFunction ) const { char tmp1[64], tmp2[64]; sprintf( tmp1, "0x%%0%illx: ", widthAddress ); sprintf( tmp2, tmp1, reinterpret_cast( address ) ); std::string stack( tmp2 ); sprintf( tmp2, "%i", line ); std::string line_str( tmp2 ); size_t N = stack.length(); stack += stripPath( object ); stack.resize( std::max( stack.size(), N + widthObject ), ' ' ); N = stack.length() + 2; stack += " " + function; if ( !filename.empty() && line > 0 ) { stack.resize( std::max( stack.size(), N + widthFunction ), ' ' ); stack += " " + stripPath( filename ) + ":" + line_str; } else if ( !filename.empty() ) { stack.resize( std::max( stack.size(), N + widthFunction ), ' ' ); stack += " " + stripPath( filename ); } else if ( line > 0 ) { stack += " : " + line_str; } return stack; } size_t StackTrace::stack_info::size() const { return 2 * sizeof( void * ) + 4 * sizeof( int ) + object.size() + function.size() + filename.size(); } char *StackTrace::stack_info::pack( char *ptr ) const { int Nobj = object.size(); int Nfun = function.size(); int Nfile = filename.size(); ptr = copy_in( sizeof( void * ), &address, ptr ); ptr = copy_in( sizeof( void * ), &address2, ptr ); ptr = copy_in( sizeof( int ), &Nobj, ptr ); ptr = copy_in( sizeof( int ), &Nfun, ptr ); ptr = copy_in( sizeof( int ), &Nfile, ptr ); ptr = copy_in( sizeof( int ), &line, ptr ); ptr = copy_in( Nobj, object.data(), ptr ); ptr = copy_in( Nfun, function.data(), ptr ); ptr = copy_in( Nfile, filename.data(), ptr ); return ptr; } const char *StackTrace::stack_info::unpack( const char *ptr ) { int Nobj, Nfun, Nfile; ptr = copy_out( sizeof( void * ), &address, ptr ); ptr = copy_out( sizeof( void * ), &address2, ptr ); ptr = copy_out( sizeof( int ), &Nobj, ptr ); ptr = copy_out( sizeof( int ), &Nfun, ptr ); ptr = copy_out( sizeof( int ), &Nfile, ptr ); ptr = copy_out( sizeof( int ), &line, ptr ); object.resize( Nobj ); function.resize( Nfun ); filename.resize( Nfile ); ptr = copy_out( Nobj, &object.front(), ptr ); ptr = copy_out( Nfun, &function.front(), ptr ); ptr = copy_out( Nfile, &filename.front(), ptr ); return ptr; } std::vector StackTrace::stack_info::packArray( const std::vector &data ) { size_t size = sizeof( int ); for ( const auto &i : data ) size += i.size(); std::vector vec( size, 0 ); char *ptr = vec.data(); int N = data.size(); ptr = copy_in( sizeof( int ), &N, ptr ); for ( const auto &i : data ) ptr = i.pack( ptr ); return vec; } std::vector StackTrace::stack_info::unpackArray( const char *ptr ) { int N; ptr = copy_out( sizeof( int ), &N, ptr ); std::vector data( N ); for ( auto &i : data ) ptr = i.unpack( ptr ); return data; } #ifdef USE_MPI static std::vector pack( const std::vector> &data ) { size_t size = sizeof( int ); for ( const auto &i : data ) { size += sizeof( int ); for ( size_t j = 0; j < i.size(); j++ ) size += i[j].size(); } std::vector out( size, 0 ); char *ptr = out.data(); int N = data.size(); ptr = copy_in( sizeof( int ), &N, ptr ); for ( int i = 0; i < N; i++ ) { int M = data[i].size(); ptr = copy_in( sizeof( int ), &M, ptr ); for ( int j = 0; j < M; j++ ) ptr = data[i][j].pack( ptr ); } return out; } static std::vector> unpack( const std::vector &in ) { const char *ptr = in.data(); int N; ptr = copy_out( sizeof( int ), &N, ptr ); std::vector> data( N ); for ( int i = 0; i < N; i++ ) { int M; ptr = copy_out( sizeof( int ), &M, ptr ); data[i].resize( M ); for ( int j = 0; j < M; j++ ) ptr = data[i][j].unpack( ptr ); } return data; } #endif /**************************************************************************** * multi_stack_info * ****************************************************************************/ StackTrace::multi_stack_info::multi_stack_info( const std::vector &rhs ) { operator=( rhs ); } StackTrace::multi_stack_info &StackTrace::multi_stack_info:: operator=( const std::vector &rhs ) { clear(); if ( rhs.empty() ) return *this; N = 1; stack = rhs[0]; if ( rhs.size() > 1 ) add( rhs.size() - 1, &rhs[1] ); return *this; } void StackTrace::multi_stack_info::clear() { N = 0; stack.clear(); children.clear(); } void StackTrace::multi_stack_info::print2( const std::string &prefix, int w[3], std::vector &text ) const { if ( stack == stack_info() ) { for ( const auto &child : children ) child.print2( "", w, text ); return; } std::string line = prefix + "[" + std::to_string( N ) + "] " + stack.print( w[0], w[1], w[2] ); text.push_back( line ); std::string prefix2 = prefix + " "; for ( size_t i = 0; i < children.size(); i++ ) { const auto &child = children[i]; std::vector text2; child.print2( "", w, text2 ); for ( size_t j = 0; j < text2.size(); j++ ) { std::string line = prefix2 + text2[j]; if ( children.size() > 1 && j > 0 && i < children.size() - 1 ) line[prefix2.size()] = '|'; text.push_back( line ); } } } std::vector StackTrace::multi_stack_info::print( const std::string &prefix ) const { std::vector text; int w[3] = { 0 }; w[0] = getAddressWidth(); w[1] = getObjectWidth(); w[2] = getFunctionWidth(); print2( prefix, w, text ); return text; } int StackTrace::multi_stack_info::getAddressWidth() const { int w = stack.getAddressWidth(); for ( const auto &child : children ) w = std::max( w, child.getAddressWidth() ); return w; } int StackTrace::multi_stack_info::getObjectWidth() const { int w = std::min( stripPath( stack.object ).size() + 1, 20 ); for ( const auto &child : children ) w = std::max( w, child.getObjectWidth() ); return w; } int StackTrace::multi_stack_info::getFunctionWidth() const { int w = std::min( stack.function.size() + 1, 40 ); for ( const auto &child : children ) w = std::max( w, child.getFunctionWidth() ); return w; } void StackTrace::multi_stack_info::add( size_t len, const stack_info *stack ) { if ( len == 0 ) return; const auto &s = stack[len - 1]; for ( auto &i : children ) { if ( i.stack == s ) { i.N++; if ( len > 1 ) i.add( len - 1, stack ); return; } } children.resize( children.size() + 1 ); children.back().N = 1; children.back().stack = s; if ( len > 1 ) children.back().add( len - 1, stack ); } /**************************************************************************** * Function to find an entry * ****************************************************************************/ template inline size_t findfirst( const std::vector &X, TYPE Y ) { if ( X.empty() ) return 0; size_t lower = 0; size_t upper = X.size() - 1; if ( X[lower] >= Y ) return lower; if ( X[upper] < Y ) return upper; while ( ( upper - lower ) != 1 ) { size_t value = ( upper + lower ) / 2; if ( X[value] >= Y ) upper = value; else lower = value; } return upper; } /**************************************************************************** * Function to get the executable name * ****************************************************************************/ static char global_exe_name[1000] = { 0 }; static bool setGlobalExecutableName( char *exe ) { try { #ifdef USE_LINUX auto *buf = new char[0x10000]; int len = ::readlink( "/proc/self/exe", buf, 0x10000 ); if ( len != -1 ) { buf[len] = '\0'; strcpy( exe, buf ); } delete[] buf; #elif defined( USE_MAC ) uint32_t size = 0x10000; char *buf = new char[size]; memset( buf, 0, size ); if ( _NSGetExecutablePath( buf, &size ) == 0 ) strcpy( exe, buf ); delete[] buf; #elif defined( USE_WINDOWS ) DWORD size = 0x10000; char *buf = new char[size]; memset( buf, 0, size ); GetModuleFileName( nullptr, buf, size ); strcpy( exe, buf ); delete[] buf; #endif } catch ( ... ) { } return true; } static bool global_exe_name_set = setGlobalExecutableName( global_exe_name ); std::string StackTrace::getExecutable() { if ( !global_exe_name_set ) global_exe_name_set = setGlobalExecutableName( global_exe_name ); return std::string( global_exe_name ); } /**************************************************************************** * Function to get symbols for the executable from nm (if availible) * * Note: this function maintains an internal cached copy to prevent * * exccessive calls to nm. This function also uses a lock to ensure * * thread safety. * ****************************************************************************/ std::mutex getSymbols_mutex; struct global_symbols_struct { std::vector address; std::vector type; std::vector obj; int error; } global_symbols; static const global_symbols_struct &getSymbols2() { static bool loaded = false; static global_symbols_struct data; // Load the symbol tables if they have not been loaded if ( !loaded ) { getSymbols_mutex.lock(); if ( !loaded ) { loaded = true; #ifdef USE_NM try { char cmd[1024]; #ifdef USE_LINUX sprintf( cmd, "nm -n --demangle %s", global_exe_name ); #elif defined( USE_MAC ) sprintf( cmd, "nm -n %s | c++filt", global_exe_name ); #else #error Unknown OS using nm #endif int code; auto output = breakString( StackTrace::exec( cmd, code ) ); for ( const auto &line : output ) { if ( line.empty() ) continue; if ( line[0] == ' ' ) continue; auto *a = const_cast( line.c_str() ); char *b = strchr( a, ' ' ); if ( b == nullptr ) continue; b[0] = 0; b++; char *c = strchr( b, ' ' ); if ( c == nullptr ) continue; c[0] = 0; c++; char *d = strchr( c, '\n' ); if ( d ) d[0] = 0; size_t add = strtoul( a, nullptr, 16 ); data.address.push_back( reinterpret_cast( add ) ); data.type.push_back( b[0] ); data.obj.emplace_back( c ); } } catch ( ... ) { data.error = -3; } data.error = 0; #else data.error = -1; #endif } getSymbols_mutex.unlock(); } return data; } int StackTrace::getSymbols( std::vector &address, std::vector &type, std::vector &obj ) { const global_symbols_struct &data = getSymbols2(); address = data.address; type = data.type; obj = data.obj; return data.error; } /**************************************************************************** * Function to get call stack info * ****************************************************************************/ #ifdef USE_MAC static void *loadAddress( const std::string &object ) { static std::map obj_map; if ( obj_map.empty() ) { uint32_t numImages = _dyld_image_count(); for ( uint32_t i = 0; i < numImages; i++ ) { const struct mach_header *header = _dyld_get_image_header( i ); const char *name = _dyld_get_image_name( i ); const char *p = strrchr( name, '/' ); struct mach_header *address = const_cast( header ); obj_map.insert( std::pair( p + 1, address ) ); // printf(" module=%s, address=%p\n", p + 1, header); } } auto it = obj_map.find( object ); void *address = 0; if ( it != obj_map.end() ) { address = it->second; } else { it = obj_map.find( stripPath( object ) ); if ( it != obj_map.end() ) address = it->second; } // printf("%s: 0x%016llx\n",object.c_str(),address); return address; } static std::tuple split_atos( const std::string &buf ) { if ( buf.empty() ) return std::tuple(); // Get the function size_t index = buf.find( " (in " ); if ( index == std::string::npos ) return std::make_tuple( buf.substr( 0, buf.length() - 1 ), std::string(), std::string(), 0 ); std::string fun = buf.substr( 0, index ); std::string tmp = buf.substr( index + 5 ); // Get the object index = tmp.find( ')' ); std::string obj = tmp.substr( 0, index ); tmp = tmp.substr( index + 1 ); // Get the filename and line number size_t p1 = tmp.find( '(' ); size_t p2 = tmp.find( ')' ); tmp = tmp.substr( p1 + 1, p2 - p1 - 1 ); index = tmp.find( ':' ); std::string file; int line = 0; if ( index != std::string::npos ) { file = tmp.substr( 0, index ); line = std::stoi( tmp.substr( index + 1 ) ); } else if ( p1 != std::string::npos ) { file = tmp; } return std::make_tuple( fun, obj, file, line ); } #endif #ifdef USE_LINUX using uint_p = uint64_t; #elif defined( USE_MAC ) typedef unsigned long uint_p; #endif #if defined( USE_LINUX ) || defined( USE_MAC ) static inline std::string generateCmd( const std::string &s1, const std::string &s2, const std::string &s3, std::vector addresses, const std::string &s4 ) { std::string cmd = s1 + s2 + s3; for ( auto &addresse : addresses ) { char tmp[32]; sprintf( tmp, "%lx ", reinterpret_cast( addresse ) ); cmd += tmp; } cmd += s4; return cmd; } #endif // clang-format off static void getFileAndLineObject( std::vector &info ) { if ( info.empty() ) return; // This gets the file and line numbers for multiple stack lines in the same object #if defined( USE_LINUX ) // Create the call command std::vector address_list(info.size(),nullptr); for (size_t i=0; iaddress; if ( info[i]->object.find( ".so" ) != std::string::npos ) address_list[i] = info[i]->address2; if ( info[i]->object.find( ".mexa64" ) != std::string::npos ) address_list[i] = info[i]->address2; } std::string cmd = generateCmd( "addr2line -C -e ", info[0]->object, " -f -i ", address_list, " 2> /dev/null" ); // Get the function/line/file int code; auto cmd_output = StackTrace::exec( cmd, code ); auto output = breakString( cmd_output ); if ( output.size() != 2*info.size() ) return; // Add the results to info for (size_t i=0; ifunction.empty() ) info[i]->function = output[2*i+0]; // get file and line const char *buf = output[2*i+1].c_str(); if ( buf[0] != '?' && buf[0] != 0 ) { size_t j = 0; for ( j = 0; j < 4095 && buf[j] != ':'; j++ ) { } info[i]->filename = std::string( buf, j ); info[i]->line = atoi( &buf[j + 1] ); } } #elif defined( USE_MAC ) // Create the call command void* load_address = loadAddress( info[0]->object ); if ( load_address == nullptr ) return; std::vector address_list(info.size(),nullptr); for (size_t i=0; iaddress; // Call atos to get the object info char tmp[64]; sprintf( tmp, " -l %lx ", (uint_p) load_address ); std::string cmd = generateCmd( "atos -o ", info[0]->object, tmp, address_list, " 2> /dev/null" ); // Get the function/line/file int code; auto cmd_output = StackTrace::exec( cmd, code ); auto output = breakString( cmd_output ); if ( output.size() != info.size() ) return; // Parse the output for function, file and line info for ( size_t i=0; ifunction.empty() ) info[i]->function = std::get<0>(data); if ( info[i]->object.empty() ) info[i]->object = std::get<1>(data); if ( info[i]->filename.empty() ) info[i]->filename = std::get<2>(data); if ( info[i]->line==0 ) info[i]->line = std::get<3>(data); } #endif } static void getFileAndLine( std::vector &info ) { // Build a list of stack elements for each object std::map> obj_map; for (auto & i : info) { auto& list = obj_map[i.object]; list.emplace_back( &i ); } // For each object, get the file/line numbers for all entries for ( auto& entry : obj_map ) getFileAndLineObject( entry.second ); } // Try to use the global symbols to decode info about the stack static void getDataFromGlobalSymbols( StackTrace::stack_info &info ) { const global_symbols_struct &data = getSymbols2(); if ( data.error == 0 ) { size_t index = findfirst( global_symbols.address, info.address ); if ( index > 0 ) info.object = global_symbols.obj[index - 1]; else info.object = std::string(global_exe_name); } } static void signal_handler( int sig ) { printf("Signal caught acquiring stack (%i)\n",sig); StackTrace::setErrorHandlers( [](std::string,StackTrace::terminateType) { exit( -1 ); } ); } StackTrace::stack_info StackTrace::getStackInfo( void *address ) { return getStackInfo( std::vector(1,address) )[0]; } std::vector StackTrace::getStackInfo( const std::vector& address ) { // Temporarily handle signals to prevent recursion on the stack auto prev_handler = signal( SIGINT, signal_handler ); // Get the detailed stack info std::vector info(address.size()); try { #ifdef USE_WINDOWS IMAGEHLP_SYMBOL64 pSym[1024]; memset( pSym, 0, sizeof( pSym ) ); pSym->SizeOfStruct = sizeof( IMAGEHLP_SYMBOL64 ); pSym->MaxNameLength = 1024; IMAGEHLP_MODULE64 Module; memset( &Module, 0, sizeof( Module ) ); Module.SizeOfStruct = sizeof( Module ); HANDLE pid = GetCurrentProcess(); for (size_t i=0; i( address[i] ); DWORD64 offsetFromSymbol; if ( SymGetSymFromAddr( pid, address2, &offsetFromSymbol, pSym ) != FALSE ) { char name[8192]={0}; DWORD rtn = UnDecorateSymbolName( pSym->Name, name, sizeof(name)-1, UNDNAME_COMPLETE ); if ( rtn == 0 ) info[i].function = std::string(pSym->Name); else info[i].function = std::string(name); } else { printf( "ERROR: SymGetSymFromAddr (%d,%p)\n", GetLastError(), address2 ); } // Get line number IMAGEHLP_LINE64 Line; memset( &Line, 0, sizeof( Line ) ); Line.SizeOfStruct = sizeof( Line ); DWORD offsetFromLine; if ( SymGetLineFromAddr64( pid, address2, &offsetFromLine, &Line ) != FALSE ) { info[i].line = Line.LineNumber; info[i].filename = std::string( Line.FileName ); } else { info[i].line = 0; info[i].filename = std::string(); } // Get the object if ( SymGetModuleInfo64( pid, address2, &Module ) != FALSE ) { //info[i].object = std::string( Module.ModuleName ); info[i].object = std::string( Module.LoadedImageName ); //info[i].baseOfImage = Module.BaseOfImage; } } #else for (size_t i=0; i(t2-t1).count()<0.15 ) { std::this_thread::yield(); t2 = std::chrono::high_resolution_clock::now(); } count = std::max(thread_backtrace_count,0); memcpy( buffer, thread_backtrace, count*sizeof(void*) ); thread_backtrace_count = -1; thread_backtrace_mutex.unlock(); } #elif defined( USE_WINDOWS ) #if defined(DBGHELP) // Load the modules for the stack trace LoadModules(); // Initialize stackframe for first call ::CONTEXT context; memset( &context, 0, sizeof( context ) ); context.ContextFlags = CONTEXT_FULL; RtlCaptureContext( &context ); STACKFRAME64 frame; // in/out stackframe memset( &frame, 0, sizeof( frame ) ); #ifdef _M_IX86 DWORD imageType = IMAGE_FILE_MACHINE_I386; frame.AddrPC.Offset = context.Eip; frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Offset = context.Ebp; frame.AddrFrame.Mode = AddrModeFlat; frame.AddrStack.Offset = context.Esp; frame.AddrStack.Mode = AddrModeFlat; #elif _M_X64 DWORD imageType = IMAGE_FILE_MACHINE_AMD64; frame.AddrPC.Offset = context.Rip; frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Offset = context.Rsp; frame.AddrFrame.Mode = AddrModeFlat; frame.AddrStack.Offset = context.Rsp; frame.AddrStack.Mode = AddrModeFlat; #elif _M_IA64 DWORD imageType = IMAGE_FILE_MACHINE_IA64; frame.AddrPC.Offset = context.StIIP; frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Offset = context.IntSp; frame.AddrFrame.Mode = AddrModeFlat; frame.AddrBStore.Offset = context.RsBSP; frame.AddrBStore.Mode = AddrModeFlat; frame.AddrStack.Offset = context.IntSp; frame.AddrStack.Mode = AddrModeFlat; #else #error "Platform not supported!" #endif auto pid = GetCurrentProcess(); for ( int frameNum = 0; frameNum<1024; ++frameNum ) { BOOL rtn = StackWalk64( imageType, pid, tid, &frame, &context, readProcMem, SymFunctionTableAccess, SymGetModuleBase64, NULL ); if ( !rtn ) { printf( "ERROR: StackWalk64 (%p)\n", frame.AddrPC.Offset ); break; } if ( frame.AddrPC.Offset != 0 ) { buffer[count] = reinterpret_cast( frame.AddrPC.Offset ) ); count++; } if ( frame.AddrReturn.Offset == 0 ) break; } SetLastError( ERROR_SUCCESS ); #endif #else #warning Stack trace is not supported on this compiler/OS #endif return count; } std::vector StackTrace::backtrace( std::thread::native_handle_type tid ) { std::vector trace( 1000, nullptr ); size_t count = backtrace_thread( tid, trace.data(), trace.size() ); trace.resize(count); return trace; } std::vector StackTrace::backtrace() { std::vector trace( 1000, nullptr ); size_t count = backtrace_thread( thisThread(), trace.data(), trace.size() ); trace.resize(count); return trace; } std::vector> StackTrace::backtraceAll() { // Get the list of threads auto threads = activeThreads( ); // Get the backtrace of each thread std::vector> trace(threads.size()); size_t i = 0; for ( auto it=threads.begin(); i StackTrace::activeThreads( ) { std::set threads; #if defined( USE_LINUX ) std::set tid; int pid = getpid(); char cmd[128]; sprintf( cmd, "ps -T -p %i", pid ); signal( SIGCHLD, SIG_DFL ); // Clear child exited int code; auto output = breakString( exec( cmd, code ) ); for ( const auto& line : output ) { int tid2 = get_tid( pid, line ); if ( tid2 != -1 ) tid.insert( tid2 ); } tid.erase( syscall(SYS_gettid) ); signal( CALLSTACK_SIG, _activeThreads_signal_handler ); for ( auto tid2 : tid ) { thread_backtrace_mutex.lock(); thread_id_finished = false; thread_handle = thisThread(); syscall( SYS_tgkill, pid, tid2, CALLSTACK_SIG ); auto t1 = std::chrono::high_resolution_clock::now(); auto t2 = std::chrono::high_resolution_clock::now(); while ( !thread_id_finished && std::chrono::duration(t2-t1).count()<0.1 ) { std::this_thread::yield(); t2 = std::chrono::high_resolution_clock::now(); } threads.insert( thread_handle ); thread_backtrace_mutex.unlock(); } #elif defined( USE_MAC ) printf("activeThreads not finished\n"); #elif defined( USE_WINDOWS ) HANDLE hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); if( hThreadSnap != INVALID_HANDLE_VALUE ) { // Fill in the size of the structure before using it THREADENTRY32 te32 te32.dwSize = sizeof(THREADENTRY32 ); // Retrieve information about the first thread, and exit if unsuccessful if( !Thread32First( hThreadSnap, &te32 ) ) { printError( TEXT("Thread32First") ); // Show cause of failure CloseHandle( hThreadSnap ); // Must clean up the snapshot object! return( FALSE ); } // Now walk the thread list of the system do { if ( te32.th32OwnerProcessID == dwOwnerPID ) threads.insert( te32.th32ThreadID ); } while( Thread32Next(hThreadSnap, &te32 ) ); CloseHandle( hThreadSnap ); // Must clean up the snapshot object! } #else #warning activeThreads is not yet supported on this compiler/OS #endif threads.insert( thisThread() ); if ( globalMonitorThread ) threads.erase( globalMonitorThread->native_handle() ); return threads; } // clang-format on /**************************************************************************** * Function to get the current call stack * ****************************************************************************/ std::vector StackTrace::getCallStack() { auto trace = StackTrace::backtrace(); auto info = getStackInfo( trace ); return info; } std::vector StackTrace::getCallStack( std::thread::native_handle_type id ) { auto trace = StackTrace::backtrace( id ); auto info = getStackInfo( trace ); return info; } static StackTrace::multi_stack_info generateMultiStack( const std::vector> &thread_backtrace ) { // Get the stack data for all pointers std::set addresses_set; for ( const auto &trace : thread_backtrace ) { for ( auto ptr : trace ) addresses_set.insert( ptr ); } std::vector addresses( addresses_set.begin(), addresses_set.end() ); auto stack_data = StackTrace::getStackInfo( addresses ); std::map map_data; for ( size_t i = 0; i < addresses.size(); i++ ) map_data.insert( std::make_pair( addresses[i], stack_data[i] ) ); // Create the multi-stack trace StackTrace::multi_stack_info multistack; for ( const auto &trace : thread_backtrace ) { if ( trace.empty() ) continue; // Create the stack for the given thread trace std::vector stack( trace.size() ); for ( size_t i = 0; i < trace.size(); i++ ) stack[i] = map_data[trace[i]]; // Add the data to the multistack multistack.add( stack.size(), stack.data() ); } return multistack; } StackTrace::multi_stack_info StackTrace::getAllCallStacks() { // Get the backtrace of each thread auto thread_backtrace = backtraceAll(); // Create the multi-stack strucutre auto stack = generateMultiStack( thread_backtrace ); return stack; } /**************************************************************************** * Function to get system search paths * ****************************************************************************/ std::string StackTrace::getSymPaths() { std::string paths; #ifdef USE_WINDOWS // Create the path list (seperated by ';' ) paths = std::string( ".;" ); paths.reserve( 1000 ); // Add the current directory paths += getCurrentDirectory() + ";"; // Now add the path for the main-module: char temp[1024]; memset( temp, 0, sizeof( temp ) ); if ( GetModuleFileNameA( nullptr, temp, sizeof( temp ) - 1 ) > 0 ) { for ( char *p = ( temp + strlen( temp ) - 1 ); p >= temp; --p ) { // locate the rightmost path separator if ( ( *p == '\\' ) || ( *p == '/' ) || ( *p == ':' ) ) { *p = 0; break; } } if ( strlen( temp ) > 0 ) { paths += temp; paths += ";"; } } memset( temp, 0, sizeof( temp ) ); if ( GetEnvironmentVariableA( "_NT_SYMBOL_PATH", temp, sizeof( temp ) - 1 ) > 0 ) { paths += temp; paths += ";"; } memset( temp, 0, sizeof( temp ) ); if ( GetEnvironmentVariableA( "_NT_ALTERNATE_SYMBOL_PATH", temp, sizeof( temp ) - 1 ) > 0 ) { paths += temp; paths += ";"; } memset( temp, 0, sizeof( temp ) ); if ( GetEnvironmentVariableA( "SYSTEMROOT", temp, sizeof( temp ) - 1 ) > 0 ) { paths += temp; paths += ";"; // also add the "system32"-directory: paths += temp; paths += "\\system32;"; } memset( temp, 0, sizeof( temp ) ); if ( GetEnvironmentVariableA( "SYSTEMDRIVE", temp, sizeof( temp ) - 1 ) > 0 ) { paths += "SRV*;" + std::string( temp ) + "\\websymbols*http://msdl.microsoft.com/download/symbols;"; } else { paths += "SRV*c:\\websymbols*http://msdl.microsoft.com/download/symbols;"; } #endif return paths; } /**************************************************************************** * Load modules for windows * ****************************************************************************/ #ifdef USE_WINDOWS BOOL StackTrace::GetModuleListTH32( HANDLE hProcess, DWORD pid ) { // CreateToolhelp32Snapshot() typedef HANDLE( __stdcall * tCT32S )( DWORD dwFlags, DWORD th32ProcessID ); // Module32First() typedef BOOL( __stdcall * tM32F )( HANDLE hSnapshot, LPMODULEENTRY32 lpme ); // Module32Next() typedef BOOL( __stdcall * tM32N )( HANDLE hSnapshot, LPMODULEENTRY32 lpme ); // try both dlls... const TCHAR *dllname[] = { _T("kernel32.dll"), _T("tlhelp32.dll") }; HINSTANCE hToolhelp = nullptr; tCT32S pCT32S = nullptr; tM32F pM32F = nullptr; tM32N pM32N = nullptr; HANDLE hSnap; MODULEENTRY32 me; me.dwSize = sizeof( me ); for ( size_t i = 0; i < ( sizeof( dllname ) / sizeof( dllname[0] ) ); i++ ) { hToolhelp = LoadLibrary( dllname[i] ); if ( hToolhelp == nullptr ) continue; pCT32S = (tCT32S) GetProcAddress( hToolhelp, "CreateToolhelp32Snapshot" ); pM32F = (tM32F) GetProcAddress( hToolhelp, "Module32First" ); pM32N = (tM32N) GetProcAddress( hToolhelp, "Module32Next" ); if ( ( pCT32S != nullptr ) && ( pM32F != nullptr ) && ( pM32N != nullptr ) ) break; // found the functions! FreeLibrary( hToolhelp ); hToolhelp = nullptr; } if ( hToolhelp == nullptr ) return FALSE; hSnap = pCT32S( TH32CS_SNAPMODULE, pid ); if ( hSnap == (HANDLE) -1 ) { FreeLibrary( hToolhelp ); return FALSE; } bool keepGoing = !!pM32F( hSnap, &me ); int cnt = 0; while ( keepGoing ) { LoadModule( hProcess, me.szExePath, me.szModule, (DWORD64) me.modBaseAddr, me.modBaseSize ); cnt++; keepGoing = !!pM32N( hSnap, &me ); } CloseHandle( hSnap ); FreeLibrary( hToolhelp ); if ( cnt <= 0 ) return FALSE; return TRUE; } DWORD StackTrace::LoadModule( HANDLE hProcess, LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size ) { CHAR *szImg = _strdup( img ); CHAR *szMod = _strdup( mod ); DWORD result = ERROR_SUCCESS; if ( ( szImg == nullptr ) || ( szMod == nullptr ) ) { result = ERROR_NOT_ENOUGH_MEMORY; } else { if ( SymLoadModule( hProcess, 0, szImg, szMod, baseAddr, size ) == 0 ) result = GetLastError(); } ULONGLONG fileVersion = 0; if ( szImg != nullptr ) { // try to retrive the file-version: VS_FIXEDFILEINFO *fInfo = nullptr; DWORD dwHandle; DWORD dwSize = GetFileVersionInfoSizeA( szImg, &dwHandle ); if ( dwSize > 0 ) { LPVOID vData = malloc( dwSize ); if ( vData != nullptr ) { if ( GetFileVersionInfoA( szImg, dwHandle, dwSize, vData ) != 0 ) { UINT len; TCHAR szSubBlock[] = _T("\\"); if ( VerQueryValue( vData, szSubBlock, (LPVOID *) &fInfo, &len ) == 0 ) { fInfo = nullptr; } else { fileVersion = ( (ULONGLONG) fInfo->dwFileVersionLS ) + ( (ULONGLONG) fInfo->dwFileVersionMS << 32 ); } } free( vData ); } } // Retrive some additional-infos about the module IMAGEHLP_MODULE64 Module; Module.SizeOfStruct = sizeof( IMAGEHLP_MODULE64 ); SymGetModuleInfo64( hProcess, baseAddr, &Module ); LPCSTR pdbName = Module.LoadedImageName; if ( Module.LoadedPdbName[0] != 0 ) pdbName = Module.LoadedPdbName; } if ( szImg != nullptr ) free( szImg ); if ( szMod != nullptr ) free( szMod ); return result; } BOOL StackTrace::GetModuleListPSAPI( HANDLE hProcess ) { DWORD cbNeeded; HMODULE hMods[1024]; char tt[8192]; char tt2[8192]; if ( !EnumProcessModules( hProcess, hMods, sizeof( hMods ), &cbNeeded ) ) { return false; } if ( cbNeeded > sizeof( hMods ) ) { printf( "Insufficient memory allocated in GetModuleListPSAPI\n" ); return false; } int cnt = 0; for ( DWORD i = 0; i < cbNeeded / sizeof( hMods[0] ); i++ ) { // base address, size MODULEINFO mi; GetModuleInformation( hProcess, hMods[i], &mi, sizeof( mi ) ); // image file name tt[0] = 0; GetModuleFileNameExA( hProcess, hMods[i], tt, sizeof( tt ) ); // module name tt2[0] = 0; GetModuleBaseNameA( hProcess, hMods[i], tt2, sizeof( tt2 ) ); DWORD dwRes = LoadModule( hProcess, tt, tt2, (DWORD64) mi.lpBaseOfDll, mi.SizeOfImage ); if ( dwRes != ERROR_SUCCESS ) printf( "ERROR: LoadModule (%d)\n", dwRes ); cnt++; } return cnt != 0; } void StackTrace::LoadModules() { static bool modules_loaded = false; if ( !modules_loaded ) { modules_loaded = true; // Get the search paths for symbols std::string paths = StackTrace::getSymPaths(); // Initialize the symbols if ( SymInitialize( GetCurrentProcess(), paths.c_str(), FALSE ) == FALSE ) printf( "ERROR: SymInitialize (%d)\n", GetLastError() ); DWORD symOptions = SymGetOptions(); symOptions |= SYMOPT_LOAD_LINES | SYMOPT_FAIL_CRITICAL_ERRORS; symOptions = SymSetOptions( symOptions ); char buf[1024] = { 0 }; if ( SymGetSearchPath( GetCurrentProcess(), buf, sizeof( buf ) ) == FALSE ) printf( "ERROR: SymGetSearchPath (%d)\n", GetLastError() ); // First try to load modules from toolhelp32 BOOL loaded = StackTrace::GetModuleListTH32( GetCurrentProcess(), GetCurrentProcessId() ); // Try to load from Psapi if ( !loaded ) loaded = StackTrace::GetModuleListPSAPI( GetCurrentProcess() ); } } #endif /**************************************************************************** * Get the signal name * ****************************************************************************/ std::string StackTrace::signalName( int sig ) { return std::string( strsignal( sig ) ); } std::vector StackTrace::allSignalsToCatch() { std::set signals; for ( int i = 1; i < 32; i++ ) signals.insert( i ); for ( int i = SIGRTMIN; i <= SIGRTMAX; i++ ) signals.insert( i ); signals.erase( SIGKILL ); signals.erase( SIGSTOP ); return std::vector( signals.begin(), signals.end() ); } std::vector StackTrace::defaultSignalsToCatch() { auto tmp = allSignalsToCatch(); std::set signals( tmp.begin(), tmp.end() ); signals.erase( SIGWINCH ); // Don't catch window changed by default signals.erase( SIGCONT ); // Don't catch continue by default return std::vector( signals.begin(), signals.end() ); } /**************************************************************************** * Set the signal handlers * ****************************************************************************/ static std::function abort_fun; static std::string rethrow() { std::string last_message; #ifdef USE_LINUX try { static int tried_throw = 0; if ( tried_throw == 0 ) { tried_throw = 1; throw; } // No active exception } catch ( const std::exception &err ) { // Caught a std::runtime_error last_message = err.what(); } catch ( ... ) { // Caught an unknown exception last_message = "unknown exception occurred."; } #endif return last_message; } static void term_func_abort( int sig ) { std::string msg( "Caught signal: " ); msg += StackTrace::signalName( sig ); abort_fun( msg, StackTrace::terminateType::signal ); } static std::set signals_set = std::set(); static void term_func() { std::string last_message = rethrow(); StackTrace::clearSignals(); abort_fun( "Unhandled exception:\n" + last_message, StackTrace::terminateType::exception ); } void StackTrace::clearSignal( int sig ) { if ( signals_set.find( sig ) != signals_set.end() ) { signal( sig, SIG_DFL ); signals_set.erase( sig ); } } void StackTrace::clearSignals() { for ( auto sig : signals_set ) signal( sig, SIG_DFL ); signals_set.clear(); } void StackTrace::setSignals( const std::vector &signals, void ( *handler )( int ) ) { for ( auto sig : signals ) { signal( sig, handler ); signals_set.insert( sig ); } } void StackTrace::setErrorHandlers( std::function abort ) { abort_fun = abort; std::set_terminate( term_func ); setSignals( defaultSignalsToCatch(), &term_func_abort ); std::set_unexpected( term_func ); } /**************************************************************************** * Global call stack functionallity * ****************************************************************************/ #ifdef USE_MPI static MPI_Comm globalCommForGlobalCommStack = MPI_COMM_NULL; static bool stopGlobalMonitorThread = false; static void runGlobalMonitorThread() { int rank = 0; int size = 1; MPI_Comm_size( globalCommForGlobalCommStack, &size ); MPI_Comm_rank( globalCommForGlobalCommStack, &rank ); while ( !stopGlobalMonitorThread ) { // Check for any messages int flag = 0; MPI_Status status; int err = MPI_Iprobe( MPI_ANY_SOURCE, 1, globalCommForGlobalCommStack, &flag, &status ); if ( err != MPI_SUCCESS ) { printf( "Internal error in StackTrace::getGlobalCallStacks::runGlobalMonitorThread\n" ); break; } else if ( flag != 0 ) { // We received a request int src_rank = status.MPI_SOURCE; int tag; MPI_Recv( &tag, 1, MPI_INT, src_rank, 1, globalCommForGlobalCommStack, &status ); // Get a trace of all threads (except this) auto threads = StackTrace::activeThreads(); threads.erase( StackTrace::thisThread() ); if ( threads.empty() ) continue; // Get the stack trace of each thread std::vector> stack; for ( auto thread : threads ) stack.push_back( StackTrace::getCallStack( thread ) ); // Pack and send the data auto data = pack( stack ); int count = data.size(); MPI_Send( data.data(), count, MPI_CHAR, src_rank, tag, globalCommForGlobalCommStack ); } else { // No requests recieved std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) ); } } } void StackTrace::globalCallStackInitialize( MPI_Comm comm ) { #ifdef USE_MPI MPI_Comm_dup( comm, &globalCommForGlobalCommStack ); #endif stopGlobalMonitorThread = false; globalMonitorThread.reset( new std::thread( runGlobalMonitorThread ) ); } void StackTrace::globalCallStackFinalize() { stopGlobalMonitorThread = true; globalMonitorThread->join(); globalMonitorThread.reset(); #ifdef USE_MPI if ( globalCommForGlobalCommStack != MPI_COMM_NULL ) MPI_Comm_free( &globalCommForGlobalCommStack ); globalCommForGlobalCommStack = MPI_COMM_NULL; #endif } StackTrace::multi_stack_info StackTrace::getGlobalCallStacks() { // Check if we properly initialized the comm if ( globalMonitorThread == nullptr ) { printf( "Warning: getGlobalCallStacks called without call to globalCallStackInitialize\n" ); return getAllCallStacks(); } if ( globalMonitorThread == nullptr ) { printf( "Warning: getGlobalCallStacks called without call to globalCallStackInitialize\n" ); return getAllCallStacks(); } #ifdef USE_MPI int provided; MPI_Query_thread( &provided ); if ( provided != MPI_THREAD_MULTIPLE ) { printf( "Warning: getGlobalCallStacks requires support for MPI_THREAD_MULTIPLE\n" ); return getAllCallStacks(); } #endif if ( activeThreads().size() == 1 ) { printf( "Warning: getAllCallStacks not supported on this OS, defaulting to basic call " "stack\n" ); return getAllCallStacks(); } // Signal all processes that we want their stack for all threads int rank = 0; int size = 1; MPI_Comm_size( globalCommForGlobalCommStack, &size ); MPI_Comm_rank( globalCommForGlobalCommStack, &rank ); std::random_device rd; std::mt19937 gen( rd() ); std::uniform_int_distribution<> dis( 2, 0x7FFF ); int tag = dis( gen ); std::vector sendRequest( size ); for ( int i = 0; i < size; i++ ) { if ( i == rank ) continue; MPI_Isend( &tag, 1, MPI_INT, i, 1, globalCommForGlobalCommStack, &sendRequest[i] ); } // Get the trace for the current process auto threads = StackTrace::activeThreads(); StackTrace::multi_stack_info multistack; for ( auto thread : threads ) { auto stack = StackTrace::getCallStack( thread ); multistack.add( stack.size(), stack.data() ); } // Recieve the backtrace for all processes/threads int N_finished = 1; auto start = std::chrono::steady_clock::now(); double time = 0; const double max_time = 2.0 + size * 20e-3; while ( N_finished < size && time < max_time ) { int flag = 0; MPI_Status status; int err = MPI_Iprobe( MPI_ANY_SOURCE, tag, globalCommForGlobalCommStack, &flag, &status ); if ( err != MPI_SUCCESS ) { printf( "Internal error in StackTrace::getGlobalCallStacks\n" ); break; } else if ( flag != 0 ) { // We recieved a response int src_rank = status.MPI_SOURCE; int count; MPI_Get_count( &status, MPI_CHAR, &count ); std::vector data( count, 0 ); MPI_Recv( data.data(), count, MPI_CHAR, src_rank, tag, globalCommForGlobalCommStack, &status ); auto stack_list = unpack( data ); for ( const auto &stack : stack_list ) multistack.add( stack.size(), stack.data() ); N_finished++; } else { auto stop = std::chrono::steady_clock::now(); time = std::chrono::duration_cast( stop - start ).count(); std::this_thread::yield(); } } for ( int i = 0; i < size; i++ ) { if ( i == rank ) continue; MPI_Request_free( &sendRequest[i] ); } return multistack; } #else void StackTrace::globalCallStackInitialize( MPI_Comm ) {} void StackTrace::globalCallStackFinalize() {} StackTrace::multi_stack_info StackTrace::getGlobalCallStacks() { return getAllCallStacks(); } #endif /**************************************************************************** * Cleanup the call stack * ****************************************************************************/ static inline size_t findMatching( const std::string &str, size_t pos ) { if ( str[pos] != '<' ) { perr << "Internal error string matching\n"; perr << " " << str << std::endl; perr << " " << pos << std::endl; return pos; } size_t pos2 = pos + 1; int count = 1; while ( count != 0 && pos2 < str.size() ) { if ( str[pos2] == '<' ) count++; if ( str[pos2] == '>' ) count--; pos2++; } return pos2; } void StackTrace::cleanupStackTrace( multi_stack_info &stack ) { auto it = stack.children.begin(); const size_t npos = std::string::npos; while ( it != stack.children.end() ) { auto &object = it->stack.object; auto &function = it->stack.function; auto &filename = it->stack.filename; bool remove_entry = false; // Cleanup object and filename object = stripPath( object ); filename = stripPath( filename ); // Remove callstack (and all children) for threads that are just contributing if ( function.find( "_callstack_signal_handler" ) != npos && filename.find( "StackTrace.cpp" ) != npos ) { it = stack.children.erase( it ); continue; } // Remove __libc_start_main if ( function.find( "__libc_start_main" ) != npos && filename.find( "libc-start.c" ) != npos ) remove_entry = true; // Remove backtrace_thread if ( function.find( "backtrace_thread" ) != npos && filename.find( "StackTrace.cpp" ) != npos ) remove_entry = true; // Remove __restore_rt if ( function.find( "__restore_rt" ) != npos && object.find( "libpthread" ) != npos ) remove_entry = true; // Remove std::condition_variable::__wait_until_impl if ( function.find( "std::condition_variable::__wait_until_impl" ) != npos && filename == "condition_variable" ) remove_entry = true; // Remove std::_Function_handler< if ( function.find( "std::_Function_handler<" ) != npos && filename == "functional" ) remove_entry = true; // Remove std::_Bind_simple< if ( function.find( "std::_Bind_simple<" ) != npos && filename == "functional" ) { auto pos = function.find( "std::_Bind_simple<" ); function = function.substr( 0, pos ) + "std::_Bind_simple<...>(...)"; remove_entry = true; } // Remove std::this_thread::__sleep_for if ( function.find( "std::this_thread::__sleep_for(" ) != npos && object.find( "libstdc++" ) != npos ) remove_entry = true; // Remove std::thread::_Impl if ( function.find( "std::thread::_Impl<" ) != npos && filename == "thread" ) remove_entry = true; // Remove MATLAB internal routines if ( object == "libmwmcr.so" || object == "libmwm_lxe.so" || object == "libmwbridge.so" || object == "libmwiqm.so" ) remove_entry = true; // Remove the desired entry if ( remove_entry ) { if ( it->children.empty() ) { it = stack.children.erase( it ); continue; } else if ( it->children.size() == 1 ) { *it = it->children[0]; continue; } } // Cleanup template space strrep( function, " >", ">" ); strrep( function, "< ", "<" ); // Replace std::chrono::duration with abbriviated version if ( function.find( "std::chrono::duration<" ) != npos ) { strrep( function, "std::chrono::duration >", "ticks" ); strrep( function, "std::chrono::duration >", "nanoseconds" ); } // Replace std::ratio with abbriviated version. if ( function.find( "std::ratio<" ) != npos ) { strrep( function, "std::ratio<1l, 1000000000000000000000000l>", "std::yocto" ); strrep( function, "std::ratio<1l, 1000000000000000000000l>", "std::zepto" ); strrep( function, "std::ratio<1l, 1000000000000000000l>", "std::atto" ); strrep( function, "std::ratio<1l, 1000000000000000l>", "std::femto" ); strrep( function, "std::ratio<1l, 1000000000000l>", "std::pico" ); strrep( function, "std::ratio<1l, 1000000000l>", "std::nano" ); strrep( function, "std::ratio<1l, 1000000l>", "std::micro" ); strrep( function, "std::ratio<1l, 1000l>", "std::milli" ); strrep( function, "std::ratio<1l, 100l>", "std::centi" ); strrep( function, "std::ratio<1l, 10l>", "std::deci" ); strrep( function, "std::ratio<1l, 1l>", "" ); strrep( function, "std::ratio<10l, 1l>", "std::deca" ); strrep( function, "std::ratio<60l, 1l>", "std::ratio<60>" ); strrep( function, "std::ratio<100l, 1l>", "std::hecto" ); strrep( function, "std::ratio<1000l, 1l>", "std::kilo" ); strrep( function, "std::ratio<3600l, 1l>", "std::ratio<3600>" ); strrep( function, "std::ratio<1000000l, 1l>", "std::mega" ); strrep( function, "std::ratio<1000000000l, 1l>", "std::giga" ); strrep( function, "std::ratio<1000000000000l, 1l>", "std::tera" ); strrep( function, "std::ratio<1000000000000000l, 1l>", "std::peta" ); strrep( function, "std::ratio<1000000000000000000l, 1l>", "std::exa" ); strrep( function, "std::ratio<1000000000000000000000l, 1l>", "std::zetta" ); strrep( function, "std::ratio<1000000000000000000000000l, 1l>", "std::yotta" ); strrep( function, " >", ">" ); strrep( function, "< ", "<" ); } // Replace std::chrono::duration with abbriviated version. if ( function.find( "std::chrono::duration<" ) != npos ) { // clang-format off strrep( function, "std::chrono::duration", "std::chrono::nanoseconds" ); strrep( function, "std::chrono::duration", "std::chrono::microseconds" ); strrep( function, "std::chrono::duration", "std::chrono::milliseconds" ); strrep( function, "std::chrono::duration", "std::chrono::seconds" ); strrep( function, "std::chrono::duration", "std::chrono::seconds" ); strrep( function, "std::chrono::duration>", "std::chrono::minutes" ); strrep( function, "std::chrono::duration>", "std::chrono::hours" ); strrep( function, " >", ">" ); strrep( function, "< ", "<" ); // clang-format on } // Replace std::this_thread::sleep_for with abbriviated version. if ( function.find( "::sleep_for<" ) != npos ) { strrep( function, "::sleep_for", "::sleep_for" ); strrep( function, "::sleep_for", "::sleep_for" ); strrep( function, "::sleep_for", "::sleep_for" ); strrep( function, "::sleep_for", "::sleep_for" ); strrep( function, "::sleep_for", "::sleep_for" ); strrep( function, "::sleep_for>", "::sleep_for" ); strrep( function, "::sleep_for>", "::sleep_for" ); strrep( function, "::sleep_for(std::chrono::nanoseconds", "::sleep_for(std::chrono::nanoseconds" ); strrep( function, "::sleep_for(std::chrono::microseconds", "::sleep_for(std::chrono::microseconds" ); strrep( function, "::sleep_for(std::chrono::milliseconds", "::sleep_for(std::chrono::milliseconds" ); strrep( function, "::sleep_for(std::chrono::seconds", "::sleep_for(std::chrono::seconds" ); strrep( function, "::sleep_for(std::chrono::minutes", "::sleep_for(std::chrono::milliseconds" ); strrep( function, "::sleep_for(std::chrono::hours", "::sleep_for(std::chrono::hours" ); } // Replace std::basic_string with abbriviated version size_t pos = 0; while ( pos < function.size() ) { // Find next instance of std::basic_string const std::string match = "std::basic_string<"; pos = function.find( match, pos ); if ( pos == npos ) break; // Find the matching > size_t pos1 = pos + match.size() - 1; size_t pos2 = findMatching( function, pos1 ); if ( pos2 == pos1 ) break; if ( function.substr( pos1 + 1, 4 ) == "char" ) function.replace( pos, pos2 - pos, "std::string" ); else if ( function.substr( pos1 + 1, 7 ) == "wchar_t" ) function.replace( pos, pos2 - pos, "std::wstring" ); else if ( function.substr( pos1 + 1, 8 ) == "char16_t" ) function.replace( pos, pos2 - pos, "std::u16string" ); else if ( function.substr( pos1 + 1, 8 ) == "char32_t" ) function.replace( pos, pos2 - pos, "std::u32string" ); pos++; } // Cleanup the children cleanupStackTrace( *it ); ++it; } }