3 * @author David Frascone
5 * eCrash Implementation
7 * eCrash will allow you to capture stack traces in the
8 * event of a crash, and write those traces to disk, stdout,
9 * or any other file handle.
11 * modified to integrate closer into citadel by Wilfried Goesgens
24 #include <sys/types.h>
27 #include <libcitadel.h>
29 #include "sysdep_decls.h"
32 #include "citserver.h"
35 #define NIY() printf("%s: Not Implemented Yet!\n", __FUNCTION__)
38 static eCrashParameters gbl_params
;
40 static int gbl_backtraceEntries
;
41 static void **gbl_backtraceBuffer
;
42 static char **gbl_backtraceSymbols
;
43 static int gbl_backtraceDoneFlag
= 0;
45 static void *stack_frames
[50];
46 static size_t size
, NThread
;
47 static char **strings
;
48 static char StaticBuf
[SIZ
];
51 * Private structures for our thread list
53 typedef struct thread_list_node
{
57 sighandler_t oldHandler
;
58 struct thread_list_node
*Next
;
61 static pthread_mutex_t ThreadListMutex
= PTHREAD_MUTEX_INITIALIZER
;
62 static ThreadListNode
*ThreadList
= NULL
;
64 /*********************************************************************
65 *********************************************************************
66 ** P R I V A T E F U N C T I O N S
67 *********************************************************************
68 ********************************************************************/
72 * Insert a node into our threadList
74 * @param name Text string indicating our thread
75 * @param thread Our Thread Id
76 * @param signo Signal to create backtrace with
77 * @param old_handler Our old handler for signo
79 * @returns zero on success
81 static int addThreadToList(char *name
, pthread_t thread
,int signo
,
82 sighandler_t old_handler
)
86 node
= malloc(sizeof(ThreadListNode
));
89 DPRINTF(ECRASH_DEBUG_VERBOSE
,
90 "Adding thread 0x%08x (%s)\n", (unsigned int)thread
, name
);
91 node
->threadName
= strdup(name
);
92 node
->thread
= thread
;
93 node
->backtraceSignal
= signo
;
94 node
->oldHandler
= old_handler
;
96 /* And, add it to the list */
97 pthread_mutex_lock(&ThreadListMutex
);
98 node
->Next
= ThreadList
;
100 pthread_mutex_unlock(&ThreadListMutex
);
107 * Remove a node from our threadList
109 * @param thread Our Thread Id
111 * @returns zero on success
113 static int removeThreadFromList(pthread_t thread
)
115 ThreadListNode
*Probe
, *Prev
=NULL
;
116 ThreadListNode
*Removed
= NULL
;
118 DPRINTF(ECRASH_DEBUG_VERBOSE
,
119 "Removing thread 0x%08x from list . . .\n", (unsigned int)thread
);
120 pthread_mutex_lock(&ThreadListMutex
);
121 for (Probe
=ThreadList
;Probe
!= NULL
; Probe
= Probe
->Next
) {
122 if (Probe
->thread
== thread
) {
123 // We found it! Unlink it and move on!
125 if (Prev
== NULL
) { // head of list
126 ThreadList
= Probe
->Next
;
128 // Prev != null, so we need to link around ourselves.
129 Prev
->Next
= Probe
->Next
;
131 Removed
->Next
= NULL
;
137 pthread_mutex_unlock(&ThreadListMutex
);
139 // Now, if something is in Removed, free it, and return success
141 DPRINTF(ECRASH_DEBUG_VERBOSE
,
142 " Found %s -- removing\n", Removed
->threadName
);
143 // Reset the signal handler
144 signal(Removed
->backtraceSignal
, Removed
->oldHandler
);
146 // And free the allocated memory
147 free (Removed
->threadName
);
152 DPRINTF(ECRASH_DEBUG_VERBOSE
,
154 return -1; // Not Found
156 } // removeThreadFromList
159 * Print out a line of output to all our destinations
161 * One by one, output a line of text to all of our output destinations.
163 * Return failure if we fail to output to any of them.
165 * @param format Normal printf style vararg format
167 * @returns nothing// bytes written, or error on failure.
169 static void outputPrintf(char *format
, ...)
173 va_start(ap
, format
);
177 snprintf (StaticBuf
, SIZ
, format
, ap
);
178 syslog( LOG_CRIT
|LOG_NDELAY
|LOG_MAIL
, StaticBuf
);
181 CtdlLogPrintf(CTDL_EMERG
, format
, ap
);
188 * Dump our backtrace into a global location
190 * This function will dump out our backtrace into our
191 * global holding area.
194 static void createGlobalBacktrace( void )
197 size
= backtrace(stack_frames
, sizeof(stack_frames
) / sizeof(void*));
199 for (NThread
= 0; NThread
< size
; NThread
++)
201 snprintf (StaticBuf
, SIZ
, "RAW: %p ", stack_frames
[NThread
]);
202 syslog( LOG_CRIT
|LOG_NDELAY
|LOG_MAIL
, StaticBuf
);
205 for (NThread
= 0; NThread
< size
; NThread
++)
206 CtdlLogPrintf(1, "RAW: %p\n", stack_frames
[NThread
]);
207 strings
= backtrace_symbols(stack_frames
, size
);
208 for (NThread
= 0; NThread
< size
; NThread
++) {
209 if (strings
!= NULL
) {
211 {// vsyslogs printf compliance sucks.
212 snprintf (StaticBuf
, SIZ
, "RAW: %p ", strings
[NThread
]);
213 syslog( LOG_CRIT
|LOG_NDELAY
|LOG_MAIL
, StaticBuf
);
216 CtdlLogPrintf(1, "%s\n", strings
[NThread
]);
219 } /* createGlobalBacktrace */
220 static void outputRawtrace( void )
223 size
= backtrace(stack_frames
, sizeof(stack_frames
) / sizeof(void*));
225 for (NThread
= 0; NThread
< size
; NThread
++)
227 snprintf (StaticBuf
, SIZ
, "RAW: %p ", stack_frames
[NThread
]);
228 syslog( LOG_CRIT
|LOG_NDELAY
|LOG_MAIL
, StaticBuf
);
231 for (NThread
= 0; NThread
< size
; NThread
++)
232 CtdlLogPrintf(1, "RAW: %p\n", stack_frames
[NThread
]);
233 } /* createGlobalBacktrace */
236 * Print out (to all the fds, etc), or global backtrace
238 static void outputGlobalBacktrace ( void )
242 for (i
=0; i
< gbl_backtraceEntries
; i
++) {
243 if (gbl_backtraceSymbols
!= FALSE
) {
244 outputPrintf("* Frame %02x: %s\n",
245 i
, gbl_backtraceSymbols
[i
]);
247 outputPrintf("* Frame %02x: %p\n", i
,
248 gbl_backtraceBuffer
[i
]);
251 } // outputGlobalBacktrace
254 * Output our current stack's backtrace
256 static void outputBacktrace( void )
258 createGlobalBacktrace();
259 outputGlobalBacktrace();
260 } /* outputBacktrace */
262 static void outputBacktraceThreads( void )
264 ThreadListNode
*probe
;
267 // When we're backtracing, don't worry about the mutex . . hopefully
268 // we're in a safe place.
270 for (probe
=ThreadList
; probe
; probe
=probe
->Next
) {
271 gbl_backtraceDoneFlag
= 0;
272 pthread_kill(probe
->thread
, probe
->backtraceSignal
);
273 for (i
=0; i
< gbl_params
.threadWaitTime
; i
++) {
274 if (gbl_backtraceDoneFlag
)
278 if (gbl_backtraceDoneFlag
) {
279 outputPrintf("* Backtrace of \"%s\" (0x%08x)\n",
280 probe
->threadName
, (unsigned int)probe
->thread
);
281 outputGlobalBacktrace();
283 outputPrintf("* Error: unable to get backtrace of \"%s\" (0x%08x)\n",
284 probe
->threadName
, (unsigned int)probe
->thread
);
288 } // outputBacktraceThreads
292 * Handle signals (crash signals)
294 * This function will catch all crash signals, and will output the
297 * It will physically write (and sync) the current thread's information
298 * before it attempts to send signals to other threads.
300 * @param signum Signal received.
302 static void crash_handler(int signo
)
305 outputPrintf("*********************************************************\n");
306 outputPrintf("* eCrash Crash Handler\n");
307 outputPrintf("*********************************************************\n");
309 outputPrintf("* Got a crash! signo=%d\n", signo
);
311 outputPrintf("* Offending Thread's Backtrace:\n");
316 if (gbl_params
.dumpAllThreads
!= FALSE
) {
317 outputBacktraceThreads();
321 outputPrintf("*********************************************************\n");
322 outputPrintf("* eCrash Crash Handler\n");
323 outputPrintf("*********************************************************\n");
329 * Handle signals (bt signals)
331 * This function shoudl be called to generate a crashdump into our
332 * global area. Once the dump has been completed, this function will
333 * return after tickling a global. Since mutexes are not async
334 * signal safe, the main thread, after signaling us to generate our
335 * own backtrace, will sleep for a few seconds waiting for us to complete.
337 * @param signum Signal received.
339 static void bt_handler(int signo
)
341 createGlobalBacktrace();
342 gbl_backtraceDoneFlag
=1;
346 * Validate a passed-in symbol table
348 * For now, just print it out (if verbose), and make sure it's
349 * sorted and none of the pointers are zero.
351 static int ValidateSymbolTable( void )
355 unsigned long lastAddress
=0;
357 // Get out of here if the table is empty
358 if (!gbl_params
.symbolTable
) return 0;
360 // Dump it in verbose mode
361 DPRINTF(ECRASH_DEBUG_VERBOSE
,
362 "Symbol Table Provided with %d symbols\n",
363 gbl_params
.symbolTable
->numSymbols
);
364 for (i
=0; i
< gbl_params
.symbolTable
->numSymbols
; i
++){
365 // Dump it in verbose mode
366 DPRINTF(ECRASH_DEBUG_VERBOSE
,
368 gbl_params
.symbolTable
->symbols
[i
].function
,
369 gbl_params
.symbolTable
->symbols
[i
].address
);
371 (unsigned long)gbl_params
.symbolTable
->symbols
[i
].address
) {
372 DPRINTF(ECRASH_DEBUG_ERROR
,
373 "Error: symbol table is not sorted (last=%p, current=%p)\n",
375 gbl_params
.symbolTable
->symbols
[i
].address
);
383 } // ValidateSymbolTable
385 /*********************************************************************
386 *********************************************************************
387 ** P U B L I C F U N C T I O N S
388 *********************************************************************
389 ********************************************************************/
394 * This function must be called before calling any other eCrash
395 * functions. It sets up the global behavior of the system, and
396 * registers the calling thread for crash dumps.
398 * @param params Our input parameters. The passed in structure will be copied.
400 * @return Zero on success.
402 int eCrash_Init(eCrashParameters
*params
)
406 #ifdef DO_SIGNALS_RIGHT
408 struct sigaction act
;
411 DPRINTF(ECRASH_DEBUG_VERY_VERBOSE
,"Init Starting params = %p\n", params
);
413 // Allocate our backtrace area
414 gbl_backtraceBuffer
= malloc(sizeof(void *) * (params
->maxStackDepth
+5));
416 #ifdef DO_SIGNALS_RIGHT
417 sigemptyset(&blocked
);
418 act
.sa_sigaction
= crash_handler
;
419 act
.sa_mask
= blocked
;
420 act
.sa_flags
= SA_SIGINFO
;
423 if (params
!= NULL
) {
424 // Make ourselves a global copy of params.
425 gbl_params
= *params
;
426 gbl_params
.filename
= strdup(params
->filename
);
428 // Set our defaults, if they weren't specified
429 if (gbl_params
.maxStackDepth
== 0 )
430 gbl_params
.maxStackDepth
= ECRASH_DEFAULT_STACK_DEPTH
;
432 if (gbl_params
.defaultBacktraceSignal
== 0 )
433 gbl_params
.defaultBacktraceSignal
= ECRASH_DEFAULT_BACKTRACE_SIGNAL
;
435 if (gbl_params
.threadWaitTime
== 0 )
436 gbl_params
.threadWaitTime
= ECRASH_DEFAULT_THREAD_WAIT_TIME
;
438 if (gbl_params
.debugLevel
== 0 )
439 gbl_params
.debugLevel
= ECRASH_DEBUG_DEFAULT
;
441 // Copy our symbol table
442 if (gbl_params
.symbolTable
) {
443 DPRINTF(ECRASH_DEBUG_VERBOSE
,
444 "symbolTable @ %p -- %d symbols\n", gbl_params
.symbolTable
,
445 gbl_params
.symbolTable
->numSymbols
);
446 // Make a copy of our symbol table
447 gbl_params
.symbolTable
= malloc(sizeof(eCrashSymbolTable
));
448 memcpy(gbl_params
.symbolTable
, params
->symbolTable
,
449 sizeof(eCrashSymbolTable
));
451 // Now allocate / copy the actual table.
452 gbl_params
.symbolTable
->symbols
= malloc(sizeof(eCrashSymbol
) *
453 gbl_params
.symbolTable
->numSymbols
);
454 memcpy(gbl_params
.symbolTable
->symbols
,
455 params
->symbolTable
->symbols
,
456 sizeof(eCrashSymbol
) * gbl_params
.symbolTable
->numSymbols
);
458 ValidateSymbolTable();
461 // And, finally, register for our signals
462 for (sigIndex
=0; gbl_params
.signals
[sigIndex
] != 0; sigIndex
++) {
463 DPRINTF(ECRASH_DEBUG_VERY_VERBOSE
,
464 " Catching signal[%d] %d\n", sigIndex
,
465 gbl_params
.signals
[sigIndex
]);
467 // I know there's a better way to catch signals with pthreads.
468 // I'll do it later TODO
469 signal(gbl_params
.signals
[sigIndex
], crash_handler
);
472 DPRINTF(ECRASH_DEBUG_ERROR
, " Error: Null Params!\n");
475 DPRINTF(ECRASH_DEBUG_VERY_VERBOSE
, "Init Complete ret=%d\n", ret
);
480 * UnInitialize eCrash.
482 * This function may be called to de-activate eCrash, release the
483 * signal handlers, and free any memory allocated by eCrash.
485 * @return Zero on success.
487 int eCrash_Uninit( void )
492 } /* eCrash_Uninit */
495 * Register a thread for backtracing on crash.
497 * This function must be called by any thread wanting it's stack
498 * dumped in the event of a crash. The thread my specify what
499 * signal should be used, or the default, SIGUSR1 will be used.
501 * @param signo Signal to use to generate dump (default: SIGUSR1)
503 * @return Zero on success.
505 int eCrash_RegisterThread(char *name
, int signo
)
507 sighandler_t old_handler
;
509 // Register for our signal
511 signo
= gbl_params
.defaultBacktraceSignal
;
514 old_handler
= signal(signo
, bt_handler
);
515 return addThreadToList(name
, pthread_self(), signo
, old_handler
);
517 } /* eCrash_RegisterThread */
520 * Un-register a thread for stack dumps.
522 * This function may be called to un-register any previously
525 * @return Zero on success.
527 int eCrash_UnregisterThread( void )
529 return removeThreadFromList(pthread_self());
530 } /* eCrash_UnregisterThread */