2febed1bd8bcc90306cbbc7fe8563d031d568809
[centaur.git] / src / model / 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   /* Find first and last LOAD PHDRs.
86    * Don't compare p_memsz - segments don't overlap in memory. */
87   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
88     if (mp->phdr.p_type != PT_LOAD) {
89       continue;
90     }
91     if (!first || mp->phdr.p_vaddr < first->phdr.p_vaddr) {
92       first = mp;
93     }
94     if (!last || mp->phdr.p_vaddr > last->phdr.p_vaddr) {
95       last = mp;
96     }
97   }
98
99   if ((w && (last->phdr.p_flags & PF_W))
100       || (x && (last->phdr.p_flags & PF_X))) {
101     /* Need to append. */
102     GElf_Off injOffset = OFFS_END(last->phdr.p_offset, last->phdr.p_filesz);
103     GElf_Word injSpace = 0;
104     GElf_Word nobitsize = last->phdr.p_memsz - last->phdr.p_filesz;
105
106     /* Expand NOBITS if any */
107     if (nobitsize > 0) {
108       GElf_Off endOff = OFFS_END(last->phdr.p_offset, last->phdr.p_filesz);
109       GElf_Off endAddr = OFFS_END(last->phdr.p_vaddr, last->phdr.p_filesz);
110       ElfuScn *ms;
111
112       ELFU_INFO("Expanding NOBITS at address 0x%jx...\n", endAddr);
113
114       CIRCLEQ_FOREACH(ms, &last->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->data.d_buf = malloc(ms->shdr.sh_size);
119           memset(ms->data.d_buf, '\0', ms->shdr.sh_size);
120           if (!ms->data.d_buf) {
121             ELFU_WARN("mExpandNobits: Could not allocate %jd bytes for NOBITS expansion. Data may be inconsistent.\n", ms->shdr.sh_size);
122             assert(0);
123             goto ERROR;
124           }
125
126           ms->data.d_align = 1;
127           ms->data.d_off  = 0;
128           ms->data.d_type = ELF_T_BYTE;
129           ms->data.d_size = ms->shdr.sh_size;
130           ms->data.d_version = elf_version(EV_NONE);
131
132           ms->shdr.sh_type = SHT_PROGBITS;
133           ms->shdr.sh_addr = endAddr;
134         }
135       }
136
137       injSpace += shiftStuffAtAfterOffset(me, endOff, nobitsize);
138       injSpace -= nobitsize;
139       injOffset += nobitsize;
140       last->phdr.p_filesz += nobitsize;
141       assert(last->phdr.p_filesz == last->phdr.p_memsz);
142     }
143
144     /* Calculate how much space we need, taking alignment into account */
145     size += ROUNDUP(injOffset, align) - injOffset;
146
147     /* If there is not enough space left, create even more. */
148     if (injSpace < size) {
149       injSpace += shiftStuffAtAfterOffset(me, injOffset, size - injSpace);
150     }
151     assert(injSpace >= size);
152
153     /* Remap ourselves */
154     last->phdr.p_filesz += size;
155     last->phdr.p_memsz += size;
156
157     injOffset = ROUNDUP(injOffset, align);
158
159     if (injPhdr) {
160       *injPhdr = last;
161     }
162     return last->phdr.p_vaddr + (injOffset - last->phdr.p_offset);
163   } else if ((w && (first->phdr.p_flags & PF_W))
164              || (x && (first->phdr.p_flags & PF_X))) {
165     /* Need to prepend or split up the PHDR. */
166     GElf_Off injOffset = OFFS_END(first->phdr.p_offset, first->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(first->phdr.p_align >= align);
172     size = ROUNDUP(size, first->phdr.p_align);
173
174     /* Find first section. We assume there is at least one. */
175     assert(!CIRCLEQ_EMPTY(&first->childScnList));
176     injOffset = CIRCLEQ_FIRST(&first->childScnList)->shdr.sh_offset;
177
178     /* Move our sections */
179     CIRCLEQ_FOREACH(ms, &first->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, &first->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, size));
197
198     /* Remap ourselves */
199     first->phdr.p_vaddr -= size;
200     first->phdr.p_paddr -= size;
201     first->phdr.p_filesz += size;
202     first->phdr.p_memsz += size;
203
204     injOffset = ROUNDUP(injOffset, align);
205
206     if (injPhdr) {
207       *injPhdr = first;
208     }
209     return first->phdr.p_vaddr + (injOffset - first->phdr.p_offset);
210   }
211
212   ERROR:
213   if (injPhdr) {
214     *injPhdr = NULL;
215   }
216   return 0;
217 }