rumpuser_dl.c revision 1.11 1 /* $NetBSD: rumpuser_dl.c,v 1.11 2012/12/11 21:16:22 pooka Exp $ */
2
3 /*
4 * Copyright (c) 2009 Antti Kantee. All Rights Reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
16 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 /*
29 * Load all module link sets and feed symbol table to the kernel.
30 * Called during rump bootstrap.
31 */
32
33 #include "rumpuser_port.h"
34
35 #if !defined(lint)
36 __RCSID("$NetBSD: rumpuser_dl.c,v 1.11 2012/12/11 21:16:22 pooka Exp $");
37 #endif /* !lint */
38
39 #include <sys/types.h>
40 #include <sys/time.h>
41 #include <assert.h>
42
43 #include <dlfcn.h>
44 #include <elf.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <link.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52
53 #include <rump/rumpuser.h>
54
55 #if defined(__ELF__) && (defined(__NetBSD__) || defined(__FreeBSD__) \
56 || (defined(__sun__) && defined(__svr4__))) || defined(__linux__) \
57 || defined(__DragonFly__)
58 static size_t symtabsize = 0, strtabsize = 0;
59 static size_t symtaboff = 0, strtaboff = 0;
60 static uint8_t *symtab = NULL;
61 static char *strtab = NULL;
62 static unsigned char eident;
63
64 /* nb5 compat */
65 #ifndef Elf_Symindx
66 #define Elf_Symindx uint32_t
67 #endif
68
69 /*
70 * Linux ld.so requires a valid handle for dlinfo(), so use the main
71 * handle. We initialize this variable in rumpuser_dl_bootstrap()
72 */
73 static void *mainhandle;
74
75 static void *
76 reservespace(void *store, size_t *storesize,
77 size_t storeoff, size_t required)
78 {
79 size_t chunk, newsize;
80
81 assert(storeoff <= *storesize);
82 chunk = *storesize - storeoff;
83
84 if (chunk >= required)
85 return store;
86
87 newsize = *storesize + ((size_t)required - chunk);
88 store = realloc(store, newsize);
89 if (store == NULL) {
90 return NULL;
91 }
92 *((uint8_t *)store + storeoff) = '\0';
93 *storesize = newsize;
94
95 return store;
96 }
97
98 /*
99 * Macros to make handling elf32/64 in the code a little saner.
100 */
101
102 #define DYNn_GETMEMBER(base, n, thevar, result) \
103 do { \
104 if (eident == ELFCLASS32) { \
105 const Elf32_Dyn *dyn = base; \
106 /*LINTED*/ \
107 result = dyn[n].thevar; \
108 } else { \
109 const Elf64_Dyn *dyn = base; \
110 /*LINTED*/ \
111 result = dyn[n].thevar; \
112 } \
113 } while (/*CONSTCOND*/0)
114
115 #define SYMn_GETMEMBER(base, n, thevar, result) \
116 do { \
117 if (eident == ELFCLASS32) { \
118 const Elf32_Sym *sym = base; \
119 /*LINTED*/ \
120 result = sym[n].thevar; \
121 } else { \
122 const Elf64_Sym *sym = base; \
123 /*LINTED*/ \
124 result = sym[n].thevar; \
125 } \
126 } while (/*CONSTCOND*/0)
127
128 #define SYMn_SETMEMBER(base, n, thevar, value) \
129 do { \
130 if (eident == ELFCLASS32) { \
131 Elf32_Sym *sym = base; \
132 /*LINTED*/ \
133 sym[n].thevar = value; \
134 } else { \
135 Elf64_Sym *sym = base; \
136 /*LINTED*/ \
137 sym[n].thevar = value; \
138 } \
139 } while (/*CONSTCOND*/0)
140
141 #define SYM_GETSIZE() ((eident==ELFCLASS32)?sizeof(Elf32_Sym):sizeof(Elf64_Sym))
142
143 /*
144 * On NetBSD, the dynamic section pointer values seem to be relative to
145 * the address the dso is mapped at. On Linux, they seem to contain
146 * the absolute address. I couldn't find anything definite from a quick
147 * read of the standard and therefore I will not go and figure beyond ifdef.
148 */
149 #ifdef __linux__
150 #define adjptr(_map_, _ptr_) ((void *)(_ptr_))
151 #else
152 #define adjptr(_map_, _ptr_) ((void *)(_map_->l_addr + (_ptr_)))
153 #endif
154
155 static int
156 getsymbols(struct link_map *map)
157 {
158 char *str_base;
159 void *syms_base = NULL; /* XXXgcc */
160 size_t curstrsize;
161 const void *ed_base;
162 uint64_t ed_tag;
163 size_t cursymcount;
164 unsigned i;
165
166 if (map->l_addr) {
167 if (memcmp((void *)map->l_addr, ELFMAG, SELFMAG) != 0)
168 return ENOEXEC;
169 eident = *(unsigned char *)(map->l_addr + EI_CLASS);
170 if (eident != ELFCLASS32 && eident != ELFCLASS64)
171 return ENOEXEC;
172 }
173
174 /*
175 * ok, we probably have only the main object. instead of going
176 * to disk and reading the ehdr, just try to guess the size.
177 */
178 if (eident == 0) {
179 if (/*CONSTCOND*/sizeof(void *) == 4)
180 eident = ELFCLASS32;
181 else
182 eident = ELFCLASS64;
183 }
184
185 /*
186 * Find symtab and strtab and their sizes.
187 */
188 str_base = NULL;
189 curstrsize = 0;
190 cursymcount = 0;
191 ed_base = map->l_ld;
192 DYNn_GETMEMBER(ed_base, 0, d_tag, ed_tag);
193 for (i = 0; ed_tag != DT_NULL;) {
194 uintptr_t edptr;
195 size_t edval;
196 Elf_Symindx *hashtab;
197
198 switch (ed_tag) {
199 case DT_SYMTAB:
200 DYNn_GETMEMBER(ed_base, i, d_un.d_ptr, edptr);
201 syms_base = adjptr(map, edptr);
202 break;
203 case DT_STRTAB:
204 DYNn_GETMEMBER(ed_base, i, d_un.d_ptr, edptr);
205 str_base = adjptr(map, edptr);
206 break;
207 case DT_STRSZ:
208 DYNn_GETMEMBER(ed_base, i, d_un.d_val, edval);
209 curstrsize = edval;
210 break;
211 case DT_HASH:
212 DYNn_GETMEMBER(ed_base, i, d_un.d_ptr, edptr);
213 hashtab = (Elf_Symindx *)adjptr(map, edptr);
214 cursymcount = hashtab[1];
215 break;
216 #ifdef DT_GNU_HASH
217 /*
218 * DT_GNU_HASH is a bit more complicated than DT_HASH
219 * in this regard since apparently there is no field
220 * telling us the total symbol count. Instead, we look
221 * for the last valid hash bucket and add its chain lenght
222 * to the bucket's base index.
223 */
224 case DT_GNU_HASH: {
225 Elf32_Word nbuck, symndx, maskwords, maxchain = 0;
226 Elf32_Word *gnuhash, *buckets, *ptr;
227 int bi;
228
229 DYNn_GETMEMBER(ed_base, i, d_un.d_ptr, edptr);
230 gnuhash = (Elf32_Word *)adjptr(map, edptr);
231
232 nbuck = gnuhash[0];
233 symndx = gnuhash[1];
234 maskwords = gnuhash[2];
235
236 /*
237 * First, find the last valid bucket and grab its index
238 */
239 if (eident == ELFCLASS64)
240 maskwords *= 2; /* sizeof(*buckets) == 4 */
241 buckets = gnuhash + 4 + maskwords;
242 for (bi = nbuck-1; bi >= 0; bi--) {
243 if (buckets[bi] != 0) {
244 maxchain = buckets[bi];
245 break;
246 }
247 }
248 if (maxchain == 0 || maxchain < symndx)
249 break;
250
251 /*
252 * Then, traverse the last chain and count symbols.
253 */
254
255 cursymcount = maxchain;
256 ptr = buckets + nbuck + (maxchain - symndx);
257 do {
258 cursymcount++;
259 } while ((*ptr++ & 1) == 0);
260 }
261 break;
262 #endif
263 case DT_SYMENT:
264 DYNn_GETMEMBER(ed_base, i, d_un.d_val, edval);
265 assert(edval == SYM_GETSIZE());
266 break;
267 default:
268 break;
269 }
270 i++;
271 DYNn_GETMEMBER(ed_base, i, d_tag, ed_tag);
272 }
273
274 if (str_base == NULL || syms_base == NULL ||
275 curstrsize == 0 || cursymcount == 0) {
276 fprintf(stderr, "could not find strtab, symtab or their sizes "
277 "in %s\n", map->l_name);
278 return ENOEXEC;
279 }
280
281 /*
282 * Make sure we have enough space for the contents of the symbol
283 * and string tables we are currently processing. The total used
284 * space will be smaller due to undefined symbols we are not
285 * interested in.
286 */
287 symtab = reservespace(symtab, &symtabsize,
288 symtaboff, cursymcount * SYM_GETSIZE());
289 strtab = reservespace(strtab, &strtabsize, strtaboff, curstrsize);
290 if (symtab == NULL || strtab == NULL) {
291 fprintf(stderr, "failed to reserve memory");
292 return ENOMEM;
293 }
294
295 /* iterate over all symbols in current symtab */
296 for (i = 0; i < cursymcount; i++) {
297 const char *cursymname;
298 int shndx, name;
299 uintptr_t value;
300 void *csym;
301
302 SYMn_GETMEMBER(syms_base, i, st_shndx, shndx);
303 SYMn_GETMEMBER(syms_base, i, st_value, value);
304 if (shndx == SHN_UNDEF || value == 0)
305 continue;
306
307 /* get symbol name */
308 SYMn_GETMEMBER(syms_base, i, st_name, name);
309 cursymname = name + str_base;
310
311 /*
312 * Only accept symbols which are decidedly in
313 * the rump kernel namespace.
314 * XXX: quirks, but they wouldn't matter here
315 */
316 if (strncmp(cursymname, "rump", 4) != 0 &&
317 strncmp(cursymname, "RUMP", 4) != 0 &&
318 strncmp(cursymname, "__", 2) != 0) {
319 continue;
320 }
321
322 memcpy(symtab + symtaboff,
323 (const uint8_t *)syms_base + i*SYM_GETSIZE(),SYM_GETSIZE());
324
325 /*
326 * set name to point at new strtab, offset symbol value
327 * with lib base address.
328 */
329 csym = symtab + symtaboff;
330 SYMn_SETMEMBER(csym, 0, st_name, strtaboff);
331 SYMn_GETMEMBER(csym, 0, st_value, value);
332 SYMn_SETMEMBER(csym, 0, st_value,(intptr_t)(value+map->l_addr));
333 symtaboff += SYM_GETSIZE();
334
335 strcpy(strtab + strtaboff, cursymname);
336 strtaboff += strlen(cursymname)+1;
337 }
338
339 return 0;
340 }
341
342 static void
343 process(const char *soname, rump_modinit_fn domodinit)
344 {
345 void *handle;
346 const struct modinfo *const *mi_start, *const *mi_end;
347
348 if (strstr(soname, "librump") == NULL)
349 return;
350
351 handle = dlopen(soname, RTLD_LAZY);
352 if (handle == NULL)
353 return;
354
355 mi_start = dlsym(handle, "__start_link_set_modules");
356 if (!mi_start)
357 goto out;
358 mi_end = dlsym(handle, "__stop_link_set_modules");
359 if (!mi_end)
360 goto out;
361
362 domodinit(mi_start, (size_t)(mi_end-mi_start));
363
364 out:
365 dlclose(handle);
366 }
367
368 /*
369 * Get the linkmap from the dynlinker. Try to load kernel modules
370 * from all objects in the linkmap.
371 */
372 void
373 rumpuser_dl_bootstrap(rump_modinit_fn domodinit,
374 rump_symload_fn symload)
375 {
376 struct link_map *map, *origmap;
377 int error;
378
379 mainhandle = dlopen(NULL, RTLD_NOW);
380 if (dlinfo(mainhandle, RTLD_DI_LINKMAP, &origmap) == -1) {
381 fprintf(stderr, "warning: rumpuser module bootstrap "
382 "failed: %s\n", dlerror());
383 return;
384 }
385 /*
386 * Process last->first because that's the most probable
387 * order for dependencies
388 */
389 for (; origmap->l_next; origmap = origmap->l_next)
390 continue;
391
392 /*
393 * Build symbol table to hand to the rump kernel. Do this by
394 * iterating over all rump libraries and collecting symbol
395 * addresses and relocation info.
396 */
397 error = 0;
398 for (map = origmap; map && !error; map = map->l_prev) {
399 if (strstr(map->l_name, "librump") != NULL)
400 error = getsymbols(map);
401 /* this should be the main object */
402 else if (!map->l_addr && map->l_prev == NULL)
403 error = getsymbols(map);
404 }
405
406 if (error == 0) {
407 void *trimmedsym, *trimmedstr;
408
409 /*
410 * Allocate optimum-sized memory for storing tables
411 * and feed to kernel. If memory allocation fails,
412 * just give the ones with extra context (although
413 * I'm pretty sure we'll die moments later due to
414 * memory running out).
415 */
416 if ((trimmedsym = malloc(symtaboff)) != NULL) {
417 memcpy(trimmedsym, symtab, symtaboff);
418 } else {
419 trimmedsym = symtab;
420 symtab = NULL;
421 }
422 if ((trimmedstr = malloc(strtaboff)) != NULL) {
423 memcpy(trimmedstr, strtab, strtaboff);
424 } else {
425 trimmedstr = strtab;
426 strtab = NULL;
427 }
428 symload(trimmedsym, symtaboff, trimmedstr, strtaboff);
429 }
430 free(symtab);
431 free(strtab);
432
433 /*
434 * Next, load modules from dynlibs.
435 */
436 for (map = origmap; map; map = map->l_prev)
437 process(map->l_name, domodinit);
438 }
439
440 void
441 rumpuser_dl_component_init(int type, rump_component_init_fn compinit)
442 {
443 struct link_map *map;
444
445 if (dlinfo(mainhandle, RTLD_DI_LINKMAP, &map) == -1) {
446 fprintf(stderr, "warning: rumpuser module bootstrap "
447 "failed: %s\n", dlerror());
448 return;
449 }
450
451 for (; map->l_next; map = map->l_next)
452 continue;
453 for (; map; map = map->l_prev) {
454 if (strstr(map->l_name, "librump") != NULL) {
455 void *handle;
456 struct rump_component **rc, **rc_end;
457
458 handle = dlopen(map->l_name, RTLD_LAZY);
459 if (handle == NULL)
460 continue;
461
462 rc = dlsym(handle,
463 "__start_link_set_rump_components");
464 if (!rc)
465 goto loop;
466 rc_end = dlsym(handle,
467 "__stop_link_set_rump_components");
468 if (!rc_end)
469 goto loop;
470
471 for (; rc < rc_end; rc++)
472 compinit(*rc, type);
473 assert(rc == rc_end);
474 loop:
475 dlclose(handle);
476 }
477 }
478 }
479 #else
480 void
481 rumpuser_dl_bootstrap(rump_modinit_fn domodinit,
482 rump_symload_fn symload)
483 {
484
485 fprintf(stderr, "Warning, dlinfo() unsupported on host?\n");
486 }
487
488 void
489 rumpuser_dl_component_init(int type, rump_component_init_fn compinit)
490 {
491
492 fprintf(stderr, "Warning, dlinfo() unsupported on host?\n");
493 }
494 #endif
495
496 void *
497 rumpuser_dl_globalsym(const char *symname)
498 {
499
500 return dlsym(RTLD_DEFAULT, symname);
501 }
502