move static/stub version of dladdr out of dynlink.c
[musl] / src / ldso / dlstart.c
index 3aaa200..46f5011 100644 (file)
@@ -9,10 +9,19 @@
 
 #include "crt_arch.h"
 
+#ifndef GETFUNCSYM
+#define GETFUNCSYM(fp, sym, got) do { \
+       __attribute__((__visibility__("hidden"))) void sym(); \
+       static void (*static_func_ptr)() = sym; \
+       __asm__ __volatile__ ( "" : "+m"(static_func_ptr) : : "memory"); \
+       *(fp) = static_func_ptr; } while(0)
+#endif
+
 __attribute__((__visibility__("hidden")))
 void _dlstart_c(size_t *sp, size_t *dynv)
 {
        size_t i, aux[AUX_CNT], dyn[DYN_CNT];
+       size_t *rel, rel_size, base;
 
        int argc = *sp;
        char **argv = (void *)(sp+1);
@@ -24,22 +33,82 @@ void _dlstart_c(size_t *sp, size_t *dynv)
        for (i=0; auxv[i]; i+=2) if (auxv[i]<AUX_CNT)
                aux[auxv[i]] = auxv[i+1];
 
+#if DL_FDPIC
+       struct fdpic_loadseg *segs, fakeseg;
+       size_t j;
+       if (dynv) {
+               /* crt_arch.h entry point asm is responsible for reserving
+                * space and moving the extra fdpic arguments to the stack
+                * vector where they are easily accessible from C. */
+               segs = ((struct fdpic_loadmap *)(sp[-1] ? sp[-1] : sp[-2]))->segs;
+       } else {
+               /* If dynv is null, the entry point was started from loader
+                * that is not fdpic-aware. We can assume normal fixed-
+                * displacement ELF loading was performed, but when ldso was
+                * run as a command, finding the Ehdr is a heursitic: we
+                * have to assume Phdrs start in the first 4k of the file. */
+               base = aux[AT_BASE];
+               if (!base) base = aux[AT_PHDR] & -4096;
+               segs = &fakeseg;
+               segs[0].addr = base;
+               segs[0].p_vaddr = 0;
+               segs[0].p_memsz = -1;
+               Ehdr *eh = (void *)base;
+               Phdr *ph = (void *)(base + eh->e_phoff);
+               size_t phnum = eh->e_phnum;
+               size_t phent = eh->e_phentsize;
+               while (phnum-- && ph->p_type != PT_DYNAMIC)
+                       ph = (void *)((size_t)ph + phent);
+               dynv = (void *)(base + ph->p_vaddr);
+       }
+#endif
+
        for (i=0; i<DYN_CNT; i++) dyn[i] = 0;
        for (i=0; dynv[i]; i+=2) if (dynv[i]<DYN_CNT)
                dyn[dynv[i]] = dynv[i+1];
 
+#if DL_FDPIC
+       for (i=0; i<DYN_CNT; i++) {
+               if (i==DT_RELASZ || i==DT_RELSZ) continue;
+               if (!dyn[i]) continue;
+               for (j=0; dyn[i]-segs[j].p_vaddr >= segs[j].p_memsz; j++);
+               dyn[i] += segs[j].addr - segs[j].p_vaddr;
+       }
+       base = 0;
+
+       const Sym *syms = (void *)dyn[DT_SYMTAB];
+
+       rel = (void *)dyn[DT_RELA];
+       rel_size = dyn[DT_RELASZ];
+       for (; rel_size; rel+=3, rel_size-=3*sizeof(size_t)) {
+               if (!IS_RELATIVE(rel[1], syms)) continue;
+               for (j=0; rel[0]-segs[j].p_vaddr >= segs[j].p_memsz; j++);
+               size_t *rel_addr = (void *)
+                       (rel[0] + segs[j].addr - segs[j].p_vaddr);
+               if (R_TYPE(rel[1]) == REL_FUNCDESC_VAL) {
+                       *rel_addr += segs[rel_addr[1]].addr
+                               - segs[rel_addr[1]].p_vaddr
+                               + syms[R_SYM(rel[1])].st_value;
+                       rel_addr[1] = dyn[DT_PLTGOT];
+               } else {
+                       size_t val = syms[R_SYM(rel[1])].st_value;
+                       for (j=0; val-segs[j].p_vaddr >= segs[j].p_memsz; j++);
+                       *rel_addr = rel[2] + segs[j].addr - segs[j].p_vaddr + val;
+               }
+       }
+#else
        /* If the dynamic linker is invoked as a command, its load
         * address is not available in the aux vector. Instead, compute
         * the load address as the difference between &_DYNAMIC and the
         * virtual address in the PT_DYNAMIC program header. */
-       unsigned char *base = (void *)aux[AT_BASE];
+       base = aux[AT_BASE];
        if (!base) {
                size_t phnum = aux[AT_PHNUM];
                size_t phentsize = aux[AT_PHENT];
                Phdr *ph = (void *)aux[AT_PHDR];
                for (i=phnum; i--; ph = (void *)((char *)ph + phentsize)) {
                        if (ph->p_type == PT_DYNAMIC) {
-                               base = (void *)((size_t)dynv - ph->p_vaddr);
+                               base = (size_t)dynv - ph->p_vaddr;
                                break;
                        }
                }
@@ -53,38 +122,29 @@ void _dlstart_c(size_t *sp, size_t *dynv)
                size_t *got = (void *)(base + dyn[DT_PLTGOT]);
                for (i=0; dynv[i]; i+=2) if (dynv[i]==DT_MIPS_LOCAL_GOTNO)
                        local_cnt = dynv[i+1];
-               for (i=0; i<local_cnt; i++) got[i] += (size_t)base;
+               for (i=0; i<local_cnt; i++) got[i] += base;
        }
 
-       size_t *rel, rel_size;
-
        rel = (void *)(base+dyn[DT_REL]);
        rel_size = dyn[DT_RELSZ];
        for (; rel_size; rel+=2, rel_size-=2*sizeof(size_t)) {
-               if (!IS_RELATIVE(rel[1])) continue;
+               if (!IS_RELATIVE(rel[1], 0)) continue;
                size_t *rel_addr = (void *)(base + rel[0]);
-               *rel_addr += (size_t)base;
+               *rel_addr += base;
        }
 
        rel = (void *)(base+dyn[DT_RELA]);
        rel_size = dyn[DT_RELASZ];
        for (; rel_size; rel+=3, rel_size-=3*sizeof(size_t)) {
-               if (!IS_RELATIVE(rel[1])) continue;
+               if (!IS_RELATIVE(rel[1], 0)) continue;
                size_t *rel_addr = (void *)(base + rel[0]);
-               *rel_addr = (size_t)base + rel[2];
+               *rel_addr = base + rel[2];
        }
+#endif
 
-       const char *strings = (void *)(base + dyn[DT_STRTAB]);
-       const Sym *syms = (void *)(base + dyn[DT_SYMTAB]);
-
-       /* Call dynamic linker stage-2, __dls2 */
-       for (i=0; ;i++) {
-               const char *s = strings + syms[i].st_name;
-               if (s[0]=='_' && s[1]=='_' && s[2]=='d'
-                && s[3]=='l' && s[4]=='s' && s[5]=='2' && !s[6])
-                       break;
-       }
-       ((stage2_func)(base + syms[i].st_value))(base, sp);
+       stage2_func dls2;
+       GETFUNCSYM(&dls2, __dls2, base+dyn[DT_PLTGOT]);
+       dls2((void *)base, sp);
 }
 
 #endif