Pol  Revision:f37d500
ExceptionParser.cpp
Go to the documentation of this file.
1 #include "ExceptionParser.h"
2 
3 #include "../Program/ProgramConfig.h"
4 #include "../logfacility.h"
5 #include "../threadhelp.h"
6 #include "pol_global_config.h"
7 #include <format/format.h>
8 
9 #include <cstddef>
10 #include <cstring>
11 #include <errno.h>
12 #include <signal.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 
16 #ifndef WINDOWS
17 #include <arpa/inet.h>
18 #include <cxxabi.h>
19 #include <execinfo.h>
20 #include <netdb.h>
21 #include <netinet/in.h>
22 #include <sys/mman.h>
23 #include <unistd.h>
24 
25 #define SOCKET int
26 #else
27 #include "../Header_Windows.h"
28 #endif
29 
30 #define MAX_STACK_TRACE_DEPTH 200
31 #define MAX_STACK_TRACE_STEP_LENGTH 512
32 
33 namespace Pol
34 {
35 namespace Clib
36 {
37 using namespace std;
38 
40 
46 
48 
49 namespace
50 {
51 void getSignalDescription( int signal, string& signalName, string& signalDescription )
52 {
53  switch ( signal )
54  {
55  case 1:
56  signalName = "SIGHUP";
57  signalDescription = "hangup detected on controlling terminal or death of controlling process";
58  break;
59  case 2:
60  signalName = "SIGINT";
61  signalDescription = "interrupt from keyboard";
62  break;
63  case 3:
64  signalName = "SIGQUIT";
65  signalDescription = "quit from keyboard";
66  break;
67  case 4:
68  signalName = "SIGILL";
69  signalDescription = "illegal Instruction";
70  break;
71  case 6:
72  signalName = "SIGABRT";
73  signalDescription = "abort signal from abort()";
74  break;
75  case 8:
76  signalName = "SIGFPE";
77  signalDescription = "floating point exception";
78  break;
79  case 9:
80  signalName = "SIGKILL";
81  signalDescription = "kill signal";
82  break;
83  case 10:
84  signalName = "SIGBUS";
85  signalDescription = "bus error";
86  break;
87  case 11:
88  signalName = "SIGSEGV";
89  signalDescription = "invalid memory reference";
90  break;
91  case 12:
92  signalName = "SIGSYS";
93  signalDescription = "bad argument to system call";
94  break;
95  case 13:
96  signalName = "SIGPIPE";
97  signalDescription = "broken pipe: write to pipe with no readers";
98  break;
99  case 14:
100  signalName = "SIGALRM";
101  signalDescription = "timer signal from alarm()";
102  break;
103  case 15:
104  signalName = "SIGTERM";
105  signalDescription = "termination signal";
106  break;
107  case 18:
108  signalName = "SIGCONT";
109  signalDescription = "continue signal from tty";
110  break;
111  case 19:
112  signalName = "SIGSTOP";
113  signalDescription = "stop signal from tty";
114  break;
115  case 20:
116  signalName = "SIGTSTP";
117  signalDescription = "stop signal from user (keyboard)";
118  break;
119  case 16:
120  case 30:
121  signalName = "SIGUSR1";
122  signalDescription = "user-defined signal 1";
123  break;
124  case 17:
125  case 31:
126  signalName = "SIGUSR2";
127  signalDescription = "user-defined signal 2";
128  break;
129  default:
130  signalName = "unsupported signal";
131  signalDescription = "unsupported signal occurred";
132  break;
133  }
134 }
135 
136 void logExceptionSignal( int signal )
137 {
138  string signalName;
139  string signalDescription;
140 
141  getSignalDescription( signal, signalName, signalDescription );
142  printf( "Signal \"%s\"(%d: %s) detected.\n", signalName.c_str(), signal,
143  signalDescription.c_str() );
144 }
145 
146 string getCompilerVersion()
147 {
148 #ifdef LINUX
149  char result[256];
150 #ifdef __clang__
151  sprintf( result, "clang %d.%d.%d", __clang_major__, __clang_minor__, __clang_patchlevel__ );
152 #else
153  sprintf( result, "gcc %d.%d.%d", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__ );
154 #endif
155 #endif
156 #ifdef WINDOWS
157  string result;
158  switch ( _MSC_VER )
159  {
160  case 1900:
161  result = "MSVC++ 14.0 (Visual Studio 2015)";
162  break;
163  case 1800:
164  result = "MSVC++ 12.0 (Visual Studio 2013)";
165  break;
166  case 1700:
167  result = "MSVC++ 11.0 (Visual Studio 2012)";
168  break;
169  case 1600:
170  result = "MSVC++ 10.0 (Visual Studio 2010)";
171  break;
172  case 1500:
173  result = "MSVC++ 9.0 (Visual Studio 2008)";
174  break;
175  case 1400:
176  result = "MSVC++ 8.0 (Visual Studio 2005)";
177  break;
178  case 1310:
179  result = "MSVC++ 7.1 (Visual Studio 2003)";
180  break;
181  case 1300:
182  result = "MSVC++ 7.0";
183  break;
184  case 1200:
185  result = "MSVC++ 6.0";
186  break;
187  case 1100:
188  result = "MSVC++ 5.0";
189  break;
190  default:
191 #if ( _MSC_VER > 1800 )
192  result = "MSVC++ newer than version 12.0";
193 #elif ( _MSC_VER < 1100 )
194  result = "MSVC++ older than version 5.0";
195 #else
196  result = "MSVC++ (some unsupported version)";
197 #endif
198  break;
199  }
200 #endif
201 
202  return result;
203 }
204 
205 void doHttpPOST( const string& host, const string& url, const string& content )
206 {
207 #define MAXLINE 4096
208  char request[MAXLINE + 1];
209  SOCKET socketFD;
210  char targetIP[INET6_ADDRSTRLEN];
211 
215 #ifdef _MSC_VER
216  _snprintf(
217 #else
218  snprintf(
219 #endif
220  request, MAXLINE,
221  "POST %s HTTP/1.0\r\n"
222  "Host: %s\r\n"
223  "Content-Type: application/x-www-form-urlencoded\r\n"
224  "User-Agent: POL in-app abort reporting system, %s\r\n"
225  "Content-length: %d\r\n\r\n"
226  "%s",
227  url.c_str(), host.c_str(), POL_VERSION_ID, (int)content.size(), content.c_str() );
228 
232  struct addrinfo* serverAddr;
233  struct addrinfo hints;
234  memset( &hints, 0, sizeof hints );
235  hints.ai_family = AF_UNSPEC; // IPv4 or IPv6
236  hints.ai_flags = AI_ADDRCONFIG;
237  hints.ai_socktype = SOCK_STREAM;
238  int res = getaddrinfo( host.c_str(), "http", &hints, &serverAddr );
239  if ( res != 0 )
240  {
241  fprintf( stderr, "getaddrinfo() failed for \"%s\" due to \"%s\"(code: %d)\n", host.c_str(),
242  gai_strerror( res ), res );
243  exit( 1 );
244  }
245 
246  switch ( serverAddr->ai_addr->sa_family )
247  {
248  case AF_INET:
249  if ( inet_ntop( AF_INET, &( (struct sockaddr_in*)serverAddr->ai_addr )->sin_addr, targetIP,
250  INET_ADDRSTRLEN ) == NULL )
251  exit( 1 );
252  break;
253 
254  case AF_INET6:
255  if ( inet_ntop( AF_INET6, &( (struct sockaddr_in*)serverAddr->ai_addr )->sin_addr, targetIP,
256  INET6_ADDRSTRLEN ) == NULL )
257  exit( 1 );
258  break;
259 
260  default:
261  fprintf( stderr, "Unknown address family found for %s\n", host.c_str() );
262  exit( 1 );
263  }
264 
265  // create the socket
266  socketFD = socket( serverAddr->ai_family, serverAddr->ai_socktype, serverAddr->ai_protocol );
267 
271  if ( ( res = connect( socketFD, serverAddr->ai_addr, (int)serverAddr->ai_addrlen ) ) != 0 )
272  {
273  fprintf( stderr, "connect() failed for server \"%s\"(IP: %s) due \"%s\"(%d)\n", host.c_str(),
274  targetIP, strerror( errno ), errno );
275  exit( 1 );
276  }
277 
278  freeaddrinfo( serverAddr ); // not needed anymore
279 
283 #ifndef _WIN32
284  send( socketFD, request, strlen( request ), MSG_NOSIGNAL );
285 #else
286  send( socketFD, request, (int)strlen( request ), 0 );
287 #endif
288  printf( "Abort report was sent to %s%s (IP: %s)\n", host.c_str(), url.c_str(), targetIP );
289 
293  ssize_t readBytes;
294  char answer[MAXLINE + 1];
295 
296 #ifndef _WIN32
297  while ( ( readBytes = recv( socketFD, answer, MAXLINE, MSG_NOSIGNAL ) ) > 0 )
298  {
299 #else
300  while ( ( readBytes = recv( socketFD, answer, MAXLINE, 0 ) ) > 0 )
301  {
302 #endif
303  answer[readBytes] = '\0';
304  printf( "Answer from bug tracking server:\n%s\n", answer );
305  // skip the received answer and proceed
306  }
307 
308 // close the socket to the bug tracking server
309 #ifndef _WIN32
310  close( socketFD );
311 #else
312  closesocket( socketFD );
313 #endif
314 }
315 } // namespace
316 
317 void ExceptionParser::reportProgramAbort( const string& stackTrace, const string& reason )
318 {
322  string host = "polserver.com";
323  string url = "/pol/report_program_abort.php";
324  if ( ( m_programAbortReportingServer.c_str() != NULL ) &&
325  ( m_programAbortReportingServer != "" ) )
326  {
327  host = m_programAbortReportingServer;
328  if ( m_programAbortReportingUrl.c_str() != NULL )
329  url = m_programAbortReportingUrl;
330  }
331 
332  // create the abort description for the subsequent POST request
333  string content = "email=" + m_programAbortReportingReporter +
334  "&"
335  "bin=" +
336  PROG_CONFIG::programName() +
337  "&"
338  "start_time=" +
339  m_programStart +
340  "&"
341  "abort_time=" +
343  "&"
344  "reason=" +
345  reason +
346  "&"
347  "trace=" +
348  stackTrace +
349  "&"
350  "comp=" +
351  getCompilerVersion() +
352  "&"
353  "comp_time=" +
355  "&"
356  "build_target=" +
358  "&"
359  "build_revision=" POL_VERSION_ID
360  "&"
361  "misc=";
362 
363  // execute the POST request
364  doHttpPOST( host, url, content );
365 }
366 
368 {
369  switch ( signal )
370  {
371  case SIGILL:
372  case SIGFPE:
373  case SIGSEGV:
374  case SIGTERM:
375  case SIGABRT:
376  {
380  printf(
381  "########################################################################################"
382  "\n" );
383  if ( m_programAbortReporting )
384  printf( "POL will exit now. The following will be sent to the POL developers:\n\n" );
385  else
386  printf(
387  "POL will exit now. Please, post the following to the forum: "
388  "http://forums.polserver.com/.\n" );
389  string tStackTrace = ExceptionParser::getTrace();
390  printf( tStackTrace.c_str() );
391  printf( "Admin contact: %s\n", m_programAbortReportingReporter.c_str() );
392  printf( "Executable: %s\n", PROG_CONFIG::programName().c_str() );
393  printf( "Start time: %s\n", m_programStart.c_str() );
394  printf( "Current time: %s\n", Pol::Clib::Logging::LogSink::getTimeStamp().c_str() );
395  printf( "\n" );
396  printf( "Stack trace:\n%s", tStackTrace.c_str() );
397  printf( "\n" );
398  printf( "Compiler: %s\n", getCompilerVersion().c_str() );
399  printf( "Compile time: %s\n", ProgramConfig::build_datetime().c_str() );
400  printf( "Build target: %s\n", ProgramConfig::build_target().c_str() );
401  printf( "Build revision: %s\n", POL_VERSION_ID );
402 #ifndef _WIN32
403  printf( "GNU C library (compile time): %d.%d\n", __GLIBC__, __GLIBC_MINOR__ );
404 #endif
405  printf( "\n" );
406  printf(
407  "########################################################################################"
408  "\n" );
409  fflush( stdout );
410 
414  if ( m_programAbortReporting )
415  {
416  string signalName;
417  string signalDescription;
418 
419  getSignalDescription( signal, signalName, signalDescription );
421  tStackTrace, "CRASH caused by signal " + signalName + " (" + signalDescription + ")" );
422  }
423 
424  // finally, go to hell
425  exit( 1 );
426  }
427  break;
428  default:
429  break;
430  }
431 }
432 
434 
436 
438 
440 
441 #ifndef _WIN32
443 {
444  string result;
445 
446  void* stackTrace[MAX_STACK_TRACE_DEPTH];
447  int stackTraceSize;
448  char** stackTraceList;
449  int stackTraceStep = 0;
450  char* stringBuf = (char*)malloc( MAX_STACK_TRACE_STEP_LENGTH );
451 
452  stackTraceSize = backtrace( stackTrace, MAX_STACK_TRACE_DEPTH );
453  stackTraceList = backtrace_symbols( stackTrace, stackTraceSize );
454 
455  size_t funcNameSize = 256;
456  char* funcnName = (char*)malloc( funcNameSize );
457 
458  // iterate over all entries and do the demangling
459  for ( int i = 0; i < stackTraceSize; i++ )
460  {
461  // get the pointers to the name, offset and end of offset
462  char* beginFuncName = nullptr;
463  char* beginFuncOffset = nullptr;
464  char* endFuncOffset = nullptr;
465  char* beginBinaryName = stackTraceList[i];
466  char* beginBinaryOffset = nullptr;
467  char* endBinaryOffset = nullptr;
468  for ( char* entryPointer = stackTraceList[i]; *entryPointer; ++entryPointer )
469  {
470  if ( *entryPointer == '(' )
471  {
472  beginFuncName = entryPointer;
473  }
474  else if ( *entryPointer == '+' )
475  {
476  beginFuncOffset = entryPointer;
477  }
478  else if ( *entryPointer == ')' && beginFuncOffset )
479  {
480  endFuncOffset = entryPointer;
481  }
482  else if ( *entryPointer == '[' )
483  {
484  beginBinaryOffset = entryPointer;
485  }
486  else if ( *entryPointer == ']' && beginBinaryOffset )
487  {
488  endBinaryOffset = entryPointer;
489  break;
490  }
491  }
492 
493  // set the default value for the output line
494  sprintf( stringBuf, "\n" );
495 
496  bool parse_succeeded = beginFuncName && beginFuncOffset && endFuncOffset && beginBinaryOffset &&
497  endBinaryOffset && beginFuncName < beginFuncOffset;
498 
499  // get the detailed values for the output line if available
500  if ( parse_succeeded )
501  {
502  // terminate the C strings
503  *beginFuncName++ = '\0';
504  *beginFuncOffset++ = '\0';
505  *endFuncOffset = '\0';
506  *beginBinaryOffset++ = '\0';
507  *endBinaryOffset = '\0';
508 
509  int res;
510  funcnName = abi::__cxa_demangle( beginFuncName, funcnName, &funcNameSize, &res );
511  unsigned int binaryOffset = strtoul( beginBinaryOffset, NULL, 16 );
512  if ( res == 0 )
513  {
514  string funcnNameStr = ( funcnName ? funcnName : "" );
515  if ( funcnName && strncmp( funcnName, "Pol::", 5 ) == 0 )
516  funcnNameStr = ">> " + funcnNameStr;
517 
518  if ( beginBinaryName && strlen( beginBinaryName ) )
519  sprintf( stringBuf, "#%02d 0x%016x in %s:[%s] from %s\n", stackTraceStep, binaryOffset,
520  funcnNameStr.c_str(), beginFuncOffset, beginBinaryName );
521  else
522  sprintf( stringBuf, "#%02d 0x%016x in %s from %s\n", stackTraceStep, binaryOffset,
523  funcnNameStr.c_str(), beginFuncOffset );
524  stackTraceStep++;
525  }
526  else
527  {
528  if ( beginBinaryName && strlen( beginBinaryName ) )
529  sprintf( stringBuf, "#%02d 0x%016x in %s:[%s] from %s\n", stackTraceStep, binaryOffset,
530  beginFuncName, beginFuncOffset, beginBinaryName );
531  else
532  sprintf( stringBuf, "#%02d 0x%016x in %s:[%s]\n", stackTraceStep, binaryOffset,
533  beginFuncName, beginFuncOffset );
534  stackTraceStep++;
535  }
536  }
537  else
538  {
539  // print the raw trace, as it is better than nothing
540  sprintf( stringBuf, "#%02d %s\n", stackTraceStep, stackTraceList[i] );
541  stackTraceStep++;
542  }
543 
544  // append the line to the result
545  result += string( stringBuf );
546  }
547 
548  // memory cleanup
549  free( funcnName );
550  free( stackTraceList );
551  free( stringBuf );
552 
553  return result;
554 }
555 
556 static void handleSignalLinux( int signal, siginfo_t* signalInfo, void* arg )
557 {
558  (void)arg;
559  logExceptionSignal( signal );
560  if ( signalInfo != NULL )
561  {
562  if ( signal == SIGSEGV )
563  {
564  if ( signalInfo->si_addr != NULL )
565  printf( "Segmentation fault detected - faulty memory reference at location: %p\n",
566  signalInfo->si_addr );
567  else
568  printf( "Segmentation fault detected - null pointer reference\n" );
569  }
570  if ( signalInfo->si_errno != 0 )
571  printf( "This signal occurred because \"%s\"(%d)\n", strerror( signalInfo->si_errno ),
572  signalInfo->si_errno );
573  if ( signalInfo->si_code != 0 )
574  printf( "Signal code is %d\n", signalInfo->si_code );
575  }
577 }
578 
579 static void handleStackTraceRequestLinux( int signal, siginfo_t* signalInfo, void* arg )
580 {
581  (void)signal;
582  (void)signalInfo;
583  (void)arg;
585  threadhelp::threadmap.CopyContents( threadDesc );
586 
587  fmt::Writer output;
588  output << "STACK TRACE for thread \"" << threadDesc[pthread_self()] << "\"(" << pthread_self()
589  << "):\n";
590  output << ExceptionParser::getTrace() << "\n";
591 
592  // print to stdout
593  printf( "%s", output.c_str() );
594 
595  // print to error output
596  POLLOG_ERROR << output.str();
597 
598  // wait here for logging facility to make sure everything was processed
601 }
602 
604 {
606  threadhelp::threadmap.CopyContents( threadsDesc );
607  for ( const auto& threadDesc : threadsDesc )
608  {
609  pthread_t threadID = (pthread_t)threadDesc.first;
610 
611  if ( pthread_kill( threadID, SIGUSR1 ) != 0 )
612  {
613  fmt::Writer output;
614  output << "pthread_kill() failed to send SIGUSR1 to thread " << threadsDesc[threadID] << "("
615  << threadID << ")\n";
616  fprintf( stderr, "%s", output.c_str() );
617  }
618  }
619 }
620 
622 {
623  struct sigaction sigAction;
624 
625  memset( &sigAction, 0, sizeof( sigAction ) );
626  sigemptyset( &sigAction.sa_mask );
627  sigAction.sa_sigaction = handleSignalLinux;
628  sigAction.sa_flags = SA_SIGINFO;
629  sigaction( SIGINT, &sigAction, NULL );
630  sigaction( SIGTERM, &sigAction, NULL );
631  sigaction( SIGSEGV, &sigAction, NULL );
632  sigaction( SIGABRT, &sigAction, NULL );
633  sigAction.sa_sigaction = handleStackTraceRequestLinux;
634  sigaction( SIGUSR1, &sigAction, NULL );
635 
636  // set handler stack
637  stack_t tStack;
638  // mmap: no false positives for leak, plus guardpages to get SIGSEGV on memory overwrites
639  char* mem = static_cast<char*>( mmap( NULL, SIGSTKSZ + 2 * getpagesize(), PROT_READ | PROT_WRITE,
640  MAP_PRIVATE | MAP_ANON, -1, 0 ) );
641  mprotect( mem, getpagesize(), PROT_NONE );
642  mprotect( mem + getpagesize() + SIGSTKSZ, getpagesize(), PROT_NONE );
643  tStack.ss_sp = mem + getpagesize();
644  tStack.ss_size = SIGSTKSZ;
645  tStack.ss_flags = 0;
646  if ( sigaltstack( &tStack, NULL ) == -1 )
647  {
648  printf( "Could not set signal handler stack\n" );
649  exit( 1 );
650  }
651 }
652 #else // _WIN32
654 {
655  string result;
656 
657  return result;
658 }
659 
661 
663 #endif // _WIN32
664 
665 void ExceptionParser::configureProgramAbortReportingSystem( bool active, std::string server,
666  std::string url, std::string reporter )
667 {
668  m_programAbortReporting = active;
669  m_programAbortReportingServer = std::move( server );
670  m_programAbortReportingUrl = std::move( url );
671  m_programAbortReportingReporter = std::move( reporter );
672 }
673 
675 {
676  return m_programAbortReporting;
677 }
678 
680 }
681 } // namespaces
static std::string m_programStart
int SOCKET
Definition: wnsckt.h:10
static std::string m_programAbortReportingServer
STL namespace.
static void configureProgramAbortReportingSystem(bool active, std::string server, std::string url, std::string reporter)
Configures the bug reporting system.
static std::string getTrace()
Returns a string containing the current stack trace.
static std::string m_programAbortReportingUrl
#define POLLOG_ERROR
Definition: logfacility.h:207
#define MAX_STACK_TRACE_STEP_LENGTH
static void handleExceptionSignal(int signal)
Handles exceptions.
void CopyContents(Contents &out) const
Definition: threadhelp.cpp:307
static std::string getTimeStamp()
Definition: LogSink.cpp:30
#define MAX_STACK_TRACE_DEPTH
static void initGlobalExceptionCatching()
Initiates globally the exception catching (signal handlers for Linux)
static void handleSignalLinux(int signal, siginfo_t *signalInfo, void *arg)
#define MAXLINE
std::map< size_t, std::string > Contents
Definition: threadhelp.h:43
static bool programAbortReporting()
Returns true if the bug reporting is active.
static void reportProgramAbort(const std::string &stackTrace, const std::string &reason)
Reports a program abort to the program devs.
static std::string m_programAbortReportingReporter
static void logAllStackTraces()
Logs stack traces of all threads to stdout and error output.
static void handleStackTraceRequestLinux(int signal, siginfo_t *signalInfo, void *arg)
LogFacility * global_logger
Definition: logfacility.cpp:62
static std::string build_target()
Definition: berror.cpp:12
ThreadMap threadmap
Definition: threadhelp.cpp:40
static std::string build_datetime()