File: toolsignal.cpp

package info (click to toggle)
libguytools2 2.0.5-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid
  • size: 288 kB
  • sloc: cpp: 2,615; ansic: 288; makefile: 45; sh: 4
file content (297 lines) | stat: -rw-r--r-- 11,678 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
// ****************************************************************************
//  Project:        libguytools
// ****************************************************************************
//  Programmer:     Guy Voncken
//                  Police Grand-Ducale
//                  Service de Police Judiciaire
//                  Section Nouvelles Technologies
// ****************************************************************************
//  Module:         Signal handler with backtrace
// ****************************************************************************

// Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 
// Guy Voncken
//
// This file is part of libguytools.
//
// libguytools is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// libguytools is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libguytools. If not, see <http://www.gnu.org/licenses/>.

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
//#define __USE_GNU
#include <string.h>
#include <unistd.h>
//#define __USE_XOPEN
#include <signal.h>
#include <execinfo.h>
#include <pthread.h>

#include "tooltypes.h"
#include "toolglobalid.h"
#include "toolsignal.h"
#include "toolerror.h"

// ------------------------------------
//          Type definitions
// ------------------------------------

typedef struct
{
   t_pToolSignalLogFn   pLogFn;
   t_pToolSignalHandler pSignalHandlerFn;
   struct sigaction      SignalActionDefault;
} t_ToolSignalLocal;


// ------------------------------------
//          Module variables
// ------------------------------------

bool ToolSignalInitialised = false;

t_ToolSignalLocal ToolSignalLocal;

// ------------------------------------
//           Log functions
// ------------------------------------

static int ToolSignalLogEntry (bool Error, const char *pFileName, const char *pFunctionName, int LineNr, const char *pFormat, ...)
{
   va_list VaList;

   va_start(VaList, pFormat);
   if (ToolSignalLocal.pLogFn)
   {
      (*ToolSignalLocal.pLogFn)(Error, pthread_self(), pFileName, pFunctionName, LineNr, pFormat, VaList);
   }
   else
   {
      printf ("\n");
      vprintf (pFormat, VaList);
   }
   va_end(VaList);

   return NO_ERROR;
}

// ------------------------------------
//           Main functions
// ------------------------------------

static void ToolSignalStandardSet (sigset_t *pSignalSet)
{
   sigfillset (pSignalSet);           // Add all known signals to set, except the ones listed below:
   sigdelset  (pSignalSet, SIGSEGV);  //    - handled by our backtrace function, see below
   sigdelset  (pSignalSet, SIGPIPE);  //    - which would cause problems in ceratin situations when our program deals with pipes
   sigdelset  (pSignalSet, SIGWINCH); //    - emitted on window resize
   sigdelset  (pSignalSet, SIGCHLD);  //    - QProcess relies on receiving this one in order to know when a child process finishes
}                                     // These settings will be valid for the current thread and all its children threads. That's the reason, why
                                      // ToolSignalInit should be called in the application main thread before starting any other thread.

static void *ToolSignalThread (void *p)
{
   sigset_t SignalSet;
   int      Signal;
   bool     Exit;
   char   *pSignalName;

   p=p; // Dummy statement to avoid compiler warning about unused p
   ToolSignalLogEntry (false, __FFL__, "Signal guard thread started with id %d", getpid());

   Exit = false;
   while (!Exit)
   {
      ToolSignalStandardSet (&SignalSet);
      sigwait (&SignalSet, &Signal);    // wait for any signal in given set

      // when we get here, we've caught a signal
      // ---------------------------------------
      pSignalName = strsignal(Signal);
      ToolSignalLogEntry (true, __FFL__, "Thread (%d-%d): Caught signal: %s", getpid(), pthread_self(), pSignalName);
      if (ToolSignalLocal.pSignalHandlerFn)
         (*ToolSignalLocal.pSignalHandlerFn) (Signal);

      switch (Signal)
      {
         case SIGTERM:
         case SIGINT :  Exit = true; break;
         default:                    break;
      }
   }
   ToolSignalLogEntry (true, __FFL__, "Stopping signal guard thread.");
   return NULL;
}

// ToolSignalBacktraceHandler is called by any
// thread in the program causing a segment violation.
static void ToolSignalBacktraceHandler (int Signal, siginfo_t *pSignalInfo, void *pSecret)
{
   const int     TraceArrLen = 50;
   char      **ppMessages;
   void         *TraceArr[TraceArrLen];
   int           TraceSize;
   int           i;
   static int    RecursiveCallDetection = 0;

   /* Code below is no longer needed. According to tests on amd64 and i386 on Ubuntu 14.04, the address of the 
      place the where exception occured nowadays is correctly included in the array data returned by backtrace(). 
      It thus seems to be no longer necessary to separately fetch and add the instruction pointer corresponding 
      to the segmentation fault.
      This wasn't the case on older systems, see for example http://www.linuxjournal.com/article/6391?page=0,1 .

      void *pIP = NULL;

      #if defined(__sparc__)
         struct sigcontext* pSigContext = (struct sigcontext*) pSecret;
         #if __WORDSIZE == 64
            pIP = (void*) pSigContext->sigc_regs.tpc ;
         #else
            pIP = (void*) pSigContext->si_regs.pc ;
         #endif
      #else
         ucontext_t* pUContext = (ucontext_t*) pSecret;
         #if   defined(__i386__)
            pIP = (void*) pUContext->uc_mcontext.gregs[REG_EIP];
         #elif defined(__x86_64__)
            pIP = (void*) pUContext->uc_mcontext.gregs[REG_RIP];
         #elif defined(__hppa__)
            pIP = (void*) pUContext->uc_mcontext.sc_iaoq[0] & ~0x3UL;
         #elif (defined (__ppc__)) || (defined (__powerpc__))
            pIP = (void*) pUContext->uc_mcontext.regs->nip;
         #elif defined(__arm__)
            pIP = (void*) pUContext->uc_mcontext.arm_pc;
         #elif defined(__aarch64__)
            pIP = (void*) pUContext->uc_mcontext.pc;
         #endif
      #endif
   */

   RecursiveCallDetection++;
   switch (RecursiveCallDetection)
   {
      case 1:
         ppMessages = NULL;

         ToolSignalLogEntry (true, __FFL__, "----------------------------------------------------------------------");
         if (Signal == SIGSEGV)
         {
            ToolSignalLogEntry (true, __FFL__, "Thread (%d-%d): Got signal '%s' (%d), faulty address is %p" /*, "from %p" */, // See remarks above
                               getpid(), pthread_self(), strsignal(Signal), Signal,
                               pSignalInfo->si_addr /*, pIP*/);
         }
         else
         {
            ToolSignalLogEntry (true, __FFL__, "Thread (%d-%d): Got signal '%s' (%d) -- strange, function should only be called on SIGSEGV.",
                               getpid(), pthread_self(), strsignal(Signal), Signal);
         }
         TraceSize = backtrace (TraceArr, TraceArrLen);
         /* TraceArr[1] = pIP;   */  // See remarks above
         ppMessages  = backtrace_symbols (TraceArr, TraceSize);

         ToolSignalLogEntry (true, __FFL__, "Backtrace execution path");
         ToolSignalLogEntry (true, __FFL__, "The first two entries are normally related to the signal handler.");
         ToolSignalLogEntry (true, __FFL__, "The faulty code generally is referenced by the 3rd line in the listing below.");
         for (i=0; i<TraceSize; ++i)
            ToolSignalLogEntry (true, __FFL__, "[Backtrace] %s", ppMessages[i]);

         if (ToolSignalLocal.pSignalHandlerFn)
            (*ToolSignalLocal.pSignalHandlerFn) (SIGSEGV);

         ToolSignalLogEntry (true, __FFL__, "Calling original SIGSEGV handler");
         ToolSignalLogEntry (true, __FFL__, "----------------------------------------------------------------------");
         sigaction (SIGSEGV, &ToolSignalLocal.SignalActionDefault, NULL);                    // Switch back to original handler, if ever another esception
         (*ToolSignalLocal.SignalActionDefault.sa_sigaction) (Signal, pSignalInfo, pSecret); // occurs (nobody knows, what the original handler is doing..)
         break;

      case 2:
         ToolSignalLogEntry (true, __FFL__, "Recursive call of backtrace handler detected, calling orignal handler now.");
         sigaction (SIGSEGV, &ToolSignalLocal.SignalActionDefault, NULL);                    // Switch back to original handler, if ever another esception
         (*ToolSignalLocal.SignalActionDefault.sa_sigaction) (Signal, pSignalInfo, pSecret); // occurs (nobody knows, what the original handler is doing..)
         break;

      default:
         ToolSignalLogEntry (true, __FFL__, "Recursive call of backtrace handler detected, iteration %d. Exiting now.", RecursiveCallDetection);
         break;
   }
   exit (10);
}

// ------------------------------
//     Module initialisation
// ------------------------------

int ToolSignalInit (t_pToolSignalLogFn pFnLog, t_pToolSignalHandler pFnSig, int *pThreadId)
{
   struct sigaction SignalAction;
   sigset_t         SignalSet;
   int              StartRc;

   if (ToolSignalInitialised)
      return ERROR_TOOLSIGNAL_ALREADY_INITIALISED;

   TOOL_CHK (TOOL_ERROR_REGISTER_CODE (ERROR_BASE_TOOL_SIGNAL))
   TOOL_CHK (TOOL_ERROR_REGISTER_CODE (ERROR_TOOLSIGNAL_ALREADY_INITIALISED))
   TOOL_CHK (TOOL_ERROR_REGISTER_CODE (ERROR_TOOLSIGNAL_STARTTHREAD_FAILED))

   ToolSignalLocal.pLogFn           = pFnLog;
   ToolSignalLocal.pSignalHandlerFn = pFnSig;

   // Install segment violation signal handler
   // ----------------------------------------
   sigemptyset (&SignalAction.sa_mask);
   SignalAction.sa_sigaction = ToolSignalBacktraceHandler;  // ToolSignalBacktraceSignalHandler will be called
   SignalAction.sa_flags     = SA_RESTART | SA_SIGINFO;     // by any thread having a segment violation
   sigaction (SIGSEGV, &SignalAction, &ToolSignalLocal.SignalActionDefault);
   if (pThreadId)
      *pThreadId = pthread_self();

   // Set all other signals to "block"
   // --------------------------------
   ToolSignalStandardSet (&SignalSet);
   pthread_sigmask (SIG_BLOCK, &SignalSet, NULL);   // reason, why ToolSignalInit should be called in the application main thread before

   // Start the signal handler thread
   // -------------------------------
   pthread_t ThreadStruct;

   StartRc = pthread_create (&ThreadStruct, 0, ToolSignalThread, NULL);
   if (StartRc != 0)
      return ERROR_TOOLSIGNAL_STARTTHREAD_FAILED;

   ToolSignalInitialised = true;

   return NO_ERROR;
}

int ToolSignalDeInit (void)
{
   sigset_t SignalSet;

   if (!ToolSignalInitialised)
      return ERROR_TOOLSIGNAL_NOT_INITIALISED;

   // Unblock the signals again
   // -------------------------
   ToolSignalStandardSet (&SignalSet);                // Unblocking is only done for this thread; signals in child
   pthread_sigmask (SIG_UNBLOCK, &SignalSet, NULL);   // threads started from this thread remain blocked.

   // Switch back to default SIGSEV signal handler
   // --------------------------------------------
   sigaction (SIGSEGV, &ToolSignalLocal.SignalActionDefault, NULL);

   ToolSignalInitialised = false;
   return NO_ERROR;
}