From 1c0e32863e0583221e430c22c1c68a023fd16195 Mon Sep 17 00:00:00 2001 From: Simon Schubert Date: Fri, 4 Sep 2009 01:17:18 +0200 Subject: [PATCH] modules: pull in most of FreeBSD's module linker changes The big user-visible difference is that the module dependency system changed. Module dependencies are now the same as for the loader, and the hack linker (and Makefile) magic is not needed anymore. Obtained-from: FreeBSD --- sys/cpu/i386/misc/elf_machdep.c | 56 +- sys/kern/kern_linker.c | 834 ++++++++++++++++++-------- sys/kern/kern_module.c | 2 +- sys/kern/link_elf.c | 403 ++++++++----- sys/kern/vfs_syscalls.c | 2 +- sys/netgraph/netgraph/ng_base.c | 7 +- sys/platform/pc64/amd64/elf_machdep.c | 86 ++- sys/sys/linker.h | 29 +- 8 files changed, 940 insertions(+), 479 deletions(-) diff --git a/sys/cpu/i386/misc/elf_machdep.c b/sys/cpu/i386/misc/elf_machdep.c index 795fe11d0c..5394f1d5b1 100644 --- a/sys/cpu/i386/misc/elf_machdep.c +++ b/sys/cpu/i386/misc/elf_machdep.c @@ -32,15 +32,14 @@ #include /* Process one elf relocation with addend. */ -int -elf_reloc(linker_file_t lf, const void *data, int type, const char *sym) +static int +elf_reloc_internal(linker_file_t lf, Elf_Addr relocbase, const void *data, + int type, int local, elf_lookup_fn lookup) { - Elf_Addr relocbase = (Elf_Addr) lf->address; Elf_Addr *where; Elf_Addr addr; Elf_Addr addend; - Elf_Word rtype; - caddr_t caddr; + Elf_Word rtype, symidx; const Elf_Rel *rel; const Elf_Rela *rela; @@ -50,39 +49,44 @@ elf_reloc(linker_file_t lf, const void *data, int type, const char *sym) where = (Elf_Addr *) (relocbase + rel->r_offset); addend = *where; rtype = ELF_R_TYPE(rel->r_info); + symidx = ELF_R_SYM(rel->r_info); break; case ELF_RELOC_RELA: rela = (const Elf_Rela *)data; where = (Elf_Addr *) (relocbase + rela->r_offset); addend = rela->r_addend; rtype = ELF_R_TYPE(rela->r_info); + symidx = ELF_R_SYM(rela->r_info); break; default: panic("unknown reloc type %d\n", type); } + if (local) { + if (rtype == R_386_RELATIVE) { /* A + B */ + addr = elf_relocaddr(lf, relocbase + addend); + if (*where != addr) + *where = addr; + } + return (0); + } + switch (rtype) { case R_386_NONE: /* none */ break; case R_386_32: /* S + A */ - if (sym == NULL) + if (lookup(lf, symidx, 1, &addr)) return -1; - if (linker_file_lookup_symbol(lf, sym, 1, &caddr) != 0) - return -1; - addr = (Elf_Addr)caddr; addr += addend; if (*where != addr) *where = addr; break; case R_386_PC32: /* S + A - P */ - if (sym == NULL) - return -1; - if (linker_file_lookup_symbol(lf, sym, 1, &caddr) != 0) + if (lookup(lf, symidx, 1, &addr)) return -1; - addr = (Elf_Addr)caddr; addr += addend - (Elf_Addr)where; if (*where != addr) *where = addr; @@ -98,19 +102,13 @@ elf_reloc(linker_file_t lf, const void *data, int type, const char *sym) break; case R_386_GLOB_DAT: /* S */ - if (sym == NULL) - return -1; - if (linker_file_lookup_symbol(lf, sym, 1, &caddr) != 0) + if (lookup(lf, symidx, 1, &addr)) return -1; - addr = (Elf_Addr)caddr; if (*where != addr) *where = addr; break; - case R_386_RELATIVE: /* B + A */ - addr = relocbase + addend; - if (*where != addr) - *where = addr; + case R_386_RELATIVE: break; default: @@ -120,3 +118,19 @@ elf_reloc(linker_file_t lf, const void *data, int type, const char *sym) } return(0); } + +int +elf_reloc(linker_file_t lf, Elf_Addr relocbase, const void *data, int type, + elf_lookup_fn lookup) +{ + + return (elf_reloc_internal(lf, relocbase, data, type, 0, lookup)); +} + +int +elf_reloc_local(linker_file_t lf, Elf_Addr relocbase, const void *data, + int type, elf_lookup_fn lookup) +{ + + return (elf_reloc_internal(lf, relocbase, data, type, 1, lookup)); +} diff --git a/sys/kern/kern_linker.c b/sys/kern/kern_linker.c index 6606a3c491..9753aaadc5 100644 --- a/sys/kern/kern_linker.c +++ b/sys/kern/kern_linker.c @@ -54,7 +54,7 @@ #endif #ifdef KLD_DEBUG -int kld_debug = 0; +int kld_debug = 1; #endif /* Metadata from the static kernel */ @@ -69,6 +69,32 @@ static linker_class_list_t classes; static linker_file_list_t linker_files; static int next_file_id = 1; +/* XXX wrong name; we're looking at version provision tags here, not modules */ +typedef TAILQ_HEAD(, modlist) modlisthead_t; +struct modlist { + TAILQ_ENTRY(modlist) link; /* chain together all modules */ + linker_file_t container; + const char *name; + int version; +}; +typedef struct modlist *modlist_t; +static modlisthead_t found_modules; + + +static int linker_load_module(const char *kldname, const char *modname, + struct linker_file *parent, struct mod_depend *verinfo, + struct linker_file **lfpp); + +static char * +linker_strdup(const char *str) +{ + char *result; + + result = kmalloc(strlen(str) + 1, M_LINKER, M_WAITOK); + strcpy(result, str); + return(result); +} + static void linker_init(void* arg) { @@ -97,35 +123,20 @@ linker_add_class(const char* desc, void* priv, return 0; } -static int +static void linker_file_sysinit(linker_file_t lf) { struct sysinit** start, ** stop; struct sysinit** sipp; struct sysinit** xipp; struct sysinit* save; - const moduledata_t *moddata; - int error; KLD_DPF(FILE, ("linker_file_sysinit: calling SYSINITs for %s\n", lf->filename)); if (linker_file_lookup_set(lf, "sysinit_set", &start, &stop, NULL) != 0) - return 0; /* XXX is this correct ? No sysinit ? */ + return; - /* HACK ALERT! */ - for (sipp = start; sipp < stop; sipp++) { - if ((*sipp)->func == module_register_init) { - moddata = (*sipp)->udata; - error = module_register(moddata, lf); - if (error) { - kprintf("linker_file_sysinit \"%s\" failed to register! %d\n", - lf->filename, error); - return error; - } - } - } - /* * Perform a bubble sort of the system initialization objects by * their subsystem (primary key) and order (secondary key). @@ -157,7 +168,6 @@ linker_file_sysinit(linker_file_t lf) /* Call function */ (*((*sipp)->func))((*sipp)->udata); } - return 0; /* no errors */ } static void @@ -235,13 +245,59 @@ linker_file_unregister_sysctls(linker_file_t lf) sysctl_unregister_oid(*oidp); } +static int +linker_file_register_modules(linker_file_t lf) +{ + struct mod_metadata **start, **stop, **mdp; + const moduledata_t *moddata; + int first_error, error; + + KLD_DPF(FILE, ("linker_file_register_modules: registering modules in %s\n", + lf->filename)); + + if (linker_file_lookup_set(lf, "modmetadata_set", &start, &stop, NULL) != 0) { + /* + * This fallback should be unnecessary, but if we get booted + * from boot2 instead of loader and we are missing our + * metadata then we have to try the best we can. + */ + if (lf == linker_kernel_file) { + start = SET_BEGIN(modmetadata_set); + stop = SET_LIMIT(modmetadata_set); + } else + return (0); + } + first_error = 0; + for (mdp = start; mdp < stop; mdp++) { + if ((*mdp)->md_type != MDT_MODULE) + continue; + moddata = (*mdp)->md_data; + KLD_DPF(FILE, ("Registering module %s in %s\n", moddata->name, lf->filename)); + error = module_register(moddata, lf); + if (error) { + kprintf("Module %s failed to register: %d\n", moddata->name, error); + if (first_error == 0) + first_error = error; + } + } + return (first_error); +} + +static void +linker_init_kernel_modules(void) +{ + + linker_file_register_modules(linker_kernel_file); +} + +SYSINIT(linker_kernel, SI_BOOT2_KLD, SI_ORDER_ANY, linker_init_kernel_modules, 0); + int -linker_load_file(const char *filename, linker_file_t *result, int load_flags) +linker_load_file(const char *filename, linker_file_t *result) { linker_class_t lc; linker_file_t lf; int foundfile, error = 0; - char *koname = NULL; /* Refuse to load modules if securelevel raised */ if (securelevel > 0 || kernel_mem_readonly) @@ -254,68 +310,31 @@ linker_load_file(const char *filename, linker_file_t *result, int load_flags) lf->refs++; goto out; } - if (find_mod_metadata(filename)) { - if (linker_kernel_file) - ++linker_kernel_file->refs; - *result = linker_kernel_file; - goto out; - } - koname = kmalloc(strlen(filename) + 4, M_LINKER, M_WAITOK); - ksprintf(koname, "%s.ko", filename); lf = NULL; foundfile = 0; TAILQ_FOREACH(lc, &classes, link) { KLD_DPF(FILE, ("linker_load_file: trying to load %s as %s\n", filename, lc->desc)); - /* First with .ko */ - error = lc->ops->load_file(koname, &lf, load_flags); - if (lf == NULL && error == ENOENT) { - /* Then try without */ - error = lc->ops->load_file(filename, &lf, load_flags); - } + error = lc->ops->load_file(filename, &lf); /* * If we got something other than ENOENT, then it exists but we cannot * load it for some other reason. */ if (error != ENOENT) foundfile = 1; - - /* - * Finish up. If this is part of a preload chain the sysinits - * have to be installed for later execution, otherwise we run - * them immediately. - */ if (lf) { - if (load_flags & LINKER_LOAD_FILE_PRELOAD) { - struct sysinit **si_start, **si_stop; - struct sysinit **sipp; - const moduledata_t *moddata; - - error = 0; - if (linker_file_lookup_set(lf, "sysinit_set", - &si_start, &si_stop, NULL) == 0) { - for (sipp = si_start; sipp < si_stop; sipp++) { - if ((*sipp)->func == module_register_init) { - moddata = (*sipp)->udata; - error = module_register(moddata, lf); - if (error) { - kprintf("Preloaded dependency \"%s\" " - "failed to register: %d\n", - koname, error); - } - } - } - sysinit_add(si_start, si_stop); - } - } else { - error = linker_file_sysinit(lf); + error = linker_file_register_modules(lf); + if (error == EEXIST) { + linker_file_unload(lf /* , LINKER_UNLOAD_FORCE */); + return (error); } - linker_file_register_sysctls(lf); + linker_file_sysinit(lf); + lf->flags |= LINKER_FILE_LINKED; *result = lf; - goto out; + return (0); } } /* @@ -323,6 +342,13 @@ linker_load_file(const char *filename, linker_file_t *result, int load_flags) * the module was not found. */ if (foundfile) { + /* + * If the file type has not been recognized by the last try + * printout a message before to fail. + */ + if (error == ENOSYS) + kprintf("linker_load_file: Unsupported file type\n"); + /* * Format not recognized or otherwise unloadable. * When loading a module that is statically built into @@ -337,8 +363,6 @@ linker_load_file(const char *filename, linker_file_t *result, int load_flags) } out: - if (koname) - kfree(koname, M_LINKER); return error; } @@ -388,7 +412,6 @@ linker_file_t linker_make_file(const char* pathname, void* priv, struct linker_file_ops* ops) { linker_file_t lf = 0; - int namelen; const char *filename; filename = rindex(pathname, '/'); @@ -399,15 +422,13 @@ linker_make_file(const char* pathname, void* priv, struct linker_file_ops* ops) KLD_DPF(FILE, ("linker_make_file: new file, filename=%s\n", filename)); lockmgr(&lock, LK_EXCLUSIVE); - namelen = strlen(filename) + 1; - lf = kmalloc(sizeof(struct linker_file) + namelen, M_LINKER, M_WAITOK); + lf = kmalloc(sizeof(struct linker_file), M_LINKER, M_WAITOK); bzero(lf, sizeof(*lf)); lf->refs = 1; lf->userrefs = 0; lf->flags = 0; - lf->filename = (char*) (lf + 1); - strcpy(lf->filename, filename); + lf->filename = linker_strdup(filename); lf->id = next_file_id++; lf->ndeps = 0; lf->deps = NULL; @@ -425,7 +446,8 @@ linker_make_file(const char* pathname, void* priv, struct linker_file_ops* ops) int linker_file_unload(linker_file_t file) { - module_t mod, next; + module_t mod, next;; + modlist_t ml, nextml; struct common_symbol* cp; int error = 0; int i; @@ -435,83 +457,83 @@ linker_file_unload(linker_file_t file) return EPERM; KLD_DPF(FILE, ("linker_file_unload: lf->refs=%d\n", file->refs)); + lockmgr(&lock, LK_EXCLUSIVE); - if (file->refs == 1) { - KLD_DPF(FILE, ("linker_file_unload: file is unloading, informing modules\n")); - /* - * Temporarily bump file->refs to prevent recursive unloading - */ - ++file->refs; - /* - * Inform any modules associated with this file. - */ - mod = TAILQ_FIRST(&file->modules); - while (mod) { - /* - * Give the module a chance to veto the unload. Note that the - * act of unloading the module may cause other modules in the - * same file list to be unloaded recursively. - */ - if ((error = module_unload(mod)) != 0) { - KLD_DPF(FILE, ("linker_file_unload: module %x vetoes unload\n", - mod)); - lockmgr(&lock, LK_RELEASE); - file->refs--; - goto out; - } + /* Easy case of just dropping a reference. */ + if (file->refs > 1) { + file->refs--; + lockmgr(&lock, LK_RELEASE); + return (0); + } - /* - * Recursive relationships may prevent the release from - * immediately removing the module, or may remove other - * modules in the list. - */ - next = module_getfnext(mod); - module_release(mod); - mod = next; - } + KLD_DPF(FILE, ("linker_file_unload: file is unloading, informing modules\n")); + + /* + * Inform any modules associated with this file. + */ + mod = TAILQ_FIRST(&file->modules); + for (mod = TAILQ_FIRST(&file->modules); mod; mod = next) { + next = module_getfnext(mod); /* - * Since we intend to destroy the file structure, we expect all - * modules to have been removed by now. + * Give the module a chance to veto the unload. Note that the + * act of unloading the module may cause other modules in the + * same file list to be unloaded recursively. */ - for (mod = TAILQ_FIRST(&file->modules); - mod; - mod = module_getfnext(mod) - ) { - kprintf("linker_file_unload: module %p still has refs!\n", mod); + if ((error = module_unload(mod)) != 0) { + KLD_DPF(FILE, ("linker_file_unload: module %p vetoes unload\n", + mod)); + lockmgr(&lock, LK_RELEASE); + file->refs--; + goto out; } - --file->refs; + module_release(mod); } - file->refs--; - if (file->refs > 0) { - lockmgr(&lock, LK_RELEASE); - goto out; + TAILQ_FOREACH_MUTABLE(ml, &found_modules, link, nextml) { + if (ml->container == file) { + TAILQ_REMOVE(&found_modules, ml, link); + kfree(ml, M_LINKER); + } } /* Don't try to run SYSUNINITs if we are unloaded due to a link error */ if (file->flags & LINKER_FILE_LINKED) { + file->flags &= ~LINKER_FILE_LINKED; + lockmgr(&lock, LK_RELEASE); linker_file_sysuninit(file); linker_file_unregister_sysctls(file); + lockmgr(&lock, LK_EXCLUSIVE); } TAILQ_REMOVE(&linker_files, file, link); - lockmgr(&lock, LK_RELEASE); - for (i = 0; i < file->ndeps; i++) - linker_file_unload(file->deps[i]); - kfree(file->deps, M_LINKER); + if (file->deps) { + lockmgr(&lock, LK_RELEASE); + for (i = 0; i < file->ndeps; i++) + linker_file_unload(file->deps[i]); + lockmgr(&lock, LK_EXCLUSIVE); + kfree(file->deps, M_LINKER); + file->deps = NULL; + } - for (cp = STAILQ_FIRST(&file->common); cp; - cp = STAILQ_FIRST(&file->common)) { - STAILQ_REMOVE(&file->common, cp, common_symbol, link); + while ((cp = STAILQ_FIRST(&file->common)) != NULL) { + STAILQ_REMOVE_HEAD(&file->common, link); kfree(cp, M_LINKER); } file->ops->unload(file); + + if (file->filename) { + kfree(file->filename, M_LINKER); + file->filename = NULL; + } + kfree(file, M_LINKER); + lockmgr(&lock, LK_RELEASE); + out: return error; } @@ -554,7 +576,7 @@ linker_file_lookup_symbol(linker_file_t file, const char* name, int deps, caddr_ size_t common_size = 0; int i; - KLD_DPF(SYM, ("linker_file_lookup_symbol: file=%x, name=%s, deps=%d\n", + KLD_DPF(SYM, ("linker_file_lookup_symbol: file=%p, name=%s, deps=%d\n", file, name, deps)); if (file->ops->lookup_symbol(file, name, &sym) == 0) { @@ -571,7 +593,7 @@ linker_file_lookup_symbol(linker_file_t file, const char* name, int deps, caddr_ */ common_size = symval.size; } else { - KLD_DPF(SYM, ("linker_file_lookup_symbol: symbol.value=%x\n", symval.value)); + KLD_DPF(SYM, ("linker_file_lookup_symbol: symbol.value=%p\n", symval.value)); *raddr = symval.value; return 0; } @@ -579,7 +601,7 @@ linker_file_lookup_symbol(linker_file_t file, const char* name, int deps, caddr_ if (deps) { for (i = 0; i < file->ndeps; i++) { if (linker_file_lookup_symbol(file->deps[i], name, 0, raddr) == 0) { - KLD_DPF(SYM, ("linker_file_lookup_symbol: deps value=%x\n", *raddr)); + KLD_DPF(SYM, ("linker_file_lookup_symbol: deps value=%p\n", *raddr)); return 0; } } @@ -596,7 +618,7 @@ linker_file_lookup_symbol(linker_file_t file, const char* name, int deps, caddr_ if (i < file->ndeps) continue; if (linker_file_lookup_symbol(lf, name, 0, raddr) == 0) { - KLD_DPF(SYM, ("linker_file_lookup_symbol: global value=%x\n", *raddr)); + KLD_DPF(SYM, ("linker_file_lookup_symbol: global value=%p\n", *raddr)); return 0; } } @@ -612,7 +634,7 @@ linker_file_lookup_symbol(linker_file_t file, const char* name, int deps, caddr_ STAILQ_FOREACH(cp, &file->common, link) if (!strcmp(cp->name, name)) { - KLD_DPF(SYM, ("linker_file_lookup_symbol: old common value=%x\n", cp->address)); + KLD_DPF(SYM, ("linker_file_lookup_symbol: old common value=%p\n", cp->address)); *raddr = cp->address; return 0; } @@ -632,7 +654,7 @@ linker_file_lookup_symbol(linker_file_t file, const char* name, int deps, caddr_ bzero(cp->address, common_size); STAILQ_INSERT_TAIL(&file->common, cp, link); - KLD_DPF(SYM, ("linker_file_lookup_symbol: new common value=%x\n", cp->address)); + KLD_DPF(SYM, ("linker_file_lookup_symbol: new common value=%p\n", cp->address)); *raddr = cp->address; return 0; } @@ -725,7 +747,8 @@ int sys_kldload(struct kldload_args *uap) { struct thread *td = curthread; - char* filename = NULL, *modulename; + char *file; + char *kldname, *modname; linker_file_t lf; int error = 0; @@ -737,30 +760,32 @@ sys_kldload(struct kldload_args *uap) if ((error = priv_check(td, PRIV_KLD_LOAD)) != 0) return error; - filename = kmalloc(MAXPATHLEN, M_TEMP, M_WAITOK); - if ((error = copyinstr(uap->file, filename, MAXPATHLEN, NULL)) != 0) + file = kmalloc(MAXPATHLEN, M_TEMP, M_WAITOK); + if ((error = copyinstr(uap->file, file, MAXPATHLEN, NULL)) != 0) goto out; - /* Can't load more than one module with the same name */ - modulename = rindex(filename, '/'); - if (modulename == NULL) - modulename = filename; - else - modulename++; - if (linker_find_file_by_name(modulename)) { - error = EEXIST; - goto out; + /* + * If file does not contain a qualified name or any dot in it + * (kldname.ko, or kldname.ver.ko) treat it as an interface + * name. + */ + if (index(file, '/') || index(file, '.')) { + kldname = file; + modname = NULL; + } else { + kldname = NULL; + modname = file; } - if ((error = linker_load_file(filename, &lf, 0)) != 0) + if ((error = linker_load_module(kldname, modname, NULL, NULL, &lf)) != 0) goto out; lf->userrefs++; uap->sysmsg_result = lf->id; out: - if (filename) - kfree(filename, M_TEMP); + if (file) + kfree(file, M_TEMP); return error; } @@ -831,23 +856,28 @@ sys_kldnext(struct kldnext_args *uap) linker_file_t lf; int error = 0; - if (uap->fileid == 0) { - if (TAILQ_FIRST(&linker_files)) - uap->sysmsg_result = TAILQ_FIRST(&linker_files)->id; - else - uap->sysmsg_result = 0; - return 0; + if (uap->fileid == 0) + lf = TAILQ_FIRST(&linker_files); + else { + lf = linker_find_file_by_id(uap->fileid); + if (lf == NULL) { + error = ENOENT; + goto out; + } + lf = TAILQ_NEXT(lf, link); } - lf = linker_find_file_by_id(uap->fileid); - if (lf) { - if (TAILQ_NEXT(lf, link)) - uap->sysmsg_result = TAILQ_NEXT(lf, link)->id; - else - uap->sysmsg_result = 0; - } else - error = ENOENT; + /* Skip partially loaded files. */ + while (lf != NULL && !(lf->flags & LINKER_FILE_LINKED)) { + lf = TAILQ_NEXT(lf, link); + } + + if (lf) + uap->sysmsg_result = lf->id; + else + uap->sysmsg_result = 0; +out: return error; } @@ -970,58 +1000,105 @@ out: } /* - * Look for module metadata in the static kernel + * Preloaded module support */ -struct mod_metadata * -find_mod_metadata(const char *modname) + +static modlist_t +modlist_lookup(const char *name, int ver) { - int len; - struct mod_metadata **mdp; - struct mod_metadata *mdt; + modlist_t mod; - /* - * Strip path prefixes and any dot extension. MDT_MODULE names - * are just the module name without a path or ".ko". - */ - for (len = strlen(modname) - 1; len >= 0; --len) { - if (modname[len] == '/') - break; + TAILQ_FOREACH(mod, &found_modules, link) { + if (strcmp(mod->name, name) == 0 && (ver == 0 || mod->version == ver)) + return (mod); } - modname += len + 1; - for (len = 0; modname[len] && modname[len] != '.'; ++len) - ; + return (NULL); +} - /* - * Look for the module declaration - */ - SET_FOREACH(mdp, modmetadata_set) { - mdt = *mdp; - if (mdt->md_type != MDT_MODULE) +static modlist_t +modlist_lookup2(const char *name, struct mod_depend *verinfo) +{ + modlist_t mod, bestmod; + int ver; + + if (verinfo == NULL) + return (modlist_lookup(name, 0)); + bestmod = NULL; + TAILQ_FOREACH(mod, &found_modules, link) { + if (strcmp(mod->name, name) != 0) + continue; + ver = mod->version; + if (ver == verinfo->md_ver_preferred) + return (mod); + if (ver >= verinfo->md_ver_minimum && + ver <= verinfo->md_ver_maximum && + (bestmod == NULL || ver > bestmod->version)) + bestmod = mod; + } + return (bestmod); +} + +static modlist_t +modlist_newmodule(const char *modname, int version, linker_file_t container) +{ + modlist_t mod; + + mod = kmalloc(sizeof(struct modlist), M_LINKER, M_NOWAIT | M_ZERO); + if (mod == NULL) + panic("no memory for module list"); + mod->container = container; + mod->name = modname; + mod->version = version; + TAILQ_INSERT_TAIL(&found_modules, mod, link); + return (mod); +} + +static void +linker_addmodules(linker_file_t lf, struct mod_metadata **start, + struct mod_metadata **stop, int preload) +{ + struct mod_metadata *mp, **mdp; + const char *modname; + int ver; + + for (mdp = start; mdp < stop; mdp++) { + mp = *mdp; + if (mp->md_type != MDT_VERSION) + continue; + modname = mp->md_cval; + ver = ((struct mod_version *)mp->md_data)->mv_version; + if (modlist_lookup(modname, ver) != NULL) { + kprintf("module %s already present!\n", modname); + /* XXX what can we do? this is a build error. :-( */ continue; - if (strlen(mdt->md_cval) == len && - strncmp(mdt->md_cval, modname, len) == 0) { - return(mdt); } + modlist_newmodule(modname, ver, lf); } - return(NULL); } -/* - * Preloaded module support - */ static void linker_preload(void* arg) { caddr_t modptr; - char *modname; + const char *modname, *nmodname; char *modtype; - linker_file_t lf; + linker_file_t lf, nlf; linker_class_t lc; int error; - struct sysinit **sipp; - const moduledata_t *moddata; + linker_file_list_t loaded_files; + linker_file_list_t depended_files; + struct mod_metadata *mp, *nmp; + struct mod_metadata **start, **stop, **mdp, **nmdp; + struct mod_depend *verinfo; + int nver; + int resolves; + modlist_t mod; struct sysinit **si_start, **si_stop; + TAILQ_INIT(&loaded_files); + TAILQ_INIT(&depended_files); + TAILQ_INIT(&found_modules); + modptr = NULL; while ((modptr = preload_search_next_name(modptr)) != NULL) { modname = (char *)preload_search_info(modptr, MODINFO_NAME); @@ -1035,54 +1112,153 @@ linker_preload(void* arg) continue; } - /* - * This is a hack at the moment, but what's in FreeBSD-5 is even - * worse so I'd rather the hack. - */ - kprintf("Preloaded %s \"%s\" at %p", modtype, modname, modptr); - if (find_mod_metadata(modname)) { - kprintf(" (ignored, already in static kernel)\n"); - continue; - } - kprintf(".\n"); - - lf = linker_find_file_by_name(modname); - if (lf) { - lf->refs++; - lf->userrefs++; - continue; - } + if (bootverbose) + kprintf("Preloaded %s \"%s\" at %p.\n", modtype, modname, modptr); lf = NULL; TAILQ_FOREACH(lc, &classes, link) { - error = lc->ops->load_file(modname, &lf, LINKER_LOAD_FILE_PRELOAD); - if (error) { - lf = NULL; + error = lc->ops->preload_file(modname, &lf); + if (!error) break; - } + lf = NULL; } - if (lf) { - lf->userrefs++; + if (lf) + TAILQ_INSERT_TAIL(&loaded_files, lf, loaded); + } - if (linker_file_lookup_set(lf, "sysinit_set", &si_start, &si_stop, NULL) == 0) { - /* HACK ALERT! - * This is to set the sysinit moduledata so that the module - * can attach itself to the correct containing file. - * The sysinit could be run at *any* time. + /* + * First get a list of stuff in the kernel. + */ + if (linker_file_lookup_set(linker_kernel_file, MDT_SETNAME, &start, + &stop, NULL) == 0) + linker_addmodules(linker_kernel_file, start, stop, 1); + + /* + * This is a once-off kinky bubble sort to resolve relocation + * dependency requirements. + */ +restart: + TAILQ_FOREACH(lf, &loaded_files, loaded) { + error = linker_file_lookup_set(lf, MDT_SETNAME, &start, &stop, NULL); + /* + * First, look to see if we would successfully link with this + * stuff. + */ + resolves = 1; /* unless we know otherwise */ + if (!error) { + for (mdp = start; mdp < stop; mdp++) { + mp = *mdp; + if (mp->md_type != MDT_DEPEND) + continue; + modname = mp->md_cval; + verinfo = mp->md_data; + for (nmdp = start; nmdp < stop; nmdp++) { + nmp = *nmdp; + if (nmp->md_type != MDT_VERSION) + continue; + nmodname = nmp->md_cval; + if (strcmp(modname, nmodname) == 0) + break; + } + if (nmdp < stop)/* it's a self reference */ + continue; + + /* + * ok, the module isn't here yet, we + * are not finished */ - for (sipp = si_start; sipp < si_stop; sipp++) { - if ((*sipp)->func == module_register_init) { - moddata = (*sipp)->udata; - error = module_register(moddata, lf); - if (error) - kprintf("Preloaded %s \"%s\" failed to register: %d\n", - modtype, modname, error); + if (modlist_lookup2(modname, verinfo) == NULL) + resolves = 0; + } + } + /* + * OK, if we found our modules, we can link. So, "provide" + * the modules inside and add it to the end of the link order + * list. + */ + if (resolves) { + if (!error) { + for (mdp = start; mdp < stop; mdp++) { + mp = *mdp; + if (mp->md_type != MDT_VERSION) + continue; + modname = mp->md_cval; + nver = ((struct mod_version *)mp->md_data)->mv_version; + if (modlist_lookup(modname, nver) != NULL) { + kprintf("module %s already present!\n", modname); + TAILQ_REMOVE(&loaded_files, lf, loaded); + linker_file_unload(lf /* , LINKER_UNLOAD_FORCE */ ); + /* we changed tailq next ptr */ + goto restart; } + modlist_newmodule(modname, nver, lf); } - sysinit_add(si_start, si_stop); } - linker_file_register_sysctls(lf); + TAILQ_REMOVE(&loaded_files, lf, loaded); + TAILQ_INSERT_TAIL(&depended_files, lf, loaded); + /* + * Since we provided modules, we need to restart the + * sort so that the previous files that depend on us + * have a chance. Also, we've busted the tailq next + * pointer with the REMOVE. + */ + goto restart; + } + } + + /* + * At this point, we check to see what could not be resolved.. + */ + while ((lf = TAILQ_FIRST(&loaded_files)) != NULL) { + TAILQ_REMOVE(&loaded_files, lf, loaded); + kprintf("KLD file %s is missing dependencies\n", lf->filename); + linker_file_unload(lf /* , LINKER_UNLOAD_FORCE */ ); + } + + /* + * We made it. Finish off the linking in the order we determined. + */ + TAILQ_FOREACH_MUTABLE(lf, &depended_files, loaded, nlf) { + if (linker_kernel_file) { + linker_kernel_file->refs++; + linker_file_add_dependancy(lf, linker_kernel_file); + } + lf->userrefs++; + + error = linker_file_lookup_set(lf, MDT_SETNAME, &start, &stop, NULL); + if (!error) { + for (mdp = start; mdp < stop; mdp++) { + mp = *mdp; + if (mp->md_type != MDT_DEPEND) + continue; + modname = mp->md_cval; + verinfo = mp->md_data; + mod = modlist_lookup2(modname, verinfo); + /* Don't count self-dependencies */ + if (lf == mod->container) + continue; + mod->container->refs++; + linker_file_add_dependancy(lf, mod->container); + } + } + /* + * Now do relocation etc using the symbol search paths + * established by the dependencies + */ + error = lf->ops->preload_finish(lf); + if (error) { + TAILQ_REMOVE(&depended_files, lf, loaded); + kprintf("KLD file %s - could not finalize loading\n", + lf->filename); + linker_file_unload(lf /* , LINKER_UNLOAD_FORCE */); + continue; } + linker_file_register_modules(lf); + if (linker_file_lookup_set(lf, "sysinit_set", &si_start, &si_stop, NULL) == 0) + sysinit_add(si_start, si_stop); + linker_file_register_sysctls(lf); + lf->flags |= LINKER_FILE_LINKED; } + /* woohoo! we made it! */ } SYSINIT(preload, SI_BOOT2_KLD, SI_ORDER_MIDDLE, linker_preload, 0); @@ -1108,25 +1284,18 @@ SYSCTL_STRING(_kern, OID_AUTO, module_path, CTLFLAG_RW, linker_path, sizeof(linker_path), "module load search path"); TUNABLE_STR("module_path", linker_path, sizeof(linker_path)); -static char * -linker_strdup(const char *str) -{ - char *result; - - result = kmalloc(strlen(str) + 1, M_LINKER, M_WAITOK); - strcpy(result, str); - return(result); -} - char * linker_search_path(const char *name) { struct nlookupdata nd; char *cp, *ep, *result; size_t name_len, prefix_len; + size_t result_len; int sep; int error; enum vtype type; + const char *exts[] = { "", ".ko", NULL }; + const char **ext; /* qualified at all? */ if (index(name, '/')) @@ -1148,30 +1317,36 @@ linker_search_path(const char *name) sep = 0; /* - * +2 : possible separator, plus terminator. + * +2+3 : possible separator, plus terminator + possible extension. */ - result = kmalloc(prefix_len + name_len + 2, M_LINKER, M_WAITOK); + result = kmalloc(prefix_len + name_len + 2+3, M_LINKER, M_WAITOK); strncpy(result, cp, prefix_len); if (sep) result[prefix_len++] = '/'; strcpy(result + prefix_len, name); - /* - * Attempt to open the file, and return the path if we succeed and it's - * a regular file. - */ - error = nlookup_init(&nd, result, UIO_SYSSPACE, NLC_FOLLOW|NLC_LOCKVP); - if (error == 0) - error = vn_open(&nd, NULL, FREAD, 0); - if (error == 0) { - type = nd.nl_open_vp->v_type; - if (type == VREG) { - nlookup_done(&nd); - return (result); + result_len = strlen(result); + for (ext = exts; *ext != NULL; ext++) { + strcpy(result + result_len, *ext); + + /* + * Attempt to open the file, and return the path if we succeed and it's + * a regular file. + */ + error = nlookup_init(&nd, result, UIO_SYSSPACE, NLC_FOLLOW|NLC_LOCKVP); + if (error == 0) + error = vn_open(&nd, NULL, FREAD, 0); + if (error == 0) { + type = nd.nl_open_vp->v_type; + if (type == VREG) { + nlookup_done(&nd); + return (result); + } } + nlookup_done(&nd); } - nlookup_done(&nd); + kfree(result, M_LINKER); if (*ep == 0) @@ -1180,3 +1355,152 @@ linker_search_path(const char *name) } return(NULL); } + +/* + * Find a file which contains given module and load it, if "parent" is not + * NULL, register a reference to it. + */ +static int +linker_load_module(const char *kldname, const char *modname, + struct linker_file *parent, struct mod_depend *verinfo, + struct linker_file **lfpp) +{ + linker_file_t lfdep; + const char *filename; + char *pathname; + int error; + + if (modname == NULL) { + /* + * We have to load KLD + */ + KASSERT(verinfo == NULL, ("linker_load_module: verinfo is not NULL")); + pathname = linker_search_path(kldname); + } else { + if (modlist_lookup2(modname, verinfo) != NULL) + return (EEXIST); + if (kldname != NULL) + pathname = linker_strdup(kldname); + else if (rootvnode == NULL) + pathname = NULL; + else + pathname = linker_search_path(modname); +#if 0 + /* + * Need to find a KLD with required module + */ + pathname = linker_search_module(modname, + strlen(modname), verinfo); +#endif + } + if (pathname == NULL) + return (ENOENT); + + /* + * Can't load more than one file with the same basename XXX: + * Actually it should be possible to have multiple KLDs with + * the same basename but different path because they can + * provide different versions of the same modules. + */ + filename = rindex(pathname, '/'); + if (filename == NULL) + filename = filename; + else + filename++; + if (linker_find_file_by_name(filename)) + error = EEXIST; + else + do { + error = linker_load_file(pathname, &lfdep); + if (error) + break; + if (modname && verinfo && modlist_lookup2(modname, verinfo) == NULL) { + linker_file_unload(lfdep /* , LINKER_UNLOAD_FORCE */ ); + error = ENOENT; + break; + } + if (parent) { + linker_file_add_dependancy(parent, lfdep); + } + if (lfpp) + *lfpp = lfdep; + } while (0); + kfree(pathname, M_LINKER); + return (error); +} + +/* + * This routine is responsible for finding dependencies of userland initiated + * kldload(2)'s of files. + */ +int +linker_load_dependencies(linker_file_t lf) +{ + linker_file_t lfdep; + struct mod_metadata **start, **stop, **mdp, **nmdp; + struct mod_metadata *mp, *nmp; + struct mod_depend *verinfo; + modlist_t mod; + const char *modname, *nmodname; + int ver, error = 0, count; + + /* + * All files are dependant on /kernel. + */ + if (linker_kernel_file) { + linker_kernel_file->refs++; + linker_file_add_dependancy(lf, linker_kernel_file); + } + if (linker_file_lookup_set(lf, MDT_SETNAME, &start, &stop, &count) != 0) + return (0); + for (mdp = start; mdp < stop; mdp++) { + mp = *mdp; + if (mp->md_type != MDT_VERSION) + continue; + modname = mp->md_cval; + ver = ((struct mod_version *)mp->md_data)->mv_version; + mod = modlist_lookup(modname, ver); + if (mod != NULL) { + kprintf("interface %s.%d already present in the KLD '%s'!\n", + modname, ver, mod->container->filename); + return (EEXIST); + } + } + + for (mdp = start; mdp < stop; mdp++) { + mp = *mdp; + if (mp->md_type != MDT_DEPEND) + continue; + modname = mp->md_cval; + verinfo = mp->md_data; + nmodname = NULL; + for (nmdp = start; nmdp < stop; nmdp++) { + nmp = *nmdp; + if (nmp->md_type != MDT_VERSION) + continue; + nmodname = nmp->md_cval; + if (strcmp(modname, nmodname) == 0) + break; + } + if (nmdp < stop) /* early exit, it's a self reference */ + continue; + mod = modlist_lookup2(modname, verinfo); + if (mod) { /* woohoo, it's loaded already */ + lfdep = mod->container; + lfdep->refs++; + linker_file_add_dependancy(lf, lfdep); + continue; + } + error = linker_load_module(NULL, modname, lf, verinfo, NULL); + if (error) { + kprintf("KLD %s: depends on %s - not available or version mismatch\n", + lf->filename, modname); + break; + } + } + + if (error) + return (error); + linker_addmodules(lf, start, stop, 0); + return (error); +} diff --git a/sys/kern/kern_module.c b/sys/kern/kern_module.c index 353d08e610..7e4ed435ed 100644 --- a/sys/kern/kern_module.c +++ b/sys/kern/kern_module.c @@ -55,7 +55,7 @@ struct module { #define MOD_EVENT(mod, type) (mod)->handler((mod), (type), (mod)->arg) -static modulelist_t modules; +static modulelist_t modules = TAILQ_HEAD_INITIALIZER(modules); static int nextid = 1; static void module_shutdown(void*, int); diff --git a/sys/kern/link_elf.c b/sys/kern/link_elf.c index 575a54feb3..5dce43bde6 100644 --- a/sys/kern/link_elf.c +++ b/sys/kern/link_elf.c @@ -50,8 +50,9 @@ #include #include -static int link_elf_load_module(const char*, linker_file_t*, int); -static int link_elf_load_file(const char*, linker_file_t*, int); +static int link_elf_preload_file(const char *, linker_file_t *); +static int link_elf_preload_finish(linker_file_t); +static int link_elf_load_file(const char*, linker_file_t*); static int link_elf_lookup_symbol(linker_file_t, const char*, c_linker_sym_t*); static int link_elf_symbol_values(linker_file_t, c_linker_sym_t, linker_symval_t*); @@ -62,25 +63,29 @@ static void link_elf_unload_file(linker_file_t); static void link_elf_unload_module(linker_file_t); static int link_elf_lookup_set(linker_file_t, const char *, void ***, void ***, int *); +static int elf_lookup(linker_file_t lf, Elf_Size symidx, int deps, Elf_Addr *); +static void link_elf_reloc_local(linker_file_t lf); static struct linker_class_ops link_elf_class_ops = { - link_elf_load_module, + link_elf_load_file, + link_elf_preload_file, }; static struct linker_file_ops link_elf_file_ops = { - link_elf_lookup_symbol, - link_elf_symbol_values, - link_elf_search_symbol, - link_elf_unload_file, - link_elf_lookup_set + .lookup_symbol = link_elf_lookup_symbol, + .symbol_values = link_elf_symbol_values, + .search_symbol = link_elf_search_symbol, + .unload = link_elf_unload_file, + .lookup_set = link_elf_lookup_set }; static struct linker_file_ops link_elf_module_ops = { - link_elf_lookup_symbol, - link_elf_symbol_values, - link_elf_search_symbol, - link_elf_unload_module, - link_elf_lookup_set + .lookup_symbol = link_elf_lookup_symbol, + .symbol_values = link_elf_symbol_values, + .search_symbol = link_elf_search_symbol, + .preload_finish = link_elf_preload_finish, + .unload = link_elf_unload_module, + .lookup_set = link_elf_lookup_set, }; typedef struct elf_file { @@ -116,7 +121,6 @@ typedef struct elf_file { } *elf_file_t; static int parse_dynamic(linker_file_t lf); -static int load_dependancies(linker_file_t lf, int load_flags); static int relocate_file(linker_file_t lf); static int parse_module_symbols(linker_file_t lf); @@ -321,7 +325,7 @@ link_elf_error(const char *s) } static int -link_elf_load_module(const char *filename, linker_file_t *result, int load_flags) +link_elf_preload_file(const char *filename, linker_file_t *result) { caddr_t modptr, baseptr, sizeptr, dynptr; char *type; @@ -335,14 +339,16 @@ link_elf_load_module(const char *filename, linker_file_t *result, int load_flags */ modptr = preload_search_by_name(filename); if (modptr == NULL) - return (link_elf_load_file(filename, result, load_flags)); + return ENOENT; /* It's preloaded, check we can handle it and collect information */ type = (char *)preload_search_info(modptr, MODINFO_TYPE); baseptr = preload_search_info(modptr, MODINFO_ADDR); sizeptr = preload_search_info(modptr, MODINFO_SIZE); dynptr = preload_search_info(modptr, MODINFO_METADATA|MODINFOMD_DYNAMIC); - if (type == NULL || strcmp(type, "elf module") != 0) + if (type == NULL || + (strcmp(type, "elf" __XSTRING(__ELF_WORD_SIZE) " module") != 0 && + strcmp(type, "elf module") != 0)) return (EFTYPE); if (baseptr == NULL || sizeptr == NULL || dynptr == NULL) return (EINVAL); @@ -368,24 +374,26 @@ link_elf_load_module(const char *filename, linker_file_t *result, int load_flags linker_file_unload(lf); return error; } - error = load_dependancies(lf, load_flags); - if (error) { - linker_file_unload(lf); - return error; - } + link_elf_reloc_local(lf); + *result = lf; + return (0); +} + +static int +link_elf_preload_finish(linker_file_t lf) +{ + int error; + error = relocate_file(lf); - if (error) { - linker_file_unload(lf); + if (error) return error; - } parse_module_symbols(lf); - lf->flags |= LINKER_FILE_LINKED; - *result = lf; + return (0); } static int -link_elf_load_file(const char* filename, linker_file_t* result, int load_flags) +link_elf_load_file(const char* filename, linker_file_t* result) { struct nlookupdata nd; struct thread *td = curthread; /* XXX */ @@ -469,8 +477,7 @@ link_elf_load_file(const char* filename, linker_file_t* result, int load_flags) goto out; } if (hdr->e_type != ET_EXEC && hdr->e_type != ET_DYN) { - link_elf_error("Unsupported file type"); - error = ENOEXEC; + error = ENOSYS; goto out; } if (hdr->e_machine != ELF_TARG_MACH) { @@ -520,6 +527,10 @@ link_elf_load_file(const char* filename, linker_file_t* result, int load_flags) case PT_DYNAMIC: phdyn = phdr; break; + + case PT_INTERP: + error = ENOSYS; + goto out; } ++phdr; @@ -621,7 +632,8 @@ link_elf_load_file(const char* filename, linker_file_t* result, int load_flags) error = parse_dynamic(lf); if (error) goto out; - error = load_dependancies(lf, load_flags); + link_elf_reloc_local(lf); + error = linker_load_dependencies(lf); if (error) goto out; error = relocate_file(lf); @@ -669,8 +681,6 @@ link_elf_load_file(const char* filename, linker_file_t* result, int load_flags) ef->ddbstrcnt = strcnt; ef->ddbstrtab = ef->strbase; - lf->flags |= LINKER_FILE_LINKED; - nosyms: *result = lf; @@ -688,6 +698,23 @@ out: return error; } +Elf_Addr +elf_relocaddr(linker_file_t lf, Elf_Addr x) +{ + elf_file_t ef; + + ef = lf->priv; +#if 0 + if (x >= ef->pcpu_start && x < ef->pcpu_stop) + return ((x - ef->pcpu_start) + ef->pcpu_base); +#ifdef VIMAGE + if (x >= ef->vnet_start && x < ef->vnet_stop) + return ((x - ef->vnet_start) + ef->vnet_base); +#endif +#endif + return (x); +} + static void link_elf_unload_file(linker_file_t file) { @@ -724,38 +751,6 @@ link_elf_unload_module(linker_file_t file) preload_delete_name(file->filename); } -static int -load_dependancies(linker_file_t lf, int load_flags) -{ - elf_file_t ef = lf->priv; - linker_file_t lfdep; - char* name; - const Elf_Dyn *dp; - int error = 0; - - /* - * All files are dependant on /kernel. - */ - if (linker_kernel_file) { - linker_kernel_file->refs++; - linker_file_add_dependancy(lf, linker_kernel_file); - } - - for (dp = ef->dynamic; dp->d_tag != DT_NULL; dp++) { - if (dp->d_tag == DT_NEEDED) { - name = ef->strtab + dp->d_un.d_val; - - error = linker_load_file(name, &lfdep, load_flags); - if (error) - goto out; - linker_file_add_dependancy(lf, lfdep); - } - } - -out: - return error; -} - static const char * symbol_name(elf_file_t ef, Elf_Size r_info) { @@ -783,8 +778,8 @@ relocate_file(linker_file_t lf) if (rel) { rellim = (const Elf_Rel *)((const char *)ef->rel + ef->relsize); while (rel < rellim) { - symname = symbol_name(ef, rel->r_info); - if (elf_reloc(lf, rel, ELF_RELOC_REL, symname)) { + if (elf_reloc(lf, (Elf_Addr)ef->address, rel, ELF_RELOC_REL, elf_lookup)) { + symname = symbol_name(ef, rel->r_info); kprintf("link_elf: symbol %s undefined\n", symname); return ENOENT; } @@ -797,8 +792,8 @@ relocate_file(linker_file_t lf) if (rela) { relalim = (const Elf_Rela *)((const char *)ef->rela + ef->relasize); while (rela < relalim) { - symname = symbol_name(ef, rela->r_info); - if (elf_reloc(lf, rela, ELF_RELOC_RELA, symname)) { + if (elf_reloc(lf, (Elf_Addr)ef->address, rela, ELF_RELOC_RELA, elf_lookup)) { + symname = symbol_name(ef, rela->r_info); kprintf("link_elf: symbol %s undefined\n", symname); return ENOENT; } @@ -811,8 +806,8 @@ relocate_file(linker_file_t lf) if (rel) { rellim = (const Elf_Rel *)((const char *)ef->pltrel + ef->pltrelsize); while (rel < rellim) { - symname = symbol_name(ef, rel->r_info); - if (elf_reloc(lf, rel, ELF_RELOC_REL, symname)) { + if (elf_reloc(lf, (Elf_Addr)ef->address, rel, ELF_RELOC_REL, elf_lookup)) { + symname = symbol_name(ef, rel->r_info); kprintf("link_elf: symbol %s undefined\n", symname); return ENOENT; } @@ -826,7 +821,7 @@ relocate_file(linker_file_t lf) relalim = (const Elf_Rela *)((const char *)ef->pltrela + ef->pltrelasize); while (rela < relalim) { symname = symbol_name(ef, rela->r_info); - if (elf_reloc(lf, rela, ELF_RELOC_RELA, symname)) { + if (elf_reloc(lf, (Elf_Addr)ef->address, rela, ELF_RELOC_RELA, elf_lookup)) { kprintf("link_elf: symbol %s undefined\n", symname); return ENOENT; } @@ -867,6 +862,12 @@ link_elf_lookup_symbol(linker_file_t lf, const char* name, c_linker_sym_t* sym) unsigned long hash; int i; + /* If we don't have a hash, bail. */ + if (ef->buckets == NULL || ef->nbuckets == 0) { + kprintf("link_elf_lookup_symbol: missing symbol hash table\n"); + return ENOENT; + } + /* First, search hashed global symbols */ hash = elf_hash(name); symnum = ef->buckets[hash % ef->nbuckets]; @@ -922,62 +923,62 @@ link_elf_lookup_symbol(linker_file_t lf, const char* name, c_linker_sym_t* sym) } static int -link_elf_symbol_values(linker_file_t lf, c_linker_sym_t sym, linker_symval_t* symval) +link_elf_symbol_values(linker_file_t lf, c_linker_sym_t sym, linker_symval_t *symval) { - elf_file_t ef = lf->priv; - const Elf_Sym* es = (const Elf_Sym*) sym; - - if (es >= ef->symtab && ((es - ef->symtab) < ef->nchains)) { - symval->name = ef->strtab + es->st_name; - symval->value = (caddr_t) ef->address + es->st_value; - symval->size = es->st_size; - return 0; - } - if (ef->symtab == ef->ddbsymtab) - return ENOENT; - if (es >= ef->ddbsymtab && ((es - ef->ddbsymtab) < ef->ddbsymcnt)) { - symval->name = ef->ddbstrtab + es->st_name; - symval->value = (caddr_t) ef->address + es->st_value; - symval->size = es->st_size; - return 0; - } + elf_file_t ef = lf->priv; + const Elf_Sym *es = (const Elf_Sym *)sym; + + if (es >= ef->symtab && ((es - ef->symtab) < ef->nchains)) { + symval->name = ef->strtab + es->st_name; + symval->value = (caddr_t) ef->address + es->st_value; + symval->size = es->st_size; + return 0; + } + if (ef->symtab == ef->ddbsymtab) return ENOENT; + if (es >= ef->ddbsymtab && ((es - ef->ddbsymtab) < ef->ddbsymcnt)) { + symval->name = ef->ddbstrtab + es->st_name; + symval->value = (caddr_t) ef->address + es->st_value; + symval->size = es->st_size; + return 0; + } + return ENOENT; } static int link_elf_search_symbol(linker_file_t lf, caddr_t value, - c_linker_sym_t* sym, long* diffp) + c_linker_sym_t *sym, long *diffp) { - elf_file_t ef = lf->priv; - u_long off = (uintptr_t) (void *) value; - u_long diff = off; - u_long st_value; - const Elf_Sym* es; - const Elf_Sym* best = 0; - int i; - - for (i = 0, es = ef->ddbsymtab; i < ef->ddbsymcnt; i++, es++) { - if (es->st_name == 0) - continue; - st_value = es->st_value + (uintptr_t) (void *) ef->address; - if (off >= st_value) { - if (off - st_value < diff) { - diff = off - st_value; - best = es; - if (diff == 0) - break; - } else if (off - st_value == diff) { - best = es; - } - } + elf_file_t ef = lf->priv; + u_long off = (uintptr_t)(void *)value; + u_long diff = off; + u_long st_value; + const Elf_Sym *es; + const Elf_Sym *best = 0; + int i; + + for (i = 0, es = ef->ddbsymtab; i < ef->ddbsymcnt; i++, es++) { + if (es->st_name == 0) + continue; + st_value = es->st_value + (uintptr_t)(void *)ef->address; + if (off >= st_value) { + if (off - st_value < diff) { + diff = off - st_value; + best = es; + if (diff == 0) + break; + } else if (off - st_value == diff) { + best = es; + } } - if (best == 0) - *diffp = off; - else - *diffp = diff; - *sym = (c_linker_sym_t) best; + } + if (best == 0) + *diffp = off; + else + *diffp = diff; + *sym = (c_linker_sym_t) best; - return 0; + return 0; } /* @@ -985,54 +986,128 @@ link_elf_search_symbol(linker_file_t lf, caddr_t value, */ static int link_elf_lookup_set(linker_file_t lf, const char *name, - void ***startp, void ***stopp, int *countp) + void ***startp, void ***stopp, int *countp) { - c_linker_sym_t sym; - linker_symval_t symval; - char *setsym; - void **start, **stop; - int len, error = 0, count; - - len = strlen(name) + sizeof("__start_set_"); /* sizeof includes \0 */ - setsym = kmalloc(len, M_LINKER, M_WAITOK); - - /* get address of first entry */ - ksnprintf(setsym, len, "%s%s", "__start_set_", name); - error = link_elf_lookup_symbol(lf, setsym, &sym); - if (error) - goto out; - link_elf_symbol_values(lf, sym, &symval); - if (symval.value == 0) { - error = ESRCH; - goto out; - } - start = (void **)symval.value; - - /* get address of last entry */ - ksnprintf(setsym, len, "%s%s", "__stop_set_", name); - error = link_elf_lookup_symbol(lf, setsym, &sym); - if (error) - goto out; - link_elf_symbol_values(lf, sym, &symval); - if (symval.value == 0) { - error = ESRCH; - goto out; - } - stop = (void **)symval.value; + c_linker_sym_t sym; + linker_symval_t symval; + char *setsym; + void **start, **stop; + int len, error = 0, count; + + len = strlen(name) + sizeof("__start_set_"); /* sizeof includes \0 */ + setsym = kmalloc(len, M_LINKER, M_WAITOK); + + /* get address of first entry */ + ksnprintf(setsym, len, "%s%s", "__start_set_", name); + error = link_elf_lookup_symbol(lf, setsym, &sym); + if (error) + goto out; + link_elf_symbol_values(lf, sym, &symval); + if (symval.value == 0) { + error = ESRCH; + goto out; + } + start = (void **)symval.value; + + /* get address of last entry */ + ksnprintf(setsym, len, "%s%s", "__stop_set_", name); + error = link_elf_lookup_symbol(lf, setsym, &sym); + if (error) + goto out; + link_elf_symbol_values(lf, sym, &symval); + if (symval.value == 0) { + error = ESRCH; + goto out; + } + stop = (void **)symval.value; - /* and the number of entries */ - count = stop - start; + /* and the number of entries */ + count = stop - start; - /* and copy out */ - if (startp) - *startp = start; - if (stopp) - *stopp = stop; - if (countp) - *countp = count; + /* and copy out */ + if (startp) + *startp = start; + if (stopp) + *stopp = stop; + if (countp) + *countp = count; - out: - kfree(setsym, M_LINKER); - return error; +out: + kfree(setsym, M_LINKER); + return error; +} + +/* + * Symbol lookup function that can be used when the symbol index is known (ie + * in relocations). It uses the symbol index instead of doing a fully fledged + * hash table based lookup when such is valid. For example for local symbols. + * This is not only more efficient, it's also more correct. It's not always + * the case that the symbol can be found through the hash table. + */ +static int +elf_lookup(linker_file_t lf, Elf_Size symidx, int deps, Elf_Addr *result) +{ + elf_file_t ef = lf->priv; + const Elf_Sym *sym; + const char *symbol; + + /* Don't even try to lookup the symbol if the index is bogus. */ + if (symidx >= ef->nchains) + return (ENOENT); + + sym = ef->symtab + symidx; + + /* + * Don't do a full lookup when the symbol is local. It may even + * fail because it may not be found through the hash table. + */ + if (ELF_ST_BIND(sym->st_info) == STB_LOCAL) { + /* Force lookup failure when we have an insanity. */ + if (sym->st_shndx == SHN_UNDEF || sym->st_value == 0) + return (ENOENT); + return ((Elf_Addr) ef->address + sym->st_value); + } + /* + * XXX we can avoid doing a hash table based lookup for global + * symbols as well. This however is not always valid, so we'll + * just do it the hard way for now. Performance tweaks can + * always be added. + */ + + symbol = ef->strtab + sym->st_name; + + /* Force a lookup failure if the symbol name is bogus. */ + if (*symbol == 0) + return (ENOENT); + + return (linker_file_lookup_symbol(lf, symbol, deps, (caddr_t *)result)); } +static void +link_elf_reloc_local(linker_file_t lf) +{ + elf_file_t ef = lf->priv; + const Elf_Rel *rellim; + const Elf_Rel *rel; + const Elf_Rela *relalim; + const Elf_Rela *rela; + + /* Perform relocations without addend if there are any: */ + if ((rel = ef->rel) != NULL) { + rellim = (const Elf_Rel *)((const char *)ef->rel + ef->relsize); + while (rel < rellim) { + elf_reloc_local(lf, (Elf_Addr)ef->address, rel, ELF_RELOC_REL, + elf_lookup); + rel++; + } + } + /* Perform relocations with addend if there are any: */ + if ((rela = ef->rela) != NULL) { + relalim = (const Elf_Rela *)((const char *)ef->rela + ef->relasize); + while (rela < relalim) { + elf_reloc_local(lf, (Elf_Addr)ef->address, rela, ELF_RELOC_RELA, + elf_lookup); + rela++; + } + } +} diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c index 2146fb8365..2354cd0ce9 100644 --- a/sys/kern/vfs_syscalls.c +++ b/sys/kern/vfs_syscalls.c @@ -273,7 +273,7 @@ sys_mount(struct mount_args *uap) vput(vp); return error; } - error = linker_load_file(fstypename, &lf, 0); + error = linker_load_file(fstypename, &lf); if (error || lf == NULL) { cache_drop(&nch); vput(vp); diff --git a/sys/netgraph/netgraph/ng_base.c b/sys/netgraph/netgraph/ng_base.c index 39de6503ff..9df0e1df72 100644 --- a/sys/netgraph/netgraph/ng_base.c +++ b/sys/netgraph/netgraph/ng_base.c @@ -334,7 +334,7 @@ ng_load_module(const char *name) ksnprintf(filename, sizeof(filename), "ng_%s.ko", name); if ((path = linker_search_path(filename)) == NULL) return (ENXIO); - error = linker_load_file(path, &lf, 0); + error = linker_load_file(path, &lf); FREE(path, M_LINKER); if (error == 0) lf->userrefs++; /* pretend kldload'ed */ @@ -355,10 +355,11 @@ ng_unload_module(const char *name) ksnprintf(filename, sizeof(filename), "ng_%s.ko", name); if ((lf = linker_find_file_by_name(filename)) == NULL) return (ENXIO); + lf->userrefs--; /* pretend kldunload'ed */ error = linker_file_unload(lf); + if (error) + lf->userrefs++; - if (error == 0) - lf->userrefs--; /* pretend kldunload'ed */ return (error); } diff --git a/sys/platform/pc64/amd64/elf_machdep.c b/sys/platform/pc64/amd64/elf_machdep.c index 0e63ef0232..258f27ad48 100644 --- a/sys/platform/pc64/amd64/elf_machdep.c +++ b/sys/platform/pc64/amd64/elf_machdep.c @@ -32,15 +32,15 @@ #include /* Process one elf relocation with addend. */ -int -elf_reloc(linker_file_t lf, const void *data, int type, const char *sym) +static int +elf_reloc_internal(linker_file_t lf, Elf_Addr relocbase, const void *data, + int type, int local, elf_lookup_fn lookup) { - Elf_Addr relocbase = (Elf_Addr) lf->address; - Elf_Addr *where; + Elf64_Addr *where, val; + Elf32_Addr *where32, val32; Elf_Addr addr; Elf_Addr addend; - Elf_Word rtype; - caddr_t caddr; + Elf_Size rtype, symidx; const Elf_Rel *rel; const Elf_Rela *rela; @@ -48,14 +48,26 @@ elf_reloc(linker_file_t lf, const void *data, int type, const char *sym) case ELF_RELOC_REL: rel = (const Elf_Rel *)data; where = (Elf_Addr *) (relocbase + rel->r_offset); - addend = *where; rtype = ELF_R_TYPE(rel->r_info); + symidx = ELF_R_SYM(rel->r_info); + /* Addend is 32 bit on 32 bit relocs */ + switch (rtype) { + case R_X86_64_PC32: + case R_X86_64_32: + case R_X86_64_32S: + addend = *(Elf32_Addr *)where; + break; + default: + addend = *where; + break; + } break; case ELF_RELOC_RELA: rela = (const Elf_Rela *)data; where = (Elf_Addr *) (relocbase + rela->r_offset); addend = rela->r_addend; rtype = ELF_R_TYPE(rela->r_info); + symidx = ELF_R_SYM(rela->r_info); break; default: panic("unknown reloc type %d\n", type); @@ -67,25 +79,30 @@ elf_reloc(linker_file_t lf, const void *data, int type, const char *sym) break; case R_X86_64_64: /* S + A */ - if (sym == NULL) - return -1; - if (linker_file_lookup_symbol(lf, sym, 1, &caddr) != 0) + if (lookup(lf, symidx, 1, &addr)) return -1; - addr = (Elf_Addr)caddr; - addr += addend; - if (*where != addr) - *where = addr; + val = addr + addend; + if (*where != val) + *where = val; break; case R_X86_64_PC32: /* S + A - P */ - if (sym == NULL) + if (lookup(lf, symidx, 1, &addr)) return -1; - if (linker_file_lookup_symbol(lf, sym, 1, &caddr) != 0) + where32 = (Elf32_Addr *)where; + val32 = (Elf32_Addr)(addr + addend - (Elf_Addr)where); + if (*where32 != val32) + *where32 = val32; + break; + + case R_X86_64_32: /* S + A zero extend */ + case R_X86_64_32S: /* S + A sign extend */ + if (lookup(lf, symidx, 1, &addr)) return -1; - addr = (Elf_Addr)caddr; - addr += addend - (Elf_Addr)where; - if (*where != addr) - *where = addr; + val32 = (Elf32_Addr)(addr + addend); + where32 = (Elf32_Addr *)where; + if (*where32 != val32) + *where32 = val32; break; case R_X86_64_COPY: /* none */ @@ -98,25 +115,40 @@ elf_reloc(linker_file_t lf, const void *data, int type, const char *sym) break; case R_X86_64_GLOB_DAT: /* S */ - if (sym == NULL) - return -1; - if (linker_file_lookup_symbol(lf, sym, 1, &caddr) != 0) + case R_X86_64_JMP_SLOT: /* XXX need addend + offset */ + if (lookup(lf, symidx, 1, &addr)) return -1; - addr = (Elf_Addr)caddr; if (*where != addr) *where = addr; break; case R_X86_64_RELATIVE: /* B + A */ addr = relocbase + addend; - if (*where != addr) - *where = addr; + val = addr; + if (*where != val) + *where = val; break; default: - kprintf("kldload: unexpected relocation type %d\n", + kprintf("kldload: unexpected relocation type %ld\n", rtype); return -1; } return(0); } + +int +elf_reloc(linker_file_t lf, Elf_Addr relocbase, const void *data, int type, + elf_lookup_fn lookup) +{ + + return (elf_reloc_internal(lf, relocbase, data, type, 0, lookup)); +} + +int +elf_reloc_local(linker_file_t lf, Elf_Addr relocbase, const void *data, + int type, elf_lookup_fn lookup) +{ + + return (elf_reloc_internal(lf, relocbase, data, type, 1, lookup)); +} diff --git a/sys/sys/linker.h b/sys/sys/linker.h index 081d319b91..68e7fe4a99 100644 --- a/sys/sys/linker.h +++ b/sys/sys/linker.h @@ -85,6 +85,11 @@ struct linker_file_ops { int (*search_symbol)(linker_file_t, caddr_t value, c_linker_sym_t* sym, long* diffp); + /* + * Completes the loading task started in preload_file. + */ + int (*preload_finish)(linker_file_t); + /* * Unload a file, releasing dependancies and freeing storage. */ @@ -114,6 +119,7 @@ struct linker_file { linker_file_t* deps; /* list of dependancies */ STAILQ_HEAD(, common_symbol) common; /* list of common symbols */ TAILQ_HEAD(, module) modules; /* modules in this file */ + TAILQ_ENTRY(linker_file) loaded; /* preload dependency support */ void* priv; /* implementation data */ struct linker_file_ops* ops; @@ -134,12 +140,14 @@ struct linker_class_ops { * file and zero returned. If some other error is detected an * appropriate errno should be returned. */ - int (*load_file)(const char *filename, linker_file_t *result, - int load_flags); + int (*load_file)(const char *filename, linker_file_t *result); + /* + * Same as load_file, but does not load dependencies. Necessary + * for tentatively loading (loader-)preloaded modules. + */ + int (*preload_file)(const char *filename, linker_file_t *result); }; -#define LINKER_LOAD_FILE_PRELOAD 0x0001 - struct linker_class { TAILQ_ENTRY(linker_class) link; /* list of all file classes */ const char* desc; /* description (e.g. "a.out") */ @@ -168,8 +176,7 @@ int linker_add_class(const char* _desc, void* _priv, /* * Load a file, trying each file class until one succeeds. */ -int linker_load_file(const char *_filename, linker_file_t *_result, - int _load_flags); +int linker_load_file(const char *_filename, linker_file_t *_result); /* * Find a currently loaded file given its filename. @@ -197,6 +204,8 @@ int linker_file_unload(linker_file_t _file); */ void linker_file_add_dependancy(linker_file_t _file, linker_file_t _dep); +int linker_load_dependencies(linker_file_t lf); + /* * Lookup a symbol in a file. If deps is TRUE, look in dependancies * if not found in file. The symbol's value is returned in the @@ -251,6 +260,7 @@ int linker_ddb_symbol_values(c_linker_sym_t _sym, linker_symval_t *_symval); #define MODINFOMD_ENVP 0x0006 /* (from 5.x) envp[] */ #define MODINFOMD_HOWTO 0x0007 /* (from 5.x) boothowto */ #define MODINFOMD_KERNEND 0x0008 /* (from 5.x) kernend */ +#define MODINFOMD_SHDR 0x0009 /* section header table */ #define MODINFOMD_NOCOPY 0x8000 /* don't copy this metadata to the kernel */ #define MODINFOMD_DEPLIST (0x4001 | MODINFOMD_NOCOPY) /* depends on */ @@ -280,6 +290,7 @@ extern void preload_bootstrap_relocate(vm_offset_t); extern struct mod_metadata *find_mod_metadata(const char *); +#define KLD_DEBUG #ifdef KLD_DEBUG extern int kld_debug; @@ -297,8 +308,12 @@ extern int kld_debug; #endif +typedef int elf_lookup_fn(linker_file_t, Elf_Size, int, Elf_Addr *); + /* Support functions */ -int elf_reloc(linker_file_t, const void *, int, const char *); +int elf_reloc(linker_file_t, Elf_Addr, const void *, int, elf_lookup_fn); +int elf_reloc_local(linker_file_t, Elf_Addr, const void *, int, elf_lookup_fn); +Elf_Addr elf_relocaddr(linker_file_t, Elf_Addr); /* values for type */ #define ELF_RELOC_REL 1 -- 2.41.0