summaryrefslogtreecommitdiff
path: root/src/modelops/layout.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modelops/layout.c')
-rw-r--r--src/modelops/layout.c320
1 files changed, 320 insertions, 0 deletions
diff --git a/src/modelops/layout.c b/src/modelops/layout.c
new file mode 100644
index 0000000..e4b3fb1
--- /dev/null
+++ b/src/modelops/layout.c
@@ -0,0 +1,320 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libelfu/libelfu.h>
+
+
+
+static GElf_Word shiftStuffAtAfterOffset(ElfuElf *me,
+ GElf_Off offset,
+ GElf_Word size)
+{
+ ElfuPhdr *mp;
+ ElfuScn *ms;
+ /* Force a minimum alignment, just to be sure. */
+ GElf_Word align = 64;
+
+ /* Find maximum alignment size by which we have to shift.
+ * Assumes alignment sizes are always 2^x. */
+ CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
+ if (mp->phdr.p_offset >= offset) {
+ if (mp->phdr.p_align > align) {
+ align = mp->phdr.p_align;
+ }
+ }
+ }
+
+ CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
+ if (ms->shdr.sh_offset >= offset) {
+ if (ms->shdr.sh_addralign > align) {
+ align = ms->shdr.sh_addralign;
+ }
+ }
+ }
+
+ size = ROUNDUP(size, align);
+
+ /* Shift stuff */
+ CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
+ if (mp->phdr.p_type != PT_LOAD) {
+ continue;
+ }
+
+ if (mp->phdr.p_offset >= offset) {
+ mp->phdr.p_offset += size;
+
+ elfu_mPhdrUpdateChildOffsets(mp);
+ }
+ }
+
+ CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
+ if (ms->shdr.sh_offset >= offset) {
+ ms->shdr.sh_offset += size;
+ }
+ }
+
+ if (me->ehdr.e_phoff >= offset) {
+ me->ehdr.e_phoff += size;
+ }
+
+ if (me->ehdr.e_shoff >= offset) {
+ me->ehdr.e_shoff += size;
+ }
+
+ return size;
+}
+
+
+
+
+
+/* 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
+ * the operation failed. */
+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 *mp;
+
+ assert(!(w && x));
+
+ /* Treat read-only data as executable.
+ * That's what the GNU toolchain does on x86. */
+ if (!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;
+ }
+ }
+
+ if ((w && (last->phdr.p_flags & PF_W))
+ || (x && (last->phdr.p_flags & PF_X))) {
+ /* Need to append. */
+ GElf_Off injOffset = OFFS_END(last->phdr.p_offset, last->phdr.p_filesz);
+ GElf_Word injSpace = 0;
+ GElf_Word nobitsize = last->phdr.p_memsz - last->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);
+ ElfuScn *ms;
+
+ ELFU_INFO("Expanding NOBITS at address 0x%jx...\n", endAddr);
+
+ CIRCLEQ_FOREACH(ms, &last->childScnList, elemChildScn) {
+ if (ms->shdr.sh_offset == endOff) {
+ assert(ms->shdr.sh_type == SHT_NOBITS);
+ assert(ms->shdr.sh_size == nobitsize);
+ ms->data.d_buf = malloc(ms->shdr.sh_size);
+ memset(ms->data.d_buf, '\0', ms->shdr.sh_size);
+ if (!ms->data.d_buf) {
+ ELFU_WARN("mExpandNobits: Could not allocate %jd bytes for NOBITS expansion. Data may be inconsistent.\n", ms->shdr.sh_size);
+ assert(0);
+ goto ERROR;
+ }
+
+ ms->data.d_align = 1;
+ ms->data.d_off = 0;
+ ms->data.d_type = ELF_T_BYTE;
+ ms->data.d_size = ms->shdr.sh_size;
+ ms->data.d_version = elf_version(EV_NONE);
+
+ ms->shdr.sh_type = SHT_PROGBITS;
+ ms->shdr.sh_addr = endAddr;
+ }
+ }
+
+ injSpace += shiftStuffAtAfterOffset(me, endOff, nobitsize);
+ injSpace -= nobitsize;
+ injOffset += nobitsize;
+ last->phdr.p_filesz += nobitsize;
+ assert(last->phdr.p_filesz == last->phdr.p_memsz);
+ }
+
+ /* Calculate how much space we need, taking alignment into account */
+ size += ROUNDUP(injOffset, align) - injOffset;
+
+ /* If there is not enough space left, create even more. */
+ if (injSpace < size) {
+ injSpace += shiftStuffAtAfterOffset(me, injOffset, size - injSpace);
+ }
+ assert(injSpace >= size);
+
+ /* Remap ourselves */
+ last->phdr.p_filesz += size;
+ last->phdr.p_memsz += size;
+
+ injOffset = ROUNDUP(injOffset, align);
+
+ if (injPhdr) {
+ *injPhdr = last;
+ }
+ 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))) {
+ /* Need to prepend or split up the PHDR. */
+ GElf_Off injOffset = OFFS_END(first->phdr.p_offset, first->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);
+
+ /* Find first section. We assume there is at least one. */
+ assert(!CIRCLEQ_EMPTY(&first->childScnList));
+ injOffset = CIRCLEQ_FIRST(&first->childScnList)->shdr.sh_offset;
+
+ /* Move our sections */
+ CIRCLEQ_FOREACH(ms, &first->childScnList, elemChildScn) {
+ if (ms->shdr.sh_offset >= injOffset) {
+ ms->shdr.sh_offset += size;
+ }
+ }
+
+ /* Move our PHDRs */
+ CIRCLEQ_FOREACH(mp, &first->childPhdrList, elemChildPhdr) {
+ if (mp->phdr.p_offset >= injOffset) {
+ mp->phdr.p_offset += size;
+ } else {
+ mp->phdr.p_vaddr -= size;
+ mp->phdr.p_paddr -= size;
+ }
+ }
+
+ /* Move other PHDRs and sections */
+ assert(size <= shiftStuffAtAfterOffset(me, injOffset, size));
+
+ /* Remap ourselves */
+ first->phdr.p_vaddr -= size;
+ first->phdr.p_paddr -= size;
+ first->phdr.p_filesz += size;
+ first->phdr.p_memsz += size;
+
+ injOffset = ROUNDUP(injOffset, align);
+
+ if (injPhdr) {
+ *injPhdr = first;
+ }
+ return first->phdr.p_vaddr + (injOffset - first->phdr.p_offset);
+ }
+
+ ERROR:
+ if (injPhdr) {
+ *injPhdr = NULL;
+ }
+ return 0;
+}
+
+
+
+
+static int cmpPhdrOffs(const void *mp1, const void *mp2)
+{
+ assert(mp1);
+ assert(mp2);
+
+ ElfuPhdr *p1 = *(ElfuPhdr**)mp1;
+ ElfuPhdr *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 *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;
+ }
+
+ 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(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);
+ }
+
+ 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) {
+ lastend = ROUNDUP(lastend, ms->shdr.sh_addralign);
+
+ ms->shdr.sh_offset = lastend;
+
+ lastend = OFFS_END(ms->shdr.sh_offset, SCNFILESIZE(&ms->shdr));
+ }
+
+
+ /* Move SHDRs to end */
+ lastend = ROUNDUP(lastend, 8);
+ me->ehdr.e_shoff = lastend;
+
+
+ return 0;
+}