NOBITS expansion, for .bss etc
[centaur.git] / src / model / insert.c
1 #include <assert.h>
2 #include <sys/types.h>
3 #include <gelf.h>
4 #include <libelfu/libelfu.h>
5
6
7 // TODO: Take p_align into account
8
9
10
11 /*
12  * Insert space at a given position in the file by moving everything
13  * after it towards the end of the file, and everything before it
14  * towards lower memory regions where it is mapped.
15  *
16  * off must not be in the middle of any data structure, such as
17  * PHDRs, SHDRs, or sections. Behaviour is undefined if it is.
18  *
19  * PHDRs will be patched such that everything AFTER off is mapped to
20  * the same address in memory, and everything BEFORE it is shifted to
21  * lower addresses, making space for the new data in-between.
22  */
23 GElf_Xword elfu_mInsertBefore(ElfuElf *me, GElf_Off off, GElf_Xword size)
24 {
25   ElfuScn *ms;
26   ElfuPhdr *mp;
27
28   assert(me);
29
30   /* Move SHDRs and PHDRs */
31   if (me->ehdr.e_shoff >= off) {
32     me->ehdr.e_shoff += size;
33   }
34
35   if (me->ehdr.e_phoff >= off) {
36     me->ehdr.e_phoff += size;
37   }
38
39   /* Patch PHDRs to include new data */
40   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
41     GElf_Off end = mp->phdr.p_offset + mp->phdr.p_filesz;
42
43     if (mp->phdr.p_offset >= off) {
44       /* Insertion before PHDR's content, so it's just shifted */
45       mp->phdr.p_offset += size;
46     } else {
47       /* mp->phdr.p_offset < off */
48
49       if (off < end) {
50         /* Insertion in the middle of PHDR, so let it span the new data */
51         mp->phdr.p_filesz += size;
52         mp->phdr.p_memsz += size;
53         mp->phdr.p_vaddr -= size;
54         mp->phdr.p_paddr -= size;
55       } else {
56         /* Insertion after PHDR's content, so it may need to be
57            remapped. This will happen in a second pass.
58          */
59       }
60     }
61   }
62
63   /* For each LOAD header, find clashing headers that need to be
64      remapped to lower memory areas.
65    */
66   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
67     if (mp->phdr.p_type == PT_LOAD) {
68       ElfuPhdr *mp2;
69
70       CIRCLEQ_FOREACH(mp2, &me->phdrList, elem) {
71         if (mp2->phdr.p_type != PT_LOAD
72             && mp2->phdr.p_offset + mp2->phdr.p_filesz <= off) {
73           /* The PHDR ends in the file before the injection site */
74           GElf_Off vend1 = mp->phdr.p_vaddr + mp->phdr.p_memsz;
75           GElf_Off pend1 = mp->phdr.p_paddr + mp->phdr.p_memsz;
76           GElf_Off vend2 = mp2->phdr.p_vaddr + mp2->phdr.p_memsz;
77           GElf_Off pend2 = mp2->phdr.p_paddr + mp2->phdr.p_memsz;
78
79           /* If mp and mp2 now overlap in memory */
80           if ((mp2->phdr.p_vaddr < vend1 && vend2 > mp->phdr.p_vaddr)
81               || (mp2->phdr.p_paddr < pend1 && pend2 > mp->phdr.p_paddr)) {
82             /* Move mp2 down in memory, as mp has been resized.
83                Maintaining the relative offset between them is the best
84                guess at maintaining consistency.
85              */
86             mp2->phdr.p_vaddr -= size;
87             mp2->phdr.p_paddr -= size;
88           }
89         }
90       }
91     }
92   }
93
94   /* Move the sections themselves */
95   CIRCLEQ_FOREACH(ms, &me->scnList, elem) {
96     if (ms->shdr.sh_offset >= off) {
97       ms->shdr.sh_offset += size;
98     } else {
99       /* sh_offset < off */
100
101       /* If this was in a LOAD segment, it has been adjusted there
102          and this synchronises it.
103          If not, it doesn't matter anyway.
104        */
105       ms->shdr.sh_addr -= size;
106     }
107   }
108
109   return size;
110 }
111
112
113
114 /*
115  * Insert space at a given position in the file by moving everything
116  * after it towards the end of the file, and towards higher memory
117  * regions where it is mapped.
118  *
119  * off must not be in the middle of any data structure, such as
120  * PHDRs, SHDRs, or sections. Behaviour is undefined if it is.
121  *
122  * PHDRs will be patched such that everything AFTER off is shifted to
123  * higher addresses, making space for the new data in-between.
124  *
125  * CAUTION: This also moves NOBITS sections. If such are present,
126  *          use mExpandNobits() first and then inject at the end of
127  *          the expansion site.
128  */
129 GElf_Xword elfu_mInsertAfter(ElfuElf *me, GElf_Off off, GElf_Xword size)
130 {
131   ElfuScn *ms;
132   ElfuPhdr *mp;
133
134   assert(me);
135
136 /* Move SHDRs and PHDRs */
137   if (me->ehdr.e_shoff >= off) {
138     me->ehdr.e_shoff += size;
139   }
140
141   if (me->ehdr.e_phoff >= off) {
142     me->ehdr.e_phoff += size;
143   }
144
145   /* Patch PHDRs to include new data */
146   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
147     GElf_Off end = mp->phdr.p_offset + mp->phdr.p_filesz;
148
149     if (mp->phdr.p_offset >= off) {
150       /* Insertion before PHDR's content, so it's shifted. */
151       mp->phdr.p_offset += size;
152
153       /* It may also need to be remapped. See second pass below. */
154     } else {
155       /* mp->phdr.p_offset < off */
156
157       if (off < end) {
158         /* Insertion in the middle of PHDR, so let it span the new data */
159         mp->phdr.p_filesz += size;
160         mp->phdr.p_memsz += size;
161       } else {
162         /* Insertion after PHDR's content. Nothing to do. */
163       }
164     }
165   }
166
167   /* For each LOAD header, find clashing headers that need to be
168      remapped to higher memory areas.
169    */
170   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
171     if (mp->phdr.p_type == PT_LOAD) {
172       ElfuPhdr *mp2;
173
174       CIRCLEQ_FOREACH(mp2, &me->phdrList, elem) {
175         if (mp2->phdr.p_type != PT_LOAD
176             && mp2->phdr.p_offset + mp2->phdr.p_filesz > off) {
177           /* The PHDR now ends in the file after the injection site */
178           GElf_Off vend1 = mp->phdr.p_vaddr + mp->phdr.p_memsz;
179           GElf_Off pend1 = mp->phdr.p_paddr + mp->phdr.p_memsz;
180           GElf_Off vend2 = mp2->phdr.p_vaddr + mp2->phdr.p_memsz;
181           GElf_Off pend2 = mp2->phdr.p_paddr + mp2->phdr.p_memsz;
182
183           /* If mp and mp2 now overlap in memory */
184           if ((mp2->phdr.p_vaddr < vend1 && vend2 > mp->phdr.p_vaddr)
185               || (mp2->phdr.p_paddr < pend1 && pend2 > mp->phdr.p_paddr)) {
186             /* Move mp2 up in memory, as mp has been resized.
187                Maintaining the relative offset between them is the best
188                guess at maintaining consistency.
189              */
190
191             mp2->phdr.p_vaddr += size;
192             mp2->phdr.p_paddr += size;
193           }
194         }
195       }
196     }
197   }
198
199   /* Move the sections themselves */
200   CIRCLEQ_FOREACH(ms, &me->scnList, elem) {
201     if (ms->shdr.sh_offset >= off) {
202       ms->shdr.sh_offset += size;
203
204       /* If this was in a LOAD segment, it has been adjusted there
205          and this synchronises it.
206          If not, it doesn't matter anyway.
207        */
208       ms->shdr.sh_addr += size;
209     }
210   }
211
212   return size;
213 }