2 * author: David Frascone
4 * eCrash Implementation
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.
10 * modified to integrate closer into citadel by Wilfried Goesgens
14 * This program is free 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.
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.
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
37 #include <sys/types.h>
40 #include <libcitadel.h>
42 #include "sysdep_decls.h"
45 #include "citserver.h"
48 #define NIY() printf("function not implemented yet!\n");
51 static eCrashParameters gbl_params;
53 static int gbl_backtraceEntries;
54 static void **gbl_backtraceBuffer;
55 static char **gbl_backtraceSymbols;
56 static int gbl_backtraceDoneFlag = 0;
58 static void *stack_frames[50];
59 static size_t size, NThread;
60 static char **strings;
61 static char StaticBuf[SIZ];
64 * Private structures for our thread list
66 typedef struct thread_list_node{
70 sighandler_t oldHandler;
71 struct thread_list_node *Next;
74 static pthread_mutex_t ThreadListMutex = PTHREAD_MUTEX_INITIALIZER;
75 static ThreadListNode *ThreadList = NULL;
77 /*********************************************************************
78 *********************************************************************
79 ** P R I V A T E F U N C T I O N S
80 *********************************************************************
81 ********************************************************************/
85 * Insert a node into our threadList
87 * @param name Text string indicating our thread
88 * @param thread Our Thread Id
89 * @param signo Signal to create backtrace with
90 * @param old_handler Our old handler for signo
92 * @returns zero on success
94 static int addThreadToList(char *name, pthread_t thread,int signo,
95 sighandler_t old_handler)
99 node = malloc(sizeof(ThreadListNode));
100 if (!node) return -1;
102 DPRINTF(ECRASH_DEBUG_VERBOSE,
103 "Adding thread 0x%08x (%s)\n", (unsigned int)thread, name);
104 node->threadName = strdup(name);
105 node->thread = thread;
106 node->backtraceSignal = signo;
107 node->oldHandler = old_handler;
109 /* And, add it to the list */
110 pthread_mutex_lock(&ThreadListMutex);
111 node->Next = ThreadList;
113 pthread_mutex_unlock(&ThreadListMutex);
120 * Remove a node from our threadList
122 * @param thread Our Thread Id
124 * @returns zero on success
126 static int removeThreadFromList(pthread_t thread)
128 ThreadListNode *Probe, *Prev=NULL;
129 ThreadListNode *Removed = NULL;
131 DPRINTF(ECRASH_DEBUG_VERBOSE,
132 "Removing thread 0x%08x from list . . .\n", (unsigned int)thread);
133 pthread_mutex_lock(&ThreadListMutex);
134 for (Probe=ThreadList;Probe != NULL; Probe = Probe->Next) {
135 if (Probe->thread == thread) {
136 // We found it! Unlink it and move on!
138 if (Prev == NULL) { // head of list
139 ThreadList = Probe->Next;
141 // Prev != null, so we need to link around ourselves.
142 Prev->Next = Probe->Next;
144 Removed->Next = NULL;
150 pthread_mutex_unlock(&ThreadListMutex);
152 // Now, if something is in Removed, free it, and return success
154 DPRINTF(ECRASH_DEBUG_VERBOSE,
155 " Found %s -- removing\n", Removed->threadName);
156 // Reset the signal handler
157 signal(Removed->backtraceSignal, Removed->oldHandler);
159 // And free the allocated memory
160 free (Removed->threadName);
165 DPRINTF(ECRASH_DEBUG_VERBOSE,
167 return -1; // Not Found
169 } // removeThreadFromList
172 * Print out a line of output to all our destinations
174 * One by one, output a line of text to all of our output destinations.
176 * Return failure if we fail to output to any of them.
178 * @param format Normal printf style vararg format
180 * @returns nothing// bytes written, or error on failure.
182 static void outputPrintf(char *format, ...)
186 va_start(ap, format);
188 vsyslog(LOG_CRIT|LOG_NDELAY|LOG_MAIL, format, ap);
194 * Dump our backtrace into a global location
196 * This function will dump out our backtrace into our
197 * global holding area.
200 static void createGlobalBacktrace( void )
203 size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
204 for (NThread = 0; NThread < size; NThread++)
206 syslog(LOG_CRIT|LOG_NDELAY|LOG_MAIL, "RAW: %p ", stack_frames[NThread]);
208 strings = backtrace_symbols(stack_frames, size);
209 for (NThread = 0; NThread < size; NThread++) {
210 if (strings != NULL) {
211 syslog(LOG_CRIT|LOG_NDELAY|LOG_MAIL, "RAW: %p ", strings[NThread]);
214 } /* createGlobalBacktrace */
215 static void outputRawtrace( void )
218 size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
219 for (NThread = 0; NThread < size; NThread++)
221 syslog(LOG_CRIT|LOG_NDELAY|LOG_MAIL, "RAW: %p ", stack_frames[NThread]);
223 } /* createGlobalBacktrace */
226 * Print out (to all the fds, etc), or global backtrace
228 static void outputGlobalBacktrace ( void )
232 for (i=0; i < gbl_backtraceEntries; i++) {
233 if (gbl_backtraceSymbols != FALSE) {
234 outputPrintf("* Frame %02x: %s\n",
235 i, gbl_backtraceSymbols[i]);
237 outputPrintf("* Frame %02x: %p\n", i,
238 gbl_backtraceBuffer[i]);
241 } // outputGlobalBacktrace
244 * Output our current stack's backtrace
246 static void outputBacktrace( void )
248 createGlobalBacktrace();
249 outputGlobalBacktrace();
250 } /* outputBacktrace */
252 static void outputBacktraceThreads( void )
254 ThreadListNode *probe;
257 // When we're backtracing, don't worry about the mutex . . hopefully
258 // we're in a safe place.
260 for (probe=ThreadList; probe; probe=probe->Next) {
261 gbl_backtraceDoneFlag = 0;
262 pthread_kill(probe->thread, probe->backtraceSignal);
263 for (i=0; i < gbl_params.threadWaitTime; i++) {
264 if (gbl_backtraceDoneFlag)
268 if (gbl_backtraceDoneFlag) {
269 outputPrintf("* Backtrace of \"%s\" (0x%08x)\n",
270 probe->threadName, (unsigned int)probe->thread);
271 outputGlobalBacktrace();
273 outputPrintf("* Error: unable to get backtrace of \"%s\" (0x%08x)\n",
274 probe->threadName, (unsigned int)probe->thread);
278 } // outputBacktraceThreads
282 * Handle signals (crash signals)
284 * This function will catch all crash signals, and will output the
287 * It will physically write (and sync) the current thread's information
288 * before it attempts to send signals to other threads.
290 * @param signum Signal received.
292 static void crash_handler(int signo)
295 outputPrintf("*********************************************************\n");
296 outputPrintf("* eCrash Crash Handler\n");
297 outputPrintf("*********************************************************\n");
299 outputPrintf("* Got a crash! signo=%d\n", signo);
301 outputPrintf("* Offending Thread's Backtrace:\n");
306 if (gbl_params.dumpAllThreads != FALSE) {
307 outputBacktraceThreads();
311 outputPrintf("*********************************************************\n");
312 outputPrintf("* eCrash Crash Handler\n");
313 outputPrintf("*********************************************************\n");
319 * Handle signals (bt signals)
321 * This function shoudl be called to generate a crashdump into our
322 * global area. Once the dump has been completed, this function will
323 * return after tickling a global. Since mutexes are not async
324 * signal safe, the main thread, after signaling us to generate our
325 * own backtrace, will sleep for a few seconds waiting for us to complete.
327 * @param signum Signal received.
329 static void bt_handler(int signo)
331 createGlobalBacktrace();
332 gbl_backtraceDoneFlag=1;
336 * Validate a passed-in symbol table
338 * For now, just print it out (if verbose), and make sure it's
339 * sorted and none of the pointers are zero.
341 static int ValidateSymbolTable( void )
345 unsigned long lastAddress =0;
347 // Get out of here if the table is empty
348 if (!gbl_params.symbolTable) return 0;
350 // Dump it in verbose mode
351 DPRINTF(ECRASH_DEBUG_VERBOSE,
352 "Symbol Table Provided with %d symbols\n",
353 gbl_params.symbolTable->numSymbols);
354 for (i=0; i < gbl_params.symbolTable->numSymbols; i++){
355 // Dump it in verbose mode
356 DPRINTF(ECRASH_DEBUG_VERBOSE,
358 gbl_params.symbolTable->symbols[i].function,
359 gbl_params.symbolTable->symbols[i].address);
361 (unsigned long)gbl_params.symbolTable->symbols[i].address) {
362 DPRINTF(ECRASH_DEBUG_ERROR,
363 "Error: symbol table is not sorted (last=%p, current=%p)\n",
365 gbl_params.symbolTable->symbols[i].address);
373 } // ValidateSymbolTable
375 /*********************************************************************
376 *********************************************************************
377 ** P U B L I C F U N C T I O N S
378 *********************************************************************
379 ********************************************************************/
384 * This function must be called before calling any other eCrash
385 * functions. It sets up the global behavior of the system, and
386 * registers the calling thread for crash dumps.
388 * @param params Our input parameters. The passed in structure will be copied.
390 * @return Zero on success.
392 int eCrash_Init(eCrashParameters *params)
396 #ifdef DO_SIGNALS_RIGHT
398 struct sigaction act;
401 DPRINTF(ECRASH_DEBUG_VERY_VERBOSE,"Init Starting params = %p\n", params);
403 // Allocate our backtrace area
404 gbl_backtraceBuffer = malloc(sizeof(void *) * (params->maxStackDepth+5));
406 #ifdef DO_SIGNALS_RIGHT
407 sigemptyset(&blocked);
408 act.sa_sigaction = crash_handler;
409 act.sa_mask = blocked;
410 act.sa_flags = SA_SIGINFO;
413 if (params != NULL) {
414 // Make ourselves a global copy of params.
415 gbl_params = *params;
416 gbl_params.filename = strdup(params->filename);
418 // Set our defaults, if they weren't specified
419 if (gbl_params.maxStackDepth == 0 )
420 gbl_params.maxStackDepth = ECRASH_DEFAULT_STACK_DEPTH;
422 if (gbl_params.defaultBacktraceSignal == 0 )
423 gbl_params.defaultBacktraceSignal = ECRASH_DEFAULT_BACKTRACE_SIGNAL;
425 if (gbl_params.threadWaitTime == 0 )
426 gbl_params.threadWaitTime = ECRASH_DEFAULT_THREAD_WAIT_TIME;
428 if (gbl_params.debugLevel == 0 )
429 gbl_params.debugLevel = ECRASH_DEBUG_DEFAULT;
431 // Copy our symbol table
432 if (gbl_params.symbolTable) {
433 DPRINTF(ECRASH_DEBUG_VERBOSE,
434 "symbolTable @ %p -- %d symbols\n", gbl_params.symbolTable,
435 gbl_params.symbolTable->numSymbols);
436 // Make a copy of our symbol table
437 gbl_params.symbolTable = malloc(sizeof(eCrashSymbolTable));
438 memcpy(gbl_params.symbolTable, params->symbolTable,
439 sizeof(eCrashSymbolTable));
441 // Now allocate / copy the actual table.
442 gbl_params.symbolTable->symbols = malloc(sizeof(eCrashSymbol) *
443 gbl_params.symbolTable->numSymbols);
444 memcpy(gbl_params.symbolTable->symbols,
445 params->symbolTable->symbols,
446 sizeof(eCrashSymbol) * gbl_params.symbolTable->numSymbols);
448 ValidateSymbolTable();
451 // And, finally, register for our signals
452 for (sigIndex=0; gbl_params.signals[sigIndex] != 0; sigIndex++) {
453 DPRINTF(ECRASH_DEBUG_VERY_VERBOSE,
454 " Catching signal[%d] %d\n", sigIndex,
455 gbl_params.signals[sigIndex]);
457 // I know there's a better way to catch signals with pthreads.
458 // I'll do it later TODO
459 signal(gbl_params.signals[sigIndex], crash_handler);
462 DPRINTF(ECRASH_DEBUG_ERROR, " Error: Null Params!\n");
465 DPRINTF(ECRASH_DEBUG_VERY_VERBOSE, "Init Complete ret=%d\n", ret);
470 * UnInitialize eCrash.
472 * This function may be called to de-activate eCrash, release the
473 * signal handlers, and free any memory allocated by eCrash.
475 * @return Zero on success.
477 int eCrash_Uninit( void )
482 } /* eCrash_Uninit */
485 * Register a thread for backtracing on crash.
487 * This function must be called by any thread wanting it's stack
488 * dumped in the event of a crash. The thread my specify what
489 * signal should be used, or the default, SIGUSR1 will be used.
491 * @param signo Signal to use to generate dump (default: SIGUSR1)
493 * @return Zero on success.
495 int eCrash_RegisterThread(char *name, int signo)
497 sighandler_t old_handler;
499 // Register for our signal
501 signo = gbl_params.defaultBacktraceSignal;
504 old_handler = signal(signo, bt_handler);
505 return addThreadToList(name, pthread_self(), signo, old_handler);
507 } /* eCrash_RegisterThread */
510 * Un-register a thread for stack dumps.
512 * This function may be called to un-register any previously
515 * @return Zero on success.
517 int eCrash_UnregisterThread( void )
519 return removeThreadFromList(pthread_self());
520 } /* eCrash_UnregisterThread */