Implement memory management TODOs
[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         && (lowestAddr->phdr.p_vaddr >= 2 * lowestAddr->phdr.p_align)) {
106       ElfuPhdr *mp;
107       ElfuScn *ms;
108       GElf_Word size = ROUNDUP(me->ehdr.e_phentsize, phdrmp->phdr.p_align);
109
110       /* Move our sections */
111       CIRCLEQ_FOREACH(ms, &phdrmp->childScnList, elemChildScn) {
112         if (ms->shdr.sh_offset >= me->ehdr.e_phoff) {
113           ms->shdr.sh_offset += size;
114         }
115       }
116
117       /* Move our PHDRs */
118       CIRCLEQ_FOREACH(mp, &phdrmp->childPhdrList, elemChildPhdr) {
119         if (mp->phdr.p_offset > me->ehdr.e_phoff) {
120           mp->phdr.p_offset += size;
121         } else {
122           mp->phdr.p_vaddr -= size;
123           mp->phdr.p_paddr -= size;
124         }
125
126         if (mp->phdr.p_type == PT_PHDR) {
127           mp->phdr.p_filesz += me->ehdr.e_phentsize;
128           mp->phdr.p_memsz += me->ehdr.e_phentsize;
129         }
130       }
131
132       /* Move other PHDRs and sections */
133       assert(size <= shiftStuffAtAfterOffset(me, me->ehdr.e_phoff + 1, size));
134
135       /* Remap ourselves */
136       phdrmp->phdr.p_vaddr -= size;
137       phdrmp->phdr.p_paddr -= size;
138       phdrmp->phdr.p_filesz += size;
139       phdrmp->phdr.p_memsz += size;
140     }
141   }
142
143   newmp = elfu_mPhdrAlloc();
144   assert(newmp);
145   CIRCLEQ_INSERT_TAIL(&me->phdrList, newmp, elem);
146
147   return newmp;
148 }
149
150
151
152
153
154 /* Finds a suitable PHDR to insert a hole into and expands it
155  * if necessary.
156  * Returns memory address the hole will be mapped to, or 0 if
157  * the operation failed. */
158 GElf_Addr elfu_mLayoutGetSpaceInPhdr(ElfuElf *me, GElf_Word size,
159                                      GElf_Word align, int w, int x,
160                                      ElfuPhdr **injPhdr)
161 {
162   ElfuPhdr *lowestAddr;
163   ElfuPhdr *highestAddr;
164   ElfuPhdr *lowestOffs;
165   ElfuPhdr *highestOffsEnd;
166   ElfuPhdr *mp;
167
168   assert(!(w && x));
169
170   /* Treat read-only data as executable.
171    * That's what the GNU toolchain does on x86. */
172   if (!w && !x) {
173     x = 1;
174   }
175
176   /* Find first and last LOAD PHDRs. */
177   elfu_mPhdrLoadLowestHighest(me, &lowestAddr, &highestAddr,
178                               &lowestOffs, &highestOffsEnd);
179
180   if (((w && (highestAddr->phdr.p_flags & PF_W))
181       || (x && (highestAddr->phdr.p_flags & PF_X)))
182       /* Merging only works if the LOAD is the last both in file and mem */
183       && highestAddr == highestOffsEnd) {
184     /* Need to append. */
185     GElf_Off injOffset = OFFS_END(highestAddr->phdr.p_offset, highestAddr->phdr.p_filesz);
186     GElf_Word injSpace = 0;
187     GElf_Word nobitsize = highestAddr->phdr.p_memsz - highestAddr->phdr.p_filesz;
188
189     /* Expand NOBITS if any */
190     if (nobitsize > 0) {
191       GElf_Off endOff = OFFS_END(highestAddr->phdr.p_offset, highestAddr->phdr.p_filesz);
192       GElf_Off endAddr = OFFS_END(highestAddr->phdr.p_vaddr, highestAddr->phdr.p_filesz);
193       ElfuScn *ms;
194
195       ELFU_INFO("Expanding NOBITS at address 0x%x...\n", (unsigned)endAddr);
196
197       CIRCLEQ_FOREACH(ms, &highestAddr->childScnList, elemChildScn) {
198         if (ms->shdr.sh_offset == endOff) {
199           assert(ms->shdr.sh_type == SHT_NOBITS);
200           assert(ms->shdr.sh_size == nobitsize);
201           ms->databuf = malloc(ms->shdr.sh_size);
202           memset(ms->databuf, '\0', ms->shdr.sh_size);
203           if (!ms->databuf) {
204             ELFU_WARN("mExpandNobits: Could not allocate %u bytes for NOBITS expansion. Data may be inconsistent.\n",
205                       (unsigned)ms->shdr.sh_size);
206             assert(0);
207             goto ERROR;
208           }
209
210           ms->shdr.sh_type = SHT_PROGBITS;
211           ms->shdr.sh_addr = endAddr;
212         }
213       }
214
215       injSpace += shiftStuffAtAfterOffset(me, endOff, nobitsize);
216       injSpace -= nobitsize;
217       injOffset += nobitsize;
218       highestAddr->phdr.p_filesz += nobitsize;
219       assert(highestAddr->phdr.p_filesz == highestAddr->phdr.p_memsz);
220     }
221
222     /* Calculate how much space we need, taking alignment into account */
223     size += ROUNDUP(injOffset, align) - injOffset;
224
225     /* If there is not enough space left, create even more. */
226     if (injSpace < size) {
227       injSpace += shiftStuffAtAfterOffset(me, injOffset, size - injSpace);
228     }
229     assert(injSpace >= size);
230
231     /* Remap ourselves */
232     highestAddr->phdr.p_filesz += size;
233     highestAddr->phdr.p_memsz += size;
234
235     injOffset = ROUNDUP(injOffset, align);
236
237     if (injPhdr) {
238       *injPhdr = highestAddr;
239     }
240     return highestAddr->phdr.p_vaddr + (injOffset - highestAddr->phdr.p_offset);
241   } else if (((w && (lowestAddr->phdr.p_flags & PF_W))
242               || (x && (lowestAddr->phdr.p_flags & PF_X)))
243              && /* Enough space to expand downwards? */
244              (lowestAddr->phdr.p_vaddr >= ((2 * lowestAddr->phdr.p_align)
245                                            + ROUNDUP(size, lowestAddr->phdr.p_align)))
246              /* Merging only works if the LOAD is the first both in file and mem */
247              && lowestAddr == lowestOffs) {
248     /* Need to prepend or split up the PHDR. */
249     GElf_Off injOffset = OFFS_END(lowestAddr->phdr.p_offset,
250                                   lowestAddr->phdr.p_filesz);
251     ElfuScn *ms;
252
253     /* Round up size to take PHDR alignment into account.
254      * We assume that this is a multiple of the alignment asked for. */
255     assert(lowestAddr->phdr.p_align >= align);
256     size = ROUNDUP(size, lowestAddr->phdr.p_align);
257
258     /* Find first section. We assume there is at least one. */
259     assert(!CIRCLEQ_EMPTY(&lowestAddr->childScnList));
260     injOffset = CIRCLEQ_FIRST(&lowestAddr->childScnList)->shdr.sh_offset;
261
262     /* Move our sections */
263     CIRCLEQ_FOREACH(ms, &lowestAddr->childScnList, elemChildScn) {
264       if (ms->shdr.sh_offset >= injOffset) {
265         ms->shdr.sh_offset += size;
266       }
267     }
268
269     /* Move our PHDRs */
270     CIRCLEQ_FOREACH(mp, &lowestAddr->childPhdrList, elemChildPhdr) {
271       if (mp->phdr.p_offset >= injOffset) {
272         mp->phdr.p_offset += size;
273       } else {
274         mp->phdr.p_vaddr -= size;
275         mp->phdr.p_paddr -= size;
276       }
277     }
278
279     /* Move other PHDRs and sections */
280     assert(size <= shiftStuffAtAfterOffset(me, injOffset + 1, size));
281
282     /* Remap ourselves */
283     lowestAddr->phdr.p_vaddr -= size;
284     lowestAddr->phdr.p_paddr -= size;
285     lowestAddr->phdr.p_filesz += size;
286     lowestAddr->phdr.p_memsz += size;
287
288     injOffset = ROUNDUP(injOffset, align);
289
290     if (injPhdr) {
291       *injPhdr = lowestAddr;
292     }
293     return lowestAddr->phdr.p_vaddr + (injOffset - lowestAddr->phdr.p_offset);
294   } else {
295     ElfuPhdr *newmp;
296     GElf_Off injOffset;
297     GElf_Addr injAddr;
298
299     /* Add a new LOAD PHDR. */
300     newmp = appendPhdr(me);
301     if (!newmp) {
302       goto ERROR;
303     }
304
305     /* ELF spec: We need (p_offset % p_align) == (p_vaddr % p_align) */
306     injOffset = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
307     injOffset = ROUNDUP(injOffset, align);
308     injAddr = OFFS_END(highestAddr->phdr.p_vaddr, highestAddr->phdr.p_memsz);
309     injAddr = ROUNDUP(injAddr, highestAddr->phdr.p_align);
310     injAddr += injOffset % highestAddr->phdr.p_align;
311
312     newmp->phdr.p_align = highestAddr->phdr.p_align;
313     newmp->phdr.p_filesz = size;
314     newmp->phdr.p_memsz = size;
315     newmp->phdr.p_flags = PF_R | (x ? PF_X : 0) | (w ? PF_W : 0);
316     newmp->phdr.p_type = PT_LOAD;
317     newmp->phdr.p_offset = injOffset;
318     newmp->phdr.p_vaddr = injAddr;
319     newmp->phdr.p_paddr = newmp->phdr.p_vaddr;
320
321     *injPhdr = newmp;
322
323     return injAddr;
324   }
325
326
327
328
329   ERROR:
330   if (injPhdr) {
331     *injPhdr = NULL;
332   }
333   return 0;
334 }
335
336
337
338
339
340 int elfu_mLayoutAuto(ElfuElf *me)
341 {
342   ElfuPhdr *lowestAddr;
343   ElfuPhdr *highestAddr;
344   ElfuPhdr *lowestOffs;
345   ElfuPhdr *highestOffsEnd;
346   ElfuPhdr *mp;
347   ElfuScn *ms;
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
357   lastend = OFFS_END(highestOffsEnd->phdr.p_offset, highestOffsEnd->phdr.p_filesz);
358
359
360   /* If PHDRs are not mapped into memory, place them after LOAD segments. */
361   mp = elfu_mPhdrByOffset(me, me->ehdr.e_phoff);
362   if (!mp) {
363     lastend = ROUNDUP(lastend, 8);
364     me->ehdr.e_phoff = lastend;
365     lastend += me->ehdr.e_phnum * me->ehdr.e_phentsize;
366   } else {
367     /* Update size of PHDR PHDR */
368     ElfuPhdr *phdrmp;
369
370     CIRCLEQ_FOREACH(phdrmp, &mp->childPhdrList, elemChildPhdr) {
371       if (phdrmp->phdr.p_type == PT_PHDR) {
372         phdrmp->phdr.p_filesz = elfu_mPhdrCount(me) * me->ehdr.e_phentsize;
373         phdrmp->phdr.p_memsz = elfu_mPhdrCount(me) * me->ehdr.e_phentsize;
374       }
375     }
376   }
377
378
379   /* Place orphaned sections afterwards, maintaining alignment */
380   CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
381     lastend = ROUNDUP(lastend, ms->shdr.sh_addralign);
382
383     ms->shdr.sh_offset = lastend;
384
385     lastend = OFFS_END(ms->shdr.sh_offset, SCNFILESIZE(&ms->shdr));
386   }
387
388
389   /* Move SHDRs to end */
390   lastend = ROUNDUP(lastend, 8);
391   me->ehdr.e_shoff = lastend;
392
393
394   return 0;
395 }