diff --git a/arch/arm/include/elf.h b/arch/arm/include/elf.h index b0bcf2c676c..813bafc56ca 100644 --- a/arch/arm/include/elf.h +++ b/arch/arm/include/elf.h @@ -296,6 +296,12 @@ #define ARCH_ELFDATA_SET_PLTREL(d, v) (d)->pltrel = (v) +/* Whether the value a relocation resolved to is itself a descriptor, which + * it is when the symbol came from another object rather than from this one. + */ + +#define ARCH_ELFDATA_SET_SYMISDESC(d, v) (d)->symisdesc = (v) + /**************************************************************************** * Public Types ****************************************************************************/ @@ -315,6 +321,7 @@ struct arch_elfdata_s uintptr_t gotbase; /* DT_PLTGOT: this object's data base */ uint16_t ndesc; /* Capacity, in descriptors */ uint16_t usedesc; /* Next free slot */ + uint8_t symisdesc; /* Symbol value is a descriptor, not code */ /* The pool the descriptors are taken from */ diff --git a/include/nuttx/lib/elf.h b/include/nuttx/lib/elf.h index fcbbdc8448c..fbe7269596c 100644 --- a/include/nuttx/lib/elf.h +++ b/include/nuttx/lib/elf.h @@ -92,12 +92,12 @@ * portion of the build */ -/* dlopen() needs a name too: it is the only way to tell that a library is - * already loaded. +/* A name is also the only way to tell that a library is already loaded, so + * dlopen() and DT_NEEDED need one. */ #if defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__) || \ - defined(CONFIG_LIBC_DLFCN) + defined(CONFIG_LIBC_DLFCN) || CONFIG_LIBC_ELF_MAXDEPEND > 0 # define HAVE_LIBC_ELF_NAMES # define LIBC_ELF_NAMEMAX NAME_MAX #endif @@ -798,7 +798,9 @@ FAR const void *libelf_getsymbol(FAR void *handle, FAR const char *name); * Name: libelf_uninit * * Description: - * Uninitialize module resources. + * Uninitialize module resources. Gives up everything the module holds, + * the DT_NEEDED libraries included, so the caller must hold the last + * reference. * ****************************************************************************/ diff --git a/libs/libc/elf/elf.h b/libs/libc/elf/elf.h index fcf3227cf11..8c661c6a7e6 100644 --- a/libs/libc/elf/elf.h +++ b/libs/libc/elf/elf.h @@ -238,6 +238,17 @@ int libelf_reallocbuffer(FAR struct mod_loadinfo_s *loadinfo, int libelf_freebuffers(FAR struct mod_loadinfo_s *loadinfo); +/**************************************************************************** + * Name: libelf_symname + * + * Description: + * Read a name out of a string table into the I/O buffer. + * + ****************************************************************************/ + +int libelf_symname(FAR struct mod_loadinfo_s *loadinfo, + FAR const Elf_Sym *sym, Elf_Off sh_offset); + /**************************************************************************** * Name: libelf_addr * diff --git a/libs/libc/elf/elf_bind.c b/libs/libc/elf/elf_bind.c index 510b336c614..6d2dccead67 100644 --- a/libs/libc/elf/elf_bind.c +++ b/libs/libc/elf/elf_bind.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -46,6 +47,14 @@ * Pre-processor Definitions ****************************************************************************/ +/* With an address environment the program lives in its own address space, + * which a library loaded by libelf_insert() cannot reach. + */ + +#if CONFIG_LIBC_ELF_MAXDEPEND > 0 && !defined(CONFIG_ARCH_ADDRENV) +# define LIBELF_NEEDED +#endif + #define I_REL 0 /* Index into relxxx[] arrays for relocations */ #define I_PLT 1 /* ... for PLTs */ #define N_RELS 2 /* Number of relxxx[] indexes */ @@ -67,7 +76,13 @@ #if defined(ARCH_ELFDATA) && defined(ARCH_ELFDATA_SET_PLTREL) # define ARCH_ELFDATA_PLTREL(v) ARCH_ELFDATA_SET_PLTREL(&arch_data, v) #else -# define ARCH_ELFDATA_PLTREL(v) +# define ARCH_ELFDATA_PLTREL(v) ((void)(v)) +#endif + +#if defined(ARCH_ELFDATA) && defined(ARCH_ELFDATA_SET_SYMISDESC) +# define ARCH_ELFDATA_SYMISDESC(v) ARCH_ELFDATA_SET_SYMISDESC(&arch_data, v) +#else +# define ARCH_ELFDATA_SYMISDESC(v) ((void)(v)) #endif #if defined(ARCH_ELFDATA) && defined(ARCH_ELFDATA_INIT) @@ -93,7 +108,12 @@ typedef struct int idx; } Elf_SymCache; -struct +/* Where a dynamic object's relocation tables live. Per load, not per file: + * loading a DT_NEEDED library re-enters this function, and a shared + * instance would be overwritten by the nested load. + */ + +struct reldata_s { int stroff; /* offset to string table */ int symoff; /* offset to symbol table */ @@ -102,12 +122,66 @@ struct int reloff[2]; /* offset to the relocation section */ int relsz[2]; /* size of relocation table */ int relrela[2]; /* type of relocation type - 0: DT_REL / 1: DT_RELA */ -} reldata; +}; /**************************************************************************** * Private Functions ****************************************************************************/ +#ifdef LIBELF_NEEDED +/**************************************************************************** + * Name: libelf_insertneeded + * + * Description: + * Load a library a module names in DT_NEEDED. A bare name is looked for + * along LD_LIBRARY_PATH, as dlopen() looks for it, and the module is + * registered under its base name, so a library two modules name is loaded + * once and reference counted. + * + * Returned Value: + * The handle of the loaded library, or NULL. + * + ****************************************************************************/ + +static FAR void *libelf_insertneeded(FAR const char *name) +{ + FAR const char *modname; + + modname = strrchr(name, '/'); + modname = modname != NULL ? modname + 1 : name; + +#ifdef CONFIG_LIBC_ENVPATH + if (name[0] != '/') + { + FAR void *handle = NULL; + FAR char *fullpath; + ENVPATH_HANDLE env; + + env = envpath_init("LD_LIBRARY_PATH"); + if (env != NULL) + { + while ((fullpath = envpath_next(env, name)) != NULL) + { + handle = libelf_insert(fullpath, modname); + lib_free(fullpath); + + if (handle != NULL) + { + break; + } + } + + envpath_release(env); + } + + return handle; + } +#endif + + return libelf_insert(name, modname); +} +#endif + /**************************************************************************** * Name: libelf_readrels * @@ -702,6 +776,13 @@ static int libelf_relocatedyn(FAR struct module_s *modp, int i; int idx_rel; int idx_sym; +#ifdef LIBELF_NEEDED + int j; + uintptr_t libs[CONFIG_LIBC_ELF_MAXDEPEND]; + int nlibs = 0; +#endif + struct reldata_s reldata; + bool symfromlib; /* Define potential architecture specific elf data container */ @@ -764,6 +845,33 @@ static int libelf_relocatedyn(FAR struct module_s *modp, case DT_PLTRELSZ: reldata.relsz[I_PLT] = dyn[i].d_un.d_val; break; + case DT_NEEDED: +#ifdef LIBELF_NEEDED + + /* Remember it; the name lives in the string table, which is + * not located until the loop has seen DT_STRTAB. + */ + + if (nlibs >= CONFIG_LIBC_ELF_MAXDEPEND) + { + berr("ERROR: More than %d DT_NEEDED entries\n", + CONFIG_LIBC_ELF_MAXDEPEND); + lib_free(sym); + lib_free(rels); + lib_free(dyn); + return -ENOEXEC; + } + + libs[nlibs++] = dyn[i].d_un.d_val; + break; +#else + berr("ERROR: Cannot load a DT_NEEDED library\n"); + lib_free(sym); + lib_free(rels); + lib_free(dyn); + return -ENOSYS; +#endif + case DT_PLTGOT: /* The object's data base. Every function descriptor built @@ -842,9 +950,81 @@ static int libelf_relocatedyn(FAR struct module_s *modp, loadinfo->gotbase); } - /* After the loop, because DT_PLTGOT is read there. Both relocation - * tables are walked under this one arch_data, so the pool cursor - * survives from one to the next. + /* Load whatever the object names in DT_NEEDED. This is what dlopen() + * does, through the same libelf_insert(), but the loader is also the + * kernel's module loader, which has no dlfcn. + */ + +#ifdef LIBELF_NEEDED + + symhdr = &loadinfo->shdr[loadinfo->dsymtabidx]; + + for (i = 0; i < nlibs; i++) + { + Elf_Sym namesym; + FAR void *handle; + + /* The name is a string table offset, which is what st_name is, so + * the existing reader can fetch it. + */ + + memset(&namesym, 0, sizeof(namesym)); + namesym.st_name = libs[i]; + + ret = libelf_symname(loadinfo, &namesym, + loadinfo->shdr[symhdr->sh_link].sh_offset); + if (ret < 0) + { + berr("ERROR: DT_NEEDED %d has no name\n", i); + lib_free(sym); + lib_free(rels); + lib_free(dyn); + return ret; + } + + handle = libelf_insertneeded((FAR const char *)loadinfo->iobuffer); + if (handle == NULL) + { + berr("ERROR: Cannot open needed library %s\n", + (FAR char *)loadinfo->iobuffer); + lib_free(sym); + lib_free(rels); + lib_free(dyn); + return -ELIBACC; + } + + binfo("Opened needed library %s\n", (FAR char *)loadinfo->iobuffer); + + /* The dependency holds the library from now on, in place of the + * reference libelf_insert() took, so libelf_undepend() lets it go. + */ + + libelf_registry_lock(); + ret = libelf_depend(modp, handle); + if (ret >= 0) + { + ((FAR struct module_s *)handle)->nopen--; + } + + libelf_registry_unlock(); + + if (ret < 0) + { + berr("ERROR: Cannot depend on %s: %d\n", + (FAR char *)loadinfo->iobuffer, ret); + libelf_remove(handle); + lib_free(sym); + lib_free(rels); + lib_free(dyn); + return ret; + } + } + +#endif + + /* Must follow the tag loop, which is where DT_PLTGOT is read. Both + * relocation tables are walked under this one arch_data, so a cursor in + * it spans the object. */ ARCH_ELFDATA_SETUP(loadinfo); @@ -943,14 +1123,36 @@ static int libelf_relocatedyn(FAR struct module_s *modp, { FAR void *ep; + symfromlib = false; ep = libelf_findglobal(modp, loadinfo, symhdr, &sym[idx_sym]); - /* libelf_findglobal() searches only the registered - * symbols. A module from exec() has its own export - * table, and an FDPIC module imports its libc there. + /* libelf_findglobal() searches only the globally + * registered symbols, and has left the name in the + * I/O buffer. Try the modules this one depends on, + * its DT_NEEDED libraries among them, then the table + * exec() supplied. */ +#ifdef LIBELF_NEEDED + for (j = 0; ep == NULL && j < CONFIG_LIBC_ELF_MAXDEPEND; + j++) + { + FAR struct module_s *dep = modp->dependencies[j]; + + if (dep != NULL) + { + ep = (FAR void *) + libelf_getsymbol(dep, + (FAR char *)loadinfo->iobuffer); + + /* An FDPIC object exports descriptors */ + + symfromlib = ep != NULL && dep->gotbase != 0; + } + } +#endif + if (ep == NULL && exports != NULL) { FAR const struct symtab_s *sm; @@ -998,6 +1200,12 @@ static int libelf_relocatedyn(FAR struct module_s *modp, extsym.st_value = (uintptr_t)ep; + /* Whether the resolved value is itself a descriptor, + * which it is when the symbol came from a library. + */ + + ARCH_ELFDATA_SYMISDESC(symfromlib); + ret = up_relocate(rel, &extsym, addr, ARCH_ELFDATA_PARM); if (ret < 0) { @@ -1011,10 +1219,9 @@ static int libelf_relocatedyn(FAR struct module_s *modp, } else if (loadinfo->fdpic) { - /* A relocation naming a symbol inside this object. A - * pointer to a static function is emitted against the - * section symbol, so the offset, Thumb bit included, is - * the addend and must not come from the patched word. + /* A symbol defined inside this object. Its value is + * the symbol's own, translated; the addend stays where + * the relocation type expects it. */ Elf_Sym defsym = sym[idx_sym]; @@ -1022,6 +1229,8 @@ static int libelf_relocatedyn(FAR struct module_s *modp, defsym.st_value = libelf_addr(loadinfo, sym[idx_sym].st_value); + ARCH_ELFDATA_SYMISDESC(false); + addr = libelf_addr(loadinfo, rel->r_offset); if (reldata.relrela[idx_rel] == 1) @@ -1058,6 +1267,8 @@ static int libelf_relocatedyn(FAR struct module_s *modp, dynsym.st_value = libelf_addr(loadinfo, *(FAR uint32_t *)addr); + ARCH_ELFDATA_SYMISDESC(false); + ret = up_relocate(rel, &dynsym, addr, ARCH_ELFDATA_PARM); } @@ -1073,9 +1284,7 @@ static int libelf_relocatedyn(FAR struct module_s *modp, } } - /* Hand back what the relocations consumed. The error paths above do - * not bother: the load is being abandoned, so the cursor has no reader. - */ + /* Hand back what the relocations consumed. */ ARCH_ELFDATA_TEARDOWN(loadinfo); diff --git a/libs/libc/elf/elf_depend.c b/libs/libc/elf/elf_depend.c index c08f1434d93..db4f8b18f67 100644 --- a/libs/libc/elf/elf_depend.c +++ b/libs/libc/elf/elf_depend.c @@ -174,6 +174,15 @@ int libelf_undepend(FAR struct module_s *importer) } importer->dependencies[i] = NULL; + + /* A library loaded for a DT_NEEDED entry is held only by the + * modules that depend on it, so the last of them unloads it. + */ + + if (exporter->dependents == 0 && exporter->nopen == 0) + { + libelf_remove(exporter); + } } } diff --git a/libs/libc/elf/elf_remove.c b/libs/libc/elf/elf_remove.c index 8f7d54bed89..82d64746e28 100644 --- a/libs/libc/elf/elf_remove.c +++ b/libs/libc/elf/elf_remove.c @@ -42,7 +42,9 @@ * Name: libelf_uninit * * Description: - * Uninitialize module resources. + * Uninitialize module resources. Gives up everything the module holds, + * the DT_NEEDED libraries included, so the caller must hold the last + * reference. * ****************************************************************************/ @@ -244,8 +246,8 @@ int libelf_remove(FAR void *handle) } /* Give back a reference. The module goes only when the last one does, - * so an rmmod() cannot pull a module out from under a dlopen() that is - * still holding it. + * so an rmmod() cannot pull a module out from under a dlopen(), or from + * under a module that names it in DT_NEEDED. */ if (modp->nopen > 1) diff --git a/libs/libc/elf/elf_symbols.c b/libs/libc/elf/elf_symbols.c index 3ba6fb5eeda..84143b68ba3 100644 --- a/libs/libc/elf/elf_symbols.c +++ b/libs/libc/elf/elf_symbols.c @@ -77,6 +77,57 @@ extern int nglobals; * Private Functions ****************************************************************************/ +/**************************************************************************** + * Name: libelf_symcallback + * + * Description: + * libelf_registry_foreach() callback function. Test if the provided + * module, modp, exports the symbol of interest. If so, return that symbol + * value and setup the module dependency relationship. + * + * Returned Value: + * 0 (OK) is returned on success and a negated errno is returned on + * failure. + * + ****************************************************************************/ + +static int libelf_symcallback(FAR struct module_s *modp, FAR void *arg) +{ + FAR struct mod_exportinfo_s *exportinfo = (FAR struct mod_exportinfo_s *) + arg; + + /* Check if this module exports a symbol of that name */ + + exportinfo->symbol = symtab_findbyname(modp->modinfo.exports, + exportinfo->name, + modp->modinfo.nexports); + + if (exportinfo->symbol != NULL) + { + /* Yes.. save the dependency relationship and return SYM_FOUND to + * stop the traversal. + */ + +#if CONFIG_LIBC_ELF_MAXDEPEND > 0 + int ret = libelf_depend(exportinfo->modp, modp); + + if (ret < 0) + { + berr("ERROR: libelf_depend failed: %d\n", ret); + return ret; + } +#endif + + return SYM_FOUND; + } + + return SYM_NOT_FOUND; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + /**************************************************************************** * Name: libelf_symname * @@ -93,8 +144,8 @@ extern int nglobals; * ****************************************************************************/ -static int libelf_symname(FAR struct mod_loadinfo_s *loadinfo, - FAR const Elf_Sym *sym, Elf_Off sh_offset) +int libelf_symname(FAR struct mod_loadinfo_s *loadinfo, + FAR const Elf_Sym *sym, Elf_Off sh_offset) { FAR uint8_t *buffer; off_t offset; @@ -187,57 +238,6 @@ static int libelf_symname(FAR struct mod_loadinfo_s *loadinfo, return OK; } -/**************************************************************************** - * Name: libelf_symcallback - * - * Description: - * libelf_registry_foreach() callback function. Test if the provided - * module, modp, exports the symbol of interest. If so, return that symbol - * value and setup the module dependency relationship. - * - * Returned Value: - * 0 (OK) is returned on success and a negated errno is returned on - * failure. - * - ****************************************************************************/ - -static int libelf_symcallback(FAR struct module_s *modp, FAR void *arg) -{ - FAR struct mod_exportinfo_s *exportinfo = (FAR struct mod_exportinfo_s *) - arg; - - /* Check if this module exports a symbol of that name */ - - exportinfo->symbol = symtab_findbyname(modp->modinfo.exports, - exportinfo->name, - modp->modinfo.nexports); - - if (exportinfo->symbol != NULL) - { - /* Yes.. save the dependency relationship and return SYM_FOUND to - * stop the traversal. - */ - -#if CONFIG_LIBC_ELF_MAXDEPEND > 0 - int ret = libelf_depend(exportinfo->modp, modp); - - if (ret < 0) - { - berr("ERROR: libelf_depend failed: %d\n", ret); - return ret; - } -#endif - - return SYM_FOUND; - } - - return SYM_NOT_FOUND; -} - -/**************************************************************************** - * Public Functions - ****************************************************************************/ - /**************************************************************************** * Name: libelf_findsymtab * diff --git a/libs/libc/machine/arm/armv7-m/arch_elf.c b/libs/libc/machine/arm/armv7-m/arch_elf.c index e0468c71ede..03e8e9e5981 100644 --- a/libs/libc/machine/arm/armv7-m/arch_elf.c +++ b/libs/libc/machine/arm/armv7-m/arch_elf.c @@ -210,28 +210,36 @@ int up_relocate(const Elf32_Rel *rel, const Elf32_Sym *sym, uintptr_t addr, "at addr=%08" PRIxPTR " to sym=%p st_value=%08" PRIx32 "\n", addr, sym, sym->st_value); - if (data->pltrel) + if (data->symisdesc) + { + /* Resolved to a function in another object, which published a + * descriptor of its own. Take both words: the callee has to + * run with its own data base, not ours. + */ + + *desc = *(struct fdpic_desc_s *)sym->st_value; + } + else if (data->pltrel) { /* A lazy descriptor holds its PLT stub address, not an * addend. Overwrite it, do not add to it. */ desc->entry = sym->st_value; + desc->got = data->gotbase; } else { desc->entry = sym->st_value + desc->entry; + desc->got = data->gotbase; } - - desc->got = data->gotbase; } break; case R_ARM_FUNCDESC: { - /* A pointer to a descriptor, which the loader has to supply. - * Carve one out of the pool reserved behind the writable segment - * and store its address. + /* A pointer to a descriptor the loader must supply. Carve one + * from the pool and store its address. */ struct fdpic_desc_s *desc; @@ -249,6 +257,16 @@ int up_relocate(const Elf32_Rel *rel, const Elf32_Sym *sym, uintptr_t addr, return -ENOEXEC; } + /* Another object's function comes with a descriptor of its own, + * which is already what this word must point to. + */ + + if (data->symisdesc) + { + *(uint32_t *)addr = sym->st_value; + break; + } + if (data->usedesc >= data->ndesc) { berr("ERROR: Out of function descriptors\n"); diff --git a/libs/libc/machine/arm/armv8-m/arch_elf.c b/libs/libc/machine/arm/armv8-m/arch_elf.c index a0e0ec998c3..479a045d349 100644 --- a/libs/libc/machine/arm/armv8-m/arch_elf.c +++ b/libs/libc/machine/arm/armv8-m/arch_elf.c @@ -210,28 +210,36 @@ int up_relocate(const Elf32_Rel *rel, const Elf32_Sym *sym, uintptr_t addr, "at addr=%08" PRIxPTR " to sym=%p st_value=%08" PRIx32 "\n", addr, sym, sym->st_value); - if (data->pltrel) + if (data->symisdesc) + { + /* Resolved to a function in another object, which published a + * descriptor of its own. Take both words: the callee has to + * run with its own data base, not ours. + */ + + *desc = *(struct fdpic_desc_s *)sym->st_value; + } + else if (data->pltrel) { /* A lazy descriptor holds its PLT stub address, not an * addend. Overwrite it, do not add to it. */ desc->entry = sym->st_value; + desc->got = data->gotbase; } else { desc->entry = sym->st_value + desc->entry; + desc->got = data->gotbase; } - - desc->got = data->gotbase; } break; case R_ARM_FUNCDESC: { - /* A pointer to a descriptor, which the loader has to supply. - * Carve one out of the pool reserved behind the writable segment - * and store its address. + /* A pointer to a descriptor the loader must supply. Carve one + * from the pool and store its address. */ struct fdpic_desc_s *desc; @@ -249,6 +257,16 @@ int up_relocate(const Elf32_Rel *rel, const Elf32_Sym *sym, uintptr_t addr, return -ENOEXEC; } + /* Another object's function comes with a descriptor of its own, + * which is already what this word must point to. + */ + + if (data->symisdesc) + { + *(uint32_t *)addr = sym->st_value; + break; + } + if (data->usedesc >= data->ndesc) { berr("ERROR: Out of function descriptors\n");