Files
LBPM/common/StackTrace.cpp
2015-08-31 11:30:37 -04:00

390 lines
14 KiB
C++

#include "StackTrace.h"
#include <iostream>
#include <sstream>
#include <cstring>
#include <algorithm>
#if __cplusplus > 199711L
#include <mutex>
#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 <iostream>
#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <tchar.h>
#include <psapi.h>
#include <DbgHelp.h>
//#pragma comment(lib, psapi.lib) //added
//#pragma comment(linker, /DEFAULTLIB:psapi.lib)
#elif defined(__APPLE__)
#define USE_MAC
#include <sys/time.h>
#include <sys/sysctl.h>
#include <signal.h>
#include <execinfo.h>
#include <dlfcn.h>
#include <mach/mach.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <unistd.h>
#include <sched.h>
#elif defined(__linux) || defined(__unix) || defined(__posix)
#define USE_LINUX
#define USE_NM
#include <sys/time.h>
#include <time.h>
#include <execinfo.h>
#include <dlfcn.h>
#include <malloc.h>
#include <unistd.h>
#include <sched.h>
#else
#error Unknown OS
#endif
#ifdef __GNUC__
#define USE_ABI
#include <cxxabi.h>
#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<void*>( std::abs(
reinterpret_cast<long long int>(a)-reinterpret_cast<long long int>(b) ) );
}
/****************************************************************************
* stack_info *
****************************************************************************/
std::string StackTrace::stack_info::print() const
{
char tmp[32];
sprintf(tmp,"0x%016llx: ",reinterpret_cast<unsigned long long int>(address));
std::string stack(tmp);
sprintf(tmp,"%i",line);
std::string line_str(tmp);
stack += stripPath(object);
stack.resize(std::max<size_t>(stack.size(),38),' ');
stack += " " + function;
if ( !filename.empty() && line>0 ) {
stack.resize(std::max<size_t>(stack.size(),70),' ');
stack += " " + stripPath(filename) + ":" + line_str;
} else if ( !filename.empty() ) {
stack.resize(std::max<size_t>(stack.size(),70),' ');
stack += " " + stripPath(filename);
} else if ( line>0 ) {
stack += " : " + line_str;
}
return stack;
}
/****************************************************************************
* Function to find an entry *
****************************************************************************/
template <class TYPE>
inline size_t findfirst( const std::vector<TYPE>& 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<void*> address;
std::vector<char> type;
std::vector<std::string> 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<void*>(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<void*>& address, std::vector<char>& type,
std::vector<std::string>& 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<unsigned long int>(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<size_t>(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::stack_info> StackTrace::getCallStack()
{
std::vector<StackTrace::stack_info> 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<trace_size; ++i)
stack_list.push_back(getStackInfo(trace[i]));
#elif defined(USE_WINDOWS)
#ifdef DBGHELP
::CONTEXT lContext;
::ZeroMemory( &lContext, sizeof( ::CONTEXT ) );
::RtlCaptureContext( &lContext );
::STACKFRAME64 lFrameStack;
::ZeroMemory( &lFrameStack, sizeof( ::STACKFRAME64 ) );
lFrameStack.AddrPC.Offset = lContext.Rip;
lFrameStack.AddrFrame.Offset = lContext.Rbp;
lFrameStack.AddrStack.Offset = lContext.Rsp;
lFrameStack.AddrPC.Mode = lFrameStack.AddrFrame.Mode = lFrameStack.AddrStack.Mode = AddrModeFlat;
#ifdef _M_IX86
DWORD MachineType = IMAGE_FILE_MACHINE_I386;
#endif
#ifdef _M_X64
DWORD MachineType = IMAGE_FILE_MACHINE_AMD64;
#endif
#ifdef _M_IA64
DWORD MachineType = IMAGE_FILE_MACHINE_IA64;
#endif
while ( 1 ) {
int rtn = ::StackWalk64( MachineType, ::GetCurrentProcess(), ::GetCurrentThread(),
&lFrameStack, MachineType == IMAGE_FILE_MACHINE_I386 ? 0 : &lContext,
NULL, &::SymFunctionTableAccess64, &::SymGetModuleBase64, NULL );
if( !rtn )
break;
if( lFrameStack.AddrPC.Offset == 0 )
break;
::MEMORY_BASIC_INFORMATION lInfoMemory;
::VirtualQuery( ( ::PVOID )lFrameStack.AddrPC.Offset, &lInfoMemory, sizeof( lInfoMemory ) );
if ( lInfoMemory.Type==MEM_PRIVATE )
continue;
::DWORD64 lBaseAllocation = reinterpret_cast< ::DWORD64 >( lInfoMemory.AllocationBase );
::TCHAR lNameModule[ 1024 ];
::HMODULE hBaseAllocation = reinterpret_cast< ::HMODULE >( lBaseAllocation );
::GetModuleFileName( hBaseAllocation, lNameModule, 1024 );
PIMAGE_DOS_HEADER lHeaderDOS = reinterpret_cast<PIMAGE_DOS_HEADER>( lBaseAllocation );
if ( lHeaderDOS==NULL )
continue;
PIMAGE_NT_HEADERS lHeaderNT = reinterpret_cast<PIMAGE_NT_HEADERS>( 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<void*>(lRVA);
char tmp[20];
sprintf(tmp,"0x%016llx",static_cast<unsigned long long int>(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;
}