1 /* This file is part of centaur.
3 * centaur is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License 2 as
5 * published by the Free Software Foundation.
7 * centaur is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with centaur. If not, see <http://www.gnu.org/licenses/>.
19 #include <libelfu/libelfu.h>
23 static GElf_Word shiftStuffAtAfterOffset(ElfuElf *me,
29 /* Force a minimum alignment, just to be sure. */
32 /* Find maximum alignment size by which we have to shift.
33 * Assumes alignment sizes are always 2^x. */
34 CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
35 if (mp->phdr.p_offset >= offset) {
36 if (mp->phdr.p_align > align) {
37 align = mp->phdr.p_align;
42 CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
43 if (ms->shdr.sh_offset >= offset) {
44 if (ms->shdr.sh_addralign > align) {
45 align = ms->shdr.sh_addralign;
50 size = ROUNDUP(size, align);
53 CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
54 if (mp->phdr.p_type != PT_LOAD) {
58 if (mp->phdr.p_offset >= offset) {
59 mp->phdr.p_offset += size;
61 elfu_mPhdrUpdateChildOffsets(mp);
65 CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
66 if (ms->shdr.sh_offset >= offset) {
67 ms->shdr.sh_offset += size;
71 if (me->ehdr.e_phoff >= offset) {
72 me->ehdr.e_phoff += size;
75 if (me->ehdr.e_shoff >= offset) {
76 me->ehdr.e_shoff += size;
86 static ElfuPhdr* appendPhdr(ElfuElf *me)
89 ElfuPhdr *highestAddr;
91 ElfuPhdr *highestOffsEnd;
95 ELFU_DEBUG("Appending new PHDR\n");
97 /* See if we have enough space for more PHDRs. If not, expand
98 * the PHDR they are in. */
99 phdrmp = elfu_mPhdrByOffset(me, me->ehdr.e_phoff);
101 /* No LOAD maps PHDRs into memory. Let re-layouter move them. */
103 GElf_Off phdr_maxsz = OFFS_END(phdrmp->phdr.p_offset, phdrmp->phdr.p_filesz);
104 ElfuScn *firstms = CIRCLEQ_FIRST(&phdrmp->childScnList);
106 /* How much can the PHDR table expand within its LOAD segment? */
108 phdr_maxsz = MIN(firstms->shdr.sh_offset, phdr_maxsz);
110 phdr_maxsz -= me->ehdr.e_phoff;
112 /* If we don't have enough space, try to make some by expanding
113 * the LOAD segment we are in. There is no other way.
114 * Also, we can only expand if it is the first LOAD PHDR. */
115 elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
116 &lowestOffs, &highestOffsEnd);
117 if (phdr_maxsz < (me->ehdr.e_phnum + 1) * me->ehdr.e_phentsize
118 && phdrmp == lowestAddr
119 && phdrmp == lowestOffs
120 && (lowestAddr->phdr.p_vaddr >= 2 * lowestAddr->phdr.p_align)) {
123 GElf_Word size = ROUNDUP(me->ehdr.e_phentsize, phdrmp->phdr.p_align);
125 /* Move our sections */
126 CIRCLEQ_FOREACH(ms, &phdrmp->childScnList, elemChildScn) {
127 if (ms->shdr.sh_offset >= me->ehdr.e_phoff) {
128 ms->shdr.sh_offset += size;
133 CIRCLEQ_FOREACH(mp, &phdrmp->childPhdrList, elemChildPhdr) {
134 if (mp->phdr.p_offset > me->ehdr.e_phoff) {
135 mp->phdr.p_offset += size;
137 mp->phdr.p_vaddr -= size;
138 mp->phdr.p_paddr -= size;
141 if (mp->phdr.p_type == PT_PHDR) {
142 mp->phdr.p_filesz += me->ehdr.e_phentsize;
143 mp->phdr.p_memsz += me->ehdr.e_phentsize;
147 /* Move other PHDRs and sections */
148 assert(size <= shiftStuffAtAfterOffset(me, me->ehdr.e_phoff + 1, size));
150 /* Remap ourselves */
151 phdrmp->phdr.p_vaddr -= size;
152 phdrmp->phdr.p_paddr -= size;
153 phdrmp->phdr.p_filesz += size;
154 phdrmp->phdr.p_memsz += size;
158 newmp = elfu_mPhdrAlloc();
160 CIRCLEQ_INSERT_TAIL(&me->phdrList, newmp, elem);
169 /* Finds a suitable PHDR to insert a hole into and expands it
171 * Returns memory address the hole will be mapped to, or 0 if
172 * the operation failed. */
173 GElf_Addr elfu_mLayoutGetSpaceInPhdr(ElfuElf *me, GElf_Word size,
174 GElf_Word align, int w, int x,
177 ElfuPhdr *lowestAddr;
178 ElfuPhdr *highestAddr;
179 ElfuPhdr *lowestOffs;
180 ElfuPhdr *highestOffsEnd;
185 /* Treat read-only data as executable.
186 * That's what the GNU toolchain does on x86. */
191 /* Find first and last LOAD PHDRs. */
192 elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
193 &lowestOffs, &highestOffsEnd);
195 if (((w && (highestAddr->phdr.p_flags & PF_W))
196 || (x && (highestAddr->phdr.p_flags & PF_X)))
197 /* Merging only works if the LOAD is the last both in file and mem */
198 && highestAddr == highestOffsEnd) {
199 /* Need to append. */
200 GElf_Off injOffset = OFFS_END(highestAddr->phdr.p_offset, highestAddr->phdr.p_filesz);
201 GElf_Word injSpace = 0;
202 GElf_Word nobitsize = highestAddr->phdr.p_memsz - highestAddr->phdr.p_filesz;
204 /* Expand NOBITS if any */
206 GElf_Off endOff = OFFS_END(highestAddr->phdr.p_offset, highestAddr->phdr.p_filesz);
207 GElf_Off endAddr = OFFS_END(highestAddr->phdr.p_vaddr, highestAddr->phdr.p_filesz);
210 ELFU_INFO("Expanding NOBITS at address 0x%x...\n", (unsigned)endAddr);
212 CIRCLEQ_FOREACH(ms, &highestAddr->childScnList, elemChildScn) {
213 if (ms->shdr.sh_offset == endOff) {
214 assert(ms->shdr.sh_type == SHT_NOBITS);
215 assert(ms->shdr.sh_size == nobitsize);
216 ms->databuf = malloc(ms->shdr.sh_size);
217 memset(ms->databuf, '\0', ms->shdr.sh_size);
219 ELFU_WARN("mExpandNobits: Could not allocate %u bytes for NOBITS expansion. Data may be inconsistent.\n",
220 (unsigned)ms->shdr.sh_size);
225 ms->shdr.sh_type = SHT_PROGBITS;
226 ms->shdr.sh_addr = endAddr;
230 injSpace += shiftStuffAtAfterOffset(me, endOff, nobitsize);
231 injSpace -= nobitsize;
232 injOffset += nobitsize;
233 highestAddr->phdr.p_filesz += nobitsize;
234 assert(highestAddr->phdr.p_filesz == highestAddr->phdr.p_memsz);
237 /* Calculate how much space we need, taking alignment into account */
238 size += ROUNDUP(injOffset, align) - injOffset;
240 /* If there is not enough space left, create even more. */
241 if (injSpace < size) {
242 injSpace += shiftStuffAtAfterOffset(me, injOffset, size - injSpace);
244 assert(injSpace >= size);
246 /* Remap ourselves */
247 highestAddr->phdr.p_filesz += size;
248 highestAddr->phdr.p_memsz += size;
250 injOffset = ROUNDUP(injOffset, align);
253 *injPhdr = highestAddr;
255 return highestAddr->phdr.p_vaddr + (injOffset - highestAddr->phdr.p_offset);
256 } else if (((w && (lowestAddr->phdr.p_flags & PF_W))
257 || (x && (lowestAddr->phdr.p_flags & PF_X)))
258 && /* Enough space to expand downwards? */
259 (lowestAddr->phdr.p_vaddr >= ((2 * lowestAddr->phdr.p_align)
260 + ROUNDUP(size, lowestAddr->phdr.p_align)))
261 /* Merging only works if the LOAD is the first both in file and mem */
262 && lowestAddr == lowestOffs) {
263 /* Need to prepend or split up the PHDR. */
264 GElf_Off injOffset = OFFS_END(lowestAddr->phdr.p_offset,
265 lowestAddr->phdr.p_filesz);
268 /* Round up size to take PHDR alignment into account.
269 * We assume that this is a multiple of the alignment asked for. */
270 assert(lowestAddr->phdr.p_align >= align);
271 size = ROUNDUP(size, lowestAddr->phdr.p_align);
273 /* Find first section. We assume there is at least one. */
274 assert(!CIRCLEQ_EMPTY(&lowestAddr->childScnList));
275 injOffset = CIRCLEQ_FIRST(&lowestAddr->childScnList)->shdr.sh_offset;
277 /* Move our sections */
278 CIRCLEQ_FOREACH(ms, &lowestAddr->childScnList, elemChildScn) {
279 if (ms->shdr.sh_offset >= injOffset) {
280 ms->shdr.sh_offset += size;
285 CIRCLEQ_FOREACH(mp, &lowestAddr->childPhdrList, elemChildPhdr) {
286 if (mp->phdr.p_offset >= injOffset) {
287 mp->phdr.p_offset += size;
289 mp->phdr.p_vaddr -= size;
290 mp->phdr.p_paddr -= size;
294 /* Move other PHDRs and sections */
295 assert(size <= shiftStuffAtAfterOffset(me, injOffset + 1, size));
297 /* Remap ourselves */
298 lowestAddr->phdr.p_vaddr -= size;
299 lowestAddr->phdr.p_paddr -= size;
300 lowestAddr->phdr.p_filesz += size;
301 lowestAddr->phdr.p_memsz += size;
303 injOffset = ROUNDUP(injOffset, align);
306 *injPhdr = lowestAddr;
308 return lowestAddr->phdr.p_vaddr + (injOffset - lowestAddr->phdr.p_offset);
314 /* Add a new LOAD PHDR. */
315 newmp = appendPhdr(me);
320 /* ELF spec: We need (p_offset % p_align) == (p_vaddr % p_align) */
321 injOffset = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
322 injOffset = ROUNDUP(injOffset, align);
323 injAddr = OFFS_END(highestAddr->phdr.p_vaddr, highestAddr->phdr.p_memsz);
324 injAddr = ROUNDUP(injAddr, highestAddr->phdr.p_align);
325 injAddr += injOffset % highestAddr->phdr.p_align;
327 newmp->phdr.p_align = highestAddr->phdr.p_align;
328 newmp->phdr.p_filesz = size;
329 newmp->phdr.p_memsz = size;
330 newmp->phdr.p_flags = PF_R | (x ? PF_X : 0) | (w ? PF_W : 0);
331 newmp->phdr.p_type = PT_LOAD;
332 newmp->phdr.p_offset = injOffset;
333 newmp->phdr.p_vaddr = injAddr;
334 newmp->phdr.p_paddr = newmp->phdr.p_vaddr;
355 int elfu_mLayoutAuto(ElfuElf *me)
357 ElfuPhdr *lowestAddr;
358 ElfuPhdr *highestAddr;
359 ElfuPhdr *lowestOffs;
360 ElfuPhdr *highestOffsEnd;
363 GElf_Off lastend = 0;
367 /* Find first and last LOAD PHDRs. */
368 elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
369 &lowestOffs, &highestOffsEnd);
372 lastend = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
375 /* If PHDRs are not mapped into memory, place them after LOAD segments. */
376 mp = elfu_mPhdrByOffset(me, me->ehdr.e_phoff);
378 lastend = ROUNDUP(lastend, 8);
379 me->ehdr.e_phoff = lastend;
380 lastend += me->ehdr.e_phnum * me->ehdr.e_phentsize;
382 /* Update size of PHDR PHDR */
385 CIRCLEQ_FOREACH(phdrmp, &mp->childPhdrList, elemChildPhdr) {
386 if (phdrmp->phdr.p_type == PT_PHDR) {
387 phdrmp->phdr.p_filesz = elfu_mPhdrCount(me) * me->ehdr.e_phentsize;
388 phdrmp->phdr.p_memsz = elfu_mPhdrCount(me) * me->ehdr.e_phentsize;
394 /* Place orphaned sections afterwards, maintaining alignment */
395 CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
396 lastend = ROUNDUP(lastend, ms->shdr.sh_addralign);
398 ms->shdr.sh_offset = lastend;
400 lastend = OFFS_END(ms->shdr.sh_offset, SCNFILESIZE(&ms->shdr));
404 /* Move SHDRs to end */
405 lastend = ROUNDUP(lastend, 8);
406 me->ehdr.e_shoff = lastend;