9ec53f0ef54c58ef553dffcc686099f9caa4571c
[citadel.git] / textclient / src / ecrash.c
1 /*
2  * author: David Frascone
3  * 
4  * eCrash Implementation
5  *
6  * eCrash will allow you to capture stack traces in the
7  * event of a crash, and write those traces to disk, stdout,
8  * or any other file handle.
9  *
10  * modified to integrate closer into citadel by Wilfried Goesgens
11  *
12  * vim: ts=4
13  *
14  * This program is open source software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27  */
28
29 #include "sysdep.h"
30 #include <stdio.h>
31 #include <unistd.h>
32 #include <stdlib.h>
33 #include <stdarg.h>
34 #include <string.h>
35 #include <fcntl.h>
36 #include <syslog.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <pthread.h>
40 #include <libcitadel.h>
41 ///#include "server.h"
42 /// #include "sysdep_decls.h"
43 //#include "support.h"
44 ///#include "config.h"
45 //#include "citserver.h"
46 #include "ecrash.h"
47
48 #define NIY()   printf("function not implemented yet!\n");
49 #ifdef HAVE_BACKTRACE
50 #include <execinfo.h>
51 static eCrashParameters gbl_params;
52
53 static int    gbl_backtraceEntries;
54 static void **gbl_backtraceBuffer;
55 static char **gbl_backtraceSymbols;
56 static int    gbl_backtraceDoneFlag = 0;
57
58 static void *stack_frames[50];
59 static size_t size, NThread;
60 static char **strings;
61
62 /* 
63  * Private structures for our thread list
64  */
65 typedef struct thread_list_node{
66         char *threadName;
67         pthread_t thread;
68         int backtraceSignal;
69         sighandler_t oldHandler;
70         struct thread_list_node *Next;
71 } ThreadListNode;
72
73 static pthread_mutex_t ThreadListMutex = PTHREAD_MUTEX_INITIALIZER;
74 static ThreadListNode *ThreadList = NULL;
75
76 /*********************************************************************
77  *********************************************************************
78  **     P  R  I  V  A  T  E      F  U  N  C  T  I  O  N  S
79  *********************************************************************
80  ********************************************************************/
81
82
83 /*!
84  * Insert a node into our threadList
85  *
86  * @param name   Text string indicating our thread
87  * @param thread Our Thread Id
88  * @param signo  Signal to create backtrace with
89  * @param old_handler Our old handler for signo
90  *
91  * @returns zero on success
92  */
93 static int addThreadToList(char *name, pthread_t thread,int signo,
94                                            sighandler_t old_handler)
95 {
96         ThreadListNode *node;
97
98         node = malloc(sizeof(ThreadListNode));
99         if (!node) return -1;
100
101         DPRINTF(ECRASH_DEBUG_VERBOSE,
102                                         "Adding thread 0x%08x (%s)\n", (unsigned int)thread, name);
103         node->threadName = strdup(name);
104         node->thread = thread;
105         node->backtraceSignal = signo;
106         node->oldHandler = old_handler;
107
108         /* And, add it to the list */
109         pthread_mutex_lock(&ThreadListMutex);
110         node->Next = ThreadList;
111         ThreadList = node;
112         pthread_mutex_unlock(&ThreadListMutex);
113         
114         return 0;
115
116 } // addThreadToList
117
118 /*!
119  * Remove a node from our threadList
120  *
121  * @param thread Our Thread Id
122  *
123  * @returns zero on success
124  */
125 static int removeThreadFromList(pthread_t thread)
126 {
127         ThreadListNode *Probe, *Prev=NULL;
128         ThreadListNode *Removed = NULL;
129
130         DPRINTF(ECRASH_DEBUG_VERBOSE,
131                                         "Removing thread 0x%08x from list . . .\n", (unsigned int)thread);
132         pthread_mutex_lock(&ThreadListMutex);
133         for (Probe=ThreadList;Probe != NULL; Probe = Probe->Next) {
134                 if (Probe->thread == thread) {
135                         // We found it!  Unlink it and move on!
136                         Removed = Probe;
137                         if (Prev == NULL) { // head of list
138                                 ThreadList = Probe->Next;
139                         } else {
140                                 // Prev != null, so we need to link around ourselves.
141                                 Prev->Next = Probe->Next;
142                         }
143                         Removed->Next = NULL;
144                         break;
145                 }
146
147                 Prev = Probe;
148         }
149         pthread_mutex_unlock(&ThreadListMutex);
150
151         // Now, if something is in Removed, free it, and return success
152         if (Removed) {
153             DPRINTF(ECRASH_DEBUG_VERBOSE,
154                                                 "   Found %s -- removing\n", Removed->threadName);
155                 // Reset the signal handler
156                 signal(Removed->backtraceSignal, Removed->oldHandler);
157
158                 // And free the allocated memory
159                 free (Removed->threadName);
160                 free (Removed);
161
162                 return 0;
163         } else {
164             DPRINTF(ECRASH_DEBUG_VERBOSE,
165                                                 "   Not Found\n");
166                 return -1; // Not Found
167         }
168 } // removeThreadFromList
169
170 /*!
171  * Print out a line of output to all our destinations
172  *
173  * One by one, output a line of text to all of our output destinations.
174  *
175  * Return failure if we fail to output to any of them.
176  *
177  * @param format   Normal printf style vararg format
178  *
179  * @returns nothing// bytes written, or error on failure.
180  */
181 static void outputPrintf(char *format, ...)
182 {
183         va_list ap;
184
185         va_start(ap, format);
186
187         vsyslog(LOG_CRIT|LOG_NDELAY|LOG_MAIL, format, ap);
188 } // outputPrintf
189
190
191
192 /*!
193  * Dump our backtrace into a global location
194  *
195  * This function will dump out our backtrace into our
196  * global holding area.
197  *
198  */
199 static void createGlobalBacktrace( void )
200 {
201
202         size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
203         for (NThread = 0; NThread < size; NThread++) 
204         {
205                 syslog(LOG_CRIT|LOG_NDELAY|LOG_MAIL, "RAW: %p  ", stack_frames[NThread]);
206         }
207         strings = backtrace_symbols(stack_frames, size);
208         for (NThread = 0; NThread < size; NThread++) {
209                 if (strings != NULL) {
210                         syslog(LOG_CRIT|LOG_NDELAY|LOG_MAIL, "RAW: %p  ", strings[NThread]);
211                 }
212         }
213 } /* createGlobalBacktrace */
214 static void outputRawtrace( void )
215 {
216
217         size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
218         for (NThread = 0; NThread < size; NThread++) 
219         {
220                 syslog(LOG_CRIT|LOG_NDELAY|LOG_MAIL, "RAW: %p  ", stack_frames[NThread]);
221         }
222 } /* createGlobalBacktrace */
223
224 /*!
225  * Print out (to all the fds, etc), or global backtrace
226  */
227 static void outputGlobalBacktrace ( void )
228 {
229         int i;
230
231         for (i=0; i < gbl_backtraceEntries; i++) {
232                 if (gbl_backtraceSymbols != FALSE) {
233                         outputPrintf("*      Frame %02x: %s\n",
234                                      i, gbl_backtraceSymbols[i]);
235                 } else {
236                         outputPrintf("*      Frame %02x: %p\n", i,
237                                      gbl_backtraceBuffer[i]);
238                 }
239         }
240 } // outputGlobalBacktrace
241
242 /*!
243  * Output our current stack's backtrace
244  */
245 static void outputBacktrace( void )
246 {
247         createGlobalBacktrace();
248         outputGlobalBacktrace();
249 } /* outputBacktrace */
250
251 static void outputBacktraceThreads( void )
252 {
253         ThreadListNode *probe;
254         int i;
255
256         // When we're backtracing, don't worry about the mutex . . hopefully
257         // we're in a safe place.
258
259         for (probe=ThreadList; probe; probe=probe->Next) {
260                 gbl_backtraceDoneFlag = 0;
261                 pthread_kill(probe->thread, probe->backtraceSignal);
262                 for (i=0; i < gbl_params.threadWaitTime; i++) {
263                         if (gbl_backtraceDoneFlag)
264                                 break;
265                         sleep(1);
266                 }
267                 if (gbl_backtraceDoneFlag) {
268                         outputPrintf("*  Backtrace of \"%s\" (0x%08x)\n", 
269                                                  probe->threadName, (unsigned int)probe->thread);
270                         outputGlobalBacktrace();
271                 } else {
272                         outputPrintf("*  Error: unable to get backtrace of \"%s\" (0x%08x)\n", 
273                                                  probe->threadName, (unsigned int)probe->thread);
274                 }
275                 outputPrintf("*\n");
276         }
277 } // outputBacktraceThreads
278
279
280 /*!
281  * Handle signals (crash signals)
282  *
283  * This function will catch all crash signals, and will output the
284  * crash dump.  
285  *
286  * It will physically write (and sync) the current thread's information
287  * before it attempts to send signals to other threads.
288  * 
289  * @param signum Signal received.
290  */
291 static void crash_handler(int signo)
292 {
293         outputRawtrace();
294         outputPrintf("*********************************************************\n");
295         outputPrintf("*               eCrash Crash Handler\n");
296         outputPrintf("*********************************************************\n");
297         outputPrintf("*\n");
298         outputPrintf("*  Got a crash! signo=%d\n", signo);
299         outputPrintf("*\n");
300         outputPrintf("*  Offending Thread's Backtrace:\n");
301         outputPrintf("*\n");
302         outputBacktrace();
303         outputPrintf("*\n");
304
305         if (gbl_params.dumpAllThreads != FALSE) {
306                 outputBacktraceThreads();
307         }
308
309         outputPrintf("*\n");
310         outputPrintf("*********************************************************\n");
311         outputPrintf("*               eCrash Crash Handler\n");
312         outputPrintf("*********************************************************\n");
313
314         exit(signo);
315 } // crash_handler
316
317 /*!
318  * Handle signals (bt signals)
319  *
320  * This function shoudl be called to generate a crashdump into our
321  * global area.  Once the dump has been completed, this function will
322  * return after tickling a global.  Since mutexes are not async
323  * signal safe, the main thread, after signaling us to generate our
324  * own backtrace, will sleep for a few seconds waiting for us to complete.
325  *
326  * @param signum Signal received.
327  */
328 static void bt_handler(int signo)
329 {
330         createGlobalBacktrace();
331         gbl_backtraceDoneFlag=1;
332 } // bt_handler
333
334 /*!
335  * Validate a passed-in symbol table
336  *
337  * For now, just print it out (if verbose), and make sure it's
338  * sorted and none of the pointers are zero.
339  */
340 static int ValidateSymbolTable( void )
341 {
342         int i;
343         int rc=0;
344         unsigned long lastAddress =0;
345
346         // Get out of here if the table is empty
347         if (!gbl_params.symbolTable) return 0;
348
349         // Dump it in verbose mode
350         DPRINTF(ECRASH_DEBUG_VERBOSE,
351                                         "Symbol Table Provided with %d symbols\n",
352                                         gbl_params.symbolTable->numSymbols);
353         for (i=0; i < gbl_params.symbolTable->numSymbols; i++){
354                 // Dump it in verbose mode
355                 DPRINTF(ECRASH_DEBUG_VERBOSE, 
356                                 "%-30s %p\n",
357                                 gbl_params.symbolTable->symbols[i].function,
358                                 gbl_params.symbolTable->symbols[i].address);
359                 if (lastAddress >
360                     (unsigned long)gbl_params.symbolTable->symbols[i].address) {
361                         DPRINTF(ECRASH_DEBUG_ERROR,
362                                         "Error: symbol table is not sorted (last=%p, current=%p)\n",
363                                         (void *)lastAddress,
364                                         gbl_params.symbolTable->symbols[i].address);
365                         rc = -1;
366                 }
367
368         } // for
369
370         return rc;
371         
372 } // ValidateSymbolTable
373
374 /*********************************************************************
375  *********************************************************************
376  **      P  U  B  L  I  C      F  U  N  C  T  I  O  N  S
377  *********************************************************************
378  ********************************************************************/
379
380 /*!
381  * Initialize eCrash.
382  * 
383  * This function must be called before calling any other eCrash
384  * functions.  It sets up the global behavior of the system, and
385  * registers the calling thread for crash dumps.
386  *
387  * @param params Our input parameters.  The passed in structure will be copied.
388  *
389  * @return Zero on success.
390  */
391 int eCrash_Init(eCrashParameters *params)
392 {
393         int sigIndex;
394         int ret = 0;
395 #ifdef DO_SIGNALS_RIGHT
396         sigset_t blocked;
397         struct sigaction act;
398 #endif
399
400         DPRINTF(ECRASH_DEBUG_VERY_VERBOSE,"Init Starting params = %p\n", params);
401
402         // Allocate our backtrace area
403         gbl_backtraceBuffer = malloc(sizeof(void *) * (params->maxStackDepth+5));
404
405 #ifdef DO_SIGNALS_RIGHT
406         sigemptyset(&blocked);
407         act.sa_sigaction = crash_handler;
408         act.sa_mask = blocked;
409         act.sa_flags = SA_SIGINFO;
410 #endif
411
412         if (params != NULL) {
413                 // Make ourselves a global copy of params.
414                 gbl_params = *params;
415                 gbl_params.filename = strdup(params->filename);
416
417                 // Set our defaults, if they weren't specified
418                 if (gbl_params.maxStackDepth == 0 )
419                         gbl_params.maxStackDepth = ECRASH_DEFAULT_STACK_DEPTH;
420
421                 if (gbl_params.defaultBacktraceSignal == 0 )
422                         gbl_params.defaultBacktraceSignal = ECRASH_DEFAULT_BACKTRACE_SIGNAL;
423
424                 if (gbl_params.threadWaitTime == 0 )
425                         gbl_params.threadWaitTime = ECRASH_DEFAULT_THREAD_WAIT_TIME;
426
427                 if (gbl_params.debugLevel == 0 )
428                         gbl_params.debugLevel = ECRASH_DEBUG_DEFAULT;
429
430                 // Copy our symbol table
431                 if (gbl_params.symbolTable) {
432                     DPRINTF(ECRASH_DEBUG_VERBOSE,
433                                                         "symbolTable @ %p -- %d symbols\n", gbl_params.symbolTable,
434                                                 gbl_params.symbolTable->numSymbols);
435                         // Make a copy of our symbol table
436                         gbl_params.symbolTable = malloc(sizeof(eCrashSymbolTable));
437                         memcpy(gbl_params.symbolTable, params->symbolTable,
438                                    sizeof(eCrashSymbolTable));
439
440                         // Now allocate / copy the actual table.
441                         gbl_params.symbolTable->symbols = malloc(sizeof(eCrashSymbol) *
442                                                                      gbl_params.symbolTable->numSymbols);
443                         memcpy(gbl_params.symbolTable->symbols,
444                                    params->symbolTable->symbols,
445                                    sizeof(eCrashSymbol) * gbl_params.symbolTable->numSymbols);
446
447                         ValidateSymbolTable();
448                 }
449         
450                 // And, finally, register for our signals
451                 for (sigIndex=0; gbl_params.signals[sigIndex] != 0; sigIndex++) {
452                         DPRINTF(ECRASH_DEBUG_VERY_VERBOSE,
453                                                         "   Catching signal[%d] %d\n", sigIndex,
454                                         gbl_params.signals[sigIndex]);
455
456                         // I know there's a better way to catch signals with pthreads.
457                         // I'll do it later TODO
458                         signal(gbl_params.signals[sigIndex], crash_handler);
459                 }
460         } else {
461                 DPRINTF(ECRASH_DEBUG_ERROR, "   Error:  Null Params!\n");
462                 ret = -1;
463         }
464         DPRINTF(ECRASH_DEBUG_VERY_VERBOSE, "Init Complete ret=%d\n", ret);
465         return ret;
466 } /* eCrash_Init */
467
468 /*!
469  * UnInitialize eCrash.
470  * 
471  * This function may be called to de-activate eCrash, release the
472  * signal handlers, and free any memory allocated by eCrash.
473  *
474  * @return Zero on success.
475  */
476 int eCrash_Uninit( void )
477 {
478         NIY();
479
480         return 0;
481 } /* eCrash_Uninit */
482
483 /*!
484  * Register a thread for backtracing on crash.
485  * 
486  * This function must be called by any thread wanting it's stack
487  * dumped in the event of a crash.  The thread my specify what 
488  * signal should be used, or the default, SIGUSR1 will be used.
489  *
490  * @param signo Signal to use to generate dump (default: SIGUSR1)
491  *
492  * @return Zero on success.
493  */
494 int eCrash_RegisterThread(char *name, int signo)
495 {
496         sighandler_t old_handler;
497
498         // Register for our signal
499         if (signo == 0) {
500                 signo = gbl_params.defaultBacktraceSignal;
501         }
502
503         old_handler = signal(signo, bt_handler);
504         return addThreadToList(name, pthread_self(), signo, old_handler);
505
506 } /* eCrash_RegisterThread */
507
508 /*!
509  * Un-register a thread for stack dumps.
510  * 
511  * This function may be called to un-register any previously 
512  * registered thread.
513  *
514  * @return Zero on success.
515  */
516 int eCrash_UnregisterThread( void )
517 {
518         return removeThreadFromList(pthread_self());
519 } /* eCrash_UnregisterThread */
520
521 #endif