- canonicalized printout formatting

- added EPICS_STACKTRACE_DYN_SYMBOL
 - added USE_DLADDR branch (which is a 'super' of USE_ELF, i.e., the latter
   depends on the former)
This commit is contained in:
Till Straumann
2014-09-04 10:59:39 -07:00
parent 68429f03f2
commit fd9fed2262
+254 -63
View File
@@ -7,6 +7,8 @@
* Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
*/
#define _GNU_SOURCE
#include "epicsStackTrace.h"
#include "epicsThread.h"
#include "epicsMutex.h"
@@ -16,7 +18,6 @@
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#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 <errno.h>
#include <inttypes.h>
/* How many chars to reserve (on avg) for a line of output */
#define MAXSYMLEN 500
#ifdef USE_MMAP
#include <sys/mman.h>
#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<n; i++ ) {
elfLookupAddr(buf[i]);
/* Somehow errlog doesn't like small, broken-up pieces of lines which is
* why we assemble into the 'btsl' buffer and use a single errlogPrintf...
*/
elfLookupAddr(buf[i], btsl, btsl_sz);
errlogPrintf("%s", btsl);
}
#if 0
#ifdef USE_ELF
elfSymsFlush();
#endif
#else
if ( (bts = backtrace_symbols(buf, n)) ) {
for ( i=0; i<n; i++ ) {
errlogPrintf("%s\n", bts[i]);
/* We'd like to use a similar layout (prepending the address) */
siz = btsl_sz;
pos = symDump(btsl, siz, buf[i], 0, 0, 0);
/* Kill '\n' */
if ( pos > 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<n; i++ ) {
errlogPrintf("[%p]\n", buf[i]);
symDump(btsl, btsl_sz, buf[i], 0, 0, 0);
errlogPrintf("%s", btsl);
}
}
#endif
free(btsl);
btsl = 0;
errlogPrintf("\n");
errlogFlush();
stackTraceUnlock();
free(buf);
}
epicsShareFunc int epicsStackTraceGetFeatures(void)
{
#if (STACKTRACE_DEBUG & 2)
errlogPrintf("Configuration -- ELF: ");
#ifdef USE_ELF
errlogPrintf("yes");
#else
errlogPrintf("no");
#endif
errlogPrintf(", MMAP: ");
#ifdef USE_MMAP
errlogPrintf("yes");
#else
errlogPrintf("no");
#endif
errlogPrintf(", dladdr: ");
#ifdef USE_DLADDR
errlogPrintf("yes");
#else
errlogPrintf("no");
#endif
errlogPrintf("\n");
#endif
/* We are a bit conservative here. The actual
* situation depends on how we are linked (something
* we don't have under control at compilation time)
* Linux' dladdr and backtrace_symbols find global symbols
* (not from dynamic libraries) when statically linked but
* not when dynamically linked.
* OTOH: for a stripped executable it is unlikely that
* even the ELF reader is able to help much...
*/
#ifdef USE_ELF
return EPICS_STACKTRACE_LCL_SYMBOLS
| EPICS_STACKTRACE_GBL_SYMBOLS
| EPICS_STACKTRACE_DYN_SYMBOLS
| EPICS_STACKTRACE_ADDRESSES;
#elif defined(__linux__) || defined(linux)
return EPICS_STACKTRACE_GBL_SYMBOLS
EPICS_STACKTRACE_ADDRESSES;
return EPICS_STACKTRACE_DYN_SYMBOLS
| EPICS_STACKTRACE_ADDRESSES;
#else
return EPICS_STACKTRACE_LCL_SYMBOLS
| EPICS_STACKTRACE_GBL_SYMBOLS
| EPICS_STACKTRACE_DYN_SYMBOLS
| EPICS_STACKTRACE_ADDRESSES;
#endif
}