Redesign data structures, make basic reladd work.
[centaur.git] / src / model / reladd.c
1 #include <assert.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/types.h>
5 #include <libelf/gelf.h>
6 #include <libelfu/libelfu.h>
7
8
9
10 static GElf_Word makeSpaceAtOffset(ElfuElf *me, GElf_Off offset, 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       ElfuScn *ms;
45       ElfuPhdr *mpc;
46
47       mp->phdr.p_offset += size;
48       CIRCLEQ_FOREACH(mpc, &mp->childPhdrList, elemChildPhdr) {
49         mpc->phdr.p_offset += size;
50       }
51       CIRCLEQ_FOREACH(ms, &mp->childScnList, elemChildScn) {
52         ms->shdr.sh_offset += size;
53       }
54     }
55   }
56
57   CIRCLEQ_FOREACH(ms, &me->orphanScnList, elemChildScn) {
58     if (ms->shdr.sh_offset >= offset) {
59       ms->shdr.sh_offset += size;
60     }
61   }
62
63   if (me->ehdr.e_phoff >= offset) {
64     me->ehdr.e_phoff += size;
65   }
66
67   if (me->ehdr.e_shoff >= offset) {
68     me->ehdr.e_shoff += size;
69   }
70
71   return size;
72 }
73
74
75
76 /* Finds a suitable PHDR to insert a hole into and expands it
77  * if necessary.
78  * Returns memory address the hole will be mapped to, or 0 if
79  * the operation failed. */
80 static GElf_Addr getSpaceInPhdr(ElfuElf *me, GElf_Word size,
81                                 GElf_Word align, int w, int x,
82                                 ElfuPhdr **injPhdr)
83 {
84   ElfuPhdr *first = NULL;
85   ElfuPhdr *last = NULL;
86   ElfuPhdr *mp;
87
88   assert(!(w && x));
89
90   /* Find first and last LOAD PHDRs.
91    * Don't compare p_memsz - segments don't overlap in memory. */
92   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
93     if (mp->phdr.p_type != PT_LOAD) {
94       continue;
95     }
96     if (!first || mp->phdr.p_vaddr < first->phdr.p_vaddr) {
97       first = mp;
98     }
99     if (!last || mp->phdr.p_vaddr > last->phdr.p_vaddr) {
100       last = mp;
101     }
102   }
103
104   if ((w && (last->phdr.p_flags & PF_W))
105       || (x && (last->phdr.p_flags & PF_X))) {
106     /* Need to append. */
107     GElf_Off injOffset = OFFS_END(last->phdr.p_offset, last->phdr.p_filesz);
108     GElf_Word injSpace = 0;
109     GElf_Word nobitsize = last->phdr.p_memsz - last->phdr.p_filesz;
110
111     /* Expand NOBITS if any */
112     if (nobitsize > 0) {
113       GElf_Off endOff = OFFS_END(last->phdr.p_offset, last->phdr.p_filesz);
114       GElf_Off endAddr = OFFS_END(last->phdr.p_vaddr, last->phdr.p_filesz);
115       ElfuScn *ms;
116
117       ELFU_INFO("Expanding NOBITS at address 0x%jx...\n", endAddr);
118
119       CIRCLEQ_FOREACH(ms, &last->childScnList, elemChildScn) {
120         if (ms->shdr.sh_offset == endOff) {
121           assert(ms->shdr.sh_type == SHT_NOBITS);
122           assert(ms->shdr.sh_size == nobitsize);
123           ms->data.d_buf = malloc(ms->shdr.sh_size);
124           memset(ms->data.d_buf, '\0', ms->shdr.sh_size);
125           if (!ms->data.d_buf) {
126             ELFU_WARN("mExpandNobits: Could not allocate %jd bytes for NOBITS expansion. Data may be inconsistent.\n", ms->shdr.sh_size);
127             assert(0);
128             goto ERROR;
129           }
130
131           ms->data.d_align = 1;
132           ms->data.d_off  = 0;
133           ms->data.d_type = ELF_T_BYTE;
134           ms->data.d_size = ms->shdr.sh_size;
135           ms->data.d_version = elf_version(EV_NONE);
136
137           ms->shdr.sh_type = SHT_PROGBITS;
138           ms->shdr.sh_addr = endAddr;
139         }
140       }
141
142       injSpace += makeSpaceAtOffset(me, endOff, nobitsize);
143       injSpace -= nobitsize;
144       injOffset += nobitsize;
145       last->phdr.p_filesz += nobitsize;
146       assert(last->phdr.p_filesz == last->phdr.p_memsz);
147     }
148
149     /* Calculate how much space we need, taking alignment into account */
150     size += ROUNDUP(injOffset, align) - injOffset;
151
152     /* If there is not enough space left, create even more. */
153     if (injSpace < size) {
154       injSpace += makeSpaceAtOffset(me, injOffset, size - injSpace);
155     }
156     assert(injSpace >= size);
157
158     /* Remap ourselves */
159     last->phdr.p_filesz += size;
160     last->phdr.p_memsz += size;
161
162     injOffset = ROUNDUP(injOffset, align);
163
164     if (injPhdr) {
165       *injPhdr = last;
166     }
167     return last->phdr.p_vaddr + (injOffset - last->phdr.p_offset);
168   } else if ((w && (first->phdr.p_flags & PF_W))
169              || (x && (first->phdr.p_flags & PF_X))) {
170     /* Need to prepend or split up the PHDR. */
171     GElf_Off injOffset = OFFS_END(first->phdr.p_offset, first->phdr.p_filesz);
172     ElfuScn *ms;
173
174     /* Round up size to take PHDR alignment into account.
175      * We assume that this is a multiple of the alignment asked for. */
176     assert(first->phdr.p_align >= align);
177     size = ROUNDUP(size, first->phdr.p_align);
178
179     /* Find first section. We assume there is at least one. */
180     assert(!CIRCLEQ_EMPTY(&first->childScnList));
181     injOffset = CIRCLEQ_FIRST(&first->childScnList)->shdr.sh_offset;
182
183     /* Move our sections */
184     CIRCLEQ_FOREACH(ms, &first->childScnList, elemChildScn) {
185       if (ms->shdr.sh_offset >= injOffset) {
186         ms->shdr.sh_offset += size;
187       }
188     }
189
190     /* Move our PHDRs */
191     CIRCLEQ_FOREACH(mp, &first->childPhdrList, elemChildPhdr) {
192       if (mp->phdr.p_offset >= injOffset) {
193         mp->phdr.p_offset += size;
194       } else {
195         mp->phdr.p_vaddr -= size;
196         mp->phdr.p_paddr -= size;
197       }
198     }
199
200     /* Move other PHDRs and sections */
201     assert(size <= makeSpaceAtOffset(me, injOffset, size));
202
203     /* Remap ourselves */
204     first->phdr.p_vaddr -= size;
205     first->phdr.p_paddr -= size;
206     first->phdr.p_filesz += size;
207     first->phdr.p_memsz += size;
208
209     injOffset = ROUNDUP(injOffset, align);
210
211     if (injPhdr) {
212       *injPhdr = first;
213     }
214     return first->phdr.p_vaddr + (injOffset - first->phdr.p_offset);
215   }
216
217   ERROR:
218   if (injPhdr) {
219     *injPhdr = NULL;
220   }
221   return 0;
222 }
223
224
225
226
227 static ElfuScn* insertSection(ElfuElf *me, ElfuElf *mrel, ElfuScn *oldscn)
228 {
229   ElfuScn *newscn = NULL;
230   GElf_Addr injAddr;
231   GElf_Off injOffset;
232   ElfuPhdr *injPhdr;
233
234   if (oldscn->shdr.sh_flags & SHF_ALLOC) {
235     newscn = elfu_mCloneScn(oldscn);
236     if (!newscn) {
237       return NULL;
238     }
239
240
241     injAddr = getSpaceInPhdr(me, newscn->shdr.sh_size,
242                              newscn->shdr.sh_addralign,
243                              newscn->shdr.sh_flags & SHF_WRITE,
244                              newscn->shdr.sh_flags & SHF_EXECINSTR,
245                              &injPhdr);
246
247     if (!injPhdr) {
248       ELFU_WARN("insertSection: Could not find a place to insert section.\n");
249       goto ERROR;
250     }
251
252     ELFU_INFO("Inserting %s at address 0x%jx...\n",
253               elfu_mScnName(mrel, oldscn),
254               injAddr);
255
256     injOffset = injAddr - injPhdr->phdr.p_vaddr + injPhdr->phdr.p_offset;
257
258     newscn->shdr.sh_addr = injAddr;
259     newscn->shdr.sh_offset = injOffset;
260
261     if (CIRCLEQ_EMPTY(&injPhdr->childScnList)
262         || CIRCLEQ_LAST(&injPhdr->childScnList)->shdr.sh_offset < injOffset) {
263       CIRCLEQ_INSERT_TAIL(&injPhdr->childScnList, newscn, elemChildScn);
264     } else {
265       ElfuScn *ms;
266       CIRCLEQ_FOREACH(ms, &injPhdr->childScnList, elemChildScn) {
267         if (injOffset < ms->shdr.sh_offset) {
268           CIRCLEQ_INSERT_BEFORE(&injPhdr->childScnList, ms, newscn, elemChildScn);
269           break;
270         }
271       }
272     }
273
274     /* Inject name */
275     // TODO
276     newscn->shdr.sh_name = 0;
277
278     // TODO: Relocate
279
280     return newscn;
281   } else {
282       ELFU_WARN("insertSection: Skipping section %s with flags %jd (type %d).\n",
283                 elfu_mScnName(mrel, oldscn),
284                 oldscn->shdr.sh_flags,
285                 oldscn->shdr.sh_type);
286       goto ERROR;
287   }
288
289   ERROR:
290   if (newscn) {
291     // TODO: Destroy newscn
292   }
293   return NULL;
294 }
295
296
297 int subScnAdd1(ElfuElf *mrel, ElfuScn *ms, void *aux1, void *aux2)
298 {
299   (void)aux2;
300   ElfuElf *me = (ElfuElf*)aux1;
301
302   ElfuScn *newscn;
303
304   switch(ms->shdr.sh_type) {
305     case SHT_PROGBITS: /* 1 */
306       /* Find a place where it belongs and shove it in. */
307       newscn = insertSection(me, mrel, ms);
308       if (!newscn) {
309         ELFU_WARN("mReladd: Could not insert section %s (type %d), skipping.\n",
310                   elfu_mScnName(mrel, ms),
311                   ms->shdr.sh_type);
312       }
313       break;
314   }
315
316   return 0;
317 }
318
319
320 int subScnAdd2(ElfuElf *mrel, ElfuScn *ms, void *aux1, void *aux2)
321 {
322   (void)aux2;
323   ElfuElf *me = (ElfuElf*)aux1;
324   (void)me;
325
326   switch(ms->shdr.sh_type) {
327     case SHT_NULL: /* 0 */
328     case SHT_PROGBITS: /* 1 */
329       break;
330
331     case SHT_SYMTAB: /* 2 */
332     case SHT_DYNSYM: /* 11 */
333       /* Merge with the existing table. Take care of string tables also. */
334
335     case SHT_STRTAB: /* 3 */
336       /* May have to be merged with the existing string table for
337        * the symbol table. */
338
339     case SHT_RELA: /* 4 */
340     case SHT_REL: /* 9 */
341       /* Possibly append this in memory to the section model
342        * that it describes. */
343
344     case SHT_NOBITS: /* 8 */
345       /* Expand this to SHT_PROGBITS, then insert as such. */
346
347     case SHT_HASH: /* 5 */
348     case SHT_DYNAMIC: /* 6 */
349     case SHT_SHLIB: /* 10 */
350     case SHT_SYMTAB_SHNDX: /* 18 */
351
352     /* Don't care about the next ones yet. I've never seen
353      * them and they can be implemented when necessary. */
354     case SHT_NOTE: /* 7 */
355     case SHT_INIT_ARRAY: /* 14 */
356     case SHT_FINI_ARRAY: /* 15 */
357     case SHT_PREINIT_ARRAY: /* 16 */
358     case SHT_GROUP: /* 17 */
359     case SHT_NUM: /* 19 */
360     default:
361       ELFU_WARN("mReladd: Skipping section %s (type %d).\n",
362                 elfu_mScnName(mrel, ms),
363                 ms->shdr.sh_type);
364   }
365
366   return 0;
367 }
368
369
370 void elfu_mReladd(ElfuElf *me, ElfuElf *mrel)
371 {
372   assert(me);
373   assert(mrel);
374
375   /* For each section in object file, guess how to insert it */
376   elfu_mScnForall(mrel, subScnAdd1, me, NULL);
377
378   /* Do relocations and other stuff */
379   elfu_mScnForall(mrel, subScnAdd2, me, NULL);
380 }