dladdr support for dynamic linker (nonstandard extension)
authorRich Felker <dalias@aerifal.cx>
Mon, 27 Aug 2012 01:09:26 +0000 (21:09 -0400)
committerRich Felker <dalias@aerifal.cx>
Mon, 27 Aug 2012 01:09:26 +0000 (21:09 -0400)
based on patches submitted by boris brezillon. this commit also fixes
the issue whereby the main application and libc don't have the address
ranges of their mappings stored, which was theoretically a problem for
RTLD_NEXT support in dlsym; it didn't actually matter because libc
never calls dlsym, and it seemed to be doing the right thing (by
chance) for symbols in the main program as well.

include/dlfcn.h
src/ldso/dladdr.c [new file with mode: 0644]
src/ldso/dynlink.c

index dea74c7dc7fc387a046f9f1dac0422ef52389c59..e98c8ca616a842fa5aa782ea2016681d2bb4cece 100644 (file)
@@ -18,6 +18,16 @@ char  *dlerror(void);
 void  *dlopen(const char *, int);
 void  *dlsym(void *, const char *);
 
+#ifdef _GNU_SOURCE
+typedef struct {
+       const char *dli_fname;
+       void *dli_fbase;
+       const char *dli_sname;
+       void *dli_saddr;
+} Dl_info;
+int dladdr(void *, Dl_info *);
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/ldso/dladdr.c b/src/ldso/dladdr.c
new file mode 100644 (file)
index 0000000..265bb68
--- /dev/null
@@ -0,0 +1,9 @@
+#define _GNU_SOURCE
+#include <dlfcn.h>
+
+int __dladdr(void *, Dl_info *);
+
+int dladdr(void *addr, Dl_info *info)
+{
+       return __dladdr(addr, info);
+}
index c733dc5d1d30b4d081fe1064491c52d241b2886b..b8c26ace57a650500e3db1582757213d2d199b87 100644 (file)
@@ -1,3 +1,4 @@
+#define _GNU_SOURCE
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -584,6 +585,22 @@ static size_t find_dyn(Phdr *ph, size_t cnt, size_t stride)
        return 0;
 }
 
+static void find_map_range(Phdr *ph, size_t cnt, size_t stride, struct dso *p)
+{
+       size_t min_addr = -1, max_addr = 0;
+       for (; cnt--; ph = (void *)((char *)ph + stride)) {
+               if (ph->p_type != PT_LOAD) continue;
+               if (ph->p_vaddr < min_addr)
+                       min_addr = ph->p_vaddr;
+               if (ph->p_vaddr+ph->p_memsz > max_addr)
+                       max_addr = ph->p_vaddr+ph->p_memsz;
+       }
+       min_addr &= -PAGE_SIZE;
+       max_addr = (max_addr + PAGE_SIZE-1) & -PAGE_SIZE;
+       p->map = p->base + min_addr;
+       p->map_len = max_addr - min_addr;
+}
+
 static void do_init_fini(struct dso *p)
 {
        size_t dyn[DYN_CNT] = {0};
@@ -647,6 +664,8 @@ void *__dynlink(int argc, char **argv)
        lib->name = lib->shortname = "libc.so";
        lib->global = 1;
        ehdr = (void *)lib->base;
+       find_map_range((void *)(aux[AT_BASE]+ehdr->e_phoff),
+               ehdr->e_phnum, ehdr->e_phentsize, lib);
        lib->dynv = (void *)(lib->base + find_dyn(
                (void *)(aux[AT_BASE]+ehdr->e_phoff),
                ehdr->e_phnum, ehdr->e_phentsize));
@@ -666,6 +685,8 @@ void *__dynlink(int argc, char **argv)
                app->name = argv[0];
                app->dynv = (void *)(app->base + find_dyn(
                        (void *)aux[AT_PHDR], aux[AT_PHNUM], aux[AT_PHENT]));
+               find_map_range((void *)aux[AT_PHDR],
+                       aux[AT_PHNUM], aux[AT_PHENT], app);
        } else {
                int fd;
                char *ldname = argv[0];
@@ -891,6 +912,67 @@ failed:
        return 0;
 }
 
+int __dladdr(void *addr, Dl_info *info)
+{
+       struct dso *p;
+       Sym *sym;
+       uint32_t nsym;
+       char *strings;
+       size_t i;
+       void *best = 0;
+       char *bestname;
+
+       pthread_rwlock_rdlock(&lock);
+       for (p=head; p && (unsigned char *)addr-p->map>p->map_len; p=p->next);
+       pthread_rwlock_unlock(&lock);
+
+       if (!p) return 0;
+
+       sym = p->syms;
+       strings = p->strings;
+       if (p->hashtab) {
+               nsym = p->hashtab[1];
+       } else {
+               uint32_t *buckets;
+               uint32_t *hashval;
+               buckets = p->ghashtab + 4 + (p->ghashtab[2]*sizeof(size_t)/4);
+               sym += p->ghashtab[1];
+               for (i = 0; i < p->ghashtab[0]; i++) {
+                       if (buckets[i] > nsym)
+                               nsym = buckets[i];
+               }
+               if (nsym) {
+                       nsym -= p->ghashtab[1];
+                       hashval = buckets + p->ghashtab[0] + nsym;
+                       do nsym++;
+                       while (!(*hashval++ & 1));
+               }
+       }
+
+       for (; nsym; nsym--, sym++) {
+               if (sym->st_shndx && sym->st_value
+                && (1<<(sym->st_info&0xf) & OK_TYPES)
+                && (1<<(sym->st_info>>4) & OK_BINDS)) {
+                       void *symaddr = p->base + sym->st_value;
+                       if (symaddr > addr || symaddr < best)
+                               continue;
+                       best = symaddr;
+                       bestname = strings + sym->st_name;
+                       if (addr == symaddr)
+                               break;
+               }
+       }
+
+       if (!best) return 0;
+
+       info->dli_fname = p->name;
+       info->dli_fbase = p->base;
+       info->dli_sname = bestname;
+       info->dli_saddr = best;
+
+       return 1;
+}
+
 void *__dlsym(void *p, const char *s, void *ra)
 {
        void *res;
@@ -908,6 +990,10 @@ void *__dlsym(void *p, const char *s, void *ra)
 {
        return 0;
 }
+int __dladdr (void *addr, Dl_info *info)
+{
+       return 0;
+}
 #endif
 
 char *dlerror()