2530 lines
91 KiB
C++
2530 lines
91 KiB
C++
#include "StackTrace/StackTrace.h"
|
|
#include "StackTrace/ErrorHandlers.h"
|
|
#include "StackTrace/Utilities.h"
|
|
|
|
// Replace sith std::string_view when we switch to c++17
|
|
#include "StackTrace/string_view.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cerrno>
|
|
#include <csignal>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <random>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
|
|
|
|
#define perr std::cerr
|
|
|
|
using StackTrace::string_view;
|
|
|
|
// 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 <windows.h>
|
|
#include <dbghelp.h>
|
|
#include <DbgHelp.h>
|
|
#include <TlHelp32.h>
|
|
#include <Psapi.h>
|
|
#include <process.h>
|
|
#include <stdio.h>
|
|
#include <tchar.h>
|
|
#pragma comment( lib, "version.lib" ) // for "VerQueryValue"
|
|
#else
|
|
#include <dlfcn.h>
|
|
#include <execinfo.h>
|
|
#include <sched.h>
|
|
#include <sys/time.h>
|
|
#include <ctime>
|
|
#include <unistd.h>
|
|
#include <sys/syscall.h>
|
|
#endif
|
|
#ifdef USE_MAC
|
|
#include <mach-o/dyld.h>
|
|
#include <mach/mach.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/types.h>
|
|
#define SIGRTMIN SIGUSR1
|
|
#define SIGRTMAX SIGUSR2
|
|
#endif
|
|
// clang-format on
|
|
|
|
|
|
#ifdef __GNUC__
|
|
#define USE_ABI
|
|
#include <cxxabi.h>
|
|
#endif
|
|
|
|
|
|
#ifndef NULL_USE
|
|
#define NULL_USE( variable ) \
|
|
do { \
|
|
if ( 0 ) { \
|
|
auto static temp = (char *) &variable; \
|
|
temp++; \
|
|
} \
|
|
} while ( 0 )
|
|
#endif
|
|
|
|
|
|
// Mutex for StackTrace opertions that need blocking
|
|
static std::mutex StackTrace_mutex;
|
|
|
|
|
|
// Helper thread
|
|
static std::shared_ptr<std::thread> globalMonitorThread;
|
|
|
|
|
|
// Function to replace all instances of a string with another
|
|
static constexpr size_t replace(
|
|
char *str, size_t N, size_t pos, size_t len, const string_view &r ) noexcept
|
|
{
|
|
size_t Nr = r.size();
|
|
auto tmp = str;
|
|
size_t k = pos;
|
|
for ( size_t i = 0; i < Nr && k < N; i++, k++ )
|
|
str[k] = r[i];
|
|
for ( size_t i = pos + len; i < N && k < N; i++, k++ )
|
|
str[k] = tmp[i];
|
|
for ( size_t m = k; m < N; m++ )
|
|
str[k] = 0;
|
|
return k;
|
|
}
|
|
template<std::size_t N>
|
|
static constexpr size_t replace(
|
|
std::array<char, N> &str, size_t pos, size_t len, const string_view &r ) noexcept
|
|
{
|
|
return replace( str.data(), N, pos, len, r );
|
|
}
|
|
static constexpr void strrep(
|
|
char *str, size_t &N, const string_view &s, const string_view &r ) noexcept
|
|
{
|
|
size_t Ns = s.size();
|
|
size_t pos = string_view( str, N ).find( s );
|
|
while ( pos != std::string::npos ) {
|
|
N = replace( str, N, pos, Ns, r );
|
|
pos = string_view( str, N ).find( s );
|
|
}
|
|
}
|
|
|
|
static void cleanupFunctionName( char * );
|
|
|
|
|
|
// Utility to strip the path from a filename
|
|
static constexpr const char *stripPath( const char *filename ) noexcept
|
|
{
|
|
const char *s = filename;
|
|
while ( *s ) {
|
|
if ( *s == 47 || *s == 92 )
|
|
filename = s + 1;
|
|
++s;
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
|
|
// Functions to hash strings
|
|
constexpr uint32_t hashString( const char *s )
|
|
{
|
|
uint32_t c = 0;
|
|
uint32_t hash = 5381;
|
|
while ( ( c = *s++ ) )
|
|
hash = ( ( hash << 5 ) + hash ) ^ c;
|
|
return hash;
|
|
}
|
|
template<std::size_t N1, std::size_t N2>
|
|
static constexpr uint64_t objHash(
|
|
const std::array<char, N1> &obj, const std::array<char, N2> &objPath )
|
|
{
|
|
uint32_t v1 = hashString( obj.data() );
|
|
uint32_t v2 = hashString( objPath.data() );
|
|
uint64_t key = ( static_cast<uint64_t>( v1 ) << 32 ) + static_cast<uint64_t>( v1 ^ v2 );
|
|
return key;
|
|
}
|
|
|
|
|
|
//! Assign a string to a std::array
|
|
template<std::size_t N2>
|
|
static constexpr void copy( const char *in, std::array<char, N2> &out ) noexcept
|
|
{
|
|
size_t N1 = strlen( in );
|
|
out.fill( 0 );
|
|
if ( N1 < N2 ) {
|
|
memcpy( out.data(), in, N1 );
|
|
} else {
|
|
memcpy( out.data(), in, N2 - 4 );
|
|
out[N2 - 4] = out[N2 - 3] = out[N2 - 2] = '.';
|
|
}
|
|
}
|
|
template<std::size_t N1, std::size_t N2>
|
|
static constexpr void copy( const std::array<char, N1> &in, std::array<char, N2> &out ) noexcept
|
|
{
|
|
out.fill( 0 );
|
|
if ( N1 < N2 ) {
|
|
memcpy( out.data(), in.data(), N1 );
|
|
} else {
|
|
memcpy( out.data(), in.data(), N2 - 4 );
|
|
out[N2 - 4] = out[N2 - 3] = out[N2 - 2] = '.';
|
|
}
|
|
}
|
|
template<std::size_t N2, std::size_t N3>
|
|
static constexpr void copy(
|
|
const char *in, std::array<char, N2> &out, std::array<char, N3> &outPath ) noexcept
|
|
{
|
|
auto ptr = stripPath( in );
|
|
copy( ptr, out );
|
|
outPath.fill( 0 );
|
|
if ( ptr != in ) {
|
|
size_t N = ptr - in - 1;
|
|
if ( N < N3 ) {
|
|
memcpy( outPath.data(), in, N );
|
|
} else {
|
|
memcpy( outPath.data(), in, N3 - 4 );
|
|
outPath[N3 - 4] = outPath[N3 - 3] = outPath[N3 - 2] = '.';
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Inline function to subtract two addresses returning the absolute difference
|
|
static inline void *subtractAddress( void *a, void *b ) noexcept
|
|
{
|
|
return reinterpret_cast<void *>(
|
|
std::abs( reinterpret_cast<int64_t>( a ) - reinterpret_cast<int64_t>( 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
|
|
|
|
|
|
/****************************************************************************
|
|
* Class to replace a std::vector with a fixed capacity *
|
|
****************************************************************************/
|
|
template<class TYPE, std::size_t CAPACITY>
|
|
class staticVector final
|
|
{
|
|
public:
|
|
staticVector() : d_size( 0 ) {}
|
|
size_t size() const { return d_size; }
|
|
bool empty() const { return d_size == 0; }
|
|
void push_back( const TYPE &v )
|
|
{
|
|
if ( d_size < CAPACITY )
|
|
d_data[d_size++] = v;
|
|
}
|
|
TYPE &operator[]( size_t i ) { return d_data[i]; }
|
|
TYPE *begin() { return d_data; }
|
|
TYPE *end() { return d_data + d_size; }
|
|
TYPE &back() { return d_data[d_size - 1]; }
|
|
TYPE *data() { return d_size == 0 ? nullptr : d_data; }
|
|
void pop_back() { d_size = std::max<size_t>( d_size, 1 ) - 1; }
|
|
const TYPE *begin() const { return d_data; }
|
|
const TYPE *end() const { return d_data + d_size; }
|
|
const TYPE &back() const { return d_data[d_size - 1]; }
|
|
void clear() { d_size = 0; }
|
|
void resize( size_t N, TYPE x = TYPE() )
|
|
{
|
|
if ( N > CAPACITY )
|
|
throw std::logic_error( "Invalid size" );
|
|
for ( size_t i = d_size; i < N; i++ )
|
|
d_data[i] = x;
|
|
d_size = N;
|
|
}
|
|
void erase( const TYPE &x )
|
|
{
|
|
size_t N = 0;
|
|
for ( size_t i = 0; i < d_size; i++ ) {
|
|
if ( d_data[i] != x )
|
|
d_data[N++] = d_data[i];
|
|
}
|
|
d_size = N;
|
|
}
|
|
void insert( const TYPE &x )
|
|
{
|
|
if ( std::find( begin(), end(), x ) == end() ) {
|
|
push_back( x );
|
|
std::sort( begin(), end() );
|
|
}
|
|
}
|
|
|
|
private:
|
|
size_t d_size;
|
|
TYPE d_data[CAPACITY];
|
|
};
|
|
|
|
|
|
/****************************************************************************
|
|
* Utility to temporarily clear a signal in a thread-safe manner *
|
|
* If multiple threads attempt to clear a signal, then it will be cleared *
|
|
* until all threads are finished *
|
|
****************************************************************************/
|
|
typedef void ( *handle_type )( int );
|
|
static std::atomic_int reset_signal_count[128];
|
|
static handle_type reset_signal_handler[128] = { nullptr };
|
|
static bool initialize_reset_signal_count()
|
|
{
|
|
for ( int i = 0; i < 128; i++ )
|
|
reset_signal_count[i].store( 0 );
|
|
return true;
|
|
}
|
|
static bool reset_signal_vars_initialize = initialize_reset_signal_count();
|
|
static void clearSignal( int sig )
|
|
{
|
|
NULL_USE( reset_signal_vars_initialize );
|
|
if ( reset_signal_count[sig].fetch_add( 1 ) == 0 )
|
|
reset_signal_handler[sig] = signal( sig, SIG_IGN );
|
|
}
|
|
static void resetSignal( int sig )
|
|
{
|
|
if ( reset_signal_count[sig].fetch_add( -1 ) == 1 )
|
|
signal( sig, reset_signal_handler[sig] );
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Utility to call system command and return output *
|
|
****************************************************************************/
|
|
#ifdef USE_WINDOWS
|
|
#define popen _popen
|
|
#define pclose _pclose
|
|
#endif
|
|
template<class FUNCTION>
|
|
static inline int exec3( const char *cmd, FUNCTION &fun )
|
|
{
|
|
clearSignal( SIGCHLD ); // Clear child exited
|
|
auto pipe = popen( cmd, "r" );
|
|
if ( pipe == nullptr )
|
|
return -1;
|
|
while ( !feof( pipe ) ) {
|
|
char buffer[0x2000];
|
|
buffer[0] = 0;
|
|
auto ptr = fgets( buffer, sizeof( buffer ), pipe );
|
|
NULL_USE( ptr );
|
|
if ( buffer[0] != 0 )
|
|
fun( buffer );
|
|
}
|
|
int code = pclose( pipe );
|
|
if ( errno == ECHILD ) {
|
|
errno = 0;
|
|
code = 0;
|
|
}
|
|
std::this_thread::yield(); // Allow any signals to process
|
|
resetSignal( SIGCHLD ); // Clear child exited
|
|
return code;
|
|
}
|
|
template<std::size_t blocKSize>
|
|
static void exec2( const char *cmd, staticVector<std::array<char, 1024>, blocKSize> &out )
|
|
{
|
|
out.clear();
|
|
auto fun = [&out]( const char *line ) {
|
|
size_t N = strlen( line );
|
|
size_t k = out.size();
|
|
out.resize( k + 1 );
|
|
out[k].fill( 0 );
|
|
memcpy( out[k].data(), line, N );
|
|
if ( out[k][N - 1] == '\n' )
|
|
out[k][N - 1] = 0;
|
|
};
|
|
exec3( cmd, fun );
|
|
}
|
|
std::string StackTrace::exec( const string_view &cmd, int &code )
|
|
{
|
|
std::string result;
|
|
auto fun = [&result]( const char *line ) { result += line; };
|
|
code = exec3( cmd.data(), fun );
|
|
return result;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* stack_info *
|
|
****************************************************************************/
|
|
static_assert( sizeof( StackTrace::stack_info ) <= 512, "Unexpected size for stack_info" );
|
|
StackTrace::stack_info::stack_info() { clear(); }
|
|
void StackTrace::stack_info::clear()
|
|
{
|
|
line = 0;
|
|
address = nullptr;
|
|
address2 = nullptr;
|
|
object.fill( 0 );
|
|
objectPath.fill( 0 );
|
|
filename.fill( 0 );
|
|
filenamePath.fill( 0 );
|
|
function.fill( 0 );
|
|
}
|
|
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<unsigned long long int>( 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 w1, int w2, int w3 ) const
|
|
{
|
|
char out[32 + sizeof( stack_info )];
|
|
print2( out, w1, w2, w3 );
|
|
return std::string( out );
|
|
}
|
|
void StackTrace::stack_info::print(
|
|
std::ostream &out, const std::vector<stack_info> &stack, const StackTrace::string_view &prefix )
|
|
{
|
|
char buf[32 + sizeof( stack_info )];
|
|
for ( const auto &tmp : stack ) {
|
|
tmp.print2( buf, 16, 20, 32 );
|
|
out << prefix << buf << std::endl;
|
|
}
|
|
}
|
|
void StackTrace::stack_info::print2( char *out, int w1, int w2, int w3 ) const
|
|
{
|
|
char tmp1[16], tmp2[16];
|
|
sprintf( tmp1, "0x%%0%illx: ", w1 );
|
|
sprintf( tmp2, "%%%is %%%is", w2, w3 );
|
|
size_t pos = 0;
|
|
pos += sprintf( &out[pos], tmp1, reinterpret_cast<unsigned long long int>( address ) );
|
|
pos += sprintf( &out[pos], tmp2, stripPath( object.data() ), function.data() );
|
|
if ( filename[0] != 0 && line > 0 ) {
|
|
pos += sprintf( &out[pos], " %s:%u", stripPath( filename.data() ), line );
|
|
} else if ( filename[0] != 0 ) {
|
|
pos += sprintf( &out[pos], " %s", stripPath( filename.data() ) );
|
|
} else if ( line > 0 ) {
|
|
pos += sprintf( &out[pos], " : %u", line );
|
|
}
|
|
NULL_USE( pos );
|
|
}
|
|
size_t StackTrace::stack_info::size() const { return sizeof( *this ); }
|
|
char *StackTrace::stack_info::pack( char *ptr ) const
|
|
{
|
|
memcpy( ptr, this, sizeof( *this ) );
|
|
return ptr + sizeof( *this );
|
|
}
|
|
const char *StackTrace::stack_info::unpack( const char *ptr )
|
|
{
|
|
memcpy( this, ptr, sizeof( *this ) );
|
|
return ptr + sizeof( *this );
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* multi_stack_info *
|
|
****************************************************************************/
|
|
StackTrace::multi_stack_info::multi_stack_info( const std::vector<stack_info> &rhs )
|
|
{
|
|
operator=( rhs );
|
|
}
|
|
StackTrace::multi_stack_info &StackTrace::multi_stack_info::operator=(
|
|
const std::vector<stack_info> &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();
|
|
}
|
|
template<class FUN>
|
|
void StackTrace::multi_stack_info::print2( int Np, char *prefix, int w[3], bool c, FUN &fun ) const
|
|
{
|
|
if ( stack.address != 0 ) {
|
|
prefix[Np] = 0;
|
|
char line[4096];
|
|
int N2 = sprintf( line, "%s[%i] ", prefix, N );
|
|
stack.print2( &line[N2], w[0], w[1], w[2] );
|
|
fun( line );
|
|
prefix[Np++] = c ? '|' : ' ';
|
|
prefix[Np++] = ' ';
|
|
}
|
|
for ( size_t i = 0; i < children.size(); i++ ) {
|
|
bool c2 = children.size() > 1 && i < children.size() - 1 && stack.address != 0;
|
|
const auto &child = children[i];
|
|
child.print2( Np, prefix, w, c2, fun );
|
|
}
|
|
}
|
|
std::vector<std::string> StackTrace::multi_stack_info::print( const string_view &prefix ) const
|
|
{
|
|
std::vector<std::string> text;
|
|
int w[3] = { getAddressWidth(), getObjectWidth(), getFunctionWidth() };
|
|
char prefix2[1024];
|
|
memcpy( prefix2, prefix.data(), prefix.size() );
|
|
auto fun = [&text]( const char *line ) { text.push_back( line ); };
|
|
print2( prefix.size(), prefix2, w, false, fun );
|
|
return text;
|
|
}
|
|
void StackTrace::multi_stack_info::print( std::ostream &out, const string_view &prefix ) const
|
|
{
|
|
int w[3] = { getAddressWidth(), getObjectWidth(), getFunctionWidth() };
|
|
char prefix2[1024];
|
|
memcpy( prefix2, prefix.data(), prefix.size() );
|
|
auto fun = [&out]( const char *line ) { out << line << std::endl; };
|
|
print2( prefix.size(), prefix2, w, false, fun );
|
|
}
|
|
std::string StackTrace::multi_stack_info::printString( const string_view &prefix ) const
|
|
{
|
|
int w[3] = { getAddressWidth(), getObjectWidth(), getFunctionWidth() };
|
|
char prefix2[1024];
|
|
memcpy( prefix2, prefix.data(), prefix.size() );
|
|
std::string out;
|
|
out.reserve( 4096 );
|
|
auto fun = [&out]( const char *line ) {
|
|
out += line;
|
|
out += '\n';
|
|
};
|
|
print2( prefix.size(), prefix2, w, false, fun );
|
|
return out;
|
|
}
|
|
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<int>( 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<int>( 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 );
|
|
}
|
|
void StackTrace::multi_stack_info::add( const multi_stack_info &rhs )
|
|
{
|
|
N += rhs.N;
|
|
for ( const auto &x : rhs.children ) {
|
|
bool found = false;
|
|
for ( auto &tmp : children ) {
|
|
if ( tmp.stack == x.stack ) {
|
|
found = true;
|
|
tmp.add( x );
|
|
}
|
|
}
|
|
if ( !found )
|
|
children.push_back( x );
|
|
}
|
|
}
|
|
size_t StackTrace::multi_stack_info::size() const
|
|
{
|
|
size_t bytes = 2 * sizeof( int ) + stack.size();
|
|
for ( const auto &tmp : children )
|
|
bytes += tmp.size();
|
|
return bytes;
|
|
}
|
|
char *StackTrace::multi_stack_info::pack( char *ptr ) const
|
|
{
|
|
int N2 = N;
|
|
memcpy( ptr, &N2, sizeof( int ) );
|
|
ptr += sizeof( int );
|
|
ptr = stack.pack( ptr );
|
|
int Nc = children.size();
|
|
memcpy( ptr, &Nc, sizeof( int ) );
|
|
ptr += sizeof( int );
|
|
for ( const auto &tmp : children )
|
|
ptr = tmp.pack( ptr );
|
|
return ptr;
|
|
}
|
|
const char *StackTrace::multi_stack_info::unpack( const char *ptr )
|
|
{
|
|
int N2, Nc;
|
|
memcpy( &N2, ptr, sizeof( int ) );
|
|
ptr += sizeof( int );
|
|
N = N2;
|
|
ptr = stack.unpack( ptr );
|
|
memcpy( &Nc, ptr, sizeof( int ) );
|
|
ptr += sizeof( int );
|
|
children.resize( Nc );
|
|
for ( auto &tmp : children )
|
|
ptr = tmp.unpack( ptr );
|
|
return ptr;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Function to get the executable name *
|
|
****************************************************************************/
|
|
static std::array<char, 1000> getExecutableName()
|
|
{
|
|
std::array<char, 1000> exe;
|
|
try {
|
|
#ifdef USE_LINUX
|
|
char buf[0x10000] = { 0 };
|
|
int len = ::readlink( "/proc/self/exe", buf, 0x10000 );
|
|
if ( len != -1 ) {
|
|
buf[len] = '\0';
|
|
strcpy( exe.data(), buf );
|
|
}
|
|
#elif defined( USE_MAC )
|
|
uint32_t size = 0x10000;
|
|
char buf[0x10000] = { 0 };
|
|
if ( _NSGetExecutablePath( buf, &size ) == 0 )
|
|
strcpy( exe.data(), buf );
|
|
#elif defined( USE_WINDOWS )
|
|
DWORD size = 0x10000;
|
|
char buf[0x10000] = { 0 };
|
|
GetModuleFileName( nullptr, buf, size );
|
|
strcpy( exe.data(), buf );
|
|
#endif
|
|
} catch ( ... ) {
|
|
}
|
|
return exe;
|
|
}
|
|
static const char *getExecutable2()
|
|
{
|
|
static auto execname = getExecutableName();
|
|
return execname.data();
|
|
}
|
|
std::string StackTrace::getExecutable() { return std::string( getExecutable2() ); }
|
|
|
|
|
|
/****************************************************************************
|
|
* 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. *
|
|
****************************************************************************/
|
|
static_assert( sizeof( StackTrace::symbols_struct ) <= 128, "Unexpected size for symbols_struct" );
|
|
std::vector<StackTrace::symbols_struct> global_symbols_data;
|
|
static bool global_symbols_loaded = false;
|
|
static std::vector<StackTrace::symbols_struct> getSymbolData()
|
|
{
|
|
std::vector<StackTrace::symbols_struct> data;
|
|
#ifdef USE_NM
|
|
try {
|
|
char cmd[1024];
|
|
#ifdef USE_LINUX
|
|
sprintf( cmd, "nm -n --demangle %s", getExecutable2() );
|
|
#elif defined( USE_MAC )
|
|
sprintf( cmd, "nm -n %s | c++filt", getExecutable2() );
|
|
#else
|
|
#error Unknown OS using nm
|
|
#endif
|
|
// Function to process a line of nm output
|
|
auto fun = [&data]( char *line ) {
|
|
if ( line[0] == ' ' )
|
|
return;
|
|
auto *a = line;
|
|
char *b = strchr( a, ' ' );
|
|
if ( b == nullptr )
|
|
return;
|
|
b[0] = 0;
|
|
b++;
|
|
char *c = strchr( b, ' ' );
|
|
if ( c == nullptr )
|
|
return;
|
|
c[0] = 0;
|
|
c++;
|
|
char *d = strchr( c, '\n' );
|
|
if ( d )
|
|
d[0] = 0;
|
|
size_t add = strtoul( a, nullptr, 16 );
|
|
size_t k = data.size();
|
|
data.resize( k + 1 );
|
|
data[k].address = reinterpret_cast<void *>( add );
|
|
data[k].type = b[0];
|
|
copy( c, data[k].obj, data[k].objPath );
|
|
};
|
|
// Call nm
|
|
exec3( cmd, fun );
|
|
} catch ( ... ) {
|
|
}
|
|
#endif
|
|
return data;
|
|
}
|
|
std::vector<StackTrace::symbols_struct> StackTrace::getSymbols()
|
|
{
|
|
StackTrace_mutex.lock();
|
|
if ( !global_symbols_loaded ) {
|
|
global_symbols_data = getSymbolData();
|
|
global_symbols_loaded = true;
|
|
}
|
|
auto data = global_symbols_data;
|
|
StackTrace_mutex.unlock();
|
|
return data;
|
|
}
|
|
void StackTrace::clearSymbols()
|
|
{
|
|
StackTrace_mutex.lock();
|
|
if ( global_symbols_loaded ) {
|
|
global_symbols_data = std::vector<StackTrace::symbols_struct>();
|
|
global_symbols_loaded = false;
|
|
}
|
|
StackTrace_mutex.unlock();
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Function to get call stack info *
|
|
****************************************************************************/
|
|
#ifdef USE_MAC
|
|
static void *loadAddress( const uint32_t &obj_hash )
|
|
{
|
|
static std::map<uint32_t, void *> obj_map;
|
|
if ( obj_map.empty() ) {
|
|
uint32_t numImages = _dyld_image_count();
|
|
for ( uint32_t i = 0; i < numImages; i++ ) {
|
|
auto header = _dyld_get_image_header( i );
|
|
auto name = _dyld_get_image_name( i );
|
|
auto p = strrchr( name, '/' );
|
|
auto address = const_cast<struct mach_header *>( header );
|
|
auto hash = hashString( p + 1 );
|
|
obj_map.insert( std::make_pair( hash, address ) );
|
|
}
|
|
}
|
|
auto it = obj_map.find( obj_hash );
|
|
void *address = 0;
|
|
if ( it != obj_map.end() ) {
|
|
address = it->second;
|
|
} else {
|
|
it = obj_map.find( obj_hash );
|
|
if ( it != obj_map.end() )
|
|
address = it->second;
|
|
}
|
|
return address;
|
|
}
|
|
static auto split_atos( const std::string &buf )
|
|
{
|
|
int line = 0;
|
|
std::array<char, 2048> fun;
|
|
std::array<char, 64> obj, file, objPath, filePath;
|
|
if ( buf.empty() )
|
|
return std::tie( fun, obj, objPath, file, filePath, line );
|
|
// Get the function
|
|
size_t index = buf.find( " (in " );
|
|
if ( index == std::string::npos ) {
|
|
copy( buf.c_str(), fun );
|
|
cleanupFunctionName( fun );
|
|
return std::tie( fun, obj, objPath, file, filePath, line );
|
|
}
|
|
copy( buf.substr( 0, index ).c_str(), fun );
|
|
cleanupFunctionName( fun );
|
|
std::string tmp = buf.substr( index + 5 );
|
|
// Get the object
|
|
index = tmp.find( ')' );
|
|
copy( tmp.substr( 0, index ).c_str(), obj, objPath );
|
|
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( ':' );
|
|
if ( index != std::string::npos ) {
|
|
copy( tmp.substr( 0, index ).c_str(), file, filePath );
|
|
line = std::stoi( tmp.substr( index + 1 ) );
|
|
} else if ( p1 != std::string::npos ) {
|
|
copy( tmp.c_str(), file, filePath );
|
|
}
|
|
return std::tie( fun, obj, objPath, file, filePath, line );
|
|
}
|
|
#endif
|
|
// clang-format off
|
|
template<std::size_t blockSize>
|
|
static void getFileAndLineObject( staticVector<StackTrace::stack_info*,blockSize> &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
|
|
uint32_t N;
|
|
char cmd[4096];
|
|
static_assert( sizeof(unsigned long) == sizeof(size_t), "Unxpected size for ul" );
|
|
if ( info[0]->objectPath[0] == 0 )
|
|
N = sprintf(cmd,"addr2line -C -e %s -f",info[0]->object.data());
|
|
else
|
|
N = sprintf(cmd,"addr2line -C -e %s/%s -f",info[0]->objectPath.data(),info[0]->object.data());
|
|
for (size_t i=0; i<info.size() && N < sizeof(cmd) - 32; i++) {
|
|
N += sprintf(&cmd[N]," %lx %lx",
|
|
reinterpret_cast<unsigned long>( info[i]->address ),
|
|
reinterpret_cast<unsigned long>( info[i]->address2 ) );
|
|
}
|
|
N += sprintf(&cmd[N]," 2> /dev/null");
|
|
// Get the function/line/file
|
|
staticVector<std::array<char, 1024>,4*blockSize> output;
|
|
exec2( cmd, output );
|
|
if ( output.size() != 4*info.size() )
|
|
return;
|
|
// Add the results to info
|
|
for (size_t i=0; i<info.size(); i++) {
|
|
char *tmp1 = output[4*i+0].data();
|
|
char *tmp2 = output[4*i+1].data();
|
|
if ( tmp1[0] == '?' && tmp1[1] == '?' ) {
|
|
tmp1 = output[4*i+2].data();
|
|
tmp2 = output[4*i+3].data();
|
|
}
|
|
if ( tmp1[0] == '?' && tmp1[1] == '?' ) {
|
|
continue;
|
|
}
|
|
// get function name
|
|
if ( info[i]->function.empty() ) {
|
|
cleanupFunctionName( tmp1 );
|
|
copy( tmp1, info[i]->function );
|
|
}
|
|
// get file and line
|
|
char *buf = tmp2;
|
|
if ( buf[0] != '?' && buf[0] != 0 ) {
|
|
size_t j = 0;
|
|
for ( j = 0; j < 1024 && buf[j] != ':'; j++ ) {
|
|
}
|
|
buf[j] = 0;
|
|
copy( buf, info[i]->filename, info[i]->filenamePath );
|
|
info[i]->line = atoi( &buf[j + 1] );
|
|
}
|
|
}
|
|
#elif defined( USE_MAC )
|
|
// Create the call command
|
|
void* load_address = loadAddress( hashString( info[0]->object.data() ) );
|
|
if ( load_address == nullptr )
|
|
return;
|
|
// Call atos to get the object info
|
|
uint32_t N;
|
|
char cmd[4096];
|
|
static_assert( sizeof(unsigned long) == sizeof(size_t), "Unxpected size for ul" );
|
|
auto addr = reinterpret_cast<unsigned long>( load_address );
|
|
if ( info[0]->objectPath[0] == 0 )
|
|
N = sprintf( cmd, "atos -o %s -f -l %lx", info[0]->object.data(), addr );
|
|
else
|
|
N = sprintf( cmd, "atos -o %s/%s -f -l %lx", info[0]->objectPath.data(), info[0]->object.data(), addr );
|
|
for (size_t i=0; i<info.size() && N < sizeof(cmd) - 32; i++)
|
|
N += sprintf( &cmd[N], " %lx", reinterpret_cast<unsigned long>( info[i]->address ) );
|
|
N += sprintf(&cmd[N]," 2> /dev/null");
|
|
// Get the function/line/file
|
|
staticVector<std::array<char, 1024>,blockSize> output;
|
|
exec2( cmd, output );
|
|
if ( output.size() != info.size() )
|
|
return;
|
|
// Parse the output for function, file and line info
|
|
for ( size_t i=0; i<info.size(); i++) {
|
|
auto data = split_atos( output[2*i].data() );
|
|
if ( info[i]->function.empty() )
|
|
info[i]->function = std::get<0>(data);
|
|
if ( info[i]->object.empty() ) {
|
|
info[i]->object = std::get<1>(data);
|
|
info[i]->objectPath = std::get<2>(data);
|
|
}
|
|
if ( info[i]->filename.empty() ) {
|
|
info[i]->filename = std::get<3>(data);
|
|
info[i]->filenamePath = std::get<4>(data);
|
|
}
|
|
if ( info[i]->line==0 )
|
|
info[i]->line = std::get<5>(data);
|
|
}
|
|
#endif
|
|
}
|
|
static void getFileAndLine( size_t N, StackTrace::stack_info *info )
|
|
{
|
|
constexpr size_t blockSize = 1024;
|
|
// Operate on blocks
|
|
size_t i0 = 0;
|
|
while ( i0 < N ) {
|
|
// Get a list of objects
|
|
staticVector<uint64_t,blockSize> objectHash;
|
|
for ( size_t i = i0; i<N && i-i0 < blockSize; i++)
|
|
objectHash.insert( objHash( info[i].object, info[i].objectPath ) );
|
|
// For each object, get the file/line numbers for all entries
|
|
for ( const auto & hash : objectHash ) {
|
|
staticVector<StackTrace::stack_info*,blockSize> list;
|
|
for ( size_t i = i0; i<N && i-i0 < blockSize; i++) {
|
|
if ( objHash( info[i].object, info[i].objectPath ) == hash )
|
|
list.push_back( &info[i] );
|
|
}
|
|
getFileAndLineObject( list );
|
|
}
|
|
i0 = std::min( N, i0 + blockSize );
|
|
}
|
|
}
|
|
// Try to use the global symbols to decode info about the stack
|
|
static void getDataFromGlobalSymbols( StackTrace::stack_info &info )
|
|
{
|
|
if ( !global_symbols_loaded ) {
|
|
global_symbols_data = getSymbolData();
|
|
global_symbols_loaded = true;
|
|
}
|
|
const auto &data = global_symbols_data;
|
|
if ( !data.empty() ) {
|
|
// Find the closest address
|
|
size_t lower = 0;
|
|
size_t upper = data.size() - 1;
|
|
while ( ( upper - lower ) != 1 ) {
|
|
size_t value = ( upper + lower ) / 2;
|
|
if ( data[value].address >= info.address )
|
|
upper = value;
|
|
else
|
|
lower = value;
|
|
}
|
|
if ( upper > 0 ) {
|
|
copy( data[lower].obj, info.object );
|
|
copy( data[lower].objPath, info.objectPath );
|
|
} else {
|
|
copy( getExecutable2(), info.object, info.objectPath );
|
|
}
|
|
}
|
|
}
|
|
static void signal_handler( int sig )
|
|
{
|
|
printf("Signal caught acquiring stack (%i)\n",sig);
|
|
StackTrace::setErrorHandler( [](const StackTrace::abort_error &err) { std::cerr << err.what(); exit( -1 ); } );
|
|
}
|
|
static void getStackInfo2( size_t N, void* const* address, StackTrace::stack_info *info )
|
|
{
|
|
// Temporarily handle signals to prevent recursion on the stack
|
|
auto prev_handler = signal( SIGINT, signal_handler );
|
|
// Get the detailed stack info
|
|
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<N; i++) {
|
|
info[i].address = address[i];
|
|
DWORD64 address2 = reinterpret_cast<DWORD64>( 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 ) {
|
|
cleanupFunctionName( pSym->Name );
|
|
copy( pSym->Name, info[i].function );
|
|
} else {
|
|
info[i].function.fill( 0 );
|
|
}
|
|
} 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;
|
|
copy( Line.FileName, info[i].filename, info[i].filenamePath );
|
|
} else {
|
|
info[i].line = 0;
|
|
copy( nullptr, info[i].filename, info[i].filenamePath );
|
|
}
|
|
|
|
// Get the object
|
|
if ( SymGetModuleInfo64( pid, address2, &Module ) != FALSE ) {
|
|
copy( Module.LoadedImageName, info[i].object, info[i].objectPath );
|
|
}
|
|
}
|
|
#else
|
|
for (size_t i=0; i<N; i++) {
|
|
info[i].address = address[i];
|
|
#if defined(_GNU_SOURCE) || defined(USE_MAC)
|
|
Dl_info dlinfo;
|
|
if ( !dladdr( info[i].address, &dlinfo ) ) {
|
|
getDataFromGlobalSymbols( info[i] );
|
|
continue;
|
|
}
|
|
info[i].address2 = subtractAddress( info[i].address, dlinfo.dli_fbase );
|
|
copy( dlinfo.dli_fname, info[i].object, info[i].objectPath );
|
|
#if defined( USE_ABI )
|
|
int status;
|
|
char *demangled = abi::__cxa_demangle( dlinfo.dli_sname, nullptr, nullptr, &status );
|
|
if ( status == 0 && demangled != nullptr ) {
|
|
cleanupFunctionName( demangled );
|
|
copy( demangled, info[i].function );
|
|
} else if ( dlinfo.dli_sname != nullptr ) {
|
|
copy( dlinfo.dli_sname, info[i].function );
|
|
}
|
|
free( demangled );
|
|
#endif
|
|
if ( dlinfo.dli_sname != nullptr && info[i].function[0] == 0 ) {
|
|
std::array<char,4096> tmp;
|
|
copy( dlinfo.dli_sname, tmp );
|
|
cleanupFunctionName( tmp.data() );
|
|
copy( tmp, info[i].function );
|
|
}
|
|
#else
|
|
getDataFromGlobalSymbols( info[i] );
|
|
#endif
|
|
}
|
|
// Get the filename / line numbers for each item on the stack
|
|
getFileAndLine( N, info );
|
|
#endif
|
|
} catch ( ... ) {
|
|
}
|
|
signal( SIGINT, prev_handler ) ;
|
|
}
|
|
StackTrace::stack_info StackTrace::getStackInfo( void *address )
|
|
{
|
|
StackTrace::stack_info info;
|
|
getStackInfo2( 1, &address, &info );
|
|
return info;
|
|
}
|
|
std::vector<StackTrace::stack_info> StackTrace::getStackInfo( const std::vector<void*>& address )
|
|
{
|
|
std::vector<StackTrace::stack_info> info( address.size() );
|
|
getStackInfo2( address.size(), address.data(), info.data() );
|
|
return info;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Helper functions for controlling interal signals *
|
|
****************************************************************************/
|
|
static int backtrace_thread( const std::thread::native_handle_type&, void**, size_t );
|
|
#if defined( USE_LINUX ) || defined( USE_MAC )
|
|
static int global_thread_backtrace_count;
|
|
static void* global_thread_backtrace[1000];
|
|
static void _callstack_signal_handler( int, siginfo_t*, void* )
|
|
{
|
|
global_thread_backtrace_count = backtrace_thread( StackTrace::thisThread(), global_thread_backtrace, 1000 );
|
|
}
|
|
static int get_thread_callstack_signal()
|
|
{
|
|
if ( 39 >= SIGRTMIN && 39 <= SIGRTMAX )
|
|
return 39;
|
|
return std::min<int>( SIGRTMIN+4, SIGRTMAX );
|
|
}
|
|
static int thread_callstack_signal = get_thread_callstack_signal();
|
|
#endif
|
|
|
|
|
|
/****************************************************************************
|
|
* Function to get the list of all active threads *
|
|
****************************************************************************/
|
|
#if defined( USE_LINUX ) || defined( USE_MAC )
|
|
static std::thread::native_handle_type thread_handle;
|
|
static bool thread_id_finished;
|
|
static void _activeThreads_signal_handler( int )
|
|
{
|
|
auto handle = StackTrace::thisThread( );
|
|
thread_handle = handle;
|
|
thread_id_finished = true;
|
|
}
|
|
#endif
|
|
#ifdef USE_LINUX
|
|
static constexpr int get_tid( int pid, const char *line )
|
|
{
|
|
char buf2[128]={0};
|
|
int i1 = 0;
|
|
while ( line[i1]==' ' ) { i1++; }
|
|
int i2 = i1;
|
|
while ( line[i2]!=' ' ) { 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]==' ' ) { i1++; }
|
|
i2 = i1;
|
|
while ( line[i2]!=' ' ) { 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
|
|
}
|
|
static staticVector<std::thread::native_handle_type,1024> getActiveThreads( )
|
|
{
|
|
staticVector<std::thread::native_handle_type,1024> threads;
|
|
#if defined( USE_LINUX )
|
|
int N_tid = 0, tid[1024];
|
|
int pid = getpid();
|
|
char cmd[128];
|
|
sprintf( cmd, "ps -T -p %i", pid );
|
|
auto fun = [&N_tid,&tid,pid]( const char* line ) {
|
|
int id = get_tid( pid, line );
|
|
if ( id != -1 && N_tid < 1024 )
|
|
tid[N_tid++] = id;
|
|
};
|
|
exec3( cmd, fun );
|
|
int myid = syscall(SYS_gettid);
|
|
for ( int i=0; i<N_tid; i++) {
|
|
if ( tid[i] == myid )
|
|
std::swap( tid[i], tid[--N_tid] );
|
|
}
|
|
auto old = signal( thread_callstack_signal, _activeThreads_signal_handler );
|
|
for ( int i=0; i<N_tid; i++) {
|
|
StackTrace_mutex.lock();
|
|
thread_id_finished = false;
|
|
thread_handle = StackTrace::thisThread();
|
|
syscall( SYS_tgkill, pid, tid[i], thread_callstack_signal );
|
|
auto t1 = std::chrono::high_resolution_clock::now();
|
|
auto t2 = std::chrono::high_resolution_clock::now();
|
|
while ( !thread_id_finished && std::chrono::duration<double>(t2-t1).count()<0.1 ) {
|
|
std::this_thread::yield();
|
|
t2 = std::chrono::high_resolution_clock::now();
|
|
}
|
|
threads.push_back( thread_handle );
|
|
StackTrace_mutex.unlock();
|
|
}
|
|
signal( thread_callstack_signal, old );
|
|
#elif defined( USE_MAC )
|
|
thread_act_port_array_t thread_list;
|
|
mach_msg_type_number_t thread_count = 0;
|
|
task_threads(mach_task_self(), &thread_list, &thread_count);
|
|
auto old = signal( thread_callstack_signal, _activeThreads_signal_handler );
|
|
for ( int i=0; i<thread_count; i++) {
|
|
if ( thread_list[i] == mach_thread_self() )
|
|
continue;
|
|
static bool called = false;
|
|
if ( !called ) {
|
|
called = true;
|
|
std::cerr << "activeThreads not finished for MAC\n";
|
|
}
|
|
/*
|
|
StackTrace_mutex.lock();
|
|
thread_id_finished = false;
|
|
thread_handle = thisThread();
|
|
x86_thread_state64_t state;
|
|
unsigned int count = MACHINE_THREAD_STATE_COUNT;
|
|
thread_abort( thread_list[i] ); // Abort system calls
|
|
thread_suspend( thread_list[i] );
|
|
thread_get_state( thread_list[i], MACHINE_THREAD_STATE, (thread_state_t) &state, &count );
|
|
state.__rip = (uint64_t) _activeThreads2;
|
|
thread_set_state( thread_list[i], MACHINE_THREAD_STATE, (thread_state_t) &state, MACHINE_THREAD_STATE_COUNT );
|
|
thread_resume( thread_list[i] );
|
|
//pthread_kill( thread_list[i], CALLSTACK_SIG );
|
|
//syscall( SYS___pthread_kill, getpid(), thread_list[i], CALLSTACK_SIG );
|
|
//syscall( SYS_kill, thread_list[i], 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<double>(t2-t1).count()<0.1 ) {
|
|
std::this_thread::yield();
|
|
t2 = std::chrono::high_resolution_clock::now();
|
|
}
|
|
threads.push_back( thread_handle );
|
|
StackTrace_mutex.unlock();*/
|
|
}
|
|
signal( thread_callstack_signal, old );
|
|
#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.push_back( 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
|
|
// Add the current thread
|
|
threads.push_back( StackTrace::thisThread() );
|
|
// Remove the globalMonitorThread
|
|
if ( globalMonitorThread ) {
|
|
auto globalThreadId = globalMonitorThread->native_handle();
|
|
for ( int i = threads.size() - 1; i >= 0; i-- ) {
|
|
if ( threads[i] == globalThreadId ) {
|
|
std::swap( threads[i], threads.back() );
|
|
threads.pop_back();
|
|
}
|
|
}
|
|
}
|
|
// Sort the threads, remove any duplicates and remove the globalMonitorThread
|
|
std::sort( threads.begin(), threads.end() );
|
|
return threads;
|
|
}
|
|
// clang-format on
|
|
std::vector<std::thread::native_handle_type> StackTrace::activeThreads()
|
|
{
|
|
auto threads = getActiveThreads();
|
|
std::sort( threads.begin(), threads.end() );
|
|
return std::vector<std::thread::native_handle_type>( threads.begin(), threads.end() );
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Function to get the backtrace *
|
|
****************************************************************************/
|
|
static int backtrace_thread(
|
|
const std::thread::native_handle_type &tid, void **buffer, size_t size )
|
|
{
|
|
int count = 0;
|
|
#if defined( USE_LINUX ) || defined( USE_MAC )
|
|
// Get the trace
|
|
if ( tid == pthread_self() ) {
|
|
count = ::backtrace( buffer, size );
|
|
} else {
|
|
// Send a signal to the desired thread to get the call stack
|
|
StackTrace_mutex.lock();
|
|
struct sigaction sa;
|
|
sigfillset( &sa.sa_mask );
|
|
sa.sa_flags = SA_SIGINFO;
|
|
sa.sa_sigaction = _callstack_signal_handler;
|
|
sigaction( thread_callstack_signal, &sa, nullptr );
|
|
global_thread_backtrace_count = -1;
|
|
pthread_kill( tid, thread_callstack_signal );
|
|
auto t1 = std::chrono::high_resolution_clock::now();
|
|
auto t2 = std::chrono::high_resolution_clock::now();
|
|
while ( global_thread_backtrace_count == -1 &&
|
|
std::chrono::duration<double>( t2 - t1 ).count() < 0.15 ) {
|
|
std::this_thread::yield();
|
|
t2 = std::chrono::high_resolution_clock::now();
|
|
}
|
|
count = std::max( global_thread_backtrace_count, 0 );
|
|
memcpy( buffer, global_thread_backtrace, count * sizeof( void * ) );
|
|
global_thread_backtrace_count = -1;
|
|
StackTrace_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<void*>( 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<void *> StackTrace::backtrace( std::thread::native_handle_type tid )
|
|
{
|
|
std::vector<void *> trace( 1000, nullptr );
|
|
size_t count = backtrace_thread( tid, trace.data(), trace.size() );
|
|
trace.resize( count );
|
|
return trace;
|
|
}
|
|
std::vector<void *> StackTrace::backtrace()
|
|
{
|
|
std::vector<void *> trace( 1000, nullptr );
|
|
size_t count = backtrace_thread( thisThread(), trace.data(), trace.size() );
|
|
trace.resize( count );
|
|
return trace;
|
|
}
|
|
std::vector<std::vector<void *>> StackTrace::backtraceAll()
|
|
{
|
|
// Get the list of threads
|
|
auto threads = getActiveThreads();
|
|
// Get the backtrace of each thread
|
|
std::vector<std::vector<void *>> trace( threads.size() );
|
|
for ( size_t i = 0; i < threads.size(); i++ ) {
|
|
trace[i].resize( 1000 );
|
|
size_t count = backtrace_thread( threads[i], trace[i].data(), trace[i].size() );
|
|
trace[i].resize( count );
|
|
}
|
|
return trace;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Function to get the current call stack *
|
|
****************************************************************************/
|
|
std::vector<StackTrace::stack_info> StackTrace::getCallStack()
|
|
{
|
|
void *trace[1000];
|
|
size_t count = backtrace_thread( thisThread(), trace, 1000 );
|
|
std::vector<StackTrace::stack_info> info( count );
|
|
getStackInfo2( count, trace, info.data() );
|
|
return info;
|
|
}
|
|
std::vector<StackTrace::stack_info> StackTrace::getCallStack( std::thread::native_handle_type id )
|
|
{
|
|
void *trace[1000];
|
|
size_t count = backtrace_thread( id, trace, 1000 );
|
|
std::vector<StackTrace::stack_info> info( count );
|
|
getStackInfo2( count, trace, info.data() );
|
|
return info;
|
|
}
|
|
static std::vector<std::vector<StackTrace::stack_info>> generateStacks(
|
|
const std::vector<std::vector<void *>> &trace )
|
|
{
|
|
// Function to find an address
|
|
auto find = []( const auto &data, auto x ) {
|
|
for ( size_t i = 0; i < data.size(); i++ ) {
|
|
if ( data[i] == x )
|
|
return static_cast<int>( i );
|
|
}
|
|
return -1;
|
|
};
|
|
// Get the stack data for all pointers
|
|
std::vector<void *> addresses;
|
|
addresses.reserve( 1024 );
|
|
for ( const auto &tmp : trace ) {
|
|
for ( auto ptr : tmp ) {
|
|
if ( find( addresses, ptr ) == -1 )
|
|
addresses.push_back( ptr );
|
|
}
|
|
}
|
|
auto stack_data = StackTrace::getStackInfo( addresses );
|
|
// Create the stack traces
|
|
std::vector<std::vector<StackTrace::stack_info>> stack( trace.size() );
|
|
for ( size_t i = 0; i < trace.size(); i++ ) {
|
|
// Create the stack for the given thread trace
|
|
stack[i].resize( trace[i].size() );
|
|
for ( size_t j = 0; j < trace[i].size(); j++ ) {
|
|
int k = find( addresses, trace[i][j] );
|
|
stack[i][j] = stack_data[k];
|
|
}
|
|
}
|
|
return stack;
|
|
}
|
|
static StackTrace::multi_stack_info generateMultiStack(
|
|
const std::vector<std::vector<void *>> &trace )
|
|
{
|
|
// Get the stack data for all pointers
|
|
auto stack = generateStacks( trace );
|
|
// Create the multi-stack trace
|
|
StackTrace::multi_stack_info multistack;
|
|
multistack.N = stack.size();
|
|
for ( const auto &tmp : stack )
|
|
multistack.add( tmp.size(), tmp.data() );
|
|
return multistack;
|
|
}
|
|
static StackTrace::multi_stack_info generateMultiStack(
|
|
const staticVector<std::thread::native_handle_type, 1024> &threads )
|
|
{
|
|
// Get the stack data for all pointers
|
|
std::vector<std::vector<void *>> trace( threads.size() );
|
|
auto it = threads.begin();
|
|
for ( size_t i = 0; i < threads.size(); i++, ++it )
|
|
trace[i] = StackTrace::backtrace( *it );
|
|
// Create the multi-stack trace
|
|
return generateMultiStack( trace );
|
|
}
|
|
StackTrace::multi_stack_info StackTrace::getAllCallStacks()
|
|
{
|
|
// Get the list of active thread
|
|
auto threads = getActiveThreads();
|
|
// Create the multi-stack strucutre
|
|
auto stack = generateMultiStack( threads );
|
|
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 *
|
|
****************************************************************************/
|
|
static char signalNames[128][32];
|
|
const char *StackTrace::signalName( int sig )
|
|
{
|
|
static bool initialized = false;
|
|
if ( !initialized ) {
|
|
StackTrace_mutex.lock();
|
|
memset( signalNames, 0, sizeof( signalNames ) );
|
|
for ( int i = 0; i < 128; i++ )
|
|
strcpy( signalNames[i], strsignal( i + 1 ) );
|
|
StackTrace_mutex.unlock();
|
|
initialized = true;
|
|
}
|
|
bool valid = sig > 0 && sig <= 128;
|
|
return valid ? signalNames[sig - 1] : nullptr;
|
|
}
|
|
std::vector<int> StackTrace::allSignalsToCatch()
|
|
{
|
|
std::vector<int> signals;
|
|
signals.reserve( SIGRTMAX );
|
|
for ( int i = 1; i < 32; i++ ) {
|
|
if ( i == SIGKILL || i == SIGSTOP )
|
|
continue;
|
|
signals.push_back( i );
|
|
}
|
|
for ( int i = SIGRTMIN; i <= SIGRTMAX; i++ ) {
|
|
if ( i == SIGKILL || i == SIGSTOP )
|
|
continue;
|
|
signals.push_back( i );
|
|
}
|
|
return signals;
|
|
}
|
|
template<class TYPE>
|
|
static inline void erase( std::vector<TYPE> &x, TYPE y )
|
|
{
|
|
x.erase( std::find( x.begin(), x.end(), y ) );
|
|
}
|
|
std::vector<int> StackTrace::defaultSignalsToCatch()
|
|
{
|
|
auto signals = allSignalsToCatch();
|
|
erase( signals, SIGWINCH ); // Don't catch window changed by default
|
|
erase( signals, SIGCONT ); // Don't catch continue by default
|
|
erase( signals, SIGCHLD ); // Don't catch child exited by default
|
|
return signals;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Set the signal handlers *
|
|
****************************************************************************/
|
|
static std::function<void( const StackTrace::abort_error &err )> abort_fun;
|
|
StackTrace::abort_error rethrow()
|
|
{
|
|
StackTrace::abort_error error;
|
|
#ifdef USE_LINUX
|
|
try {
|
|
static int tried_throw = 0;
|
|
if ( tried_throw == 0 ) {
|
|
tried_throw = 1;
|
|
throw;
|
|
}
|
|
// No active exception
|
|
} catch ( const StackTrace::abort_error &err ) {
|
|
// Caught a std::runtime_error
|
|
error = err;
|
|
} catch ( const std::exception &err ) {
|
|
// Caught a std::runtime_error
|
|
error.message = err.what();
|
|
} catch ( ... ) {
|
|
// Caught an unknown exception
|
|
error.message = "Unknown exception";
|
|
}
|
|
#else
|
|
error.message = "Unknown exception";
|
|
#endif
|
|
if ( error.type == StackTrace::terminateType::unknown )
|
|
error.type = StackTrace::terminateType::exception;
|
|
if ( error.bytes == 0 )
|
|
error.bytes = StackTrace::Utilities::getMemoryUsage();
|
|
if ( error.stack.empty() ) {
|
|
error.stackType = StackTrace::printStackType::local;
|
|
error.stack = StackTrace::backtrace();
|
|
}
|
|
return error;
|
|
}
|
|
void StackTrace::terminateFunctionSignal( int sig )
|
|
{
|
|
StackTrace::abort_error err;
|
|
err.type = StackTrace::terminateType::signal;
|
|
err.signal = sig;
|
|
err.bytes = StackTrace::Utilities::getMemoryUsage();
|
|
err.stack = StackTrace::backtrace();
|
|
err.stackType = StackTrace::getDefaultStackType();
|
|
abort_fun( err );
|
|
}
|
|
static bool signals_set[256] = { false };
|
|
static void term_func()
|
|
{
|
|
auto err = rethrow();
|
|
StackTrace::clearSignals();
|
|
abort_fun( err );
|
|
}
|
|
static void null_term_func() {}
|
|
void StackTrace::clearSignal( int sig )
|
|
{
|
|
if ( signals_set[sig] ) {
|
|
signal( sig, SIG_DFL );
|
|
signals_set[sig] = false;
|
|
}
|
|
}
|
|
void StackTrace::clearSignals( const std::vector<int> &signals )
|
|
{
|
|
for ( auto sig : signals ) {
|
|
signal( sig, SIG_DFL );
|
|
signals_set[sig] = false;
|
|
}
|
|
}
|
|
void StackTrace::clearSignals()
|
|
{
|
|
for ( size_t i = 0; i < sizeof( signals_set ); i++ ) {
|
|
if ( signals_set[i] ) {
|
|
signal( i, SIG_DFL );
|
|
signals_set[i] = false;
|
|
}
|
|
}
|
|
}
|
|
void StackTrace::setSignals( const std::vector<int> &signals, void ( *handler )( int ) )
|
|
{
|
|
for ( auto sig : signals ) {
|
|
signal( sig, handler );
|
|
signals_set[sig] = true;
|
|
}
|
|
std::this_thread::yield();
|
|
}
|
|
void StackTrace::raiseSignal( int signal ) { std::raise( signal ); }
|
|
void StackTrace::setErrorHandler( std::function<void( const StackTrace::abort_error & )> abort )
|
|
{
|
|
abort_fun = abort;
|
|
std::set_terminate( term_func );
|
|
setSignals( defaultSignalsToCatch(), &terminateFunctionSignal );
|
|
std::set_unexpected( term_func );
|
|
}
|
|
void StackTrace::clearErrorHandler()
|
|
{
|
|
abort_fun = []( const StackTrace::abort_error & ) {};
|
|
std::set_terminate( null_term_func );
|
|
clearSignals();
|
|
std::set_unexpected( null_term_func );
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Functions to handle MPI errors *
|
|
****************************************************************************/
|
|
#ifdef USE_MPI
|
|
static bool MPI_Initialized()
|
|
{
|
|
int initialized = 0, finalized = 0;
|
|
MPI_Initialized( &initialized );
|
|
MPI_Finalized( &finalized );
|
|
return initialized != 0 && finalized == 0;
|
|
}
|
|
static std::shared_ptr<MPI_Errhandler> mpierr;
|
|
static void MPI_error_handler_fun( MPI_Comm *comm, int *err, ... )
|
|
{
|
|
if ( *err == MPI_ERR_COMM && *comm == MPI_COMM_WORLD ) {
|
|
// Special error handling for an invalid MPI_COMM_WORLD
|
|
std::cerr << "Error invalid MPI_COMM_WORLD";
|
|
exit( -1 );
|
|
}
|
|
int msg_len = 0;
|
|
char message[1000] = { 0 };
|
|
MPI_Error_string( *err, message, &msg_len );
|
|
StackTrace::abort_error error;
|
|
error.message = std::string( message );
|
|
error.type = StackTrace::terminateType::MPI;
|
|
error.bytes = StackTrace::Utilities::getMemoryUsage();
|
|
error.stack = StackTrace::backtrace();
|
|
error.stackType = StackTrace::printStackType::global;
|
|
throw error;
|
|
}
|
|
void StackTrace::setMPIErrorHandler( MPI_Comm comm )
|
|
{
|
|
if ( !MPI_Initialized() )
|
|
return;
|
|
if ( mpierr.get() == nullptr ) {
|
|
mpierr = std::make_shared<MPI_Errhandler>();
|
|
MPI_Comm_create_errhandler( MPI_error_handler_fun, mpierr.get() );
|
|
}
|
|
MPI_Comm_set_errhandler( comm, *mpierr );
|
|
}
|
|
void StackTrace::clearMPIErrorHandler( MPI_Comm comm )
|
|
{
|
|
if ( !MPI_Initialized() )
|
|
return;
|
|
if ( mpierr.get() != nullptr )
|
|
MPI_Errhandler_free( mpierr.get() ); // Delete the error handler
|
|
mpierr.reset();
|
|
MPI_Comm_set_errhandler( comm, MPI_ERRORS_ARE_FATAL );
|
|
}
|
|
#else
|
|
void StackTrace::setMPIErrorHandler( MPI_Comm ) {}
|
|
void StackTrace::clearMPIErrorHandler( MPI_Comm ) {}
|
|
#endif
|
|
|
|
|
|
/****************************************************************************
|
|
* Global call stack functionallity *
|
|
****************************************************************************/
|
|
#ifdef USE_MPI
|
|
static MPI_Comm globalCommForGlobalCommStack = MPI_COMM_NULL;
|
|
static volatile int globalMonitorThreadStatus = -1;
|
|
static void runGlobalMonitorThread()
|
|
{
|
|
int rank = 0;
|
|
int size = 1;
|
|
MPI_Comm_size( globalCommForGlobalCommStack, &size );
|
|
MPI_Comm_rank( globalCommForGlobalCommStack, &rank );
|
|
while ( globalMonitorThreadStatus == 1 ) {
|
|
// 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 the list of threads (except this)
|
|
auto threads = getActiveThreads();
|
|
if ( threads.empty() )
|
|
continue;
|
|
// Get the stack info for the threads
|
|
auto multistack = generateMultiStack( threads );
|
|
// Pack and send the data
|
|
size_t bytes = multistack.size();
|
|
char *data = new char[bytes];
|
|
multistack.pack( data );
|
|
MPI_Send( data, bytes, MPI_CHAR, src_rank, tag, globalCommForGlobalCommStack );
|
|
delete[] data;
|
|
} else {
|
|
// No requests recieved
|
|
std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) );
|
|
}
|
|
}
|
|
}
|
|
void StackTrace::globalCallStackInitialize( MPI_Comm comm )
|
|
{
|
|
globalMonitorThreadStatus = 3;
|
|
// Check that we have the necessary MPI thread support
|
|
if ( !MPI_Initialized() ) {
|
|
printf( "Warning: MPI not initialized before calling globalCallStackInitialize\n" );
|
|
return;
|
|
}
|
|
int rank = 0;
|
|
MPI_Comm_rank( comm, &rank );
|
|
int provided;
|
|
MPI_Query_thread( &provided );
|
|
if ( provided != MPI_THREAD_MULTIPLE ) {
|
|
if ( rank == 0 )
|
|
printf( "Warning: getGlobalCallStacks requires support for MPI_THREAD_MULTIPLE\n" );
|
|
return;
|
|
}
|
|
// Check that we have support to get call stacks from threads
|
|
int N_threads = 0;
|
|
if ( rank == 0 ) {
|
|
std::thread thread( StackTrace::Utilities::sleep_ms, 200 );
|
|
std::this_thread::yield();
|
|
auto thread_ids = getActiveThreads();
|
|
N_threads = thread_ids.size();
|
|
thread.join();
|
|
}
|
|
MPI_Bcast( &N_threads, 1, MPI_INT, 0, comm );
|
|
if ( N_threads == 1 ) {
|
|
if ( rank == 0 )
|
|
printf( "Warning: getAllCallStacks not supported on this OS\n" );
|
|
return;
|
|
}
|
|
// Create the communicator and initialize the helper thread
|
|
globalMonitorThreadStatus = 1;
|
|
MPI_Comm_dup( comm, &globalCommForGlobalCommStack );
|
|
globalMonitorThread.reset( new std::thread( runGlobalMonitorThread ) );
|
|
std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) );
|
|
}
|
|
void StackTrace::globalCallStackFinalize()
|
|
{
|
|
if ( globalMonitorThread ) {
|
|
globalMonitorThreadStatus = 2;
|
|
globalMonitorThread->join();
|
|
globalMonitorThread.reset();
|
|
}
|
|
if ( globalCommForGlobalCommStack != MPI_COMM_NULL )
|
|
MPI_Comm_free( &globalCommForGlobalCommStack );
|
|
globalCommForGlobalCommStack = MPI_COMM_NULL;
|
|
}
|
|
StackTrace::multi_stack_info getRemoteCallStacks()
|
|
{
|
|
if ( globalMonitorThreadStatus == -1 ) {
|
|
// User did not call globalCallStackInitialize
|
|
printf( "Warning: getGlobalCallStacks called without call to globalCallStackInitialize\n" );
|
|
return StackTrace::multi_stack_info();
|
|
} else if ( globalMonitorThreadStatus != 1 ) {
|
|
// globalCallStackInitialize is not supported
|
|
return StackTrace::multi_stack_info();
|
|
}
|
|
// 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<MPI_Request> sendRequest( size );
|
|
for ( int i = 0; i < size; i++ ) {
|
|
if ( i == rank )
|
|
continue;
|
|
MPI_Isend( &tag, 1, MPI_INT, i, 1, globalCommForGlobalCommStack, &sendRequest[i] );
|
|
}
|
|
// Recieve the backtrace for all remote processes/threads
|
|
int N_finished = 1;
|
|
auto start = std::chrono::steady_clock::now();
|
|
double time = 0;
|
|
const double max_time = 10.0 + size * 20e-3;
|
|
StackTrace::multi_stack_info multistack;
|
|
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 );
|
|
char *data = new char[count];
|
|
MPI_Recv( data, count, MPI_CHAR, src_rank, tag, globalCommForGlobalCommStack, &status );
|
|
StackTrace::multi_stack_info tmp;
|
|
tmp.unpack( data );
|
|
delete[] data;
|
|
multistack.add( tmp );
|
|
N_finished++;
|
|
} else {
|
|
auto stop = std::chrono::steady_clock::now();
|
|
time = std::chrono::duration_cast<std::chrono::seconds>( 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 getRemoteCallStacks() { return StackTrace::multi_stack_info(); }
|
|
#endif
|
|
StackTrace::multi_stack_info StackTrace::getGlobalCallStacks()
|
|
{
|
|
auto threads = getActiveThreads();
|
|
auto multistack = generateMultiStack( threads );
|
|
multistack.add( getRemoteCallStacks() );
|
|
return multistack;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Cleanup the call stack *
|
|
****************************************************************************/
|
|
static constexpr size_t findMatching( const char *str, size_t N, size_t pos ) noexcept
|
|
{
|
|
size_t pos2 = pos + 1;
|
|
int count = 1;
|
|
while ( count != 0 && pos2 < N ) {
|
|
if ( str[pos2] == '<' )
|
|
count++;
|
|
if ( str[pos2] == '>' )
|
|
count--;
|
|
pos2++;
|
|
}
|
|
return pos2;
|
|
}
|
|
template<std::size_t N>
|
|
static constexpr size_t findMatching( const std::array<char, N> &str, size_t pos ) noexcept
|
|
{
|
|
return findMatching( str.data(), N );
|
|
}
|
|
static void cleanupFunctionName( char *function )
|
|
{
|
|
constexpr size_t npos = std::string::npos;
|
|
// First find the string length
|
|
size_t N = strlen( function );
|
|
// Cleanup template space
|
|
strrep( function, N, " >", ">" );
|
|
strrep( function, N, "< ", "<" );
|
|
// Remove std::__1::
|
|
strrep( function, N, "std::__1::", "std::" );
|
|
// Replace std::ratio with abbriviated version
|
|
auto find = [&function, &N]( const string_view &str, size_t pos = 0 ) {
|
|
return string_view( function, N ).find( str, pos );
|
|
};
|
|
if ( find( "std::ratio<" ) != npos ) {
|
|
strrep( function, N, "std::ratio<1l, 1000000000000000000000000l>", "std::yocto" );
|
|
strrep( function, N, "std::ratio<1l, 1000000000000000000000l>", "std::zepto" );
|
|
strrep( function, N, "std::ratio<1l, 1000000000000000000l>", "std::atto" );
|
|
strrep( function, N, "std::ratio<1l, 1000000000000000l>", "std::femto" );
|
|
strrep( function, N, "std::ratio<1l, 1000000000000l>", "std::pico" );
|
|
strrep( function, N, "std::ratio<1l, 1000000000l>", "std::nano" );
|
|
strrep( function, N, "std::ratio<1l, 1000000l>", "std::micro" );
|
|
strrep( function, N, "std::ratio<1l, 1000l>", "std::milli" );
|
|
strrep( function, N, "std::ratio<1l, 100l>", "std::centi" );
|
|
strrep( function, N, "std::ratio<1l, 10l>", "std::deci" );
|
|
strrep( function, N, "std::ratio<1l, 1l>", "" );
|
|
strrep( function, N, "std::ratio<10l, 1l>", "std::deca" );
|
|
strrep( function, N, "std::ratio<60l, 1l>", "std::ratio<60>" );
|
|
strrep( function, N, "std::ratio<100l, 1l>", "std::hecto" );
|
|
strrep( function, N, "std::ratio<1000l, 1l>", "std::kilo" );
|
|
strrep( function, N, "std::ratio<3600l, 1l>", "std::ratio<3600>" );
|
|
strrep( function, N, "std::ratio<1000000l, 1l>", "std::mega" );
|
|
strrep( function, N, "std::ratio<1000000000l, 1l>", "std::giga" );
|
|
strrep( function, N, "std::ratio<1000000000000l, 1l>", "std::tera" );
|
|
strrep( function, N, "std::ratio<1000000000000000l, 1l>", "std::peta" );
|
|
strrep( function, N, "std::ratio<1000000000000000000l, 1l>", "std::exa" );
|
|
strrep( function, N, "std::ratio<1000000000000000000000l, 1l>", "std::zetta" );
|
|
strrep( function, N, "std::ratio<1000000000000000000000000l, 1l>", "std::yotta" );
|
|
strrep( function, N, " >", ">" );
|
|
strrep( function, N, "< ", "<" );
|
|
}
|
|
// Replace std::chrono::duration with abbriviated version
|
|
if ( find( "std::chrono::duration<" ) != npos ) {
|
|
// clang-format off
|
|
strrep( function, N, "std::chrono::duration<long, std::nano>", "std::chrono::nanoseconds" );
|
|
strrep( function, N, "std::chrono::duration<long, std::micro>", "std::chrono::microseconds" );
|
|
strrep( function, N, "std::chrono::duration<long, std::milli>", "std::chrono::milliseconds" );
|
|
strrep( function, N, "std::chrono::duration<long>", "std::chrono::seconds" );
|
|
strrep( function, N, "std::chrono::duration<long,>", "std::chrono::seconds" );
|
|
strrep( function, N, "std::chrono::duration<long, std::ratio<60>>", "std::chrono::minutes" );
|
|
strrep( function, N, "std::chrono::duration<long, std::ratio<3600>>", "std::chrono::hours" );
|
|
strrep( function, N, " >", ">" );
|
|
strrep( function, N, "< ", "<" );
|
|
// clang-format on
|
|
}
|
|
// Replace std::this_thread::sleep_for with abbriviated version.
|
|
if ( find( "::sleep_for<" ) != npos ) {
|
|
strrep( function, N, "::sleep_for<long, std::nano>", "::sleep_for<nanoseconds>" );
|
|
strrep( function, N, "::sleep_for<long, std::micro>", "::sleep_for<microseconds>" );
|
|
strrep( function, N, "::sleep_for<long, std::milli>", "::sleep_for<milliseconds>" );
|
|
strrep( function, N, "::sleep_for<long>", "::sleep_for<seconds>" );
|
|
strrep( function, N, "::sleep_for<long,>", "::sleep_for<seconds>" );
|
|
strrep( function, N, "::sleep_for<long, std::ratio<60>>", "::sleep_for<minutes>" );
|
|
strrep( function, N, "::sleep_for<long, std::ratio<3600>>", "::sleep_for<hours>" );
|
|
strrep( function, N, "::sleep_for<nanoseconds>(std::chrono::nanoseconds",
|
|
"::sleep_for(std::chrono::nanoseconds" );
|
|
strrep( function, N, "::sleep_for<microseconds>(std::chrono::microseconds",
|
|
"::sleep_for(std::chrono::microseconds" );
|
|
strrep( function, N, "::sleep_for<milliseconds>(std::chrono::milliseconds",
|
|
"::sleep_for(std::chrono::milliseconds" );
|
|
strrep( function, N, "::sleep_for<seconds>(std::chrono::seconds",
|
|
"::sleep_for(std::chrono::seconds" );
|
|
strrep( function, N, "::sleep_for<milliseconds>(std::chrono::minutes",
|
|
"::sleep_for(std::chrono::milliseconds" );
|
|
strrep( function, N, "::sleep_for<milliseconds>(std::chrono::hours",
|
|
"::sleep_for(std::chrono::hours" );
|
|
}
|
|
// Replace std::basic_string with abbriviated version
|
|
strrep( function, N, "std::__cxx11::basic_string<", "std::basic_string<" );
|
|
size_t pos = 0;
|
|
while ( pos < N ) {
|
|
// Find next instance of std::basic_string
|
|
pos = find( "std::basic_string<", pos );
|
|
if ( pos == npos )
|
|
break;
|
|
// Find the matching >
|
|
size_t pos1 = pos + 17;
|
|
size_t pos2 = findMatching( function, N, pos1 );
|
|
if ( pos2 == pos1 )
|
|
break;
|
|
if ( strncmp( &function[pos1 + 1], "char", 4 ) == 0 )
|
|
N = replace( function, N, pos, pos2 - pos, "std::string" );
|
|
else if ( strncmp( &function[pos1 + 1], "wchar_t", 7 ) == 0 )
|
|
N = replace( function, N, pos, pos2 - pos, "std::wstring" );
|
|
else if ( strncmp( &function[pos1 + 1], "char16_t", 8 ) == 0 )
|
|
N = replace( function, N, pos, pos2 - pos, "std::u16string" );
|
|
else if ( strncmp( &function[pos1 + 1], "char32_t", 8 ) == 0 )
|
|
N = replace( function, N, pos, pos2 - pos, "std::u32string" );
|
|
pos++;
|
|
}
|
|
// Replace std::make_shared with abbriviated version
|
|
if ( find( "std::make_shared<" ) != npos ) {
|
|
size_t pos1 = find( "std::make_shared<" );
|
|
size_t pos2 = find( ",", pos1 );
|
|
size_t pos3 = find( "(", pos1 );
|
|
N = replace( function, N, pos2, pos3 - pos2, ">" );
|
|
}
|
|
// Remove std::allocator in std::vector
|
|
if ( find( "std::vector<" ) != npos ) {
|
|
size_t pos1 = find( "std::vector<" );
|
|
size_t pos2 = find( ", std::allocator", pos1 );
|
|
size_t pos3 = findMatching( function, N, pos1 + 11 );
|
|
N = replace( function, N, pos2, pos3 - 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() ) {
|
|
string_view object( it->stack.object.data() );
|
|
string_view function( it->stack.function.data() );
|
|
string_view filename( it->stack.filename.data() );
|
|
bool remove_entry = false;
|
|
// Remove StackTrace functions
|
|
if ( filename == "StackTrace.cpp" ) {
|
|
// Remove callstack (and all children) for threads that are just contributing
|
|
bool test = function.find( "_callstack_signal_handler" ) != npos ||
|
|
function.find( "getGlobalCallStacks" ) != npos ||
|
|
function.find( "backtrace" ) != npos || function.find( "(" ) == npos;
|
|
if ( test ) {
|
|
it = stack.children.erase( it );
|
|
continue;
|
|
}
|
|
// Remove backtrace_thread
|
|
if ( function.find( "backtrace_thread" ) != npos )
|
|
remove_entry = true;
|
|
}
|
|
// Remove libc functions
|
|
if ( object.find( "libc.so" ) != npos ) {
|
|
// Remove __libc_start_main
|
|
if ( function.find( "__libc_start_main" ) != npos )
|
|
remove_entry = true;
|
|
// Remove libc fgets children
|
|
if ( function.find( "fgets" ) != npos )
|
|
it->children.clear();
|
|
}
|
|
// Remove libc++ functions
|
|
if ( object.find( "libstdc++" ) != npos ) {
|
|
// Remove std::this_thread::__sleep_for
|
|
if ( function.find( "std::this_thread::__sleep_for(" ) != npos )
|
|
remove_entry = true;
|
|
}
|
|
// Remove pthread functions
|
|
if ( object.find( "libpthread" ) != npos ) {
|
|
// Remove __restore_rt
|
|
if ( function.find( "__restore_rt" ) != npos && object.find( "libpthread" ) != npos )
|
|
remove_entry = true;
|
|
}
|
|
// Remove condition_variable functions
|
|
if ( filename == "condition_variable" ) {
|
|
// Remove std::condition_variable::__wait_until_impl
|
|
if ( function.find( "std::condition_variable::__wait_until_impl" ) != npos )
|
|
remove_entry = true;
|
|
}
|
|
// Remove std::function references
|
|
if ( filename == "functional" ) {
|
|
remove_entry = remove_entry || function.find( "std::_Function_handler<" ) != npos;
|
|
remove_entry = remove_entry || function.find( "std::_Bind_simple<" ) != npos;
|
|
remove_entry = remove_entry || function.find( "_M_invoke" ) != npos;
|
|
}
|
|
// Remove std::thread::_Impl
|
|
if ( filename == "thread" ) {
|
|
if ( function.find( "std::thread::_Impl<" ) != npos ||
|
|
function.find( "std::thread::_Invoker<" ) != npos )
|
|
remove_entry = true;
|
|
}
|
|
if ( filename == "invoke.h" ) {
|
|
remove_entry = remove_entry || function.find( "std::__invoke_impl" ) != npos;
|
|
remove_entry = remove_entry || function.find( "std::__invoke_result" ) != npos;
|
|
}
|
|
// Remove pthread internals
|
|
if ( function == "__GI___pthread_timedjoin_ex" )
|
|
remove_entry = true;
|
|
// Remove MPI internal routines
|
|
if ( function == "MPIR_Barrier_impl" || function == "MPIR_Barrier_intra" ||
|
|
function == "MPIC_Sendrecv" )
|
|
remove_entry = true;
|
|
// Remove OpenMPI specific internal routines
|
|
if ( function == "opal_libevent2022_event_set_log_callback" ||
|
|
function == "opal_libevent2022_event_base_loop" )
|
|
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 std::shared_ptr functions
|
|
if ( filename == "shared_ptr.h" ) {
|
|
if ( function.find( "> std::allocate_shared<" ) != npos ||
|
|
function.find( "std::_Sp_make_shared_tag," ) != npos )
|
|
remove_entry = true;
|
|
}
|
|
if ( filename == "shared_ptr_base.h" )
|
|
remove_entry = true;
|
|
// Remove new_allocator functions
|
|
if ( filename == "new_allocator.h" )
|
|
remove_entry = true;
|
|
// Remove alloc_traits functions
|
|
if ( filename == "alloc_traits.h" )
|
|
remove_entry = true;
|
|
// Remove gthr-default functions
|
|
if ( filename == "gthr-default.h" )
|
|
remove_entry = true;
|
|
// Remove entries with no useful information
|
|
if ( function.empty() && filename.empty() )
|
|
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 the children
|
|
cleanupStackTrace( *it );
|
|
// Combine any children with the same address (can occur when we remove items)
|
|
bool remove = false;
|
|
for ( auto it2 = stack.children.begin(); it2 != it; it2++ ) {
|
|
if ( it->stack == it2->stack ) {
|
|
remove = true;
|
|
it2->N += it->N;
|
|
for ( auto &tmp : it->children )
|
|
it2->children.push_back( tmp );
|
|
cleanupStackTrace( *it2 );
|
|
}
|
|
}
|
|
if ( remove ) {
|
|
it = stack.children.erase( it );
|
|
continue;
|
|
}
|
|
++it;
|
|
}
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Generate stack from string *
|
|
****************************************************************************/
|
|
static StackTrace::stack_info parseLine( const char *str )
|
|
{
|
|
char tmp[1000];
|
|
StackTrace::stack_info stack;
|
|
// Load the address
|
|
const char *p0 = strchr( str, 0 );
|
|
const char *p1 = strchr( str, 'x' );
|
|
const char *p2 = strchr( str, ':' );
|
|
memset( tmp, 0, sizeof( tmp ) );
|
|
memcpy( tmp, p1 + 1, p2 - p1 - 1 );
|
|
uint64_t address = strtol( tmp, nullptr, 16 );
|
|
stack.address = reinterpret_cast<void *>( address );
|
|
stack.address2 = stack.address;
|
|
// Load object, function, file
|
|
const char *p3 = p2 + 1;
|
|
while ( *p3 == ' ' )
|
|
p3++;
|
|
if ( *p3 == 0 )
|
|
return stack;
|
|
const char *p4 = strstr( p3, " " );
|
|
const char *p5 = nullptr;
|
|
if ( p4 != nullptr ) {
|
|
while ( *p4 == ' ' )
|
|
p4++;
|
|
p5 = strstr( p4, " " );
|
|
if ( p5 != nullptr ) {
|
|
while ( *p5 == ' ' )
|
|
p5++;
|
|
}
|
|
}
|
|
if ( p5 == nullptr ) {
|
|
if ( p3 - p2 > 20 ) {
|
|
p5 = p4;
|
|
p4 = p3;
|
|
}
|
|
}
|
|
if ( p4 == nullptr )
|
|
p4 = p0;
|
|
if ( p5 == nullptr )
|
|
p5 = p0;
|
|
// Load line
|
|
const char *p6 = strchr( p5, ':' );
|
|
if ( p6 == nullptr )
|
|
p6 = p0;
|
|
// Store the results
|
|
auto copyField = []( const char *p1, const char *p2, auto &field ) {
|
|
field.fill( 0 );
|
|
memcpy( field.data(), p1, std::min<int>( p2 - p1, field.size() ) );
|
|
for ( int i = field.size() - 1; i > 0 && ( field[i] == ' ' || field[i] == 0 ); i-- )
|
|
field[i] = 0;
|
|
};
|
|
copyField( p3, p4, stack.object );
|
|
copyField( p4, p5, stack.function );
|
|
copyField( p5, p6, stack.filename );
|
|
if ( p6 != p0 )
|
|
stack.line = atoi( p6 + 1 );
|
|
return stack;
|
|
}
|
|
StackTrace::multi_stack_info StackTrace::generateFromString( const std::string &str )
|
|
{
|
|
// Break the string according to line breaks
|
|
std::vector<std::string> data;
|
|
size_t p1 = 0;
|
|
size_t p2 = str.find( '\n' );
|
|
while ( p2 != std::string::npos ) {
|
|
data.push_back( str.substr( p1, p2 - p1 ) );
|
|
p1 = p2 + 1;
|
|
p2 = str.find( '\n', p1 );
|
|
}
|
|
data.push_back( str.substr( p1 ) );
|
|
// Generate the stack
|
|
return generateFromString( data );
|
|
}
|
|
StackTrace::multi_stack_info StackTrace::generateFromString( const std::vector<std::string> &text )
|
|
{
|
|
// Get the data from the text
|
|
std::vector<int> indent;
|
|
std::vector<multi_stack_info> stack;
|
|
for ( const auto &str : text ) {
|
|
auto p1 = str.find( '[' );
|
|
auto p2 = str.find( ']' );
|
|
auto p3 = str.find( 'x' );
|
|
if ( p3 == std::string::npos )
|
|
continue;
|
|
multi_stack_info tmp;
|
|
tmp.N = 1;
|
|
if ( p1 < p2 && p1 < p3 )
|
|
tmp.N = std::stoi( str.substr( p1 + 1, p2 - p1 - 1 ) );
|
|
tmp.stack = parseLine( &str[p3 - 1] );
|
|
indent.push_back( std::min( p1, p3 - 1 ) );
|
|
stack.push_back( tmp );
|
|
}
|
|
// Generate the stack hierarchy
|
|
multi_stack_info stack2;
|
|
std::vector<std::pair<int, std::vector<multi_stack_info> *>> map;
|
|
map.emplace_back( 0, &stack2.children );
|
|
for ( size_t i = 0; i < stack.size(); i++ ) {
|
|
while ( indent[i] < map.back().first )
|
|
map.resize( map.size() - 1 );
|
|
if ( indent[i] == map.back().first ) {
|
|
map.back().second->push_back( stack[i] );
|
|
} else {
|
|
map.back().second->back().children.push_back( stack[i] );
|
|
map.emplace_back( indent[i], &map.back().second->back().children );
|
|
}
|
|
}
|
|
return stack2;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* abort_error *
|
|
****************************************************************************/
|
|
StackTrace::abort_error::abort_error()
|
|
: type( terminateType::unknown ), signal( 0 ), line( -1 ), bytes( 0 )
|
|
{
|
|
}
|
|
const char *StackTrace::abort_error::what() const noexcept
|
|
{
|
|
d_msg.clear();
|
|
if ( type == terminateType::abort ) {
|
|
d_msg += "Program abort called";
|
|
} else if ( type == terminateType::signal ) {
|
|
d_msg += "Unhandled signal (" + std::to_string( signal ) + ") caught";
|
|
} else if ( type == terminateType::exception ) {
|
|
d_msg += "Unhandled exception caught";
|
|
} else if ( type == terminateType::MPI ) {
|
|
d_msg += "Error calling MPI routine";
|
|
} else {
|
|
d_msg += "Unknown error called";
|
|
}
|
|
if ( !filename.empty() ) {
|
|
d_msg += " in file '" + filename + "'";
|
|
if ( line > 0 ) {
|
|
d_msg += " at line " + std::to_string( line );
|
|
}
|
|
}
|
|
d_msg += ":\n";
|
|
d_msg += " " + message + "\n";
|
|
if ( bytes > 0 ) {
|
|
d_msg += "Bytes used = " + std::to_string( bytes ) + "\n";
|
|
}
|
|
if ( !stack.empty() ) {
|
|
d_msg += "Stack Trace:\n";
|
|
if ( stackType == printStackType::local ) {
|
|
for ( const auto &item : getStackInfo( stack ) ) {
|
|
char txt[1000];
|
|
item.print2( txt );
|
|
d_msg += " \n";
|
|
d_msg += txt;
|
|
}
|
|
} else if ( stackType == printStackType::threaded || stackType == printStackType::global ) {
|
|
// Get the call stack
|
|
std::vector<std::vector<void *>> trace;
|
|
trace.push_back( stack );
|
|
// Get the call stack for all threads except the current one
|
|
auto threads = getActiveThreads();
|
|
threads.erase( thisThread() );
|
|
for ( auto tid : threads )
|
|
trace.push_back( backtrace( tid ) );
|
|
// Generate call stack
|
|
auto multistack = generateMultiStack( trace );
|
|
// Add remote call stack info
|
|
if ( stackType == printStackType::global )
|
|
multistack.add( getRemoteCallStacks() );
|
|
// Cleanup call stack
|
|
cleanupStackTrace( multistack );
|
|
// Print the results
|
|
d_msg += multistack.printString( " " );
|
|
} else {
|
|
d_msg += "Unknown value for stackType\n";
|
|
}
|
|
}
|
|
for ( size_t i = 0; i < d_msg.size(); i++ )
|
|
if ( d_msg[i] == 0 )
|
|
d_msg.erase( i, 1 );
|
|
return d_msg.c_str();
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Get/Set default stack type *
|
|
****************************************************************************/
|
|
static StackTrace::printStackType abort_stackType = StackTrace::printStackType::global;
|
|
void StackTrace::setDefaultStackType( StackTrace::printStackType type ) { abort_stackType = type; }
|
|
StackTrace::printStackType StackTrace::getDefaultStackType() { return abort_stackType; }
|