GPLv2 release
[centaur.git] / src / libelfu / modelops / layout.c
1 /* This file is part of centaur.
2  *
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.
6
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.
11
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/>.
14  */
15
16 #include <assert.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <libelfu/libelfu.h>
20
21
22
23 static GElf_Word shiftStuffAtAfterOffset(ElfuElf *me,
24                                          GElf_Off offset,
25                                          GElf_Word size)
26 {
27   ElfuPhdr *mp;
28   ElfuScn *ms;
29   /* Force a minimum alignment, just to be sure. */
30   GElf_Word align = 64;
31
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;
38       }
39     }
40   }
41
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;
46       }
47     }
48   }
49
50   size = ROUNDUP(size, align);
51
52   /* Shift stuff */
53   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
54     if (mp->phdr.p_type != PT_LOAD) {
55       continue;
56     }
57
58     if (mp->phdr.p_offset >= offset) {
59       mp->phdr.p_offset += size;
60
61       elfu_mPhdrUpdateChildOffsets(mp);
62     }
63   }
64
65   CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
66     if (ms->shdr.sh_offset >= offset) {
67       ms->shdr.sh_offset += size;
68     }
69   }
70
71   if (me->ehdr.e_phoff >= offset) {
72     me->ehdr.e_phoff += size;
73   }
74
75   if (me->ehdr.e_shoff >= offset) {
76     me->ehdr.e_shoff += size;
77   }
78
79   return size;
80 }
81
82
83
84
85
86 static ElfuPhdr* appendPhdr(ElfuElf *me)
87 {
88   ElfuPhdr *lowestAddr;
89   ElfuPhdr *highestAddr;
90   ElfuPhdr *lowestOffs;
91   ElfuPhdr *highestOffsEnd;
92   ElfuPhdr *phdrmp;
93   ElfuPhdr *newmp;
94
95   ELFU_DEBUG("Appending new PHDR\n");
96
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);
100   if (!phdrmp) {
101     /* No LOAD maps PHDRs into memory. Let re-layouter move them. */
102   } else {
103     GElf_Off phdr_maxsz = OFFS_END(phdrmp->phdr.p_offset, phdrmp->phdr.p_filesz);
104     ElfuScn *firstms = CIRCLEQ_FIRST(&phdrmp->childScnList);
105
106     /* How much can the PHDR table expand within its LOAD segment? */
107     if (firstms) {
108       phdr_maxsz = MIN(firstms->shdr.sh_offset, phdr_maxsz);
109     }
110     phdr_maxsz -= me->ehdr.e_phoff;
111
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)) {
121       ElfuPhdr *mp;
122       ElfuScn *ms;
123       GElf_Word size = ROUNDUP(me->ehdr.e_phentsize, phdrmp->phdr.p_align);
124
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;
129         }
130       }
131
132       /* Move our PHDRs */
133       CIRCLEQ_FOREACH(mp, &phdrmp->childPhdrList, elemChildPhdr) {
134         if (mp->phdr.p_offset > me->ehdr.e_phoff) {
135           mp->phdr.p_offset += size;
136         } else {
137           mp->phdr.p_vaddr -= size;
138           mp->phdr.p_paddr -= size;
139         }
140
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;
144         }
145       }
146
147       /* Move other PHDRs and sections */
148       assert(size <= shiftStuffAtAfterOffset(me, me->ehdr.e_phoff + 1, size));
149
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;
155     }
156   }
157
158   newmp = elfu_mPhdrAlloc();
159   assert(newmp);
160   CIRCLEQ_INSERT_TAIL(&me->phdrList, newmp, elem);
161
162   return newmp;
163 }
164
165
166
167
168
169 /* Finds a suitable PHDR to insert a hole into and expands it
170  * if necessary.
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,
175                                      ElfuPhdr **injPhdr)
176 {
177   ElfuPhdr *lowestAddr;
178   ElfuPhdr *highestAddr;
179   ElfuPhdr *lowestOffs;
180   ElfuPhdr *highestOffsEnd;
181   ElfuPhdr *mp;
182
183   assert(!(w && x));
184
185   /* Treat read-only data as executable.
186    * That's what the GNU toolchain does on x86. */
187   if (!w && !x) {
188     x = 1;
189   }
190
191   /* Find first and last LOAD PHDRs. */
192   elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
193                               &lowestOffs, &highestOffsEnd);
194
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;
203
204     /* Expand NOBITS if any */
205     if (nobitsize > 0) {
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);
208       ElfuScn *ms;
209
210       ELFU_INFO("Expanding NOBITS at address 0x%x...\n", (unsigned)endAddr);
211
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);
218           if (!ms->databuf) {
219             ELFU_WARN("mExpandNobits: Could not allocate %u bytes for NOBITS expansion. Data may be inconsistent.\n",
220                       (unsigned)ms->shdr.sh_size);
221             assert(0);
222             goto ERROR;
223           }
224
225           ms->shdr.sh_type = SHT_PROGBITS;
226           ms->shdr.sh_addr = endAddr;
227         }
228       }
229
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);
235     }
236
237     /* Calculate how much space we need, taking alignment into account */
238     size += ROUNDUP(injOffset, align) - injOffset;
239
240     /* If there is not enough space left, create even more. */
241     if (injSpace < size) {
242       injSpace += shiftStuffAtAfterOffset(me, injOffset, size - injSpace);
243     }
244     assert(injSpace >= size);
245
246     /* Remap ourselves */
247     highestAddr->phdr.p_filesz += size;
248     highestAddr->phdr.p_memsz += size;
249
250     injOffset = ROUNDUP(injOffset, align);
251
252     if (injPhdr) {
253       *injPhdr = highestAddr;
254     }
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);
266     ElfuScn *ms;
267
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);
272
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;
276
277     /* Move our sections */
278     CIRCLEQ_FOREACH(ms, &lowestAddr->childScnList, elemChildScn) {
279       if (ms->shdr.sh_offset >= injOffset) {
280         ms->shdr.sh_offset += size;
281       }
282     }
283
284     /* Move our PHDRs */
285     CIRCLEQ_FOREACH(mp, &lowestAddr->childPhdrList, elemChildPhdr) {
286       if (mp->phdr.p_offset >= injOffset) {
287         mp->phdr.p_offset += size;
288       } else {
289         mp->phdr.p_vaddr -= size;
290         mp->phdr.p_paddr -= size;
291       }
292     }
293
294     /* Move other PHDRs and sections */
295     assert(size <= shiftStuffAtAfterOffset(me, injOffset + 1, size));
296
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;
302
303     injOffset = ROUNDUP(injOffset, align);
304
305     if (injPhdr) {
306       *injPhdr = lowestAddr;
307     }
308     return lowestAddr->phdr.p_vaddr + (injOffset - lowestAddr->phdr.p_offset);
309   } else {
310     ElfuPhdr *newmp;
311     GElf_Off injOffset;
312     GElf_Addr injAddr;
313
314     /* Add a new LOAD PHDR. */
315     newmp = appendPhdr(me);
316     if (!newmp) {
317       goto ERROR;
318     }
319
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;
326
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;
335
336     *injPhdr = newmp;
337
338     return injAddr;
339   }
340
341
342
343
344   ERROR:
345   if (injPhdr) {
346     *injPhdr = NULL;
347   }
348   return 0;
349 }
350
351
352
353
354
355 int elfu_mLayoutAuto(ElfuElf *me)
356 {
357   ElfuPhdr *lowestAddr;
358   ElfuPhdr *highestAddr;
359   ElfuPhdr *lowestOffs;
360   ElfuPhdr *highestOffsEnd;
361   ElfuPhdr *mp;
362   ElfuScn *ms;
363   GElf_Off lastend = 0;
364
365   assert(me);
366
367   /* Find first and last LOAD PHDRs. */
368   elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
369                               &lowestOffs, &highestOffsEnd);
370
371
372   lastend = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
373
374
375   /* If PHDRs are not mapped into memory, place them after LOAD segments. */
376   mp = elfu_mPhdrByOffset(me, me->ehdr.e_phoff);
377   if (!mp) {
378     lastend = ROUNDUP(lastend, 8);
379     me->ehdr.e_phoff = lastend;
380     lastend += me->ehdr.e_phnum * me->ehdr.e_phentsize;
381   } else {
382     /* Update size of PHDR PHDR */
383     ElfuPhdr *phdrmp;
384
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;
389       }
390     }
391   }
392
393
394   /* Place orphaned sections afterwards, maintaining alignment */
395   CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
396     lastend = ROUNDUP(lastend, ms->shdr.sh_addralign);
397
398     ms->shdr.sh_offset = lastend;
399
400     lastend = OFFS_END(ms->shdr.sh_offset, SCNFILESIZE(&ms->shdr));
401   }
402
403
404   /* Move SHDRs to end */
405   lastend = ROUNDUP(lastend, 8);
406   me->ehdr.e_shoff = lastend;
407
408
409   return 0;
410 }