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);
 }
 

Reply via email to