This is Unofficial EPICS BASE Doxygen Site
osdElfFindAddr.c
Go to the documentation of this file.
1 /*
2  * Copyright: Stanford University / SLAC National Laboratory.
3  *
4  * EPICS BASE is distributed subject to a Software License Agreement found
5  * in file LICENSE that is included with this distribution.
6  *
7  * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
8  */
9 
10 #include <unistd.h>
11 #include <dlfcn.h>
12 #include <stdlib.h>
13 #include <fcntl.h>
14 #include <string.h>
15 #include <elf.h>
16 #include <errno.h>
17 #include <inttypes.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
20 #include <time.h>
21 
22 #ifdef _POSIX_MAPPED_FILES
23 #include <sys/mman.h>
24 #endif
25 
26 #include "epicsMutex.h"
27 #include "epicsThread.h"
28 #include "epicsTime.h"
29 #include "errlog.h"
30 #include "epicsStackTrace.h"
31 #include "epicsStackTracePvt.h"
32 
33 /* This routine is provided by osiClockTime.c */
34 LIBCOM_API extern void ClockTime_GetProgramStart(epicsTimeStamp *pDest);
35 
36 #define FIND_ADDR_DEBUG 0
37 
38 /*
39  * On some systems (linux, solaris) dladdr doesn't find local symbols
40  * or symbols in the main executable.
41  * Hence, we want to use dladdr() to find the file name
42  * where a symbol is defined and if not more information is available
43  * then proceed to lookup symbols in the ELF symbol tables.
44  */
45 
46 /* Macros to handle elf32 vs. elf64 access to unions etc. */
47 
48 #define FLD(c,s,f) (ELFCLASS32==c ? s.e32.f : s.e64.f )
49 #define ARR(c,s,i,f) (ELFCLASS32==c ? s.e32[i].f : s.e64[i].f)
50 
51 /* Elf header */
52 typedef union Ehdr_ {
53  Elf32_Ehdr e32;
54  Elf64_Ehdr e64;
55 } Ehdr;
56 
57 /* Section header */
58 typedef union Shdr_ {
59  Elf32_Shdr e32;
60  Elf64_Shdr e64;
61 } Shdr;
62 
63 /* Elf symbol */
64 typedef union Sym_ {
65  void *raw;
66  Elf32_Sym *e32;
67  Elf64_Sym *e64;
68 } Sym;
69 
70 /* Memory mapped portion of a file; we must
71  * keep additional information because the
72  * map's starting address + length must be
73  * page-aligned (man mmap).
74  */
75 typedef struct MMap_ {
76  void *addr;
77  off_t off; /* offset into the map where 'real' data start */
78  size_t len;
79  size_t max; /* max offset: legal data from addr+off .. addr+off+max-1 */
80  void (*freeMap)(struct MMap_*); /* 'method' to destroy the mapping */
81 } *MMap;
82 
83 /* Structure describing symbol information
84  * contained in a file.
85  * We keep these around (so that the file
86  * doesn't have to be opened + parsed every
87  * time we do a lookup).
88  */
89 typedef struct ESyms_ {
90  struct ESyms_ *next; /* linked list; one struct per executable */
91  const char *fname; /* file name */
92  int fd; /* file descriptor */
93  uintptr_t addr; /* address where file is loaded */
96  size_t nsyms;
97  uint8_t eclss;
98 } *ESyms;
99 
100 /* LOCKING NOTE: if the ELF symbol facility is ever expanded to be truly used
101  * in a multithreaded way then proper multiple-readers, single-writer locking
102  * should be implemented.
103  */
104 
105 /* Linked list where we keep all our ESyms */
106 static ESyms elfs = 0;
107 
108 static epicsMutexId listMtx;
109 static epicsThreadOnceId listMtxInitId = EPICS_THREAD_ONCE_INIT;
110 
111 static void listMtxInit(void *unused)
112 {
113  listMtx = epicsMutexMustCreate();
114 }
115 
116 static void
117 elfsLockWrite()
118 {
119  epicsThreadOnce(&listMtxInitId, listMtxInit, 0);
120  epicsMutexMustLock(listMtx);
121 }
122 
123 static void
124 elfsUnlockWrite()
125 {
126  epicsMutexUnlock(listMtx);
127 }
128 
129 static void
130 freeMap(MMap m)
131 {
132  if ( m ) {
133  m->freeMap(m);
134  free(m);
135  }
136 }
137 
138 /* Helper to read exactly 'sz' bytes into 'buf'
139  * RETURNS: # chars read or negative value on error.
140  */
141 static ssize_t
142 do_read(int fd, void *buf, ssize_t sz)
143 {
144  ssize_t got;
145  char *ptr=(char*)buf;
146 
147  while ( sz > 0 ) {
148  if ( (got=read(fd,ptr,sz)) <= 0 ) {
149  return got;
150  }
151  ptr+=got;
152  sz -=got;
153  }
154  return ptr-(char*)buf;
155 }
156 
157 /* Elf file access -- can either be with mmap or by sequential read */
158 
159 #ifdef _POSIX_MAPPED_FILES
160 /* Destructor for data that is mmap()ed */
161 static void
162 freeMapMmap(MMap m)
163 {
164  if ( MAP_FAILED != m->addr )
165  munmap( m->addr, m->len );
166 }
167 
168 /* Obtain section data with mmap() */
169 static MMap
170 getscn_mmap(int fd, uint8_t c, Shdr *shdr_p)
171 {
172  off_t n;
173  MMap rval = 0;
174  size_t pgsz = sysconf(_SC_PAGESIZE);
175 
176  if ( 0 == (n = (off_t)FLD(c,(*shdr_p),sh_size)) ) {
177  errlogPrintf("elfRead - getscn() -- no section data\n");
178  goto bail;
179  }
180 
181  if ( ! (rval = (MMap) malloc(sizeof(*rval))) ) {
182  errlogPrintf("elfRead - getscn() -- no memory for section map\n");
183  goto bail;
184  }
185 
186  rval->freeMap = freeMapMmap;
187 
188  rval->off = (off_t) (FLD(c,(*shdr_p),sh_offset) & (pgsz-1));
189  rval->len = (n + rval->off + (pgsz - 1)) & ~(pgsz - 1);
190  rval->max = rval->len - rval->off;
191 
192  if ( MAP_FAILED == (rval->addr = mmap(0, rval->len, PROT_READ, MAP_SHARED, fd, (off_t) (FLD(c,(*shdr_p),sh_offset) & ~(pgsz-1)))) ) {
193  errlogPrintf("elfRead - getscn() -- mapping section contents: %s\n", strerror(errno));
194  goto bail;
195  }
196 
197  return rval;
198 
199 bail:
200  freeMap(rval);
201  return 0;
202 }
203 #else
204 static MMap getscn_mmap(int fd, uint8_t c, Shrd *shdr_p)
205 {
206  return 0;
207 }
208 #endif
209 
210 /* Destructor for data that is read into a malloc()ed buffer */
211 static void
212 freeMapMalloc(MMap m)
213 {
214  free(m->addr);
215 }
216 
217 /* Read section data into a malloc()ed buffer */
218 static MMap
219 getscn_read(int fd, uint8_t c, Shdr *shdr_p)
220 {
221  ssize_t n;
222  MMap rval = 0;
223 
224  if ( 0 == (n = (ssize_t) FLD(c,(*shdr_p),sh_size)) ) {
225  errlogPrintf("elfRead - getscn() -- no section data\n");
226  goto bail;
227  }
228 
229  if ( ! (rval = (MMap) malloc(sizeof(*rval))) ) {
230  errlogPrintf("elfRead - getscn() -- no memory for section map\n");
231  goto bail;
232  }
233 
234  rval->freeMap = freeMapMalloc;
235 
236  if ( ! (rval->addr = malloc(n)) ) {
237  errlogPrintf("elfRead - getscn() -- no memory for section data\n");
238  goto bail;
239  }
240 
241  rval->off = 0;
242  rval->len = n;
243  rval->max = rval->len - rval->off;
244 
245  /* seek to symbol table contents */
246  if ( (off_t)-1 == lseek(fd, (off_t) FLD(c,(*shdr_p),sh_offset), SEEK_SET) ) {
247  errlogPrintf("elfRead - getscn() -- seeking to sh_offset: %s\n", strerror(errno));
248  goto bail;
249  }
250 
251  if ( n != do_read(fd, rval->addr, n) ) {
252  errlogPrintf("elfRead - getscn() -- reading section contents: %s\n", strerror(errno));
253  goto bail;
254  }
255 
256  return rval;
257 
258 bail:
259  freeMap(rval);
260  return 0;
261 }
262 
263 static MMap
264 getscn(int fd, uint8_t c, Shdr *shdr_p)
265 {
266  MMap rval = getscn_mmap(fd, c, shdr_p);
267 
268  if ( ! rval )
269  rval = getscn_read(fd, c, shdr_p);
270 
271  return rval;
272 }
273 
274 /* Release resources but keep filename so that
275  * a file w/o symbol table is not read over and over again.
276  */
277 static void
278 elfSymsRelease(ESyms es)
279 {
280  if ( es ) {
281  freeMap(es->symMap);
282  es->symMap = 0;
283  freeMap(es->strMap);
284  es->strMap = 0;
285  if ( es->fd >= 0 )
286  close(es->fd);
287  es->fd = -1;
288  es->nsyms = 0;
289  }
290 }
291 
292 static ESyms
293 elfRead(const char *fname, uintptr_t fbase)
294 {
295  int i;
296  Ehdr ehdr;
297  Shdr shdr;
298  uint8_t c;
299  ESyms es;
300  ssize_t idx,n;
301  const char *cp;
302  struct stat stat_b;
303 
304  if ( !(es = (ESyms) malloc(sizeof(*es))) ) {
305  /* no memory -- give up */
306  return 0;
307  }
308 
309  memset(es, 0, sizeof(*es));
310  es->fd = -1;
311  es->fname = fname;
312 
313  if ( (es->fd = open(fname, O_RDONLY)) < 0 ) {
314  errlogPrintf("elfRead() -- unable to open file: %s\n", strerror(errno));
315  goto bail;
316  }
317 
318  if ( EI_NIDENT != do_read(es->fd, &ehdr, EI_NIDENT) ) {
319  errlogPrintf("elfRead() -- unable to read ELF e_ident: %s\n", strerror(errno));
320  goto bail;
321  }
322 
323  if ( ELFMAG0 != ehdr.e32.e_ident[EI_MAG0]
324  || ELFMAG1 != ehdr.e32.e_ident[EI_MAG1]
325  || ELFMAG2 != ehdr.e32.e_ident[EI_MAG2]
326  || ELFMAG3 != ehdr.e32.e_ident[EI_MAG3] ) {
327  errlogPrintf("bad ELF magic number\n");
328  goto bail;
329  }
330 
331  if ( EV_CURRENT != ehdr.e32.e_ident[EI_VERSION] ) {
332  errlogPrintf("bad ELF version\n");
333  goto bail;
334  }
335 
336  switch ( (es->eclss = c = ehdr.e32.e_ident[EI_CLASS]) ) {
337  default:
338  errlogPrintf("bad ELF class\n");
339  goto bail;
340 
341  case ELFCLASS32:
342  n = sizeof(Elf32_Ehdr);
343  break;
344  case ELFCLASS64:
345  n = sizeof(Elf64_Ehdr);
346  break;
347  }
348  n -= EI_NIDENT;
349 
350  if ( 0 == fstat(es->fd, &stat_b) ) {
351  epicsTimeStamp progStartStamp;
352  time_t progStartTime;
353 
354  ClockTime_GetProgramStart(&progStartStamp);
355  epicsTimeToTime_t(&progStartTime, &progStartStamp);
356  if ( stat_b.st_mtime >= progStartTime ) {
357  errlogPrintf("elfRead() -- WARNING: '%s' was modified after program start -- symbol information may be inaccurate or invalid\n", fname);
358  }
359  }
360 
361  /* read rest */
362  if ( n != do_read(es->fd, ehdr.e32.e_ident + EI_NIDENT, n) ) {
363  errlogPrintf("elfRead() -- unable to read ELF ehdr: %s\n", strerror(errno));
364  goto bail;
365  }
366 
367  /* seek to section header table */
368  if ( (off_t)-1 == lseek(es->fd, (off_t) FLD(c,ehdr,e_shoff), SEEK_SET) ) {
369  errlogPrintf("elfRead() -- unable to seek to shoff: %s\n", strerror(errno));
370  goto bail;
371  }
372 
373  n = ELFCLASS32 == c ? sizeof(shdr.e32) : sizeof(shdr.e64);
374 
375  for ( i = 0; i<FLD(c,ehdr,e_shnum); i++ ) {
376  if ( n != do_read(es->fd, &shdr, n) ) {
377  errlogPrintf("elfRead() -- unable to read section header: %s\n", strerror(errno));
378  goto bail;
379  }
380  if ( SHT_SYMTAB == FLD(c,shdr,sh_type) )
381  break;
382  }
383 
384  if ( i>=FLD(c,ehdr,e_shnum) ) {
385  /* no SYMTAB -- try dynamic symbols */
386 
387  if ( (off_t)-1 == lseek(es->fd, (off_t) FLD(c,ehdr,e_shoff), SEEK_SET) ) {
388  errlogPrintf("elfRead() -- unable to seek to shoff: %s\n", strerror(errno));
389  goto bail;
390  }
391 
392  for ( i = 0; i<FLD(c,ehdr,e_shnum); i++ ) {
393  if ( n != do_read(es->fd, &shdr, n) ) {
394  errlogPrintf("elfRead() -- unable to read section header: %s\n", strerror(errno));
395  goto bail;
396  }
397  if ( SHT_DYNSYM == FLD(c,shdr,sh_type) )
398  break;
399  }
400  }
401 
402  if ( i>=FLD(c,ehdr,e_shnum) ) {
403  errlogPrintf("elfRead() -- no symbol table found\n");
404  goto bail;
405  }
406 
407  if ( 0 == (n = (ssize_t) FLD(c,shdr,sh_size)) ) {
408  errlogPrintf("elfRead() -- no symbol table data\n");
409  goto bail;
410  }
411 
412  if ( !(es->symMap = getscn(es->fd, c, &shdr)) ) {
413  errlogPrintf("elfRead() -- unable to read ELF symtab\n");
414  goto bail;
415  }
416 
417  es->nsyms = n / (ELFCLASS32==c ? sizeof(Elf32_Sym) : sizeof(Elf64_Sym));
418 
419  /* find and read string table */
420 
421  n = ELFCLASS32 == c ? sizeof(shdr.e32) : sizeof(shdr.e64);
422 
423  /* seek to section header table */
424  if ( (off_t)-1 == lseek(es->fd, (off_t) (FLD(c,ehdr,e_shoff) + n * FLD(c,shdr,sh_link)), SEEK_SET) ) {
425  errlogPrintf("elfRead() -- unable to lseek to ELF e_shoff: %s\n", strerror(errno));
426  goto bail;
427  }
428 
429  if ( n != do_read(es->fd, &shdr, n) ) {
430  errlogPrintf("elfRead() -- unable to read ELF strtab section header: %s\n", strerror(errno));
431  goto bail;
432  }
433 
434  if ( !(es->strMap = getscn(es->fd,c,&shdr)) ) {
435  errlogPrintf("elfRead() -- unable to read ELF strtab\n");
436  goto bail;
437  }
438 
439  /* Make sure there is a terminating NUL - unfortunately, memrchr is not portable */
440  cp = (char*)es->strMap->addr + es->strMap->off;
441  for ( idx = es->strMap->max - 1; i >= 0; i-- ) {
442  if ( !cp[i] )
443  break;
444  }
445  es->strMap->max = idx + 1;
446 
447  switch ( FLD(c,ehdr,e_type) ) {
448  case ET_EXEC:
449  /* Symbols in an executable already has absolute addresses */
450  es->addr = 0;
451  break;
452  case ET_DYN:
453  /* Symbols in an shared library are relative to base address */
454  es->addr = fbase;
455  break;
456  default:
457  errlogPrintf("dlLookupAddr(): Unexpected ELF object file type %u\n", FLD(c,ehdr,e_type));
458  goto bail;
459  }
460 
461  return es;
462 
463 bail:
464  elfSymsRelease(es);
465  return es;
466 }
467 
468 /* Destroy a cached ELF symbol table */
469 static void
470 elfSymsDestroy(ESyms es)
471 {
472  if ( es ) {
473  elfSymsRelease(es);
474  free(es);
475  }
476 }
477 
478 /* Destroy all cached ELF symbol tables
479  *
480  * However - w/o proper locking for read access
481  * this must not be used. Otherwise, readers
482  * will hold stale pointers...
483  *
484  * We leave the commented code here to show
485  * how the tables can be torn down.
486 
487 void
488 elfSymTblFlush()
489 {
490 ESyms es;
491 
492  elfsLockWrite();
493  while ( (es = elfs) ) {
494  elfs = es->next;
495  es->next = 0;
496  elfsUnlockWrite();
497  elfSymsDestroy(es);
498  elfsLockWrite();
499  }
500  elfsUnlockWrite();
501 }
502 
503 */
504 
505 
506 /* This routine must be called with the write-lock held */
507 static ESyms
508 elfSymsFind(const char *fname)
509 {
510  ESyms es;
511 
512  for ( es=elfs; es && strcmp(fname, es->fname); es = es->next )
513  /* nothing else to do */;
514  return es;
515 }
516 
517 int
518 epicsFindAddr(void *addr, epicsSymbol *sym_p)
519 {
520  Dl_info inf;
521  ESyms es,nes = 0;
522  uintptr_t minoff,off;
523  size_t i;
524  Sym sym;
525  Sym nearest;
526  const char *strtab;
527  uint8_t c;
528  size_t idx;
529 
530  if ( ! dladdr(addr, &inf) || (!inf.dli_fname && !inf.dli_sname) ) {
531  sym_p->f_nam = 0;
532  sym_p->s_nam = 0;
533  /* unable to lookup */
534  return 0;
535  }
536 
537  sym_p->f_nam = inf.dli_fname;
538 
539  /* If the symbol is in the main executable then solaris' dladdr returns bogus info */
540 #ifndef __sun
541  if ( (sym_p->s_nam = inf.dli_sname) ) {
542  sym_p->s_val = inf.dli_saddr;
543  /* Have a symbol name - just use it and be done */
544  return 0;
545  }
546 #endif
547 
548  /* No symbol info; try to access ELF file and ready symbol table from there */
549 
550  elfsLockWrite();
551 
552  /* See if we have loaded this file already */
553  es = elfSymsFind(inf.dli_fname);
554 
555  if ( !es ) {
556 
557  elfsUnlockWrite();
558 
559  if ( ! (nes = elfRead(inf.dli_fname, (uintptr_t)inf.dli_fbase)) ) {
560  /* this path can only be taken if there is no memory for '*nes' */
561  return 0;
562  }
563 
564  elfsLockWrite();
565 
566  /* Has someone else intervened and already added this file while we were reading ? */
567  es = elfSymsFind(inf.dli_fname);
568 
569  if ( es ) {
570  /* will undo our work in the unlikely event... */
571  } else {
572  nes->next = elfs;
573  es = elfs = nes;
574  nes = 0;
575  }
576  }
577 
578  elfsUnlockWrite();
579 
580  /* Undo our work in the unlikely event that it was redundant */
581  if ( nes )
582  elfSymsDestroy( nes );
583 
584  nearest.raw = 0;
585  minoff = (uintptr_t)-1LL;
586 
587  if ( es->nsyms ) {
588  c = es->eclss;
589  sym.raw = (char*)es->symMap->addr + es->symMap->off;
590  strtab = (char*)es->strMap->addr + es->strMap->off;
591 
592  /* Do a brute-force search through the symbol table; if this is executed
593  * very often then it would be worthwhile constructing a sorted list of
594  * symbol addresses but for the stack trace we don't care...
595  */
596 #if (FIND_ADDR_DEBUG & 1)
597  printf("Looking for %p\n", addr);
598 #endif
599 
600  if ( ELFCLASS32 == c ) {
601  for ( i=0; i<es->nsyms; i++ ) {
602  if ( STT_FUNC != ELF32_ST_TYPE(sym.e32[i].st_info) )
603  continue;
604  /* don't bother about undefined symbols */
605  if ( 0 == sym.e32[i].st_shndx )
606  continue;
607 #if (FIND_ADDR_DEBUG & 1)
608  printf("Trying: %s (0x%lx)\n", strtab + sym.e32[i].st_name, (unsigned long)(sym.e32[i].st_value + es->addr));
609 #endif
610  if ( (uintptr_t)addr >= (uintptr_t)sym.e32[i].st_value + es->addr ) {
611  off = (uintptr_t)addr - ((uintptr_t)sym.e32[i].st_value + es->addr);
612  if ( off < minoff ) {
613  minoff = off;
614  nearest.e32 = &sym.e32[i];
615  }
616  }
617  }
618  } else {
619  for ( i=0; i<es->nsyms; i++ ) {
620  if ( STT_FUNC != ELF64_ST_TYPE(sym.e64[i].st_info) )
621  continue;
622  /* don't bother about undefined symbols */
623  if ( 0 == sym.e64[i].st_shndx )
624  continue;
625 #if (FIND_ADDR_DEBUG & 1)
626  printf("Trying: %s (0x%llx)\n", strtab + sym.e64[i].st_name, (unsigned long long)(sym.e64[i].st_value + es->addr));
627 #endif
628  if ( (uintptr_t)addr >= (uintptr_t)sym.e64[i].st_value + es->addr ) {
629  off = (uintptr_t)addr - ((uintptr_t)sym.e64[i].st_value + es->addr);
630  if ( off < minoff ) {
631  minoff = off;
632  nearest.e64 = &sym.e64[i];
633  }
634  }
635  }
636  }
637  }
638 
639  if ( nearest.raw && ( (idx = ARR(c,nearest,0,st_name)) < es->strMap->max ) ) {
640  sym_p->s_nam = strtab + idx;
641  sym_p->s_val = (char*) ARR(c, nearest, 0, st_value) + es->addr;
642  }
643 
644  return 0;
645 }
646 
648 {
649  /* The static information given here may not be correct;
650  * it also depends on
651  * - compilation (frame pointer optimization)
652  * - linkage (static vs. dynamic)
653  * - stripping
654  */
658 }
union Shdr_ Shdr
union Sym_ Sym
#define EPICS_STACKTRACE_LCL_SYMBOLS
Elf32_Ehdr e32
MMap symMap
void * addr
int i
Definition: scan.c:967
MMap strMap
#define EPICS_STACKTRACE_GBL_SYMBOLS
size_t max
LIBCOM_API void ClockTime_GetProgramStart(epicsTimeStamp *pDest)
#define printf
Definition: epicsStdio.h:41
Elf64_Sym * e64
#define epicsMutexMustCreate()
Create an epicsMutex semaphore for use from C code.
Definition: epicsMutex.h:179
void epicsStdCall epicsMutexUnlock(epicsMutexId pmutexNode)
Release the semaphore.
Definition: epicsMutex.cpp:140
void(* freeMap)(struct MMap_ *)
int epicsFindAddr(void *addr, epicsSymbol *sym_p)
int epicsFindAddrGetFeatures(void)
const char * fname
LIBCOM_API int epicsStdCall epicsTimeToTime_t(time_t *pDest, const epicsTimeStamp *pSrc)
Convert epicsTimeStamp to ANSI C time_t.
Definition: epicsTime.cpp:933
#define EPICS_THREAD_ONCE_INIT
Definition: epicsThread.h:109
const char * f_nam
struct ESyms_ * ESyms
#define EPICS_STACKTRACE_DYN_SYMBOLS
APIs for the epicsMutex mutual exclusion semaphore.
LIBCOM_API void epicsStdCall epicsThreadOnce(epicsThreadOnceId *id, EPICSTHREADFUNC, void *arg)
size_t len
uintptr_t addr
int errlogPrintf(const char *pFormat,...)
Definition: errlog.c:105
struct MMap_ * MMap
off_t off
uint8_t eclss
size_t nsyms
#define FLD(c, s, f)
const char * s_nam
EPICS time stamp, for use from C code.
Definition: epicsTime.h:33
Elf32_Shdr e32
Elf32_Sym * e32
Elf64_Shdr e64
struct ESyms_ * next
union Ehdr_ Ehdr
EPICS time-stamps (epicsTimeStamp), epicsTime C++ class and C functions for handling wall-clock times...
void * raw
C++ and C descriptions for a thread.
#define epicsMutexMustLock(ID)
Claim a semaphore (see epicsMutexLock()).
Definition: epicsMutex.h:214
#define ARR(c, s, i, f)
Elf64_Ehdr e64