C90 compliance, except variadic macros and TODOs
[centaur.git] / src / 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 /* Finds a suitable PHDR to insert a hole into and expands it
72  * if necessary.
73  * Returns memory address the hole will be mapped to, or 0 if
74  * the operation failed. */
75 GElf_Addr elfu_mLayoutGetSpaceInPhdr(ElfuElf *me, GElf_Word size,
76                                      GElf_Word align, int w, int x,
77                                      ElfuPhdr **injPhdr)
78 {
79   ElfuPhdr *first = NULL;
80   ElfuPhdr *last = NULL;
81   ElfuPhdr *mp;
82
83   assert(!(w && x));
84
85   /* Treat read-only data as executable.
86    * That's what the GNU toolchain does on x86. */
87   if (!w && !x) {
88     x = 1;
89   }
90
91   /* Find first and last LOAD PHDRs.
92    * Don't compare p_memsz - segments don't overlap in memory. */
93   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
94     if (mp->phdr.p_type != PT_LOAD) {
95       continue;
96     }
97     if (!first || mp->phdr.p_vaddr < first->phdr.p_vaddr) {
98       first = mp;
99     }
100     if (!last || mp->phdr.p_vaddr > last->phdr.p_vaddr) {
101       last = mp;
102     }
103   }
104
105   if ((w && (last->phdr.p_flags & PF_W))
106       || (x && (last->phdr.p_flags & PF_X))) {
107     /* Need to append. */
108     GElf_Off injOffset = OFFS_END(last->phdr.p_offset, last->phdr.p_filesz);
109     GElf_Word injSpace = 0;
110     GElf_Word nobitsize = last->phdr.p_memsz - last->phdr.p_filesz;
111
112     /* Expand NOBITS if any */
113     if (nobitsize > 0) {
114       GElf_Off endOff = OFFS_END(last->phdr.p_offset, last->phdr.p_filesz);
115       GElf_Off endAddr = OFFS_END(last->phdr.p_vaddr, last->phdr.p_filesz);
116       ElfuScn *ms;
117
118       ELFU_INFO("Expanding NOBITS at address 0x%x...\n", (unsigned)endAddr);
119
120       CIRCLEQ_FOREACH(ms, &last->childScnList, elemChildScn) {
121         if (ms->shdr.sh_offset == endOff) {
122           assert(ms->shdr.sh_type == SHT_NOBITS);
123           assert(ms->shdr.sh_size == nobitsize);
124           ms->data.d_buf = malloc(ms->shdr.sh_size);
125           memset(ms->data.d_buf, '\0', ms->shdr.sh_size);
126           if (!ms->data.d_buf) {
127             ELFU_WARN("mExpandNobits: Could not allocate %u bytes for NOBITS expansion. Data may be inconsistent.\n",
128                       (unsigned)ms->shdr.sh_size);
129             assert(0);
130             goto ERROR;
131           }
132
133           ms->data.d_align = 1;
134           ms->data.d_off  = 0;
135           ms->data.d_type = ELF_T_BYTE;
136           ms->data.d_size = ms->shdr.sh_size;
137           ms->data.d_version = elf_version(EV_NONE);
138
139           ms->shdr.sh_type = SHT_PROGBITS;
140           ms->shdr.sh_addr = endAddr;
141         }
142       }
143
144       injSpace += shiftStuffAtAfterOffset(me, endOff, nobitsize);
145       injSpace -= nobitsize;
146       injOffset += nobitsize;
147       last->phdr.p_filesz += nobitsize;
148       assert(last->phdr.p_filesz == last->phdr.p_memsz);
149     }
150
151     /* Calculate how much space we need, taking alignment into account */
152     size += ROUNDUP(injOffset, align) - injOffset;
153
154     /* If there is not enough space left, create even more. */
155     if (injSpace < size) {
156       injSpace += shiftStuffAtAfterOffset(me, injOffset, size - injSpace);
157     }
158     assert(injSpace >= size);
159
160     /* Remap ourselves */
161     last->phdr.p_filesz += size;
162     last->phdr.p_memsz += size;
163
164     injOffset = ROUNDUP(injOffset, align);
165
166     if (injPhdr) {
167       *injPhdr = last;
168     }
169     return last->phdr.p_vaddr + (injOffset - last->phdr.p_offset);
170   } else if ((w && (first->phdr.p_flags & PF_W))
171              || (x && (first->phdr.p_flags & PF_X))) {
172     /* Need to prepend or split up the PHDR. */
173     GElf_Off injOffset = OFFS_END(first->phdr.p_offset, first->phdr.p_filesz);
174     ElfuScn *ms;
175
176     /* Round up size to take PHDR alignment into account.
177      * We assume that this is a multiple of the alignment asked for. */
178     assert(first->phdr.p_align >= align);
179     size = ROUNDUP(size, first->phdr.p_align);
180
181     /* Find first section. We assume there is at least one. */
182     assert(!CIRCLEQ_EMPTY(&first->childScnList));
183     injOffset = CIRCLEQ_FIRST(&first->childScnList)->shdr.sh_offset;
184
185     /* Move our sections */
186     CIRCLEQ_FOREACH(ms, &first->childScnList, elemChildScn) {
187       if (ms->shdr.sh_offset >= injOffset) {
188         ms->shdr.sh_offset += size;
189       }
190     }
191
192     /* Move our PHDRs */
193     CIRCLEQ_FOREACH(mp, &first->childPhdrList, elemChildPhdr) {
194       if (mp->phdr.p_offset >= injOffset) {
195         mp->phdr.p_offset += size;
196       } else {
197         mp->phdr.p_vaddr -= size;
198         mp->phdr.p_paddr -= size;
199       }
200     }
201
202     /* Move other PHDRs and sections */
203     assert(size <= shiftStuffAtAfterOffset(me, injOffset, size));
204
205     /* Remap ourselves */
206     first->phdr.p_vaddr -= size;
207     first->phdr.p_paddr -= size;
208     first->phdr.p_filesz += size;
209     first->phdr.p_memsz += size;
210
211     injOffset = ROUNDUP(injOffset, align);
212
213     if (injPhdr) {
214       *injPhdr = first;
215     }
216     return first->phdr.p_vaddr + (injOffset - first->phdr.p_offset);
217   }
218
219   ERROR:
220   if (injPhdr) {
221     *injPhdr = NULL;
222   }
223   return 0;
224 }
225
226
227
228
229 static int cmpPhdrOffs(const void *mp1, const void *mp2)
230 {
231   ElfuPhdr *p1;
232   ElfuPhdr *p2;
233
234   assert(mp1);
235   assert(mp2);
236
237   p1 = *(ElfuPhdr**)mp1;
238   p2 = *(ElfuPhdr**)mp2;
239
240   assert(p1);
241   assert(p2);
242
243
244   if (p1->phdr.p_offset < p2->phdr.p_offset) {
245     return -1;
246   } else if (p1->phdr.p_offset == p2->phdr.p_offset) {
247     return 0;
248   } else /* if (p1->phdr.p_offset > p2->phdr.p_offset) */ {
249     return 1;
250   }
251 }
252
253 int elfu_mLayoutAuto(ElfuElf *me)
254 {
255   ElfuPhdr *mp;
256   ElfuScn *ms;
257   ElfuPhdr **phdrArr;
258   GElf_Off lastend = 0;
259   size_t i, j;
260
261   assert(me);
262
263   phdrArr = malloc(elfu_mPhdrCount(me) * sizeof(*phdrArr));
264   if (!phdrArr) {
265     ELFU_WARN("elfu_mLayoutAuto: malloc failed for phdrArr.\n");
266     return 1;
267   }
268
269   i = 0;
270   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
271     if (mp->phdr.p_type != PT_LOAD) {
272       continue;
273     }
274
275     phdrArr[i] = mp;
276     i++;
277   }
278
279   /* Assume we have at least one LOAD PHDR,
280    * and that it ends after EHDR and PHDRs */
281   assert(i > 1);
282
283   /* Sort array by file offset */
284   qsort(phdrArr, i, sizeof(*phdrArr), cmpPhdrOffs);
285
286   lastend = OFFS_END(phdrArr[0]->phdr.p_offset, phdrArr[0]->phdr.p_filesz);
287
288   /* Wiggle offsets of 2nd, 3rd etc so take minimum space */
289   for (j = 1; j < i; j++) {
290     GElf_Off subalign = phdrArr[j]->phdr.p_offset % phdrArr[j]->phdr.p_align;
291
292     if ((lastend % phdrArr[j]->phdr.p_align) <= subalign) {
293       lastend += subalign - (lastend % phdrArr[j]->phdr.p_align);
294     } else {
295       lastend += phdrArr[j]->phdr.p_align - ((lastend % phdrArr[j]->phdr.p_align) - subalign);
296     }
297
298     phdrArr[j]->phdr.p_offset = lastend;
299
300     elfu_mPhdrUpdateChildOffsets(phdrArr[j]);
301
302     lastend = OFFS_END(phdrArr[j]->phdr.p_offset, phdrArr[j]->phdr.p_filesz);
303   }
304
305   free(phdrArr);
306
307
308   /* Place orphaned sections afterwards, maintaining alignment */
309   CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
310     lastend = ROUNDUP(lastend, ms->shdr.sh_addralign);
311
312     ms->shdr.sh_offset = lastend;
313
314     lastend = OFFS_END(ms->shdr.sh_offset, SCNFILESIZE(&ms->shdr));
315   }
316
317
318   /* Move SHDRs to end */
319   lastend = ROUNDUP(lastend, 8);
320   me->ehdr.e_shoff = lastend;
321
322
323   return 0;
324 }