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