Expand downwards only if there is enough space
[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 >= ((2 * lowestAddr->phdr.p_align)
244                                            + ROUNDUP(size, lowestAddr->phdr.p_align)))
245              /* Merging only works if the LOAD is the first both in file and mem */
246              && lowestAddr == lowestOffs) {
247     /* Need to prepend or split up the PHDR. */
248     GElf_Off injOffset = OFFS_END(lowestAddr->phdr.p_offset,
249                                   lowestAddr->phdr.p_filesz);
250     ElfuScn *ms;
251
252     /* Round up size to take PHDR alignment into account.
253      * We assume that this is a multiple of the alignment asked for. */
254     assert(lowestAddr->phdr.p_align >= align);
255     size = ROUNDUP(size, lowestAddr->phdr.p_align);
256
257     /* Find first section. We assume there is at least one. */
258     assert(!CIRCLEQ_EMPTY(&lowestAddr->childScnList));
259     injOffset = CIRCLEQ_FIRST(&lowestAddr->childScnList)->shdr.sh_offset;
260
261     /* Move our sections */
262     CIRCLEQ_FOREACH(ms, &lowestAddr->childScnList, elemChildScn) {
263       if (ms->shdr.sh_offset >= injOffset) {
264         ms->shdr.sh_offset += size;
265       }
266     }
267
268     /* Move our PHDRs */
269     CIRCLEQ_FOREACH(mp, &lowestAddr->childPhdrList, elemChildPhdr) {
270       if (mp->phdr.p_offset >= injOffset) {
271         mp->phdr.p_offset += size;
272       } else {
273         mp->phdr.p_vaddr -= size;
274         mp->phdr.p_paddr -= size;
275       }
276     }
277
278     /* Move other PHDRs and sections */
279     assert(size <= shiftStuffAtAfterOffset(me, injOffset + 1, size));
280
281     /* Remap ourselves */
282     lowestAddr->phdr.p_vaddr -= size;
283     lowestAddr->phdr.p_paddr -= size;
284     lowestAddr->phdr.p_filesz += size;
285     lowestAddr->phdr.p_memsz += size;
286
287     injOffset = ROUNDUP(injOffset, align);
288
289     if (injPhdr) {
290       *injPhdr = lowestAddr;
291     }
292     return lowestAddr->phdr.p_vaddr + (injOffset - lowestAddr->phdr.p_offset);
293   } else {
294     ElfuPhdr *newmp;
295     GElf_Off injOffset;
296     GElf_Addr injAddr;
297
298     /* Add a new LOAD PHDR. */
299     newmp = appendPhdr(me);
300     if (!newmp) {
301       goto ERROR;
302     }
303
304     /* ELF spec: We need (p_offset % p_align) == (p_vaddr % p_align) */
305     injOffset = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
306     injOffset = ROUNDUP(injOffset, align);
307     injAddr = OFFS_END(highestAddr->phdr.p_vaddr, highestAddr->phdr.p_memsz);
308     injAddr = ROUNDUP(injAddr, highestAddr->phdr.p_align);
309     injAddr += injOffset % highestAddr->phdr.p_align;
310
311     newmp->phdr.p_align = highestAddr->phdr.p_align;
312     newmp->phdr.p_filesz = size;
313     newmp->phdr.p_memsz = size;
314     newmp->phdr.p_flags = PF_R | (x ? PF_X : 0) | (w ? PF_W : 0);
315     newmp->phdr.p_type = PT_LOAD;
316     newmp->phdr.p_offset = injOffset;
317     newmp->phdr.p_vaddr = injAddr;
318     newmp->phdr.p_paddr = newmp->phdr.p_vaddr;
319
320     *injPhdr = newmp;
321
322     return injAddr;
323   }
324
325
326
327
328   ERROR:
329   if (injPhdr) {
330     *injPhdr = NULL;
331   }
332   return 0;
333 }
334
335
336
337
338
339 int elfu_mLayoutAuto(ElfuElf *me)
340 {
341   ElfuPhdr *lowestAddr;
342   ElfuPhdr *highestAddr;
343   ElfuPhdr *lowestOffs;
344   ElfuPhdr *highestOffsEnd;
345   ElfuPhdr *mp;
346   ElfuScn *ms;
347   ElfuPhdr **phdrArr;
348   GElf_Off lastend = 0;
349
350   assert(me);
351
352   /* Find first and last LOAD PHDRs. */
353   elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
354                               &lowestOffs, &highestOffsEnd);
355
356   phdrArr = malloc(elfu_mPhdrCount(me) * sizeof(*phdrArr));
357   if (!phdrArr) {
358     ELFU_WARN("elfu_mLayoutAuto: malloc failed for phdrArr.\n");
359     return 1;
360   }
361
362
363   lastend = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
364
365
366   /* If PHDRs are not mapped into memory, place them after LOAD segments. */
367   mp = elfu_mPhdrByOffset(me, me->ehdr.e_phoff);
368   if (!mp) {
369     lastend = ROUNDUP(lastend, 8);
370     me->ehdr.e_phoff = lastend;
371     lastend += me->ehdr.e_phnum * me->ehdr.e_phentsize;
372   } else {
373     /* Update size of PHDR PHDR */
374     ElfuPhdr *phdrmp;
375
376     CIRCLEQ_FOREACH(phdrmp, &mp->childPhdrList, elemChildPhdr) {
377       if (phdrmp->phdr.p_type == PT_PHDR) {
378         phdrmp->phdr.p_filesz = elfu_mPhdrCount(me) * me->ehdr.e_phentsize;
379         phdrmp->phdr.p_memsz = elfu_mPhdrCount(me) * me->ehdr.e_phentsize;
380       }
381     }
382   }
383
384
385   /* Place orphaned sections afterwards, maintaining alignment */
386   CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
387     lastend = ROUNDUP(lastend, ms->shdr.sh_addralign);
388
389     ms->shdr.sh_offset = lastend;
390
391     lastend = OFFS_END(ms->shdr.sh_offset, SCNFILESIZE(&ms->shdr));
392   }
393
394
395   /* Move SHDRs to end */
396   lastend = ROUNDUP(lastend, 8);
397   me->ehdr.e_shoff = lastend;
398
399
400   return 0;
401 }