+/* This file is part of centaur.
+ *
+ * centaur is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 2 as
+ * published by the Free Software Foundation.
+
+ * centaur is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with centaur. If not, see <http://www.gnu.org/licenses/>.
+ */
+
#include <assert.h>
#include <stdlib.h>
#include <string.h>
+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
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));
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);
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 */
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 {
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;
-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);
- phdrArr = malloc(elfu_mPhdrCount(me) * sizeof(*phdrArr));
- if (!phdrArr) {
- ELFU_WARN("elfu_mLayoutAuto: malloc failed for phdrArr.\n");
- return 1;
- }
+ /* Find first and last LOAD PHDRs. */
+ elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
+ &lowestOffs, &highestOffsEnd);
- i = 0;
- CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
- if (mp->phdr.p_type != PT_LOAD) {
- continue;
- }
- phdrArr[i] = mp;
- i++;
- }
+ lastend = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
- /* 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);
+ /* 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;
- 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 ((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) {