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