diff options
Diffstat (limited to 'arch/x86/kernel/module.c')
-rw-r--r-- | arch/x86/kernel/module.c | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/arch/x86/kernel/module.c b/arch/x86/kernel/module.c new file mode 100644 index 000000000000..89f386f044e4 --- /dev/null +++ b/arch/x86/kernel/module.c | |||
@@ -0,0 +1,248 @@ | |||
1 | /* Kernel module help for x86. | ||
2 | Copyright (C) 2001 Rusty Russell. | ||
3 | |||
4 | This program is free software; you can redistribute it and/or modify | ||
5 | it under the terms of the GNU General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or | ||
7 | (at your option) any later version. | ||
8 | |||
9 | This program is distributed in the hope that it will be useful, | ||
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | GNU General Public License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU General Public License | ||
15 | along with this program; if not, write to the Free Software | ||
16 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
17 | */ | ||
18 | #include <linux/moduleloader.h> | ||
19 | #include <linux/elf.h> | ||
20 | #include <linux/vmalloc.h> | ||
21 | #include <linux/fs.h> | ||
22 | #include <linux/string.h> | ||
23 | #include <linux/kernel.h> | ||
24 | #include <linux/bug.h> | ||
25 | #include <linux/mm.h> | ||
26 | |||
27 | #include <asm/system.h> | ||
28 | #include <asm/page.h> | ||
29 | #include <asm/pgtable.h> | ||
30 | |||
31 | #if 0 | ||
32 | #define DEBUGP printk | ||
33 | #else | ||
34 | #define DEBUGP(fmt...) | ||
35 | #endif | ||
36 | |||
37 | void *module_alloc(unsigned long size) | ||
38 | { | ||
39 | struct vm_struct *area; | ||
40 | |||
41 | if (!size) | ||
42 | return NULL; | ||
43 | size = PAGE_ALIGN(size); | ||
44 | if (size > MODULES_LEN) | ||
45 | return NULL; | ||
46 | |||
47 | area = __get_vm_area(size, VM_ALLOC, MODULES_VADDR, MODULES_END); | ||
48 | if (!area) | ||
49 | return NULL; | ||
50 | |||
51 | return __vmalloc_area(area, GFP_KERNEL | __GFP_HIGHMEM, | ||
52 | PAGE_KERNEL_EXEC); | ||
53 | } | ||
54 | |||
55 | /* Free memory returned from module_alloc */ | ||
56 | void module_free(struct module *mod, void *module_region) | ||
57 | { | ||
58 | vfree(module_region); | ||
59 | } | ||
60 | |||
61 | /* We don't need anything special. */ | ||
62 | int module_frob_arch_sections(Elf_Ehdr *hdr, | ||
63 | Elf_Shdr *sechdrs, | ||
64 | char *secstrings, | ||
65 | struct module *mod) | ||
66 | { | ||
67 | return 0; | ||
68 | } | ||
69 | |||
70 | #ifdef CONFIG_X86_32 | ||
71 | int apply_relocate(Elf32_Shdr *sechdrs, | ||
72 | const char *strtab, | ||
73 | unsigned int symindex, | ||
74 | unsigned int relsec, | ||
75 | struct module *me) | ||
76 | { | ||
77 | unsigned int i; | ||
78 | Elf32_Rel *rel = (void *)sechdrs[relsec].sh_addr; | ||
79 | Elf32_Sym *sym; | ||
80 | uint32_t *location; | ||
81 | |||
82 | DEBUGP("Applying relocate section %u to %u\n", relsec, | ||
83 | sechdrs[relsec].sh_info); | ||
84 | for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) { | ||
85 | /* This is where to make the change */ | ||
86 | location = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr | ||
87 | + rel[i].r_offset; | ||
88 | /* This is the symbol it is referring to. Note that all | ||
89 | undefined symbols have been resolved. */ | ||
90 | sym = (Elf32_Sym *)sechdrs[symindex].sh_addr | ||
91 | + ELF32_R_SYM(rel[i].r_info); | ||
92 | |||
93 | switch (ELF32_R_TYPE(rel[i].r_info)) { | ||
94 | case R_386_32: | ||
95 | /* We add the value into the location given */ | ||
96 | *location += sym->st_value; | ||
97 | break; | ||
98 | case R_386_PC32: | ||
99 | /* Add the value, subtract its postition */ | ||
100 | *location += sym->st_value - (uint32_t)location; | ||
101 | break; | ||
102 | default: | ||
103 | printk(KERN_ERR "module %s: Unknown relocation: %u\n", | ||
104 | me->name, ELF32_R_TYPE(rel[i].r_info)); | ||
105 | return -ENOEXEC; | ||
106 | } | ||
107 | } | ||
108 | return 0; | ||
109 | } | ||
110 | |||
111 | int apply_relocate_add(Elf32_Shdr *sechdrs, | ||
112 | const char *strtab, | ||
113 | unsigned int symindex, | ||
114 | unsigned int relsec, | ||
115 | struct module *me) | ||
116 | { | ||
117 | printk(KERN_ERR "module %s: ADD RELOCATION unsupported\n", | ||
118 | me->name); | ||
119 | return -ENOEXEC; | ||
120 | } | ||
121 | #else /*X86_64*/ | ||
122 | int apply_relocate_add(Elf64_Shdr *sechdrs, | ||
123 | const char *strtab, | ||
124 | unsigned int symindex, | ||
125 | unsigned int relsec, | ||
126 | struct module *me) | ||
127 | { | ||
128 | unsigned int i; | ||
129 | Elf64_Rela *rel = (void *)sechdrs[relsec].sh_addr; | ||
130 | Elf64_Sym *sym; | ||
131 | void *loc; | ||
132 | u64 val; | ||
133 | |||
134 | DEBUGP("Applying relocate section %u to %u\n", relsec, | ||
135 | sechdrs[relsec].sh_info); | ||
136 | for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) { | ||
137 | /* This is where to make the change */ | ||
138 | loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr | ||
139 | + rel[i].r_offset; | ||
140 | |||
141 | /* This is the symbol it is referring to. Note that all | ||
142 | undefined symbols have been resolved. */ | ||
143 | sym = (Elf64_Sym *)sechdrs[symindex].sh_addr | ||
144 | + ELF64_R_SYM(rel[i].r_info); | ||
145 | |||
146 | DEBUGP("type %d st_value %Lx r_addend %Lx loc %Lx\n", | ||
147 | (int)ELF64_R_TYPE(rel[i].r_info), | ||
148 | sym->st_value, rel[i].r_addend, (u64)loc); | ||
149 | |||
150 | val = sym->st_value + rel[i].r_addend; | ||
151 | |||
152 | switch (ELF64_R_TYPE(rel[i].r_info)) { | ||
153 | case R_X86_64_NONE: | ||
154 | break; | ||
155 | case R_X86_64_64: | ||
156 | *(u64 *)loc = val; | ||
157 | break; | ||
158 | case R_X86_64_32: | ||
159 | *(u32 *)loc = val; | ||
160 | if (val != *(u32 *)loc) | ||
161 | goto overflow; | ||
162 | break; | ||
163 | case R_X86_64_32S: | ||
164 | *(s32 *)loc = val; | ||
165 | if ((s64)val != *(s32 *)loc) | ||
166 | goto overflow; | ||
167 | break; | ||
168 | case R_X86_64_PC32: | ||
169 | val -= (u64)loc; | ||
170 | *(u32 *)loc = val; | ||
171 | #if 0 | ||
172 | if ((s64)val != *(s32 *)loc) | ||
173 | goto overflow; | ||
174 | #endif | ||
175 | break; | ||
176 | default: | ||
177 | printk(KERN_ERR "module %s: Unknown rela relocation: %llu\n", | ||
178 | me->name, ELF64_R_TYPE(rel[i].r_info)); | ||
179 | return -ENOEXEC; | ||
180 | } | ||
181 | } | ||
182 | return 0; | ||
183 | |||
184 | overflow: | ||
185 | printk(KERN_ERR "overflow in relocation type %d val %Lx\n", | ||
186 | (int)ELF64_R_TYPE(rel[i].r_info), val); | ||
187 | printk(KERN_ERR "`%s' likely not compiled with -mcmodel=kernel\n", | ||
188 | me->name); | ||
189 | return -ENOEXEC; | ||
190 | } | ||
191 | |||
192 | int apply_relocate(Elf_Shdr *sechdrs, | ||
193 | const char *strtab, | ||
194 | unsigned int symindex, | ||
195 | unsigned int relsec, | ||
196 | struct module *me) | ||
197 | { | ||
198 | printk(KERN_ERR "non add relocation not supported\n"); | ||
199 | return -ENOSYS; | ||
200 | } | ||
201 | |||
202 | #endif | ||
203 | |||
204 | int module_finalize(const Elf_Ehdr *hdr, | ||
205 | const Elf_Shdr *sechdrs, | ||
206 | struct module *me) | ||
207 | { | ||
208 | const Elf_Shdr *s, *text = NULL, *alt = NULL, *locks = NULL, | ||
209 | *para = NULL; | ||
210 | char *secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset; | ||
211 | |||
212 | for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) { | ||
213 | if (!strcmp(".text", secstrings + s->sh_name)) | ||
214 | text = s; | ||
215 | if (!strcmp(".altinstructions", secstrings + s->sh_name)) | ||
216 | alt = s; | ||
217 | if (!strcmp(".smp_locks", secstrings + s->sh_name)) | ||
218 | locks = s; | ||
219 | if (!strcmp(".parainstructions", secstrings + s->sh_name)) | ||
220 | para = s; | ||
221 | } | ||
222 | |||
223 | if (alt) { | ||
224 | /* patch .altinstructions */ | ||
225 | void *aseg = (void *)alt->sh_addr; | ||
226 | apply_alternatives(aseg, aseg + alt->sh_size); | ||
227 | } | ||
228 | if (locks && text) { | ||
229 | void *lseg = (void *)locks->sh_addr; | ||
230 | void *tseg = (void *)text->sh_addr; | ||
231 | alternatives_smp_module_add(me, me->name, | ||
232 | lseg, lseg + locks->sh_size, | ||
233 | tseg, tseg + text->sh_size); | ||
234 | } | ||
235 | |||
236 | if (para) { | ||
237 | void *pseg = (void *)para->sh_addr; | ||
238 | apply_paravirt(pseg, pseg + para->sh_size); | ||
239 | } | ||
240 | |||
241 | return module_bug_finalize(hdr, sechdrs, me); | ||
242 | } | ||
243 | |||
244 | void module_arch_cleanup(struct module *mod) | ||
245 | { | ||
246 | alternatives_smp_module_del(mod); | ||
247 | module_bug_cleanup(mod); | ||
248 | } | ||