/* 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 .
*/
#include
#include
#include
#include
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;
}
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
* the operation failed. */
GElf_Addr elfu_mLayoutGetSpaceInPhdr(ElfuElf *me, GElf_Word size,
GElf_Word align, int w, int x,
ElfuPhdr **injPhdr)
{
ElfuPhdr *lowestAddr;
ElfuPhdr *highestAddr;
ElfuPhdr *lowestOffs;
ElfuPhdr *highestOffsEnd;
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. */
elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
&lowestOffs, &highestOffsEnd);
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(highestAddr->phdr.p_offset, highestAddr->phdr.p_filesz);
GElf_Word injSpace = 0;
GElf_Word nobitsize = highestAddr->phdr.p_memsz - highestAddr->phdr.p_filesz;
/* Expand NOBITS if any */
if (nobitsize > 0) {
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, &highestAddr->childScnList, elemChildScn) {
if (ms->shdr.sh_offset == endOff) {
assert(ms->shdr.sh_type == SHT_NOBITS);
assert(ms->shdr.sh_size == nobitsize);
ms->databuf = malloc(ms->shdr.sh_size);
memset(ms->databuf, '\0', ms->shdr.sh_size);
if (!ms->databuf) {
ELFU_WARN("mExpandNobits: Could not allocate %u bytes for NOBITS expansion. Data may be inconsistent.\n",
(unsigned)ms->shdr.sh_size);
assert(0);
goto ERROR;
}
ms->shdr.sh_type = SHT_PROGBITS;
ms->shdr.sh_addr = endAddr;
}
}
injSpace += shiftStuffAtAfterOffset(me, endOff, nobitsize);
injSpace -= nobitsize;
injOffset += nobitsize;
highestAddr->phdr.p_filesz += nobitsize;
assert(highestAddr->phdr.p_filesz == highestAddr->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 */
highestAddr->phdr.p_filesz += size;
highestAddr->phdr.p_memsz += size;
injOffset = ROUNDUP(injOffset, align);
if (injPhdr) {
*injPhdr = highestAddr;
}
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? */
(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(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(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(&lowestAddr->childScnList));
injOffset = CIRCLEQ_FIRST(&lowestAddr->childScnList)->shdr.sh_offset;
/* Move our sections */
CIRCLEQ_FOREACH(ms, &lowestAddr->childScnList, elemChildScn) {
if (ms->shdr.sh_offset >= injOffset) {
ms->shdr.sh_offset += size;
}
}
/* Move our PHDRs */
CIRCLEQ_FOREACH(mp, &lowestAddr->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 + 1, size));
/* Remap ourselves */
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 = 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;
}
/* 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;
}
return 0;
}
int elfu_mLayoutAuto(ElfuElf *me)
{
ElfuPhdr *lowestAddr;
ElfuPhdr *highestAddr;
ElfuPhdr *lowestOffs;
ElfuPhdr *highestOffsEnd;
ElfuPhdr *mp;
ElfuScn *ms;
GElf_Off lastend = 0;
assert(me);
/* Find first and last LOAD PHDRs. */
elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
&lowestOffs, &highestOffsEnd);
lastend = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
/* 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;
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;
}
}
}
/* 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;
}