fix dynamic loader library mapping for nommu systems
[musl] / src / ldso / dynlink.c
index 2c822b6..5fbe2bb 100644 (file)
@@ -139,7 +139,7 @@ static int dl_strcmp(const char *l, const char *r)
 #define strcmp(l,r) dl_strcmp(l,r)
 
 /* Compute load address for a virtual address in a given dso. */
-#ifdef DL_FDPIC
+#if DL_FDPIC
 static void *laddr(const struct dso *p, size_t v)
 {
        size_t j=0;
@@ -482,8 +482,14 @@ static void reclaim_gaps(struct dso *dso)
 
 static void *mmap_fixed(void *p, size_t n, int prot, int flags, int fd, off_t off)
 {
-       char *q = mmap(p, n, prot, flags, fd, off);
-       if (q != MAP_FAILED || errno != EINVAL) return q;
+       static int no_map_fixed;
+       char *q;
+       if (!no_map_fixed) {
+               q = mmap(p, n, prot, flags|MAP_FIXED, fd, off);
+               if (!DL_NOMMU_SUPPORT || q != MAP_FAILED || errno != EINVAL)
+                       return q;
+               no_map_fixed = 1;
+       }
        /* Fallbacks for MAP_FIXED failure on NOMMU kernels. */
        if (flags & MAP_ANONYMOUS) {
                memset(p, 0, n);
@@ -604,6 +610,19 @@ static void *map_library(int fd, struct dso *dso)
                        dso->loadmap->segs[i].p_vaddr = ph->p_vaddr;
                        dso->loadmap->segs[i].p_memsz = ph->p_memsz;
                        i++;
+                       if (prot & PROT_WRITE) {
+                               size_t brk = (ph->p_vaddr & PAGE_SIZE-1)
+                                       + ph->p_filesz;
+                               size_t pgbrk = brk + PAGE_SIZE-1 & -PAGE_SIZE;
+                               size_t pgend = brk + ph->p_memsz - ph->p_filesz
+                                       + PAGE_SIZE-1 & -PAGE_SIZE;
+                               if (pgend > pgbrk && mmap_fixed(map+pgbrk,
+                                       pgend-pgbrk, prot,
+                                       MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS,
+                                       -1, off_start) == MAP_FAILED)
+                                       goto error;
+                               memset(map + brk, 0, pgbrk-brk);
+                       }
                }
                map = (void *)dso->loadmap->segs[0].addr;
                map_len = 0;
@@ -618,7 +637,11 @@ static void *map_library(int fd, struct dso *dso)
         * the length of the file. This is okay because we will not
         * use the invalid part; we just need to reserve the right
         * amount of virtual address space to map over later. */
-       map = mmap((void *)addr_min, map_len, prot, MAP_PRIVATE, fd, off_start);
+       map = DL_NOMMU_SUPPORT
+               ? mmap((void *)addr_min, map_len, PROT_READ|PROT_WRITE|PROT_EXEC,
+                       MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
+               : mmap((void *)addr_min, map_len, prot,
+                       MAP_PRIVATE, fd, off_start);
        if (map==MAP_FAILED) goto error;
        dso->map = map;
        dso->map_len = map_len;
@@ -643,7 +666,8 @@ static void *map_library(int fd, struct dso *dso)
                        dso->phentsize = eh->e_phentsize;
                }
                /* Reuse the existing mapping for the lowest-address LOAD */
-               if ((ph->p_vaddr & -PAGE_SIZE) == addr_min) continue;
+               if ((ph->p_vaddr & -PAGE_SIZE) == addr_min && !DL_NOMMU_SUPPORT)
+                       continue;
                this_min = ph->p_vaddr & -PAGE_SIZE;
                this_max = ph->p_vaddr+ph->p_memsz+PAGE_SIZE-1 & -PAGE_SIZE;
                off_start = ph->p_offset & -PAGE_SIZE;
@@ -1203,6 +1227,11 @@ static void do_init_fini(struct dso *p)
        if (need_locking) pthread_mutex_unlock(&init_fini_lock);
 }
 
+void __libc_start_init(void)
+{
+       do_init_fini(tail);
+}
+
 static void dl_debug_state(void)
 {
 }
@@ -1325,6 +1354,7 @@ static void update_tls_size()
  * linker itself, but some of the relocations performed may need to be
  * replaced later due to copy relocations in the main program. */
 
+__attribute__((__visibility__("hidden")))
 void __dls2(unsigned char *base, size_t *sp)
 {
        if (DL_FDPIC) {
@@ -1512,8 +1542,7 @@ _Noreturn void __dls3(size_t *sp)
                if (ldd_mode) {
                        for (i=0; i<app.phnum; i++) {
                                if (app.phdr[i].p_type == PT_INTERP)
-                                       ldso.name = (void *)(app.base
-                                               + app.phdr[i].p_vaddr);
+                                       ldso.name = laddr(&app, app.phdr[i].p_vaddr);
                        }
                        dprintf(1, "\t%s (%p)\n", ldso.name, ldso.base);
                }
@@ -1539,8 +1568,10 @@ _Noreturn void __dls3(size_t *sp)
                if (!app.loadmap) {
                        app.loadmap = (void *)&app_dummy_loadmap;
                        app.loadmap->nsegs = 1;
-                       app.loadmap->segs[0].addr = (size_t)app.base;
-                       app.loadmap->segs[0].p_memsz = -1;
+                       app.loadmap->segs[0].addr = (size_t)app.map;
+                       app.loadmap->segs[0].p_vaddr = (size_t)app.map
+                               - (size_t)app.base;
+                       app.loadmap->segs[0].p_memsz = app.map_len;
                }
                argv[-3] = (void *)app.loadmap;
        }
@@ -1631,7 +1662,6 @@ _Noreturn void __dls3(size_t *sp)
        __init_libc(envp, argv[0]);
        atexit(do_fini);
        errno = 0;
-       do_init_fini(tail);
 
        CRTJMP((void *)aux[AT_ENTRY], argv-1);
        for(;;);
@@ -1735,6 +1765,30 @@ static int invalid_dso_handle(void *h)
        return 1;
 }
 
+static void *addr2dso(size_t a)
+{
+       struct dso *p;
+       size_t i;
+       if (DL_FDPIC) for (p=head; p; p=p->next) {
+               i = count_syms(p);
+               if (a-(size_t)p->funcdescs < i*sizeof(*p->funcdescs))
+                       return p;
+       }
+       for (p=head; p; p=p->next) {
+               if (DL_FDPIC && p->loadmap) {
+                       for (i=0; i<p->loadmap->nsegs; i++) {
+                               if (a-p->loadmap->segs[i].p_vaddr
+                                   < p->loadmap->segs[i].p_memsz)
+                                       return p;
+                       }
+               } else {
+                       if (a-(size_t)p->map < p->map_len)
+                               return p;
+               }
+       }
+       return 0;
+}
+
 void *__tls_get_addr(size_t *);
 
 static void *do_dlsym(struct dso *p, const char *s, void *ra)
@@ -1746,7 +1800,7 @@ static void *do_dlsym(struct dso *p, const char *s, void *ra)
                if (p == RTLD_DEFAULT) {
                        p = head;
                } else if (p == RTLD_NEXT) {
-                       for (p=head; p && (unsigned char *)ra-p->map>p->map_len; p=p->next);
+                       p = addr2dso((size_t)ra);
                        if (!p) p=head;
                        p = p->next;
                }
@@ -1754,6 +1808,8 @@ static void *do_dlsym(struct dso *p, const char *s, void *ra)
                if (!def.sym) goto failed;
                if ((def.sym->st_info&0xf) == STT_TLS)
                        return __tls_get_addr((size_t []){def.dso->tls_id, def.sym->st_value});
+               if (DL_FDPIC && (def.sym->st_info&0xf) == STT_FUNC)
+                       return def.dso->funcdescs + (def.sym - def.dso->syms);
                return laddr(def.dso, def.sym->st_value);
        }
        if (invalid_dso_handle(p))
@@ -1767,6 +1823,8 @@ static void *do_dlsym(struct dso *p, const char *s, void *ra)
        }
        if (sym && (sym->st_info&0xf) == STT_TLS)
                return __tls_get_addr((size_t []){p->tls_id, sym->st_value});
+       if (DL_FDPIC && sym && sym->st_shndx && (sym->st_info&0xf) == STT_FUNC)
+               return p->funcdescs + (sym - p->syms);
        if (sym && sym->st_value && (1<<(sym->st_info&0xf) & OK_TYPES))
                return laddr(p, sym->st_value);
        if (p->deps) for (i=0; p->deps[i]; i++) {
@@ -1779,6 +1837,8 @@ static void *do_dlsym(struct dso *p, const char *s, void *ra)
                }
                if (sym && (sym->st_info&0xf) == STT_TLS)
                        return __tls_get_addr((size_t []){p->deps[i]->tls_id, sym->st_value});
+               if (DL_FDPIC && sym && sym->st_shndx && (sym->st_info&0xf) == STT_FUNC)
+                       return p->deps[i]->funcdescs + (sym - p->deps[i]->syms);
                if (sym && sym->st_value && (1<<(sym->st_info&0xf) & OK_TYPES))
                        return laddr(p->deps[i], sym->st_value);
        }
@@ -1790,14 +1850,13 @@ failed:
 int __dladdr(const void *addr, Dl_info *info)
 {
        struct dso *p;
-       Sym *sym;
+       Sym *sym, *bestsym;
        uint32_t nsym;
        char *strings;
        void *best = 0;
-       char *bestname;
 
        pthread_rwlock_rdlock(&lock);
-       for (p=head; p && (unsigned char *)addr-p->map>p->map_len; p=p->next);
+       p = addr2dso((size_t)addr);
        pthread_rwlock_unlock(&lock);
 
        if (!p) return 0;
@@ -1806,7 +1865,16 @@ int __dladdr(const void *addr, Dl_info *info)
        strings = p->strings;
        nsym = count_syms(p);
 
-       for (; nsym; nsym--, sym++) {
+       if (DL_FDPIC) {
+               size_t idx = ((size_t)addr-(size_t)p->funcdescs)
+                       / sizeof(*p->funcdescs);
+               if (idx < nsym && (sym[idx].st_info&0xf) == STT_FUNC) {
+                       best = p->funcdescs + idx;
+                       bestsym = sym + idx;
+               }
+       }
+
+       if (!best) for (; nsym; nsym--, sym++) {
                if (sym->st_value
                 && (1<<(sym->st_info&0xf) & OK_TYPES)
                 && (1<<(sym->st_info>>4) & OK_BINDS)) {
@@ -1814,7 +1882,7 @@ int __dladdr(const void *addr, Dl_info *info)
                        if (symaddr > addr || symaddr < best)
                                continue;
                        best = symaddr;
-                       bestname = strings + sym->st_name;
+                       bestsym = sym;
                        if (addr == symaddr)
                                break;
                }
@@ -1822,9 +1890,12 @@ int __dladdr(const void *addr, Dl_info *info)
 
        if (!best) return 0;
 
+       if (DL_FDPIC && (bestsym->st_info&0xf) == STT_FUNC)
+               best = p->funcdescs + (bestsym - p->syms);
+
        info->dli_fname = p->name;
        info->dli_fbase = p->base;
-       info->dli_sname = bestname;
+       info->dli_sname = strings + bestsym->st_name;
        info->dli_saddr = best;
 
        return 1;