#include "common/StackTrace.h" #include #include #include #include #include #include #include #include #include #include #include #include // 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( __unix ) || defined( __posix ) #define USE_LINUX #define USE_NM #else #error Unknown OS #endif // clang-format on // Include/detect MPI // clang-format off #ifndef USE_MPI #ifdef USE_EXT_MPI #define USE_MPI #elif defined(__has_include) #if __has_include("mpi.h") #define USE_MPI #endif #endif #endif #ifdef USE_MPI #include "mpi.h" #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 // 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; } // 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(); }; #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) != NULL ) result += buffer; } auto status = pclose( pipe ); code = WEXITSTATUS(status); return result; } /**************************************************************************** * stack_info * ****************************************************************************/ 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 ); } std::string StackTrace::stack_info::print() const { char tmp[32]; sprintf( tmp, "0x%016llx: ", reinterpret_cast( address ) ); std::string stack( tmp ); sprintf( tmp, "%i", line ); std::string line_str( tmp ); stack += stripPath( object ); stack.resize( std::max( stack.size(), 38 ), ' ' ); stack += " " + function; if ( !filename.empty() && line > 0 ) { stack.resize( std::max( stack.size(), 72 ), ' ' ); stack += " " + stripPath( filename ) + ":" + line_str; } else if ( !filename.empty() ) { stack.resize( std::max( stack.size(), 72 ), ' ' ); 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 (size_t i=0; i vec(size,0); char* ptr = vec.data(); int N = data.size(); ptr = copy_in( sizeof(int), &N, ptr ); for (size_t i=0; i StackTrace::stack_info::unpackArray( const char* ptr ) { int N; ptr = copy_out( sizeof(int), &N, ptr ); std::vector data(N); for (size_t i=0; i pack( const std::vector>& data ) { size_t size = sizeof(int); for (size_t i=0; i out( size, 0 ); char* ptr = out.data(); int N = data.size(); ptr = copy_in( sizeof(int), &N, ptr ); for (int i=0; i> 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( depth, maxDepth( child ) ); return depth+1; }*/ std::vector StackTrace::multi_stack_info::print( const std::string& prefix ) const { std::vector text; if ( stack == stack_info() ) { for ( const auto& child : children ) { auto tmp = child.print( ); text.insert( text.end(), tmp.begin(), tmp.end() ); } return text; } //auto depth = maxDepth( *this ); //std::string line = prefix + "[" + std::to_string( N ) + "] "; //for (auto i=1; i1 && j>0 && i 1 ) children[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 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; std::string StackTrace::getExecutable() { std::string exe; try { #ifdef USE_LINUX char *buf = new char[0x10000]; int len = ::readlink( "/proc/self/exe", buf, 0x10000 ); if ( len != -1 ) { buf[len] = '\0'; exe = std::string( 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 ) exe = std::string( buf ); delete[] buf; #elif defined( USE_WINDOWS ) DWORD size = 0x10000; char *buf = new char[size]; memset( buf, 0, size ); GetModuleFileName( nullptr, buf, size ); exe = std::string( buf ); delete[] buf; #endif } catch ( ... ) { } return exe; } std::string global_exe_name = StackTrace::getExecutable(); 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.c_str() ); #elif defined( USE_MAC ) sprintf( cmd, "nm -n %s | c++filt", global_exe_name.c_str() ); #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; char *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.push_back( std::string( 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 typedef uint64_t uint_p; #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 (size_t i=0; i( addresses[i] ) ); 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; } 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 (size_t i=0; i 0 ) info.object = global_symbols.obj[index - 1]; else info.object = 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 thread_backtrace; static bool thread_backtrace_finished; static std::mutex thread_backtrace_mutex; static void _callstack_signal_handler( int, siginfo_t*, void* ) { thread_backtrace = StackTrace::backtrace( ); thread_backtrace_finished = true; } #endif std::vector StackTrace::backtrace( std::thread::native_handle_type tid ) { std::vector trace; #if defined( USE_LINUX ) || defined( USE_MAC ) // Get the trace if ( tid == pthread_self() ) { trace.resize(1000,nullptr); int trace_size = ::backtrace( trace.data(), trace.size() ); trace.resize (trace_size ); } else { // Note: this will get the backtrace, but terminates the thread in the process!!! thread_backtrace_mutex.lock(); struct sigaction sa; sigfillset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = _callstack_signal_handler; sigaction(CALLSTACK_SIG, &sa, NULL); thread_backtrace_finished = false; pthread_kill( tid, CALLSTACK_SIG ); auto t1 = std::chrono::high_resolution_clock::now(); auto t2 = std::chrono::high_resolution_clock::now(); while ( !thread_backtrace_finished && std::chrono::duration(t2-t1).count()<0.1 ) { std::this_thread::yield(); t2 = std::chrono::high_resolution_clock::now(); } std::swap( trace, thread_backtrace ); thread_backtrace_finished = false; 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 trace.reserve( 1000 ); 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 ) trace.push_back( reinterpret_cast( frame.AddrPC.Offset ) ); if ( frame.AddrReturn.Offset == 0 ) break; } SetLastError( ERROR_SUCCESS ); #endif #else #warning Stack trace is not supported on this compiler/OS #endif return trace; } std::vector StackTrace::backtrace() { std::vector trace = backtrace( thisThread() ); return trace; } std::vector> StackTrace::backtraceAll() { // Get the list of threads auto threads = activeThreads( ); // Get the backtrace of each thread std::vector> thread_backtrace; for ( auto thread : threads ) thread_backtrace.push_back( backtrace( thread ) ); return thread_backtrace; } /**************************************************************************** * Function to get the list of all active threads * ****************************************************************************/ #if defined( USE_LINUX ) static std::thread::native_handle_type thread_handle; static void _activeThreads_signal_handler( int ) { auto handle = StackTrace::thisThread( ); thread_handle = handle; thread_backtrace_finished = true; } static inline int get_tid( int pid, const std::string& line ) { char buf2[128]; int i1 = 0; while ( line[i1]==' ' && line[i1]!=0 ) { i1++; } int i2 = i1; while ( line[i2]!=' ' && line[i2]!=0 ) { i2++; } memcpy(buf2,&line[i1],i2-i1); buf2[i2-i1+1] = 0; int pid2 = atoi(buf2); if ( pid2 != pid ) return -1; i1 = i2; while ( line[i1]==' ' && line[i1]!=0 ) { i1++; } i2 = i1; while ( line[i2]!=' ' && line[i2]!=0 ) { i2++; } memcpy(buf2,&line[i1],i2-i1); buf2[i2-i1+1] = 0; int tid = atoi(buf2); return tid; } #endif std::thread::native_handle_type StackTrace::thisThread( ) { #if defined( USE_LINUX ) || defined( USE_MAC ) return pthread_self(); #elif defined( USE_WINDOWS ) return GetCurrentThread(); #else #warning Stack trace is not supported on this compiler/OS return std::thread::native_handle_type(); #endif } std::set 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_backtrace_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_backtrace_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() ); 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 stack( trace.size() ); for (size_t i=0; i 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 std::shared_ptr globalMonitorThread; 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_free( &globalCommForGlobalCommStack ); #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 ( 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; inative_handle() ); 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 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(); } } return multistack; } #else void StackTrace::globalCallStackInitialize( MPI_Comm ) { } void StackTrace::globalCallStackFinalize( ) { } StackTrace::multi_stack_info StackTrace::getGlobalCallStacks( ) { return getAllCallStacks( ); } #endif