summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2012-08-26 21:09:26 -0400
committerRich Felker <dalias@aerifal.cx>2012-08-26 21:09:26 -0400
commitf419bcb9dcfb6af60fbf1d58d92caf4b3d62a4da (patch)
treef9b1fc4c34d59ee892504c7259100e94140d3e98
parent9bff7c133e73ecfb200614d7a7d386a164a1a61f (diff)
downloadmusl-f419bcb9dcfb6af60fbf1d58d92caf4b3d62a4da.tar.gz
dladdr support for dynamic linker (nonstandard extension)
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.
-rw-r--r--include/dlfcn.h10
-rw-r--r--src/ldso/dladdr.c9
-rw-r--r--src/ldso/dynlink.c86
3 files changed, 105 insertions, 0 deletions
diff --git a/include/dlfcn.h b/include/dlfcn.h
index dea74c7d..e98c8ca6 100644
--- a/include/dlfcn.h
+++ b/include/dlfcn.h
@@ -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
index 00000000..265bb681
--- /dev/null
+++ b/src/ldso/dladdr.c
@@ -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);
+}
diff --git a/src/ldso/dynlink.c b/src/ldso/dynlink.c
index c733dc5d..b8c26ace 100644
--- a/src/ldso/dynlink.c
+++ b/src/ldso/dynlink.c
@@ -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()