This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit e47be608d5757a8b323f5e1d300c9ac3903f0414 Author: Marco Casaroli <[email protected]> AuthorDate: Mon Aug 3 12:05:40 2026 +0200 libc/dlfcn: Count opens so a library can be shared. dlopen() of a library that is already loaded fails. libelf_insert() rejects a name that is already in the module registry with EEXIST, and dlinsert() passes that straight out, so the second caller gets NULL. POSIX says dlopen() shall return a handle to the object, and there is no way today for two modules to hold the same library at once -- which is what a shared library is for. So dlopen() now takes another reference on a library that is already there, and dlclose() only tears it down when the last handle goes. The count lives in the dlfcn layer rather than in libelf_insert() so that insmod keeps its own behaviour: a second insmod of the same name still fails with EEXIST, which is right for a kernel module. The module name is what makes any of this possible, and a PROTECTED build did not have one. Names were defined for CONFIG_BUILD_FLAT or the kernel side of a split build, on the reasoning that only the kernel needed them, which predates dlopen() being usable from user space. Without a name the user-space copy of libelf cannot recognise a second open of a library, cannot count opens, and cannot make dlclose() mean anything -- two dlopen()s there produce two independent copies of the library and lose track of the first. Names are therefore defined wherever CONFIG_LIBC_DLFCN is, which costs NAME_MAX per loaded module in that configuration. The path no longer has to be copied either. The module name is the basename of the file and libelf_insert() takes it as a const string, so dlinsert() finds it with strrchr() instead of handing a writable duplicate of the whole path to basename(). BUILD_KERNEL is deliberately untouched. dlopen() returns NULL there unconditionally: dlinsert() is a stub, because sharing a library between processes with separate address spaces needs the text in a shared region and the data per process at a matching virtual address, which is a different problem from this one. Built for mps3-an547:picostest with and without CONFIG_LIBC_DLFCN, and for stm32f4discovery:kostest, a PROTECTED configuration, with it enabled. Assisted-by: Claude Opus 5 (1M context) <[email protected]> Signed-off-by: Marco Casaroli <[email protected]> --- include/nuttx/lib/elf.h | 13 ++++++++++++- libs/libc/dlfcn/lib_dlclose.c | 5 +++++ libs/libc/dlfcn/lib_dlopen.c | 25 ++++++++++++------------- libs/libc/elf/elf_insert.c | 21 +++++++++++++++++---- libs/libc/elf/elf_remove.c | 14 ++++++++++++++ sched/module/mod_insmod.c | 13 +++++++++++++ 6 files changed, 73 insertions(+), 18 deletions(-) diff --git a/include/nuttx/lib/elf.h b/include/nuttx/lib/elf.h index 784210d38a9..bcb8a039c80 100644 --- a/include/nuttx/lib/elf.h +++ b/include/nuttx/lib/elf.h @@ -79,7 +79,12 @@ * portion of the build */ -#if defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__) +/* dlopen() needs a name too: it is the only way to tell that a library is + * already loaded. + */ + +#if defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__) || \ + defined(CONFIG_LIBC_DLFCN) # define HAVE_LIBC_ELF_NAMES # define LIBC_ELF_NAMEMAX NAME_MAX #endif @@ -174,6 +179,12 @@ struct module_s size_t datasize; /* Size of the kernel .bss/.data memory allocation */ #endif + uint8_t nopen; /* Outstanding references: insmod() + * and dlopen() each take one, rmmod() + * and dlclose() give one back, and the + * module goes when the last does + */ + #if CONFIG_LIBC_ELF_MAXDEPEND > 0 uint8_t dependents; /* Number of modules that depend on this module */ diff --git a/libs/libc/dlfcn/lib_dlclose.c b/libs/libc/dlfcn/lib_dlclose.c index 2dfa5da484d..67b96c97bd5 100644 --- a/libs/libc/dlfcn/lib_dlclose.c +++ b/libs/libc/dlfcn/lib_dlclose.c @@ -82,6 +82,7 @@ int dlclose(FAR void *handle) { #if defined(CONFIG_BUILD_FLAT) || defined(CONFIG_BUILD_PROTECTED) + /* In the FLAT build, a shared library is essentially the same as a kernel * module. * @@ -93,6 +94,10 @@ int dlclose(FAR void *handle) * dlremove() is essentially a clone of rmmod(). */ + /* libelf_remove() gives back a reference and unloads only when the last + * one goes, so closing one of two handles leaves the other usable. + */ + return libelf_remove(handle); #else /* if defined(CONFIG_BUILD_KERNEL) */ diff --git a/libs/libc/dlfcn/lib_dlopen.c b/libs/libc/dlfcn/lib_dlopen.c index 408a0f3d523..79ef2270549 100644 --- a/libs/libc/dlfcn/lib_dlopen.c +++ b/libs/libc/dlfcn/lib_dlopen.c @@ -26,8 +26,10 @@ #include <nuttx/config.h> -#include <libgen.h> #include <dlfcn.h> +#include <errno.h> +#include <stdint.h> +#include <string.h> #include <nuttx/envpath.h> #include <nuttx/lib/elf.h> @@ -82,24 +84,21 @@ static inline FAR void *dlinsert(FAR const char *filename) { - FAR void *handle; - FAR char *name; + FAR const char *modname; DEBUGASSERT(filename != NULL); - name = strdup(filename); - if (name == NULL) - { - return NULL; - } + /* The module name is the basename of the file */ - /* Then install the file using the basename of the file as the module - * name. + modname = strrchr(filename, '/'); + modname = modname != NULL ? modname + 1 : filename; + + /* libelf_insert() returns the module already loaded under this name, + * with another reference taken, so a library opened twice is one + * instance shared by both callers. */ - handle = libelf_insert(filename, basename(name)); - lib_free(name); - return handle; + return libelf_insert(filename, modname); } #else /* if defined(CONFIG_BUILD_KERNEL) */ /* The KERNEL build is considerably more complex: In order to be shared, diff --git a/libs/libc/elf/elf_insert.c b/libs/libc/elf/elf_insert.c index 3f52dc0e54d..5b646115248 100644 --- a/libs/libc/elf/elf_insert.c +++ b/libs/libc/elf/elf_insert.c @@ -307,14 +307,26 @@ FAR void *libelf_insert(FAR const char *filename, FAR const char *modname) libelf_registry_lock(); - /* Check if this module is already installed */ + /* Already installed? Take another reference rather than load a second + * copy: there is one instance of a module per name, and every caller + * shares it. The count is kept here so that insmod()/rmmod() and + * dlopen()/dlclose() get the same behaviour from the same code. + */ #ifdef HAVE_LIBC_ELF_NAMES - if (libelf_registry_find(modname) != NULL) + modp = libelf_registry_find(modname); + if (modp != NULL) { + if (modp->nopen == UINT8_MAX) + { + libelf_registry_unlock(); + set_errno(EMFILE); + return NULL; + } + + modp->nopen++; libelf_registry_unlock(); - set_errno(EEXIST); - return NULL; + return modp; } #endif @@ -342,6 +354,7 @@ FAR void *libelf_insert(FAR const char *filename, FAR const char *modname) /* Save the module name in the registry entry */ strlcpy(modp->modname, modname, sizeof(modp->modname)); + modp->nopen = 1; #endif /* Load the program binary */ diff --git a/libs/libc/elf/elf_remove.c b/libs/libc/elf/elf_remove.c index cc7a16d96e0..67722937b33 100644 --- a/libs/libc/elf/elf_remove.c +++ b/libs/libc/elf/elf_remove.c @@ -205,6 +205,20 @@ int libelf_remove(FAR void *handle) goto errout_with_lock; } + /* 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. + */ + + if (modp->nopen > 1) + { + modp->nopen--; + libelf_registry_unlock(); + return OK; + } + + modp->nopen = 0; + ret = libelf_uninit(modp); if (ret < 0) { diff --git a/sched/module/mod_insmod.c b/sched/module/mod_insmod.c index d8c37da15d9..28a9e0c0169 100644 --- a/sched/module/mod_insmod.c +++ b/sched/module/mod_insmod.c @@ -26,6 +26,8 @@ #include <nuttx/config.h> +#include <errno.h> + #include <nuttx/module.h> #include <nuttx/lib/elf.h> @@ -63,6 +65,17 @@ FAR void *insmod(FAR const char *filename, FAR const char *modname) { + /* A duplicate name is an error here, where it has always been. + * libelf_insert() would take a reference instead, which is what + * dlopen() wants and insmod() does not. + */ + + if (libelf_gethandle(modname) != NULL) + { + set_errno(EEXIST); + return NULL; + } + return libelf_insert(filename, modname); }
