polserver/pol-core/clib/Debugging/ExceptionParser.cpp

684 lines
19 KiB
C++

#include "ExceptionParser.h"
#include "../Program/ProgramConfig.h"
#include "../logfacility.h"
#include "../threadhelp.h"
#include <format/format.h>
#ifdef WINDOWS
#include "../pol_global_config_win.h"
#else
#include "pol_global_config.h"
#endif
#include <cstddef>
#include <cstring>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#ifndef WINDOWS
#include <arpa/inet.h>
#include <cxxabi.h>
#include <execinfo.h>
#include <netdb.h>
#include <netinet/in.h>
#include <unistd.h>
#define SOCKET int
#else
#include "../Header_Windows.h"
#endif
#define MAX_STACK_TRACE_DEPTH 200
#define MAX_STACK_TRACE_STEP_LENGTH 512
namespace Pol
{
namespace Clib
{
using namespace std;
///////////////////////////////////////////////////////////////////////////////
bool ExceptionParser::m_programAbortReporting = false;
std::string ExceptionParser::m_programAbortReportingServer = "";
std::string ExceptionParser::m_programAbortReportingUrl = "";
std::string ExceptionParser::m_programAbortReportingReporter = "";
std::string ExceptionParser::m_programStart = Pol::Clib::Logging::LogSink::getTimeStamp();
///////////////////////////////////////////////////////////////////////////////
namespace
{
void getSignalDescription( int signal, string& signalName, string& signalDescription )
{
switch ( signal )
{
case 1:
signalName = "SIGHUP";
signalDescription = "hangup detected on controlling terminal or death of controlling process";
break;
case 2:
signalName = "SIGINT";
signalDescription = "interrupt from keyboard";
break;
case 3:
signalName = "SIGQUIT";
signalDescription = "quit from keyboard";
break;
case 4:
signalName = "SIGILL";
signalDescription = "illegal Instruction";
break;
case 6:
signalName = "SIGABRT";
signalDescription = "abort signal from abort()";
break;
case 8:
signalName = "SIGFPE";
signalDescription = "floating point exception";
break;
case 9:
signalName = "SIGKILL";
signalDescription = "kill signal";
break;
case 10:
signalName = "SIGBUS";
signalDescription = "bus error";
break;
case 11:
signalName = "SIGSEGV";
signalDescription = "invalid memory reference";
break;
case 12:
signalName = "SIGSYS";
signalDescription = "bad argument to system call";
break;
case 13:
signalName = "SIGPIPE";
signalDescription = "broken pipe: write to pipe with no readers";
break;
case 14:
signalName = "SIGALRM";
signalDescription = "timer signal from alarm()";
break;
case 15:
signalName = "SIGTERM";
signalDescription = "termination signal";
break;
case 18:
signalName = "SIGCONT";
signalDescription = "continue signal from tty";
break;
case 19:
signalName = "SIGSTOP";
signalDescription = "stop signal from tty";
break;
case 20:
signalName = "SIGTSTP";
signalDescription = "stop signal from user (keyboard)";
break;
case 16:
case 30:
signalName = "SIGUSR1";
signalDescription = "user-defined signal 1";
break;
case 17:
case 31:
signalName = "SIGUSR2";
signalDescription = "user-defined signal 2";
break;
default:
signalName = "unsupported signal";
signalDescription = "unsupported signal occurred";
break;
}
}
void logExceptionSignal( int signal )
{
string signalName;
string signalDescription;
getSignalDescription( signal, signalName, signalDescription );
printf( "Signal \"%s\"(%d: %s) detected.\n", signalName.c_str(), signal,
signalDescription.c_str() );
}
string getCompilerVersion()
{
#ifdef LINUX
char result[256];
#ifdef __clang__
sprintf( result, "clang %d.%d.%d", __clang_major__, __clang_minor__, __clang_patchlevel__ );
#else
sprintf( result, "gcc %d.%d.%d", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__ );
#endif
#endif
#ifdef WINDOWS
string result;
switch ( _MSC_VER )
{
case 1900:
result = "MSVC++ 14.0 (Visual Studio 2015)";
break;
case 1800:
result = "MSVC++ 12.0 (Visual Studio 2013)";
break;
case 1700:
result = "MSVC++ 11.0 (Visual Studio 2012)";
break;
case 1600:
result = "MSVC++ 10.0 (Visual Studio 2010)";
break;
case 1500:
result = "MSVC++ 9.0 (Visual Studio 2008)";
break;
case 1400:
result = "MSVC++ 8.0 (Visual Studio 2005)";
break;
case 1310:
result = "MSVC++ 7.1 (Visual Studio 2003)";
break;
case 1300:
result = "MSVC++ 7.0";
break;
case 1200:
result = "MSVC++ 6.0";
break;
case 1100:
result = "MSVC++ 5.0";
break;
default:
#if ( _MSC_VER > 1800 )
result = "MSVC++ newer than version 12.0";
#elif ( _MSC_VER < 1100 )
result = "MSVC++ older than version 5.0";
#else
result = "MSVC++ (some unsupported version)";
#endif
break;
}
#endif
return result;
}
void doHttpPOST( const string& host, const string& url, const string& content )
{
#define MAXLINE 4096
char request[MAXLINE + 1];
SOCKET socketFD;
char targetIP[INET6_ADDRSTRLEN];
/**
* prepare the request
*/
#ifdef _MSC_VER
_snprintf(
#else
snprintf(
#endif
request, MAXLINE,
"POST %s HTTP/1.0\r\n"
"Host: %s\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"User-Agent: POL in-app abort reporting system, %s\r\n"
"Content-length: %d\r\n\r\n"
"%s",
url.c_str(), host.c_str(), POL_VERSION_ID, (int)content.size(), content.c_str() );
/**
* DNS lookup if needed
*/
struct addrinfo* serverAddr;
struct addrinfo hints;
memset( &hints, 0, sizeof hints );
hints.ai_family = AF_UNSPEC; // IPv4 or IPv6
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_socktype = SOCK_STREAM;
int res = getaddrinfo( host.c_str(), "http", &hints, &serverAddr );
if ( res != 0 )
{
fprintf( stderr, "getaddrinfo() failed for \"%s\" due to \"%s\"(code: %d)\n", host.c_str(),
gai_strerror( res ), res );
exit( 1 );
}
switch ( serverAddr->ai_addr->sa_family )
{
case AF_INET:
if ( inet_ntop( AF_INET, &( (struct sockaddr_in*)serverAddr->ai_addr )->sin_addr, targetIP,
INET_ADDRSTRLEN ) == NULL )
exit( 1 );
break;
case AF_INET6:
if ( inet_ntop( AF_INET6, &( (struct sockaddr_in*)serverAddr->ai_addr )->sin_addr, targetIP,
INET6_ADDRSTRLEN ) == NULL )
exit( 1 );
break;
default:
fprintf( stderr, "Unknown address family found for %s\n", host.c_str() );
exit( 1 );
}
// create the socket
socketFD = socket( serverAddr->ai_family, serverAddr->ai_socktype, serverAddr->ai_protocol );
/**
* connect to the bug tracking server
*/
if ( ( res = connect( socketFD, serverAddr->ai_addr, (int)serverAddr->ai_addrlen ) ) != 0 )
{
fprintf( stderr, "connect() failed for server \"%s\"(IP: %s) due \"%s\"(%d)\n", host.c_str(),
targetIP, strerror( errno ), errno );
exit( 1 );
}
freeaddrinfo( serverAddr ); // not needed anymore
/**
* send the request
*/
#ifndef _WIN32
send( socketFD, request, strlen( request ), MSG_NOSIGNAL );
#else
send( socketFD, request, (int)strlen( request ), 0 );
#endif
printf( "Abort report was sent to %s%s (IP: %s)\n", host.c_str(), url.c_str(), targetIP );
/**
* wait for some answers and print them on the screen
*/
ssize_t readBytes;
char answer[MAXLINE + 1];
#ifndef _WIN32
while ( ( readBytes = recv( socketFD, answer, MAXLINE, MSG_NOSIGNAL ) ) > 0 )
{
#else
while ( ( readBytes = recv( socketFD, answer, MAXLINE, 0 ) ) > 0 )
{
#endif
answer[readBytes] = '\0';
printf( "Answer from bug tracking server:\n%s\n", answer );
// skip the received answer and proceed
}
// close the socket to the bug tracking server
#ifndef _WIN32
close( socketFD );
#else
closesocket( socketFD );
#endif
}
} // namespace
void ExceptionParser::reportProgramAbort( const string& stackTrace, const string& reason )
{
/**
* set some default values if the abort occurs too early and pol.cfg wasn't parsed yet
*/
string host = "polserver.com";
string url = "/pol/report_program_abort.php";
if ( ( m_programAbortReportingServer.c_str() != NULL ) &&
( m_programAbortReportingServer != "" ) )
{
host = m_programAbortReportingServer;
if ( m_programAbortReportingUrl.c_str() != NULL )
url = m_programAbortReportingUrl;
}
// create the abort description for the subsequent POST request
string content = "email=" + m_programAbortReportingReporter +
"&"
"bin=" +
PROG_CONFIG::programName() +
"&"
"start_time=" +
m_programStart +
"&"
"abort_time=" +
Pol::Clib::Logging::LogSink::getTimeStamp() +
"&"
"reason=" +
reason +
"&"
"trace=" +
stackTrace +
"&"
"comp=" +
getCompilerVersion() +
"&"
"comp_time=" POL_BUILD_DATE "(" POL_BUILD_TIME
")&"
"build_target=" +
POL_BUILD_TARGET +
"&"
"build_revision=" POL_VERSION_ID
"&"
"misc=";
// execute the POST request
doHttpPOST( host, url, content );
}
void ExceptionParser::handleExceptionSignal( int signal )
{
switch ( signal )
{
case SIGILL:
case SIGFPE:
case SIGSEGV:
case SIGTERM:
case SIGABRT:
{
/**
* inform the user about the program abort
*/
printf(
"########################################################################################"
"\n" );
if ( m_programAbortReporting )
printf( "POL will exit now. The following will be sent to the POL developers:\n\n" );
else
printf(
"POL will exit now. Please, post the following to the forum: "
"http://forums.polserver.com/.\n" );
string tStackTrace = ExceptionParser::getTrace();
printf( tStackTrace.c_str() );
printf( "Admin contact: %s\n", m_programAbortReportingReporter.c_str() );
printf( "Executable: %s\n", PROG_CONFIG::programName().c_str() );
printf( "Start time: %s\n", m_programStart.c_str() );
printf( "Current time: %s\n", Pol::Clib::Logging::LogSink::getTimeStamp().c_str() );
printf( "\n" );
printf( "Stack trace:\n%s", tStackTrace.c_str() );
printf( "\n" );
printf( "Compiler: %s\n", getCompilerVersion().c_str() );
printf( "Compile time: %s\n", POL_BUILD_TIME );
printf( "Build target: %s\n", POL_BUILD_TARGET );
printf( "Build revision: %s\n", POL_VERSION_ID );
#ifndef _WIN32
printf( "GNU C library (compile time): %d.%d\n", __GLIBC__, __GLIBC_MINOR__ );
#endif
printf( "\n" );
printf(
"########################################################################################"
"\n" );
fflush( stdout );
/**
* use the program abort reporting system
*/
if ( m_programAbortReporting )
{
string signalName;
string signalDescription;
getSignalDescription( signal, signalName, signalDescription );
ExceptionParser::reportProgramAbort(
tStackTrace, "CRASH caused by signal " + signalName + " (" + signalDescription + ")" );
}
// finally, go to hell
exit( 1 );
}
break;
default:
break;
}
}
///////////////////////////////////////////////////////////////////////////////
ExceptionParser::ExceptionParser() {}
ExceptionParser::~ExceptionParser() {}
///////////////////////////////////////////////////////////////////////////////
#ifndef _WIN32
string ExceptionParser::getTrace()
{
string result;
void* stackTrace[MAX_STACK_TRACE_DEPTH];
int stackTraceSize;
char** stackTraceList;
int stackTraceStep = 0;
char* stringBuf = (char*)malloc( MAX_STACK_TRACE_STEP_LENGTH );
stackTraceSize = backtrace( stackTrace, MAX_STACK_TRACE_DEPTH );
stackTraceList = backtrace_symbols( stackTrace, stackTraceSize );
size_t funcNameSize = 256;
char* funcnName = (char*)malloc( funcNameSize );
// iterate over all entries and do the demangling
for ( int i = 0; i < stackTraceSize; i++ )
{
// get the pointers to the name, offset and end of offset
char* beginFuncName = nullptr;
char* beginFuncOffset = nullptr;
char* endFuncOffset = nullptr;
char* beginBinaryName = stackTraceList[i];
char* beginBinaryOffset = nullptr;
char* endBinaryOffset = nullptr;
for ( char* entryPointer = stackTraceList[i]; *entryPointer; ++entryPointer )
{
if ( *entryPointer == '(' )
{
beginFuncName = entryPointer;
}
else if ( *entryPointer == '+' )
{
beginFuncOffset = entryPointer;
}
else if ( *entryPointer == ')' && beginFuncOffset )
{
endFuncOffset = entryPointer;
}
else if ( *entryPointer == '[' )
{
beginBinaryOffset = entryPointer;
}
else if ( *entryPointer == ']' && beginBinaryOffset )
{
endBinaryOffset = entryPointer;
break;
}
}
// set the default value for the output line
sprintf( stringBuf, "\n" );
bool parse_succeeded = beginFuncName && beginFuncOffset && endFuncOffset && beginBinaryOffset &&
endBinaryOffset && beginFuncName < beginFuncOffset;
// get the detailed values for the output line if available
if ( parse_succeeded )
{
// terminate the C strings
*beginFuncName++ = '\0';
*beginFuncOffset++ = '\0';
*endFuncOffset = '\0';
*beginBinaryOffset++ = '\0';
*endBinaryOffset = '\0';
int res;
funcnName = abi::__cxa_demangle( beginFuncName, funcnName, &funcNameSize, &res );
unsigned int binaryOffset = strtoul( beginBinaryOffset, NULL, 16 );
if ( res == 0 )
{
string funcnNameStr = ( funcnName ? funcnName : "" );
if ( funcnName && strncmp( funcnName, "Pol::", 5 ) == 0 )
funcnNameStr = ">> " + funcnNameStr;
if ( beginBinaryName && strlen( beginBinaryName ) )
sprintf( stringBuf, "#%02d 0x%016x in %s:[%s] from %s\n", stackTraceStep, binaryOffset,
funcnNameStr.c_str(), beginFuncOffset, beginBinaryName );
else
sprintf( stringBuf, "#%02d 0x%016x in %s from %s\n", stackTraceStep, binaryOffset,
funcnNameStr.c_str(), beginFuncOffset );
stackTraceStep++;
}
else
{
if ( beginBinaryName && strlen( beginBinaryName ) )
sprintf( stringBuf, "#%02d 0x%016x in %s:[%s] from %s\n", stackTraceStep, binaryOffset,
beginFuncName, beginFuncOffset, beginBinaryName );
else
sprintf( stringBuf, "#%02d 0x%016x in %s:[%s]\n", stackTraceStep, binaryOffset,
beginFuncName, beginFuncOffset );
stackTraceStep++;
}
}
else
{
// print the raw trace, as it is better than nothing
sprintf( stringBuf, "#%02d %s\n", stackTraceStep, stackTraceList[i] );
stackTraceStep++;
}
// append the line to the result
result += string( stringBuf );
}
// memory cleanup
free( funcnName );
free( stackTraceList );
free( stringBuf );
return result;
}
static void handleSignalLinux( int signal, siginfo_t* signalInfo, void* arg )
{
(void)arg;
logExceptionSignal( signal );
if ( signalInfo != NULL )
{
if ( signal == SIGSEGV )
{
if ( signalInfo->si_addr != NULL )
printf( "Segmentation fault detected - faulty memory reference at location: %p\n",
signalInfo->si_addr );
else
printf( "Segmentation fault detected - null pointer reference\n" );
}
if ( signalInfo->si_errno != 0 )
printf( "This signal occurred because \"%s\"(%d)\n", strerror( signalInfo->si_errno ),
signalInfo->si_errno );
if ( signalInfo->si_code != 0 )
printf( "Signal code is %d\n", signalInfo->si_code );
}
ExceptionParser::handleExceptionSignal( signal );
}
static void handleStackTraceRequestLinux( int signal, siginfo_t* signalInfo, void* arg )
{
(void)signal;
(void)signalInfo;
(void)arg;
threadhelp::ThreadMap::Contents threadDesc;
threadhelp::threadmap.CopyContents( threadDesc );
fmt::Writer output;
output << "STACK TRACE for thread \"" << threadDesc[pthread_self()] << "\"(" << pthread_self()
<< "):\n";
output << ExceptionParser::getTrace() << "\n";
// print to stdout
printf( "%s", output.c_str() );
// print to error output
POLLOG_ERROR << output.str();
// wait here for logging facility to make sure everything was processed
if ( Clib::Logging::global_logger )
Clib::Logging::global_logger->wait_for_empty_queue();
}
void ExceptionParser::logAllStackTraces()
{
threadhelp::ThreadMap::Contents threadsDesc;
threadhelp::threadmap.CopyContents( threadsDesc );
for ( const auto& threadDesc : threadsDesc )
{
pthread_t threadID = (pthread_t)threadDesc.first;
if ( pthread_kill( threadID, SIGUSR1 ) != 0 )
{
fmt::Writer output;
output << "pthread_kill() failed to send SIGUSR1 to thread " << threadsDesc[threadID] << "("
<< threadID << ")\n";
fprintf( stderr, "%s", output.c_str() );
}
}
}
void ExceptionParser::initGlobalExceptionCatching()
{
struct sigaction sigAction;
memset( &sigAction, 0, sizeof( sigAction ) );
sigemptyset( &sigAction.sa_mask );
sigAction.sa_sigaction = handleSignalLinux;
sigAction.sa_flags = SA_SIGINFO;
sigaction( SIGINT, &sigAction, NULL );
sigaction( SIGTERM, &sigAction, NULL );
sigaction( SIGSEGV, &sigAction, NULL );
sigaction( SIGABRT, &sigAction, NULL );
sigAction.sa_sigaction = handleStackTraceRequestLinux;
sigaction( SIGUSR1, &sigAction, NULL );
// set handler stack
stack_t tStack;
tStack.ss_sp = malloc( SIGSTKSZ );
if ( tStack.ss_sp == NULL )
{
printf( "Could not allocate signal handler stack\n" );
exit( 1 );
}
tStack.ss_size = SIGSTKSZ;
tStack.ss_flags = 0;
if ( sigaltstack( &tStack, NULL ) == -1 )
{
printf( "Could not set signal handler stack\n" );
exit( 1 );
}
}
#else // _WIN32
string ExceptionParser::getTrace()
{
string result;
return result;
}
void ExceptionParser::logAllStackTraces() {}
void ExceptionParser::initGlobalExceptionCatching() {}
#endif // _WIN32
void ExceptionParser::configureProgramAbortReportingSystem( bool active, std::string server,
std::string url, std::string reporter )
{
m_programAbortReporting = active;
m_programAbortReportingServer = std::move( server );
m_programAbortReportingUrl = std::move( url );
m_programAbortReportingReporter = std::move( reporter );
}
bool ExceptionParser::programAbortReporting()
{
return m_programAbortReporting;
}
///////////////////////////////////////////////////////////////////////////////
}
} // namespaces