Expand PHDR space only if there are pages available
[centaur.git] / src / libelfu / modelops / layout.c
index afa12c94f02b2e61d8f0018c57130ade72710876..2fd610ce3b0808f6858854c0aca0666b3d5ac4d4 100644 (file)
@@ -68,6 +68,89 @@ static GElf_Word shiftStuffAtAfterOffset(ElfuElf *me,
 
 
 
+static ElfuPhdr* appendPhdr(ElfuElf *me)
+{
+  ElfuPhdr *lowestAddr;
+  ElfuPhdr *highestAddr;
+  ElfuPhdr *lowestOffs;
+  ElfuPhdr *highestOffsEnd;
+  ElfuPhdr *phdrmp;
+  ElfuPhdr *newmp;
+
+  ELFU_DEBUG("Appending new PHDR\n");
+
+  /* See if we have enough space for more PHDRs. If not, expand
+   * the PHDR they are in. */
+  phdrmp = elfu_mPhdrByOffset(me, me->ehdr.e_phoff);
+  if (!phdrmp) {
+    /* No LOAD maps PHDRs into memory. Let re-layouter move them. */
+  } else {
+    GElf_Off phdr_maxsz = OFFS_END(phdrmp->phdr.p_offset, phdrmp->phdr.p_filesz);
+    ElfuScn *firstms = CIRCLEQ_FIRST(&phdrmp->childScnList);
+
+    /* How much can the PHDR table expand within its LOAD segment? */
+    if (firstms) {
+      phdr_maxsz = MIN(firstms->shdr.sh_offset, phdr_maxsz);
+    }
+    phdr_maxsz -= me->ehdr.e_phoff;
+
+    /* If we don't have enough space, try to make some by expanding
+     * the LOAD segment we are in. There is no other way.
+     * Also, we can only expand if it is the first LOAD PHDR. */
+    elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
+                                &lowestOffs, &highestOffsEnd);
+    if (phdr_maxsz < (me->ehdr.e_phnum + 1) * me->ehdr.e_phentsize
+        && phdrmp == lowestAddr
+        && phdrmp == lowestOffs
+        && (lowestAddr->phdr.p_vaddr >= 2 * lowestAddr->phdr.p_align)) {
+      ElfuPhdr *mp;
+      ElfuScn *ms;
+      GElf_Word size = ROUNDUP(me->ehdr.e_phentsize, phdrmp->phdr.p_align);
+
+      /* Move our sections */
+      CIRCLEQ_FOREACH(ms, &phdrmp->childScnList, elemChildScn) {
+        if (ms->shdr.sh_offset >= me->ehdr.e_phoff) {
+          ms->shdr.sh_offset += size;
+        }
+      }
+
+      /* Move our PHDRs */
+      CIRCLEQ_FOREACH(mp, &phdrmp->childPhdrList, elemChildPhdr) {
+        if (mp->phdr.p_offset > me->ehdr.e_phoff) {
+          mp->phdr.p_offset += size;
+        } else {
+          mp->phdr.p_vaddr -= size;
+          mp->phdr.p_paddr -= size;
+        }
+
+        if (mp->phdr.p_type == PT_PHDR) {
+          mp->phdr.p_filesz += me->ehdr.e_phentsize;
+          mp->phdr.p_memsz += me->ehdr.e_phentsize;
+        }
+      }
+
+      /* Move other PHDRs and sections */
+      assert(size <= shiftStuffAtAfterOffset(me, me->ehdr.e_phoff + 1, size));
+
+      /* Remap ourselves */
+      phdrmp->phdr.p_vaddr -= size;
+      phdrmp->phdr.p_paddr -= size;
+      phdrmp->phdr.p_filesz += size;
+      phdrmp->phdr.p_memsz += size;
+    }
+  }
+
+  newmp = elfu_mPhdrAlloc();
+  assert(newmp);
+  CIRCLEQ_INSERT_TAIL(&me->phdrList, newmp, elem);
+
+  return newmp;
+}
+
+
+
+
+
 /* Finds a suitable PHDR to insert a hole into and expands it
  * if necessary.
  * Returns memory address the hole will be mapped to, or 0 if
@@ -76,8 +159,10 @@ GElf_Addr elfu_mLayoutGetSpaceInPhdr(ElfuElf *me, GElf_Word size,
                                      GElf_Word align, int w, int x,
                                      ElfuPhdr **injPhdr)
 {
-  ElfuPhdr *first = NULL;
-  ElfuPhdr *last = NULL;
+  ElfuPhdr *lowestAddr;
+  ElfuPhdr *highestAddr;
+  ElfuPhdr *lowestOffs;
+  ElfuPhdr *highestOffsEnd;
   ElfuPhdr *mp;
 
   assert(!(w && x));
@@ -88,36 +173,28 @@ GElf_Addr elfu_mLayoutGetSpaceInPhdr(ElfuElf *me, GElf_Word size,
     x = 1;
   }
 
-  /* Find first and last LOAD PHDRs.
-   * Don't compare p_memsz - segments don't overlap in memory. */
-  CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
-    if (mp->phdr.p_type != PT_LOAD) {
-      continue;
-    }
-    if (!first || mp->phdr.p_vaddr < first->phdr.p_vaddr) {
-      first = mp;
-    }
-    if (!last || mp->phdr.p_vaddr > last->phdr.p_vaddr) {
-      last = mp;
-    }
-  }
+  /* Find first and last LOAD PHDRs. */
+  elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
+                              &lowestOffs, &highestOffsEnd);
 
-  if ((w && (last->phdr.p_flags & PF_W))
-      || (x && (last->phdr.p_flags & PF_X))) {
+  if (((w && (highestAddr->phdr.p_flags & PF_W))
+      || (x && (highestAddr->phdr.p_flags & PF_X)))
+      /* Merging only works if the LOAD is the last both in file and mem */
+      && highestAddr == highestOffsEnd) {
     /* Need to append. */
-    GElf_Off injOffset = OFFS_END(last->phdr.p_offset, last->phdr.p_filesz);
+    GElf_Off injOffset = OFFS_END(highestAddr->phdr.p_offset, highestAddr->phdr.p_filesz);
     GElf_Word injSpace = 0;
-    GElf_Word nobitsize = last->phdr.p_memsz - last->phdr.p_filesz;
+    GElf_Word nobitsize = highestAddr->phdr.p_memsz - highestAddr->phdr.p_filesz;
 
     /* Expand NOBITS if any */
     if (nobitsize > 0) {
-      GElf_Off endOff = OFFS_END(last->phdr.p_offset, last->phdr.p_filesz);
-      GElf_Off endAddr = OFFS_END(last->phdr.p_vaddr, last->phdr.p_filesz);
+      GElf_Off endOff = OFFS_END(highestAddr->phdr.p_offset, highestAddr->phdr.p_filesz);
+      GElf_Off endAddr = OFFS_END(highestAddr->phdr.p_vaddr, highestAddr->phdr.p_filesz);
       ElfuScn *ms;
 
       ELFU_INFO("Expanding NOBITS at address 0x%x...\n", (unsigned)endAddr);
 
-      CIRCLEQ_FOREACH(ms, &last->childScnList, elemChildScn) {
+      CIRCLEQ_FOREACH(ms, &highestAddr->childScnList, elemChildScn) {
         if (ms->shdr.sh_offset == endOff) {
           assert(ms->shdr.sh_type == SHT_NOBITS);
           assert(ms->shdr.sh_size == nobitsize);
@@ -138,8 +215,8 @@ GElf_Addr elfu_mLayoutGetSpaceInPhdr(ElfuElf *me, GElf_Word size,
       injSpace += shiftStuffAtAfterOffset(me, endOff, nobitsize);
       injSpace -= nobitsize;
       injOffset += nobitsize;
-      last->phdr.p_filesz += nobitsize;
-      assert(last->phdr.p_filesz == last->phdr.p_memsz);
+      highestAddr->phdr.p_filesz += nobitsize;
+      assert(highestAddr->phdr.p_filesz == highestAddr->phdr.p_memsz);
     }
 
     /* Calculate how much space we need, taking alignment into account */
@@ -152,41 +229,45 @@ GElf_Addr elfu_mLayoutGetSpaceInPhdr(ElfuElf *me, GElf_Word size,
     assert(injSpace >= size);
 
     /* Remap ourselves */
-    last->phdr.p_filesz += size;
-    last->phdr.p_memsz += size;
+    highestAddr->phdr.p_filesz += size;
+    highestAddr->phdr.p_memsz += size;
 
     injOffset = ROUNDUP(injOffset, align);
 
     if (injPhdr) {
-      *injPhdr = last;
+      *injPhdr = highestAddr;
     }
-    return last->phdr.p_vaddr + (injOffset - last->phdr.p_offset);
-  } else if (((w && (first->phdr.p_flags & PF_W))
-              || (x && (first->phdr.p_flags & PF_X)))
+    return highestAddr->phdr.p_vaddr + (injOffset - highestAddr->phdr.p_offset);
+  } else if (((w && (lowestAddr->phdr.p_flags & PF_W))
+              || (x && (lowestAddr->phdr.p_flags & PF_X)))
              && /* Enough space to expand downwards? */
-             (first->phdr.p_vaddr > 3 * first->phdr.p_align)) {
+             (lowestAddr->phdr.p_vaddr >= ((2 * lowestAddr->phdr.p_align)
+                                           + ROUNDUP(size, lowestAddr->phdr.p_align)))
+             /* Merging only works if the LOAD is the first both in file and mem */
+             && lowestAddr == lowestOffs) {
     /* Need to prepend or split up the PHDR. */
-    GElf_Off injOffset = OFFS_END(first->phdr.p_offset, first->phdr.p_filesz);
+    GElf_Off injOffset = OFFS_END(lowestAddr->phdr.p_offset,
+                                  lowestAddr->phdr.p_filesz);
     ElfuScn *ms;
 
     /* Round up size to take PHDR alignment into account.
      * We assume that this is a multiple of the alignment asked for. */
-    assert(first->phdr.p_align >= align);
-    size = ROUNDUP(size, first->phdr.p_align);
+    assert(lowestAddr->phdr.p_align >= align);
+    size = ROUNDUP(size, lowestAddr->phdr.p_align);
 
     /* Find first section. We assume there is at least one. */
-    assert(!CIRCLEQ_EMPTY(&first->childScnList));
-    injOffset = CIRCLEQ_FIRST(&first->childScnList)->shdr.sh_offset;
+    assert(!CIRCLEQ_EMPTY(&lowestAddr->childScnList));
+    injOffset = CIRCLEQ_FIRST(&lowestAddr->childScnList)->shdr.sh_offset;
 
     /* Move our sections */
-    CIRCLEQ_FOREACH(ms, &first->childScnList, elemChildScn) {
+    CIRCLEQ_FOREACH(ms, &lowestAddr->childScnList, elemChildScn) {
       if (ms->shdr.sh_offset >= injOffset) {
         ms->shdr.sh_offset += size;
       }
     }
 
     /* Move our PHDRs */
-    CIRCLEQ_FOREACH(mp, &first->childPhdrList, elemChildPhdr) {
+    CIRCLEQ_FOREACH(mp, &lowestAddr->childPhdrList, elemChildPhdr) {
       if (mp->phdr.p_offset >= injOffset) {
         mp->phdr.p_offset += size;
       } else {
@@ -199,19 +280,52 @@ GElf_Addr elfu_mLayoutGetSpaceInPhdr(ElfuElf *me, GElf_Word size,
     assert(size <= shiftStuffAtAfterOffset(me, injOffset + 1, size));
 
     /* Remap ourselves */
-    first->phdr.p_vaddr -= size;
-    first->phdr.p_paddr -= size;
-    first->phdr.p_filesz += size;
-    first->phdr.p_memsz += size;
+    lowestAddr->phdr.p_vaddr -= size;
+    lowestAddr->phdr.p_paddr -= size;
+    lowestAddr->phdr.p_filesz += size;
+    lowestAddr->phdr.p_memsz += size;
 
     injOffset = ROUNDUP(injOffset, align);
 
     if (injPhdr) {
-      *injPhdr = first;
+      *injPhdr = lowestAddr;
+    }
+    return lowestAddr->phdr.p_vaddr + (injOffset - lowestAddr->phdr.p_offset);
+  } else {
+    ElfuPhdr *newmp;
+    GElf_Off injOffset;
+    GElf_Addr injAddr;
+
+    /* Add a new LOAD PHDR. */
+    newmp = appendPhdr(me);
+    if (!newmp) {
+      goto ERROR;
     }
-    return first->phdr.p_vaddr + (injOffset - first->phdr.p_offset);
+
+    /* ELF spec: We need (p_offset % p_align) == (p_vaddr % p_align) */
+    injOffset = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
+    injOffset = ROUNDUP(injOffset, align);
+    injAddr = OFFS_END(highestAddr->phdr.p_vaddr, highestAddr->phdr.p_memsz);
+    injAddr = ROUNDUP(injAddr, highestAddr->phdr.p_align);
+    injAddr += injOffset % highestAddr->phdr.p_align;
+
+    newmp->phdr.p_align = highestAddr->phdr.p_align;
+    newmp->phdr.p_filesz = size;
+    newmp->phdr.p_memsz = size;
+    newmp->phdr.p_flags = PF_R | (x ? PF_X : 0) | (w ? PF_W : 0);
+    newmp->phdr.p_type = PT_LOAD;
+    newmp->phdr.p_offset = injOffset;
+    newmp->phdr.p_vaddr = injAddr;
+    newmp->phdr.p_paddr = newmp->phdr.p_vaddr;
+
+    *injPhdr = newmp;
+
+    return injAddr;
   }
 
+
+
+
   ERROR:
   if (injPhdr) {
     *injPhdr = NULL;
@@ -222,84 +336,52 @@ GElf_Addr elfu_mLayoutGetSpaceInPhdr(ElfuElf *me, GElf_Word size,
 
 
 
-static int cmpPhdrOffs(const void *mp1, const void *mp2)
-{
-  ElfuPhdr *p1;
-  ElfuPhdr *p2;
-
-  assert(mp1);
-  assert(mp2);
-
-  p1 = *(ElfuPhdr**)mp1;
-  p2 = *(ElfuPhdr**)mp2;
-
-  assert(p1);
-  assert(p2);
-
-
-  if (p1->phdr.p_offset < p2->phdr.p_offset) {
-    return -1;
-  } else if (p1->phdr.p_offset == p2->phdr.p_offset) {
-    return 0;
-  } else /* if (p1->phdr.p_offset > p2->phdr.p_offset) */ {
-    return 1;
-  }
-}
 
 int elfu_mLayoutAuto(ElfuElf *me)
 {
+  ElfuPhdr *lowestAddr;
+  ElfuPhdr *highestAddr;
+  ElfuPhdr *lowestOffs;
+  ElfuPhdr *highestOffsEnd;
   ElfuPhdr *mp;
   ElfuScn *ms;
   ElfuPhdr **phdrArr;
   GElf_Off lastend = 0;
-  size_t i, j;
 
   assert(me);
 
+  /* Find first and last LOAD PHDRs. */
+  elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
+                              &lowestOffs, &highestOffsEnd);
+
   phdrArr = malloc(elfu_mPhdrCount(me) * sizeof(*phdrArr));
   if (!phdrArr) {
     ELFU_WARN("elfu_mLayoutAuto: malloc failed for phdrArr.\n");
     return 1;
   }
 
-  i = 0;
-  CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
-    if (mp->phdr.p_type != PT_LOAD) {
-      continue;
-    }
-
-    phdrArr[i] = mp;
-    i++;
-  }
-
-  /* Assume we have at least one LOAD PHDR,
-   * and that it ends after EHDR and PHDRs */
-  assert(i > 1);
 
-  /* Sort array by file offset */
-  qsort(phdrArr, i, sizeof(*phdrArr), cmpPhdrOffs);
+  lastend = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
 
-  lastend = OFFS_END(phdrArr[0]->phdr.p_offset, phdrArr[0]->phdr.p_filesz);
 
-  /* Wiggle offsets of 2nd, 3rd etc so take minimum space */
-  for (j = 1; j < i; j++) {
-    GElf_Off subalign = phdrArr[j]->phdr.p_offset % phdrArr[j]->phdr.p_align;
+  /* If PHDRs are not mapped into memory, place them after LOAD segments. */
+  mp = elfu_mPhdrByOffset(me, me->ehdr.e_phoff);
+  if (!mp) {
+    lastend = ROUNDUP(lastend, 8);
+    me->ehdr.e_phoff = lastend;
+    lastend += me->ehdr.e_phnum * me->ehdr.e_phentsize;
+  } else {
+    /* Update size of PHDR PHDR */
+    ElfuPhdr *phdrmp;
 
-    if ((lastend % phdrArr[j]->phdr.p_align) <= subalign) {
-      lastend += subalign - (lastend % phdrArr[j]->phdr.p_align);
-    } else {
-      lastend += phdrArr[j]->phdr.p_align - ((lastend % phdrArr[j]->phdr.p_align) - subalign);
+    CIRCLEQ_FOREACH(phdrmp, &mp->childPhdrList, elemChildPhdr) {
+      if (phdrmp->phdr.p_type == PT_PHDR) {
+        phdrmp->phdr.p_filesz = elfu_mPhdrCount(me) * me->ehdr.e_phentsize;
+        phdrmp->phdr.p_memsz = elfu_mPhdrCount(me) * me->ehdr.e_phentsize;
+      }
     }
-
-    phdrArr[j]->phdr.p_offset = lastend;
-
-    elfu_mPhdrUpdateChildOffsets(phdrArr[j]);
-
-    lastend = OFFS_END(phdrArr[j]->phdr.p_offset, phdrArr[j]->phdr.p_filesz);
   }
 
-  free(phdrArr);
-
 
   /* Place orphaned sections afterwards, maintaining alignment */
   CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {