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