Only make space for more PHDRs if they are in the first LOAD
[centaur.git] / src / libelfu / modelops / layout.c
1 #include <assert.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <libelfu/libelfu.h>
5
6
7
8 static GElf_Word shiftStuffAtAfterOffset(ElfuElf *me,
9                                          GElf_Off offset,
10                                          GElf_Word size)
11 {
12   ElfuPhdr *mp;
13   ElfuScn *ms;
14   /* Force a minimum alignment, just to be sure. */
15   GElf_Word align = 64;
16
17   /* Find maximum alignment size by which we have to shift.
18    * Assumes alignment sizes are always 2^x. */
19   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
20     if (mp->phdr.p_offset >= offset) {
21       if (mp->phdr.p_align > align) {
22         align = mp->phdr.p_align;
23       }
24     }
25   }
26
27   CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
28     if (ms->shdr.sh_offset >= offset) {
29       if (ms->shdr.sh_addralign > align) {
30         align = ms->shdr.sh_addralign;
31       }
32     }
33   }
34
35   size = ROUNDUP(size, align);
36
37   /* Shift stuff */
38   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
39     if (mp->phdr.p_type != PT_LOAD) {
40       continue;
41     }
42
43     if (mp->phdr.p_offset >= offset) {
44       mp->phdr.p_offset += size;
45
46       elfu_mPhdrUpdateChildOffsets(mp);
47     }
48   }
49
50   CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
51     if (ms->shdr.sh_offset >= offset) {
52       ms->shdr.sh_offset += size;
53     }
54   }
55
56   if (me->ehdr.e_phoff >= offset) {
57     me->ehdr.e_phoff += size;
58   }
59
60   if (me->ehdr.e_shoff >= offset) {
61     me->ehdr.e_shoff += size;
62   }
63
64   return size;
65 }
66
67
68
69
70
71 static ElfuPhdr* appendPhdr(ElfuElf *me)
72 {
73   ElfuPhdr *lowestAddr;
74   ElfuPhdr *highestAddr;
75   ElfuPhdr *lowestOffs;
76   ElfuPhdr *highestOffsEnd;
77   ElfuPhdr *phdrmp;
78   ElfuPhdr *newmp;
79
80   ELFU_DEBUG("Appending new PHDR\n");
81
82   /* See if we have enough space for more PHDRs. If not, expand
83    * the PHDR they are in. */
84   phdrmp = elfu_mPhdrByOffset(me, me->ehdr.e_phoff);
85   if (!phdrmp) {
86     /* No LOAD maps PHDRs into memory. Let re-layouter move them. */
87   } else {
88     GElf_Off phdr_maxsz = OFFS_END(phdrmp->phdr.p_offset, phdrmp->phdr.p_filesz);
89     ElfuScn *firstms = CIRCLEQ_FIRST(&phdrmp->childScnList);
90
91     /* How much can the PHDR table expand within its LOAD segment? */
92     if (firstms) {
93       phdr_maxsz = MIN(firstms->shdr.sh_offset, phdr_maxsz);
94     }
95     phdr_maxsz -= me->ehdr.e_phoff;
96
97     /* If we don't have enough space, try to make some by expanding
98      * the LOAD segment we are in. There is no other way.
99      * Also, we can only expand if it is the first LOAD PHDR. */
100     elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
101                                 &lowestOffs, &highestOffsEnd);
102     if (phdr_maxsz < (me->ehdr.e_phnum + 1) * me->ehdr.e_phentsize
103         && phdrmp == lowestAddr
104         && phdrmp == lowestOffs) {
105       ElfuPhdr *mp;
106       ElfuScn *ms;
107       GElf_Word size = ROUNDUP(me->ehdr.e_phentsize, phdrmp->phdr.p_align);
108
109       /* Move our sections */
110       CIRCLEQ_FOREACH(ms, &phdrmp->childScnList, elemChildScn) {
111         if (ms->shdr.sh_offset >= me->ehdr.e_phoff) {
112           ms->shdr.sh_offset += size;
113         }
114       }
115
116       /* Move our PHDRs */
117       CIRCLEQ_FOREACH(mp, &phdrmp->childPhdrList, elemChildPhdr) {
118         if (mp->phdr.p_offset > me->ehdr.e_phoff) {
119           mp->phdr.p_offset += size;
120         } else {
121           mp->phdr.p_vaddr -= size;
122           mp->phdr.p_paddr -= size;
123         }
124
125         if (mp->phdr.p_type == PT_PHDR) {
126           mp->phdr.p_filesz += me->ehdr.e_phentsize;
127           mp->phdr.p_memsz += me->ehdr.e_phentsize;
128         }
129       }
130
131       /* Move other PHDRs and sections */
132       assert(size <= shiftStuffAtAfterOffset(me, me->ehdr.e_phoff + 1, size));
133
134       /* Remap ourselves */
135       phdrmp->phdr.p_vaddr -= size;
136       phdrmp->phdr.p_paddr -= size;
137       phdrmp->phdr.p_filesz += size;
138       phdrmp->phdr.p_memsz += size;
139     }
140   }
141
142   newmp = elfu_mPhdrAlloc();
143   assert(newmp);
144   CIRCLEQ_INSERT_TAIL(&me->phdrList, newmp, elem);
145
146   return newmp;
147 }
148
149
150
151
152
153 /* Finds a suitable PHDR to insert a hole into and expands it
154  * if necessary.
155  * Returns memory address the hole will be mapped to, or 0 if
156  * the operation failed. */
157 GElf_Addr elfu_mLayoutGetSpaceInPhdr(ElfuElf *me, GElf_Word size,
158                                      GElf_Word align, int w, int x,
159                                      ElfuPhdr **injPhdr)
160 {
161   ElfuPhdr *lowestAddr;
162   ElfuPhdr *highestAddr;
163   ElfuPhdr *lowestOffs;
164   ElfuPhdr *highestOffsEnd;
165   ElfuPhdr *mp;
166
167   assert(!(w && x));
168
169   /* Treat read-only data as executable.
170    * That's what the GNU toolchain does on x86. */
171   if (!w && !x) {
172     x = 1;
173   }
174
175   /* Find first and last LOAD PHDRs. */
176   elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
177                               &lowestOffs, &highestOffsEnd);
178
179   if (((w && (highestAddr->phdr.p_flags & PF_W))
180       || (x && (highestAddr->phdr.p_flags & PF_X)))
181       /* Merging only works if the LOAD is the last both in file and mem */
182       && highestAddr == highestOffsEnd) {
183     /* Need to append. */
184     GElf_Off injOffset = OFFS_END(highestAddr->phdr.p_offset, highestAddr->phdr.p_filesz);
185     GElf_Word injSpace = 0;
186     GElf_Word nobitsize = highestAddr->phdr.p_memsz - highestAddr->phdr.p_filesz;
187
188     /* Expand NOBITS if any */
189     if (nobitsize > 0) {
190       GElf_Off endOff = OFFS_END(highestAddr->phdr.p_offset, highestAddr->phdr.p_filesz);
191       GElf_Off endAddr = OFFS_END(highestAddr->phdr.p_vaddr, highestAddr->phdr.p_filesz);
192       ElfuScn *ms;
193
194       ELFU_INFO("Expanding NOBITS at address 0x%x...\n", (unsigned)endAddr);
195
196       CIRCLEQ_FOREACH(ms, &highestAddr->childScnList, elemChildScn) {
197         if (ms->shdr.sh_offset == endOff) {
198           assert(ms->shdr.sh_type == SHT_NOBITS);
199           assert(ms->shdr.sh_size == nobitsize);
200           ms->databuf = malloc(ms->shdr.sh_size);
201           memset(ms->databuf, '\0', ms->shdr.sh_size);
202           if (!ms->databuf) {
203             ELFU_WARN("mExpandNobits: Could not allocate %u bytes for NOBITS expansion. Data may be inconsistent.\n",
204                       (unsigned)ms->shdr.sh_size);
205             assert(0);
206             goto ERROR;
207           }
208
209           ms->shdr.sh_type = SHT_PROGBITS;
210           ms->shdr.sh_addr = endAddr;
211         }
212       }
213
214       injSpace += shiftStuffAtAfterOffset(me, endOff, nobitsize);
215       injSpace -= nobitsize;
216       injOffset += nobitsize;
217       highestAddr->phdr.p_filesz += nobitsize;
218       assert(highestAddr->phdr.p_filesz == highestAddr->phdr.p_memsz);
219     }
220
221     /* Calculate how much space we need, taking alignment into account */
222     size += ROUNDUP(injOffset, align) - injOffset;
223
224     /* If there is not enough space left, create even more. */
225     if (injSpace < size) {
226       injSpace += shiftStuffAtAfterOffset(me, injOffset, size - injSpace);
227     }
228     assert(injSpace >= size);
229
230     /* Remap ourselves */
231     highestAddr->phdr.p_filesz += size;
232     highestAddr->phdr.p_memsz += size;
233
234     injOffset = ROUNDUP(injOffset, align);
235
236     if (injPhdr) {
237       *injPhdr = highestAddr;
238     }
239     return highestAddr->phdr.p_vaddr + (injOffset - highestAddr->phdr.p_offset);
240   } else if (((w && (lowestAddr->phdr.p_flags & PF_W))
241               || (x && (lowestAddr->phdr.p_flags & PF_X)))
242              && /* Enough space to expand downwards? */
243              (lowestAddr->phdr.p_vaddr > 3 * lowestAddr->phdr.p_align)
244              /* Merging only works if the LOAD is the first both in file and mem */
245              && lowestAddr == lowestOffs) {
246     /* Need to prepend or split up the PHDR. */
247     GElf_Off injOffset = OFFS_END(lowestAddr->phdr.p_offset,
248                                   lowestAddr->phdr.p_filesz);
249     ElfuScn *ms;
250
251     /* Round up size to take PHDR alignment into account.
252      * We assume that this is a multiple of the alignment asked for. */
253     assert(lowestAddr->phdr.p_align >= align);
254     size = ROUNDUP(size, lowestAddr->phdr.p_align);
255
256     /* Find first section. We assume there is at least one. */
257     assert(!CIRCLEQ_EMPTY(&lowestAddr->childScnList));
258     injOffset = CIRCLEQ_FIRST(&lowestAddr->childScnList)->shdr.sh_offset;
259
260     /* Move our sections */
261     CIRCLEQ_FOREACH(ms, &lowestAddr->childScnList, elemChildScn) {
262       if (ms->shdr.sh_offset >= injOffset) {
263         ms->shdr.sh_offset += size;
264       }
265     }
266
267     /* Move our PHDRs */
268     CIRCLEQ_FOREACH(mp, &lowestAddr->childPhdrList, elemChildPhdr) {
269       if (mp->phdr.p_offset >= injOffset) {
270         mp->phdr.p_offset += size;
271       } else {
272         mp->phdr.p_vaddr -= size;
273         mp->phdr.p_paddr -= size;
274       }
275     }
276
277     /* Move other PHDRs and sections */
278     assert(size <= shiftStuffAtAfterOffset(me, injOffset + 1, size));
279
280     /* Remap ourselves */
281     lowestAddr->phdr.p_vaddr -= size;
282     lowestAddr->phdr.p_paddr -= size;
283     lowestAddr->phdr.p_filesz += size;
284     lowestAddr->phdr.p_memsz += size;
285
286     injOffset = ROUNDUP(injOffset, align);
287
288     if (injPhdr) {
289       *injPhdr = lowestAddr;
290     }
291     return lowestAddr->phdr.p_vaddr + (injOffset - lowestAddr->phdr.p_offset);
292   } else {
293     ElfuPhdr *newmp;
294     GElf_Off injOffset;
295     GElf_Addr injAddr;
296
297     /* Add a new LOAD PHDR. */
298     newmp = appendPhdr(me);
299     if (!newmp) {
300       goto ERROR;
301     }
302
303     /* ELF spec: We need (p_offset % p_align) == (p_vaddr % p_align) */
304     injOffset = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
305     injOffset = ROUNDUP(injOffset, align);
306     injAddr = OFFS_END(highestAddr->phdr.p_vaddr, highestAddr->phdr.p_memsz);
307     injAddr = ROUNDUP(injAddr, highestAddr->phdr.p_align);
308     injAddr += injOffset % highestAddr->phdr.p_align;
309
310     newmp->phdr.p_align = highestAddr->phdr.p_align;
311     newmp->phdr.p_filesz = size;
312     newmp->phdr.p_memsz = size;
313     newmp->phdr.p_flags = PF_R | (x ? PF_X : 0) | (w ? PF_W : 0);
314     newmp->phdr.p_type = PT_LOAD;
315     newmp->phdr.p_offset = injOffset;
316     newmp->phdr.p_vaddr = injAddr;
317     newmp->phdr.p_paddr = newmp->phdr.p_vaddr;
318
319     *injPhdr = newmp;
320
321     return injAddr;
322   }
323
324
325
326
327   ERROR:
328   if (injPhdr) {
329     *injPhdr = NULL;
330   }
331   return 0;
332 }
333
334
335
336
337
338 int elfu_mLayoutAuto(ElfuElf *me)
339 {
340   ElfuPhdr *lowestAddr;
341   ElfuPhdr *highestAddr;
342   ElfuPhdr *lowestOffs;
343   ElfuPhdr *highestOffsEnd;
344   ElfuPhdr *mp;
345   ElfuScn *ms;
346   ElfuPhdr **phdrArr;
347   GElf_Off lastend = 0;
348
349   assert(me);
350
351   /* Find first and last LOAD PHDRs. */
352   elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
353                               &lowestOffs, &highestOffsEnd);
354
355   phdrArr = malloc(elfu_mPhdrCount(me) * sizeof(*phdrArr));
356   if (!phdrArr) {
357     ELFU_WARN("elfu_mLayoutAuto: malloc failed for phdrArr.\n");
358     return 1;
359   }
360
361
362   lastend = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
363
364
365   /* If PHDRs are not mapped into memory, place them after LOAD segments. */
366   mp = elfu_mPhdrByOffset(me, me->ehdr.e_phoff);
367   if (!mp) {
368     lastend = ROUNDUP(lastend, 8);
369     me->ehdr.e_phoff = lastend;
370     lastend += me->ehdr.e_phnum * me->ehdr.e_phentsize;
371   } else {
372     /* Update size of PHDR PHDR */
373     ElfuPhdr *phdrmp;
374
375     CIRCLEQ_FOREACH(phdrmp, &mp->childPhdrList, elemChildPhdr) {
376       if (phdrmp->phdr.p_type == PT_PHDR) {
377         phdrmp->phdr.p_filesz = elfu_mPhdrCount(me) * me->ehdr.e_phentsize;
378         phdrmp->phdr.p_memsz = elfu_mPhdrCount(me) * me->ehdr.e_phentsize;
379       }
380     }
381   }
382
383
384   /* Place orphaned sections afterwards, maintaining alignment */
385   CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
386     lastend = ROUNDUP(lastend, ms->shdr.sh_addralign);
387
388     ms->shdr.sh_offset = lastend;
389
390     lastend = OFFS_END(ms->shdr.sh_offset, SCNFILESIZE(&ms->shdr));
391   }
392
393
394   /* Move SHDRs to end */
395   lastend = ROUNDUP(lastend, 8);
396   me->ehdr.e_shoff = lastend;
397
398
399   return 0;
400 }