Support non-zero p_vaddr in first PT_LOAD segment

Before changing mmap() permission to PROT_NONE in alloc_mem_region(),
such libraries once worked with a bug that uses mmap'ed region outside of
memory region allocated by alloc_mem_region().  This possibly incurs
SIGSEGV because it can overwrite memory region of previously loaded
library, but it sometimes worked, depending on loading order.

After PROT_NONE, this caused SIGSEGV earlier during calculation of
si->phdr in load_library(), but this was fixed by:

    75917c84d16c35b8fceff6977fa717a3de9ef65d  Use mmap to read an ...

Now the behaviour is the same as before applying PROT_NONE in
alloc_mem_region().

This CL fixed the original issue, supporting shared libraries that have
non-zero p_vaddr in first (= with lowest p_vaddr) PT_LOAD segment.

Bug: 6561258
Change-Id: Ib6176dd3e44c4d99a340eb1cbd16fb037586b0bc
diff --git a/linker/linker.c b/linker/linker.c
index 739d3e4..be90fd7 100644
--- a/linker/linker.c
+++ b/linker/linker.c
@@ -347,7 +347,7 @@
     for (si = solist; si != 0; si = si->next){
         if ((addr >= si->base) && (addr < (si->base + si->size))) {
             *pcount = si->ARM_exidx_count;
-            return (_Unwind_Ptr)(si->base + (unsigned long)si->ARM_exidx);
+            return (_Unwind_Ptr)si->ARM_exidx;
         }
     }
    *pcount = 0;
@@ -423,7 +423,7 @@
 }
 
 static Elf32_Sym *
-_do_lookup(soinfo *si, const char *name, unsigned *base)
+_do_lookup(soinfo *si, const char *name, unsigned *base, Elf32_Addr *offset)
 {
     unsigned elf_hash = elfhash(name);
     Elf32_Sym *s;
@@ -486,9 +486,11 @@
 done:
     if(s != NULL) {
         TRACE_TYPE(LOOKUP, "%5d si %s sym %s s->st_value = 0x%08x, "
-                   "found in %s, base = 0x%08x\n",
-                   pid, si->name, name, s->st_value, lsi->name, lsi->base);
+                   "found in %s, base = 0x%08x, load offset = 0x%08x\n",
+                   pid, si->name, name, s->st_value,
+                   lsi->name, lsi->base, lsi->load_offset);
         *base = lsi->base;
+        *offset = lsi->load_offset;
         return s;
     }
 
@@ -866,7 +868,8 @@
 {
     Elf32_Ehdr *ehdr = (Elf32_Ehdr *)header;
     Elf32_Phdr *phdr = (Elf32_Phdr *)((unsigned char *)header + ehdr->e_phoff);
-    Elf32_Addr base = (Elf32_Addr) si->base;
+    Elf32_Phdr *phdr0 = 0;  /* program header for the first LOAD segment */
+    Elf32_Addr base;
     int cnt;
     unsigned len;
     Elf32_Addr tmp;
@@ -880,8 +883,27 @@
 
     TRACE("[ %5d - Begin loading segments for '%s' @ 0x%08x ]\n",
           pid, si->name, (unsigned)si->base);
+
+    for (cnt = 0; cnt < ehdr->e_phnum; ++cnt, ++phdr) {
+        if (phdr->p_type == PT_LOAD) {
+            phdr0 = phdr;
+            /*
+             * ELF specification section 2-2.
+             *
+             * PT_LOAD: "Loadable segment entries in the program
+             * header table appear in ascending order, sorted on the
+             * p_vaddr member."
+             */
+            si->load_offset = phdr->p_vaddr & (~PAGE_MASK);
+            break;
+        }
+    }
+    /* "base" might wrap around UINT32_MAX. */
+    base = (Elf32_Addr)(si->base - si->load_offset);
+
     /* Now go through all the PT_LOAD segments and map them into memory
      * at the appropriate locations. */
+    phdr = (Elf32_Phdr *)((unsigned char *)header + ehdr->e_phoff);
     for (cnt = 0; cnt < ehdr->e_phnum; ++cnt, ++phdr) {
         if (phdr->p_type == PT_LOAD) {
             DEBUG_DUMP_PHDR(phdr, "PT_LOAD", pid);
@@ -991,9 +1013,9 @@
             /* this segment contains the dynamic linking information */
             si->dynamic = (unsigned *)(base + phdr->p_vaddr);
         } else if (phdr->p_type == PT_GNU_RELRO) {
-            if ((phdr->p_vaddr >= si->size)
-                    || ((phdr->p_vaddr + phdr->p_memsz) > si->size)
-                    || ((base + phdr->p_vaddr + phdr->p_memsz) < base)) {
+            if (((base + phdr->p_vaddr) >= si->base + si->size)
+                    || ((base + phdr->p_vaddr + phdr->p_memsz) > si->base + si->size)
+                    || ((base + phdr->p_vaddr + phdr->p_memsz) < si->base)) {
                 DL_ERR("%d invalid GNU_RELRO in '%s' "
                        "p_vaddr=0x%08x p_memsz=0x%08x", pid, si->name,
                        phdr->p_vaddr, phdr->p_memsz);
@@ -1007,7 +1029,7 @@
                 DEBUG_DUMP_PHDR(phdr, "PT_ARM_EXIDX", pid);
                 /* exidx entries (used for stack unwinding) are 8 bytes each.
                  */
-                si->ARM_exidx = (unsigned *)phdr->p_vaddr;
+                si->ARM_exidx = (unsigned *)(base + phdr->p_vaddr);
                 si->ARM_exidx_count = phdr->p_memsz / 8;
             }
 #endif
@@ -1023,6 +1045,21 @@
         goto fail;
     }
 
+    /* vaddr    : Real virtual address in process' address space.
+     * p_vaddr  : Relative virtual address in ELF object
+     * p_offset : File offset in ELF object
+     *
+     * vaddr        p_vaddr                         p_offset
+     * -----        ------------                    --------
+     * base         0
+     * si->base     phdr0->p_vaddr & ~PAGE_MASK
+     *              phdr0->p_vaddr                  phdr0->p_offset
+     * phdr                                         ehdr->e_phoff
+     */
+    si->phdr = (Elf32_Phdr *)(base + phdr0->p_vaddr +
+                              ehdr->e_phoff - phdr0->p_offset);
+    si->phnum = ehdr->e_phnum;
+
     TRACE("[ %5d - Finish loading segments for '%s' @ 0x%08x. "
           "Total memory footprint: 0x%08x bytes ]\n", pid, si->name,
           (unsigned)si->base, si->size);
@@ -1143,9 +1180,6 @@
         goto fail;
     }
 
-    si->phdr = (Elf32_Phdr *)((unsigned char *)si->base + hdr->e_phoff);
-    si->phnum = hdr->e_phnum;
-
     munmap(hdr, sb.st_size);
     close(fd);
     return si;
@@ -1282,13 +1316,14 @@
     const char *strtab = si->strtab;
     Elf32_Sym *s;
     unsigned base;
+    Elf32_Addr offset;
     Elf32_Rel *start = rel;
     unsigned idx;
 
     for (idx = 0; idx < count; ++idx) {
         unsigned type = ELF32_R_TYPE(rel->r_info);
         unsigned sym = ELF32_R_SYM(rel->r_info);
-        unsigned reloc = (unsigned)(rel->r_offset + si->base);
+        unsigned reloc = (unsigned)(rel->r_offset + si->base - si->load_offset);
         unsigned sym_addr = 0;
         char *sym_name = NULL;
 
@@ -1296,7 +1331,7 @@
               si->name, idx);
         if(sym != 0) {
             sym_name = (char *)(strtab + symtab[sym].st_name);
-            s = _do_lookup(si, sym_name, &base);
+            s = _do_lookup(si, sym_name, &base, &offset);
             if(s == NULL) {
                 /* We only allow an undefined symbol if this is a weak
                    reference..   */
@@ -1363,7 +1398,7 @@
                 return -1;
             }
 #endif
-                sym_addr = (unsigned)(s->st_value + base);
+                sym_addr = (unsigned)(s->st_value + base - offset);
 	    }
             COUNT_RELOC(RELOC_SYMBOL);
         } else {
@@ -1668,6 +1703,8 @@
 static int link_image(soinfo *si, unsigned wr_offset)
 {
     unsigned *d;
+    /* "base" might wrap around UINT32_MAX. */
+    Elf32_Addr base = si->base - si->load_offset;
     Elf32_Phdr *phdr = si->phdr;
     int phnum = si->phnum;
 
@@ -1703,7 +1740,7 @@
                    We use the range [si->base, si->base + si->size) to
                    determine whether a PC value falls within the executable
                    section. Of course, if a value is between si->base and
-                   (si->base + phdr->p_vaddr), it's not in the executable
+                   (base + phdr->p_vaddr), it's not in the executable
                    section, but a) we shouldn't be asking for such a value
                    anyway, and b) if we have to provide an EXIDX for such a
                    value, then the executable's EXIDX is probably the better
@@ -1717,9 +1754,9 @@
                 if (!(phdr->p_flags & PF_W)) {
                     unsigned _end;
 
-                    if (si->base + phdr->p_vaddr < si->wrprotect_start)
-                        si->wrprotect_start = si->base + phdr->p_vaddr;
-                    _end = (((si->base + phdr->p_vaddr + phdr->p_memsz + PAGE_SIZE - 1) &
+                    if (base + phdr->p_vaddr < si->wrprotect_start)
+                        si->wrprotect_start = base + phdr->p_vaddr;
+                    _end = (((base + phdr->p_vaddr + phdr->p_memsz + PAGE_SIZE - 1) &
                              (~PAGE_MASK)));
                     if (_end > si->wrprotect_end)
                         si->wrprotect_end = _end;
@@ -1728,7 +1765,7 @@
                      * However, we will remember what range of addresses
                      * should be write protected.
                      */
-                    mprotect((void *) (si->base + phdr->p_vaddr),
+                    mprotect((void *) (base + phdr->p_vaddr),
                              phdr->p_memsz,
                              PFLAGS_TO_PROT(phdr->p_flags) | PROT_WRITE);
                 }
@@ -1736,22 +1773,22 @@
                 if (si->dynamic != (unsigned *)-1) {
                     DL_ERR("%5d multiple PT_DYNAMIC segments found in '%s'. "
                           "Segment at 0x%08x, previously one found at 0x%08x",
-                          pid, si->name, si->base + phdr->p_vaddr,
+                          pid, si->name, base + phdr->p_vaddr,
                           (unsigned)si->dynamic);
                     goto fail;
                 }
                 DEBUG_DUMP_PHDR(phdr, "PT_DYNAMIC", pid);
-                si->dynamic = (unsigned *) (si->base + phdr->p_vaddr);
+                si->dynamic = (unsigned *) (base + phdr->p_vaddr);
             } else if (phdr->p_type == PT_GNU_RELRO) {
-                if ((phdr->p_vaddr >= si->size)
-                        || ((phdr->p_vaddr + phdr->p_memsz) > si->size)
-                        || ((si->base + phdr->p_vaddr + phdr->p_memsz) < si->base)) {
+                if ((base + phdr->p_vaddr >= si->base + si->size)
+                        || ((base + phdr->p_vaddr + phdr->p_memsz) > si->base + si->size)
+                        || ((base + phdr->p_vaddr + phdr->p_memsz) < si->base)) {
                     DL_ERR("%d invalid GNU_RELRO in '%s' "
                            "p_vaddr=0x%08x p_memsz=0x%08x", pid, si->name,
                            phdr->p_vaddr, phdr->p_memsz);
                     goto fail;
                 }
-                si->gnu_relro_start = (Elf32_Addr) (si->base + phdr->p_vaddr);
+                si->gnu_relro_start = (Elf32_Addr) (base + phdr->p_vaddr);
                 si->gnu_relro_len = (unsigned) phdr->p_memsz;
             }
         }
@@ -1769,16 +1806,16 @@
         DEBUG("%5d d = %p, d[0] = 0x%08x d[1] = 0x%08x\n", pid, d, d[0], d[1]);
         switch(*d++){
         case DT_HASH:
-            si->nbucket = ((unsigned *) (si->base + *d))[0];
-            si->nchain = ((unsigned *) (si->base + *d))[1];
-            si->bucket = (unsigned *) (si->base + *d + 8);
-            si->chain = (unsigned *) (si->base + *d + 8 + si->nbucket * 4);
+            si->nbucket = ((unsigned *) (base + *d))[0];
+            si->nchain = ((unsigned *) (base + *d))[1];
+            si->bucket = (unsigned *) (base + *d + 8);
+            si->chain = (unsigned *) (base + *d + 8 + si->nbucket * 4);
             break;
         case DT_STRTAB:
-            si->strtab = (const char *) (si->base + *d);
+            si->strtab = (const char *) (base + *d);
             break;
         case DT_SYMTAB:
-            si->symtab = (Elf32_Sym *) (si->base + *d);
+            si->symtab = (Elf32_Sym *) (base + *d);
             break;
         case DT_PLTREL:
             if(*d != DT_REL) {
@@ -1787,20 +1824,20 @@
             }
             break;
         case DT_JMPREL:
-            si->plt_rel = (Elf32_Rel*) (si->base + *d);
+            si->plt_rel = (Elf32_Rel*) (base + *d);
             break;
         case DT_PLTRELSZ:
             si->plt_rel_count = *d / 8;
             break;
         case DT_REL:
-            si->rel = (Elf32_Rel*) (si->base + *d);
+            si->rel = (Elf32_Rel*) (base + *d);
             break;
         case DT_RELSZ:
             si->rel_count = *d / 8;
             break;
         case DT_PLTGOT:
             /* Save this in case we decide to do lazy binding. We don't yet. */
-            si->plt_got = (unsigned *)(si->base + *d);
+            si->plt_got = (unsigned *)(base + *d);
             break;
         case DT_DEBUG:
             // Set the DT_DEBUG entry to the addres of _r_debug for GDB
@@ -1810,17 +1847,17 @@
             DL_ERR("%5d DT_RELA not supported", pid);
             goto fail;
         case DT_INIT:
-            si->init_func = (void (*)(void))(si->base + *d);
+            si->init_func = (void (*)(void))(base + *d);
             DEBUG("%5d %s constructors (init func) found at %p\n",
                   pid, si->name, si->init_func);
             break;
         case DT_FINI:
-            si->fini_func = (void (*)(void))(si->base + *d);
+            si->fini_func = (void (*)(void))(base + *d);
             DEBUG("%5d %s destructors (fini func) found at %p\n",
                   pid, si->name, si->fini_func);
             break;
         case DT_INIT_ARRAY:
-            si->init_array = (unsigned *)(si->base + *d);
+            si->init_array = (unsigned *)(base + *d);
             DEBUG("%5d %s constructors (init_array) found at %p\n",
                   pid, si->name, si->init_array);
             break;
@@ -1828,7 +1865,7 @@
             si->init_array_count = ((unsigned)*d) / sizeof(Elf32_Addr);
             break;
         case DT_FINI_ARRAY:
-            si->fini_array = (unsigned *)(si->base + *d);
+            si->fini_array = (unsigned *)(base + *d);
             DEBUG("%5d %s destructors (fini_array) found at %p\n",
                   pid, si->name, si->fini_array);
             break;
@@ -1836,7 +1873,7 @@
             si->fini_array_count = ((unsigned)*d) / sizeof(Elf32_Addr);
             break;
         case DT_PREINIT_ARRAY:
-            si->preinit_array = (unsigned *)(si->base + *d);
+            si->preinit_array = (unsigned *)(base + *d);
             DEBUG("%5d %s constructors (preinit_array) found at %p\n",
                   pid, si->name, si->preinit_array);
             break;
@@ -2150,6 +2187,7 @@
             break;
         }
     }
+    si->load_offset = 0;
     si->dynamic = (unsigned *)-1;
     si->wrprotect_start = 0xffffffff;
     si->wrprotect_end = 0;
@@ -2267,6 +2305,7 @@
     memset(&linker_so, 0, sizeof(soinfo));
 
     linker_so.base = linker_addr;
+    linker_so.load_offset = 0;
     linker_so.dynamic = (unsigned *) -1;
     linker_so.phdr = phdr;
     linker_so.phnum = elf_hdr->e_phnum;
diff --git a/linker/linker.h b/linker/linker.h
index 0c986cd..c6b81ea 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -148,6 +148,10 @@
     Elf32_Addr gnu_relro_start;
     unsigned gnu_relro_len;
 
+    /* When you read a virtual address from the ELF file, add the load
+     * address (= "base" field) minus this value (= "load_offset") to get the
+     * real, corresponding address in the process' address space */
+    Elf32_Addr load_offset;
 };