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 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.
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;
63 * Private structures for our thread list
65 typedef struct thread_list_node{
69 sighandler_t oldHandler;
70 struct thread_list_node *Next;
73 static pthread_mutex_t ThreadListMutex = PTHREAD_MUTEX_INITIALIZER;
74 static ThreadListNode *ThreadList = NULL;
76 /*********************************************************************
77 *********************************************************************
78 ** P R I V A T E F U N C T I O N S
79 *********************************************************************
80 ********************************************************************/
84 * Insert a node into our threadList
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
91 * @returns zero on success
93 static int addThreadToList(char *name, pthread_t thread,int signo,
94 sighandler_t old_handler)
98 node = malloc(sizeof(ThreadListNode));
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;
108 /* And, add it to the list */
109 pthread_mutex_lock(&ThreadListMutex);
110 node->Next = ThreadList;
112 pthread_mutex_unlock(&ThreadListMutex);
119 * Remove a node from our threadList
121 * @param thread Our Thread Id
123 * @returns zero on success
125 static int removeThreadFromList(pthread_t thread)
127 ThreadListNode *Probe, *Prev=NULL;
128 ThreadListNode *Removed = NULL;
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!
137 if (Prev == NULL) { // head of list
138 ThreadList = Probe->Next;
140 // Prev != null, so we need to link around ourselves.
141 Prev->Next = Probe->Next;
143 Removed->Next = NULL;
149 pthread_mutex_unlock(&ThreadListMutex);
151 // Now, if something is in Removed, free it, and return success
153 DPRINTF(ECRASH_DEBUG_VERBOSE,
154 " Found %s -- removing\n", Removed->threadName);
155 // Reset the signal handler
156 signal(Removed->backtraceSignal, Removed->oldHandler);
158 // And free the allocated memory
159 free (Removed->threadName);
164 DPRINTF(ECRASH_DEBUG_VERBOSE,
166 return -1; // Not Found
168 } // removeThreadFromList
171 * Print out a line of output to all our destinations
173 * One by one, output a line of text to all of our output destinations.
175 * Return failure if we fail to output to any of them.
177 * @param format Normal printf style vararg format
179 * @returns nothing// bytes written, or error on failure.
181 static void outputPrintf(char *format, ...)
185 va_start(ap, format);
187 vsyslog(LOG_CRIT|LOG_NDELAY|LOG_MAIL, format, ap);
193 * Dump our backtrace into a global location
195 * This function will dump out our backtrace into our
196 * global holding area.
199 static void createGlobalBacktrace( void )
202 size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
203 for (NThread = 0; NThread < size; NThread++)
205 syslog(LOG_CRIT|LOG_NDELAY|LOG_MAIL, "RAW: %p ", stack_frames[NThread]);
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]);
213 } /* createGlobalBacktrace */
214 static void outputRawtrace( void )
217 size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
218 for (NThread = 0; NThread < size; NThread++)
220 syslog(LOG_CRIT|LOG_NDELAY|LOG_MAIL, "RAW: %p ", stack_frames[NThread]);
222 } /* createGlobalBacktrace */
225 * Print out (to all the fds, etc), or global backtrace
227 static void outputGlobalBacktrace ( void )
231 for (i=0; i < gbl_backtraceEntries; i++) {
232 if (gbl_backtraceSymbols != FALSE) {
233 outputPrintf("* Frame %02x: %s\n",
234 i, gbl_backtraceSymbols[i]);
236 outputPrintf("* Frame %02x: %p\n", i,
237 gbl_backtraceBuffer[i]);
240 } // outputGlobalBacktrace
243 * Output our current stack's backtrace
245 static void outputBacktrace( void )
247 createGlobalBacktrace();
248 outputGlobalBacktrace();
249 } /* outputBacktrace */
251 static void outputBacktraceThreads( void )
253 ThreadListNode *probe;
256 // When we're backtracing, don't worry about the mutex . . hopefully
257 // we're in a safe place.
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)
267 if (gbl_backtraceDoneFlag) {
268 outputPrintf("* Backtrace of \"%s\" (0x%08x)\n",
269 probe->threadName, (unsigned int)probe->thread);
270 outputGlobalBacktrace();
272 outputPrintf("* Error: unable to get backtrace of \"%s\" (0x%08x)\n",
273 probe->threadName, (unsigned int)probe->thread);
277 } // outputBacktraceThreads
281 * Handle signals (crash signals)
283 * This function will catch all crash signals, and will output the
286 * It will physically write (and sync) the current thread's information
287 * before it attempts to send signals to other threads.
289 * @param signum Signal received.
291 static void crash_handler(int signo)
294 outputPrintf("*********************************************************\n");
295 outputPrintf("* eCrash Crash Handler\n");
296 outputPrintf("*********************************************************\n");
298 outputPrintf("* Got a crash! signo=%d\n", signo);
300 outputPrintf("* Offending Thread's Backtrace:\n");
305 if (gbl_params.dumpAllThreads != FALSE) {
306 outputBacktraceThreads();
310 outputPrintf("*********************************************************\n");
311 outputPrintf("* eCrash Crash Handler\n");
312 outputPrintf("*********************************************************\n");
318 * Handle signals (bt signals)
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.
326 * @param signum Signal received.
328 static void bt_handler(int signo)
330 createGlobalBacktrace();
331 gbl_backtraceDoneFlag=1;
335 * Validate a passed-in symbol table
337 * For now, just print it out (if verbose), and make sure it's
338 * sorted and none of the pointers are zero.
340 static int ValidateSymbolTable( void )
344 unsigned long lastAddress =0;
346 // Get out of here if the table is empty
347 if (!gbl_params.symbolTable) return 0;
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,
357 gbl_params.symbolTable->symbols[i].function,
358 gbl_params.symbolTable->symbols[i].address);
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",
364 gbl_params.symbolTable->symbols[i].address);
372 } // ValidateSymbolTable
374 /*********************************************************************
375 *********************************************************************
376 ** P U B L I C F U N C T I O N S
377 *********************************************************************
378 ********************************************************************/
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.
387 * @param params Our input parameters. The passed in structure will be copied.
389 * @return Zero on success.
391 int eCrash_Init(eCrashParameters *params)
395 #ifdef DO_SIGNALS_RIGHT
397 struct sigaction act;
400 DPRINTF(ECRASH_DEBUG_VERY_VERBOSE,"Init Starting params = %p\n", params);
402 // Allocate our backtrace area
403 gbl_backtraceBuffer = malloc(sizeof(void *) * (params->maxStackDepth+5));
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;
412 if (params != NULL) {
413 // Make ourselves a global copy of params.
414 gbl_params = *params;
415 gbl_params.filename = strdup(params->filename);
417 // Set our defaults, if they weren't specified
418 if (gbl_params.maxStackDepth == 0 )
419 gbl_params.maxStackDepth = ECRASH_DEFAULT_STACK_DEPTH;
421 if (gbl_params.defaultBacktraceSignal == 0 )
422 gbl_params.defaultBacktraceSignal = ECRASH_DEFAULT_BACKTRACE_SIGNAL;
424 if (gbl_params.threadWaitTime == 0 )
425 gbl_params.threadWaitTime = ECRASH_DEFAULT_THREAD_WAIT_TIME;
427 if (gbl_params.debugLevel == 0 )
428 gbl_params.debugLevel = ECRASH_DEBUG_DEFAULT;
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));
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);
447 ValidateSymbolTable();
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]);
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);
461 DPRINTF(ECRASH_DEBUG_ERROR, " Error: Null Params!\n");
464 DPRINTF(ECRASH_DEBUG_VERY_VERBOSE, "Init Complete ret=%d\n", ret);
469 * UnInitialize eCrash.
471 * This function may be called to de-activate eCrash, release the
472 * signal handlers, and free any memory allocated by eCrash.
474 * @return Zero on success.
476 int eCrash_Uninit( void )
481 } /* eCrash_Uninit */
484 * Register a thread for backtracing on crash.
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.
490 * @param signo Signal to use to generate dump (default: SIGUSR1)
492 * @return Zero on success.
494 int eCrash_RegisterThread(char *name, int signo)
496 sighandler_t old_handler;
498 // Register for our signal
500 signo = gbl_params.defaultBacktraceSignal;
503 old_handler = signal(signo, bt_handler);
504 return addThreadToList(name, pthread_self(), signo, old_handler);
506 } /* eCrash_RegisterThread */
509 * Un-register a thread for stack dumps.
511 * This function may be called to un-register any previously
514 * @return Zero on success.
516 int eCrash_UnregisterThread( void )
518 return removeThreadFromList(pthread_self());
519 } /* eCrash_UnregisterThread */