#include "StackTrace.h" #include #include #include #include #if __cplusplus > 199711L #include #endif // Detect the OS and include system dependent headers #if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) || defined(_MSC_VER) // Note: windows has not been testeds #define USE_WINDOWS #include #include #include #include #include #include #include //#pragma comment(lib, psapi.lib) //added //#pragma comment(linker, /DEFAULTLIB:psapi.lib) #elif defined(__APPLE__) #define USE_MAC #include #include #include #include #include #include #include #include #include #include #elif defined(__linux) || defined(__unix) || defined(__posix) #define USE_LINUX #define USE_NM #include #include #include #include #include #include #include #else #error Unknown OS #endif #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 // Utility to strip the path from a filename 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 inline void* subtractAddress( void* a, void* b ) { return reinterpret_cast( std::abs( reinterpret_cast(a)-reinterpret_cast(b) ) ); } /**************************************************************************** * stack_info * ****************************************************************************/ 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(),70),' '); stack += " " + stripPath(filename) + ":" + line_str; } else if ( !filename.empty() ) { stack.resize(std::max(stack.size(),70),' '); stack += " " + stripPath(filename); } else if ( line>0 ) { stack += " : " + line_str; } return 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. * ****************************************************************************/ #if __cplusplus <= 199711L class mutex_class { public: void lock() {} void unlock() {} }; mutex_class getSymbols_mutex; #else std::mutex getSymbols_mutex; #endif struct global_symbols_struct { std::vector address; std::vector type; std::vector obj; int error; } global_symbols; static std::string get_executable() { 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; #endif } catch (...) {} return exe; } std::string global_exe_name = get_executable(); 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]; sprintf(cmd,"nm --demangle --numeric-sort %s",global_exe_name.c_str()); FILE *in = popen(cmd,"r"); if ( in==NULL ) { data.error = -2; return data; } char *buf = new char[0x100000]; while ( fgets(buf,0xFFFFF,in)!=NULL ) { if ( buf[0]==' ' || buf==NULL ) continue; char *a = buf; char *b = strchr(a,' '); if (b==NULL) {continue;} b[0] = 0; b++; char *c = strchr(b,' '); if (c==NULL) {continue;} c[0] = 0; c++; char *d = strchr(c,'\n'); if ( d ) { d[0]=0; } size_t add = strtoul(a,NULL,16); data.address.push_back( reinterpret_cast(add) ); data.type.push_back( b[0] ); data.obj.push_back( std::string(c) ); } pclose(in); delete [] buf; } 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 the current call stack * ****************************************************************************/ static void getFileAndLine( StackTrace::stack_info& info ) { #if defined(USE_LINUX) || defined(USE_MAC) void *address = info.address; if ( info.object.find(".so")!=std::string::npos ) address = info.address2; char buf[4096]; sprintf(buf, "addr2line -C -e %s -f -i %lx 2> /dev/null", info.object.c_str(),reinterpret_cast(address)); FILE* f = popen(buf, "r"); if (f == NULL) return; buf[4095] = 0; // get function name char *rtn = fgets(buf,4095,f); if ( info.function.empty() && rtn==buf ) { info.function = std::string(buf); info.function.resize(std::max(info.function.size(),1)-1); } // get file and line rtn = fgets(buf,4095,f); if ( buf[0]!='?' && buf[0]!=0 && rtn==buf ) { size_t i = 0; for (i=0; i<4095 && buf[i]!=':'; i++) { } info.filename = std::string(buf,i); info.line = atoi(&buf[i+1]); } pclose(f); #endif } // 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 = global_exe_name; } } StackTrace::stack_info StackTrace::getStackInfo( void* address ) { StackTrace::stack_info info; info.address = address; #ifdef _GNU_SOURCE Dl_info dlinfo; if ( !dladdr(address, &dlinfo) ) { getDataFromGlobalSymbols( info ); getFileAndLine(info); return info; } info.address2 = subtractAddress(info.address,dlinfo.dli_fbase); info.object = std::string(dlinfo.dli_fname); #if defined(USE_ABI) int status; char *demangled = abi::__cxa_demangle(dlinfo.dli_sname,NULL,0,&status); if ( status == 0 && demangled!=NULL ) { info.function = std::string(demangled); } else if ( dlinfo.dli_sname!=NULL ) { info.function = std::string(dlinfo.dli_sname); } free(demangled); #else if ( dlinfo.dli_sname!=NULL ) info.function = std::string(dlinfo.dli_sname); #endif #else getDataFromGlobalSymbols( info ); #endif // Get the filename / line number getFileAndLine(info); return info; } std::vector StackTrace::getCallStack() { std::vector stack_list; #if defined(USE_LINUX) || defined(USE_MAC) // Get the trace void *trace[100]; memset(trace,0,100*sizeof(void*)); int trace_size = backtrace(trace,100); stack_list.reserve(trace_size); for (int i=0; i( lInfoMemory.AllocationBase ); ::TCHAR lNameModule[ 1024 ]; ::HMODULE hBaseAllocation = reinterpret_cast< ::HMODULE >( lBaseAllocation ); ::GetModuleFileName( hBaseAllocation, lNameModule, 1024 ); PIMAGE_DOS_HEADER lHeaderDOS = reinterpret_cast( lBaseAllocation ); if ( lHeaderDOS==NULL ) continue; PIMAGE_NT_HEADERS lHeaderNT = reinterpret_cast( lBaseAllocation + lHeaderDOS->e_lfanew ); PIMAGE_SECTION_HEADER lHeaderSection = IMAGE_FIRST_SECTION( lHeaderNT ); ::DWORD64 lRVA = lFrameStack.AddrPC.Offset - lBaseAllocation; ::DWORD64 lNumberSection = ::DWORD64(); ::DWORD64 lOffsetSection = ::DWORD64(); for( int lCnt = ::DWORD64(); lCnt < lHeaderNT->FileHeader.NumberOfSections; lCnt++, lHeaderSection++ ) { ::DWORD64 lSectionBase = lHeaderSection->VirtualAddress; ::DWORD64 lSectionEnd = lSectionBase + std::max<::DWORD64>( lHeaderSection->SizeOfRawData, lHeaderSection->Misc.VirtualSize ); if( ( lRVA >= lSectionBase ) && ( lRVA <= lSectionEnd ) ) { lNumberSection = lCnt + 1; lOffsetSection = lRVA - lSectionBase; //break; } } StackTrace::stack_info info; info.object = lNameModule; info.address = reinterpret_cast(lRVA); char tmp[20]; sprintf(tmp,"0x%016llx",static_cast(lOffsetSection)); info.function = std::to_string(lNumberSection) + ":" + std::string(tmp); stack_list.push_back(info); } #endif #else #warning Stack trace is not supported on this compiler/OS #endif return stack_list; }