diff --git a/src/libCom/osi/execinfoStackTrace.c b/src/libCom/osi/execinfoStackTrace.c index 95d32b3f1..0c8145465 100644 --- a/src/libCom/osi/execinfoStackTrace.c +++ b/src/libCom/osi/execinfoStackTrace.c @@ -7,6 +7,8 @@ * Author: Till Straumann , 2011, 2014 */ +#define _GNU_SOURCE + #include "epicsStackTrace.h" #include "epicsThread.h" #include "epicsMutex.h" @@ -16,7 +18,6 @@ #include #include #include -#include #define MAXDEPTH 100 @@ -27,7 +28,6 @@ * Hence, on linux we want to use dladdr() and lookup static * symbols in the ELF symbol table. */ -#define __USE_GNU #ifdef freebsd /* Some freebsd versions seem to export dladdr() only if __BSD_VISIBLE */ @@ -39,6 +39,8 @@ /* Check if we actually have the gnu/darwin extensions */ #ifdef RTLD_DEFAULT +#define USE_DLADDR + #if defined(__linux__) || defined(linux) #define USE_ELF #define USE_MMAP @@ -52,6 +54,9 @@ #include #include +/* How many chars to reserve (on avg) for a line of output */ +#define MAXSYMLEN 500 + #ifdef USE_MMAP #include #endif /* USE_MMAP */ @@ -62,6 +67,12 @@ #undef USE_ELF #endif /* RTLD_DEFAULT */ +/* Forward Declaration */ +#define NO_OFF ((unsigned long)-1L) + +static ssize_t +symDump(char *buf, size_t buf_sz, void *addr, const char *fnam, const char *snam, unsigned long off); + #ifdef USE_ELF /* Macros to handle elf32 vs. elf64 access to unions etc. */ @@ -118,27 +129,52 @@ typedef struct ESyms_ { uint8_t class; } *ESyms; +/* LOCKING NOTE: if the ELF symbol facility is ever expanded to be truly used + * in a multithreaded way then proper multiple-readers, single-writer locking + * should be implemented: + * - elfsLockWrite() must block until all readers have left + * - elfsLockRead() must block until writer has left. + * - elfsLockConvertWriteRead() atomically converts writer holding the + * writer's lock into a reader. + * Right now we just use a single, global lock (for the stack trace) since we + * only need to guard against multiple threads dumping stacks simultaneously and + * we do not lock the symbol table(s) at all. + */ + /* Linked list where we keep all our ESyms */ static ESyms elfs = 0; -static epicsThreadOnceId elfsInitId = EPICS_THREAD_ONCE_INIT; -static epicsMutexId elfsMtx; -static void elfsInit(void *unused) +static void +elfsLockWrite() { - elfsMtx = epicsMutexMustCreate(); + /* Only a single writer can hold this while no readers are active */ } -static void elfsLock(void) +static void +elfsUnlockWrite() { - epicsThreadOnce( &elfsInitId, elfsInit, 0 ); - epicsMutexLock( elfsMtx ); + /* Must wake up readers blocking in elfsLockRead() */ } -static void elfsUnlock(void) +static void +elfsLockConvertWriteRead() { - epicsMutexUnlock( elfsMtx ); + /* Must atomically convert a writer into a reader, i.e., unlock + * the writer's lock and atomically acquire the reader's lock + */ } +static void +elfsLockRead() +{ + /* Multiple readers can hold this while the writer is not active */ +} + +static void +elfsUnlockRead() +{ + /* Must wake up a (single) writer blockingĀ in elfsLockWrite */ +} static void freeMap(MMap m) @@ -433,20 +469,6 @@ bail: return es; } -#if 0 -/* IMPLEMENTATION IS INCORRECT: - * with only a single lock 'elfLookupAddr()' would - * have to hold that the entire time while accessing - * a symbol table. This is inefficient if there are - * multiple readers. A better implementation would - * use a 'multiple-readers/single-writer' type of lock - * to protect the symbol tables. - * - * ATM we simply don't support destroying the - * tables (after dumping a stack trace the process is - * likely to be terminated anyways). - */ - /* Destroy a cached ELF symbol table */ static void elfSymsDestroy(ESyms es) @@ -463,22 +485,36 @@ elfSymsFlush() { ESyms es; - elfsLock(); + elfsLockWrite(); while ( (es = elfs) ) { elfs = es->next; es->next = 0; /* paranoia */ elfSymsDestroy(es); } - elfsUnlock(); + elfsUnlockWrite(); } -#endif -static void -elfLookupAddr(void *addr) +static ESyms +elfSymsFind(const char *fname) +{ +ESyms es; + for ( es=elfs; es && strcmp(fname, es->fname); es = es->next ) + /* nothing else to do */; + return es; +} +#endif /* USE_ELF */ + +#ifdef USE_DLADDR + +static ssize_t +elfLookupAddr(void *addr, char *buf, size_t buf_sz) { Dl_info inf; -ESyms es; +ssize_t rval; + +#ifdef USE_ELF +ESyms es,nes; uintptr_t minoff,off; int i; Sym sym; @@ -486,38 +522,55 @@ Sym nearest; const char *strtab; uint8_t c; size_t idx; +#endif if ( ! dladdr(addr, &inf) || (!inf.dli_fname && !inf.dli_sname) ) { /* unable to lookup */ - errlogPrintf("[%p]\n", addr); - return; + return symDump(buf, buf_sz, addr, 0, 0, NO_OFF); } if ( inf.dli_sname ) { /* Have a symbol name - just use it and be done */ - errlogPrintf("%s(%s+0x%lu): [%p]\n", inf.dli_fname ? inf.dli_fname : "", inf.dli_sname, addr - inf.dli_saddr, addr); - return; + return symDump(buf, buf_sz, addr, inf.dli_fname, inf.dli_sname, (unsigned long)(addr - inf.dli_saddr)); } +#ifndef USE_ELF + + rval = symDump(buf, buf_sz, addr, inf.dli_fname, 0, NO_OFF); + +#else + /* No symbol info; try to access ELF file and ready symbol table from there */ - elfsLock(); + elfsLockRead(); /* See if we have loaded this file already */ - for ( es=elfs; es && strcmp(inf.dli_fname, es->fname); es = es->next ) - /* nothing else to do */; + es = elfSymsFind(inf.dli_fname); if ( !es ) { - if ( ! (es = elfRead(inf.dli_fname, (uintptr_t)inf.dli_fbase)) ) { - elfsUnlock(); - /* this path can only be taken if there is no memory for '*es' */ - return; - } - es->next = elfs; - elfs = es; - } + elfsUnlockRead(); - elfsUnlock(); + if ( ! (nes = elfRead(inf.dli_fname, (uintptr_t)inf.dli_fbase)) ) { + /* this path can only be taken if there is no memory for '*nes' */ + if ( buf && buf_sz > 0 ) + *buf = 0; + return 0; + } + + elfsLockWrite(); + + /* Has someone else intervened and already added this file while we were reading ? */ + es = elfSymsFind(inf.dli_fname); + + if ( es ) { + /* undo our work in the unlikely event... */ + elfSymsDestroy( nes ); + } else { + nes->next = elfs; + es = elfs = nes; + } + elfsLockConvertWriteRead(); + } nearest.raw = 0; minoff = (uintptr_t)-1LL; @@ -575,77 +628,215 @@ size_t idx; } if ( nearest.raw && ( (idx = ARR(c,nearest,0,st_name)) < es->strMap->max ) ) { - errlogPrintf("%s(%s+0x%"PRIxPTR"): [%p]\n", es->fname, strtab + idx, minoff, addr); + rval = symDump(buf, buf_sz, addr, es->fname, strtab + idx, (unsigned long)minoff); } else { - errlogPrintf("%s[%p]\n", es->fname, addr); + rval = symDump(buf, buf_sz, addr, es->fname, 0, NO_OFF); } + + elfsUnlockRead(); + +#endif /* USE_ELF */ + + return rval; +} + +#endif /* USE_DLADDR */ + +static epicsThreadOnceId stackTraceInitId = EPICS_THREAD_ONCE_INIT; +static epicsMutexId stackTraceMtx; + +static void stackTraceInit(void *unused) +{ + stackTraceMtx = epicsMutexMustCreate(); +} + +static void stackTraceLock(void) +{ + epicsThreadOnce( &stackTraceInitId, stackTraceInit, 0 ); + epicsMutexLock( stackTraceMtx ); +} + +static void stackTraceUnlock(void) +{ + epicsMutexUnlock( stackTraceMtx ); +} + +static ssize_t +dump(char **buf, size_t *buf_sz, size_t *good, const char *fmt, ...) +{ +va_list ap; +ssize_t rval, put; + va_start(ap, fmt); + if ( *buf ) { + put = rval = vsnprintf(*buf, *buf_sz, fmt, ap); + if ( put > *buf_sz ) + put = *buf_sz; + *buf += put; + *buf_sz -= put; + } else { + rval = errlogVprintf(fmt, ap); + } + va_end(ap); + if ( rval > 0 ) + *good += rval; + return rval; +} + + +static ssize_t +symDump(char *buf, size_t buf_sz, void *addr, const char *fnam, const char *snam, unsigned long off) +{ +size_t rval = 0; + + dump( &buf, &buf_sz, &rval, "[%*p]", sizeof(addr)*2 + 2, addr); + if ( fnam ) { + dump( &buf, &buf_sz, &rval, ": %s", fnam ); + } + if ( snam ) { + dump( &buf, &buf_sz, &rval, "(%s", snam ); + if ( NO_OFF != off ) { + dump( &buf, &buf_sz, &rval, "+0x%lx", off); + } + dump( &buf, &buf_sz, &rval, ")" ); + } + dump( &buf, &buf_sz, &rval, "\n"); + + return rval; } -#endif epicsShareFunc void epicsStackTrace(void) { void **buf; -#ifndef USE_ELF +#ifndef USE_DLADDR char **bts; +ssize_t pos, siz; +char *ptr; #endif +char *btsl = 0; +size_t btsl_sz = sizeof(*btsl)*MAXSYMLEN; int i,n; - errlogPrintf("Dumping a stack trace:\n"); - - errlogFlush(); - - if ( ! (buf = malloc(sizeof(*buf) * MAXDEPTH)) ) { + if ( ! (buf = malloc(sizeof(*buf) * MAXDEPTH)) + || ! (btsl = malloc(btsl_sz)) + ) { + free(buf); errlogPrintf("epicsStackTrace(): not enough memory for backtrace\n"); return; } n = backtrace(buf, MAXDEPTH); + stackTraceLock(); + + errlogPrintf("Dumping a stack trace of thread '%s':\n", epicsThreadGetNameSelf()); + + errlogFlush(); + /* backtrace_symbols() only works for global symbols on linux. * If we have dladdr() and then we can actually lookup local * symbols, too. */ -#ifdef USE_ELF +#ifdef USE_DLADDR for ( i=0; i 0 ) + btsl[--pos] = 0; + siz -= pos; + if ( siz >= 3 ) { + strcat(btsl, ": "); + pos += 2; + siz -= 2; + } + strncat(btsl + pos, bts[i], siz); + /* wipe out the trailing address */ + if ( (ptr = strrchr(btsl, '[')) ) + *ptr = 0; + errlogPrintf("%s\n", btsl); } free(bts); } else { /* failed to create symbolic information; just print addresses */ for ( i=0; i