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 840791e927746bddd77957675b6f1e742d7398df
Author: Marco Casaroli <[email protected]>
AuthorDate: Tue Oct 20 00:00:07 2026 +0200

    libs/libbuiltin/gcov: Forget the coverage data of an unloaded module.
    
    With CONFIG_COVERAGE_MINI, __gcov_init() adds the gcov_info of each
    object file to a list, and __gcov_dump() and __gcov_reset() walk this
    list.  A loadable module that is built with the coverage options adds
    its own gcov_info, which is in the memory of the module.  After the
    module is unloaded, the list still points to this memory, and the next
    dump or reset reads freed memory.
    
    Add __gcov_unload(), which removes from the list each gcov_info in an
    address range.  libelf_uninit() calls it for the memory of a module
    before it frees the memory.  This is the path of dlclose(), rmmod() and
    the exit of a program that exec() loaded.  struct module_s now always
    keeps the sizes of the text and the data, and binfmt saves them too.  A
    mutex protects the list, because a module can load or unload while a
    dump runs.
    
    The unload does not dump the data of the module.  The dump writes the
    data of all object files, and it can run in the context of a task that
    exits.  To keep the data of a module, dump it while the module is
    loaded: with "gcov -d", or with a call to __gcov_dump() in the module.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <[email protected]>
---
 binfmt/elf.c                  |  2 ++
 include/gcov.h                | 18 ++++++++++++++++++
 include/nuttx/lib/elf.h       |  2 --
 libs/libbuiltin/libgcc/gcov.c | 42 ++++++++++++++++++++++++++++++++++++++++++
 libs/libc/elf/elf_insert.c    |  4 ----
 libs/libc/elf/elf_remove.c    | 10 ++++++++--
 6 files changed, 70 insertions(+), 8 deletions(-)

diff --git a/binfmt/elf.c b/binfmt/elf.c
index ca24544a08b..e59a066e317 100644
--- a/binfmt/elf.c
+++ b/binfmt/elf.c
@@ -264,6 +264,8 @@ static int elf_loadbinary(FAR struct binary_s *binp,
 
   binp->mod.textalloc = (FAR void *)loadinfo.textalloc;
   binp->mod.dataalloc = (FAR void *)loadinfo.datastart;
+  binp->mod.textsize  = loadinfo.textsize;
+  binp->mod.datasize  = loadinfo.datasize;
   binp->mod.gotbase   = loadinfo.fdpic ? loadinfo.gotbase : 0;
 #  ifdef CONFIG_BINFMT_CONSTRUCTORS
   binp->mod.initarr = loadinfo.initarr;
diff --git a/include/gcov.h b/include/gcov.h
index 5dc38d07dda..46c9ed34400 100644
--- a/include/gcov.h
+++ b/include/gcov.h
@@ -27,6 +27,8 @@
  * Included Files
  ****************************************************************************/
 
+#include <nuttx/config.h>
+
 #include <sys/types.h>
 
 /****************************************************************************
@@ -61,6 +63,22 @@ extern void __gcov_reset(void);
 
 extern void __gcov_dump(void);
 
+#if defined(CONFIG_COVERAGE_MINI) && defined(CONFIG_ARCH_TOOLCHAIN_GCC)
+
+/****************************************************************************
+ * Name: __gcov_unload
+ *
+ * Description:
+ *   Forget the coverage data in the memory from addr to addr + size.  The
+ *   loader calls it before it frees a module.
+ *
+ ****************************************************************************/
+
+void __gcov_unload(FAR const void *addr, size_t size);
+#else
+#  define __gcov_unload(addr, size)
+#endif
+
 #undef EXTERN
 #ifdef __cplusplus
 }
diff --git a/include/nuttx/lib/elf.h b/include/nuttx/lib/elf.h
index 63f78867a2a..c847e09f4f0 100644
--- a/include/nuttx/lib/elf.h
+++ b/include/nuttx/lib/elf.h
@@ -204,10 +204,8 @@ struct module_s
                                         * the module is unloaded
                                         */
 #endif
-#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_MODULE)
   size_t textsize;                     /* Size of the kernel .text memory 
allocation */
   size_t datasize;                     /* Size of the kernel .bss/.data memory 
allocation */
-#endif
 
   uint8_t nopen;                       /* Outstanding references: insmod()
                                         * and dlopen() each take one, rmmod()
diff --git a/libs/libbuiltin/libgcc/gcov.c b/libs/libbuiltin/libgcc/gcov.c
index 2ff033bd111..8e8c2b97e45 100644
--- a/libs/libbuiltin/libgcc/gcov.c
+++ b/libs/libbuiltin/libgcc/gcov.c
@@ -30,6 +30,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
+#include <nuttx/mutex.h>
 #include <nuttx/streams.h>
 #include <nuttx/lib/lib.h>
 #include <nuttx/reboot_notifier.h>
@@ -156,6 +157,14 @@ typedef CODE int (*dump_fn_t)(FAR struct dump_args *,
 FAR struct gcov_info *__gcov_info_start;
 FAR struct gcov_info *__gcov_info_end;
 
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* Protects the list: a module can add to it or leave it at any time */
+
+static rmutex_t g_gcov_lock = NXRMUTEX_INITIALIZER;
+
 /****************************************************************************
  * Private Functions
  ****************************************************************************/
@@ -341,6 +350,7 @@ static int gcov_dump_foreach(dump_fn_t handler, FAR struct 
dump_args *args)
   int size;
   int i;
 
+  nxrmutex_lock(&g_gcov_lock);
   for (info = __gcov_info_start; info; info = info->next)
     {
       memset(args->path_buffer, 0x00, PATH_MAX);
@@ -350,6 +360,7 @@ static int gcov_dump_foreach(dump_fn_t handler, FAR struct 
dump_args *args)
       if (data == NULL)
         {
           syslog(LOG_ERR, "gcov alloc failed!");
+          nxrmutex_unlock(&g_gcov_lock);
           return -ERROR;
         }
 
@@ -360,6 +371,7 @@ static int gcov_dump_foreach(dump_fn_t handler, FAR struct 
dump_args *args)
         {
           syslog(LOG_ERR, "gcov alloc failed! skip %s", info->filename);
           lib_free(data);
+          nxrmutex_unlock(&g_gcov_lock);
           return -ERROR;
         }
 
@@ -397,12 +409,14 @@ static int gcov_dump_foreach(dump_fn_t handler, FAR 
struct dump_args *args)
       lib_free(data);
     }
 
+  nxrmutex_unlock(&g_gcov_lock);
   return OK;
 
 exit:
   lib_free(filename);
   lib_free(data);
 
+  nxrmutex_unlock(&g_gcov_lock);
   return -ERROR;
 }
 
@@ -441,8 +455,10 @@ void __gcov_init(FAR struct gcov_info *info)
       inited++;
     }
 
+  nxrmutex_lock(&g_gcov_lock);
   info->next = __gcov_info_start;
   __gcov_info_start = info;
+  nxrmutex_unlock(&g_gcov_lock);
 }
 
 void __gcov_merge_add(FAR gcov_type *counters, unsigned int n_counters)
@@ -558,6 +574,7 @@ void __gcov_reset(void)
   uint32_t functions;
   uint32_t counts;
 
+  nxrmutex_lock(&g_gcov_lock);
   for (info = __gcov_info_start; info; info = info->next)
     {
       for (functions = 0; functions < info->n_functions; functions++)
@@ -576,4 +593,29 @@ void __gcov_reset(void)
             }
         }
     }
+
+  nxrmutex_unlock(&g_gcov_lock);
+}
+
+void __gcov_unload(FAR const void *addr, size_t size)
+{
+  FAR const char *start = addr;
+  FAR struct gcov_info **prev;
+  FAR const char *info;
+
+  nxrmutex_lock(&g_gcov_lock);
+  for (prev = &__gcov_info_start; *prev != NULL; )
+    {
+      info = (FAR const char *)*prev;
+      if (info >= start && info < start + size)
+        {
+          *prev = (*prev)->next;
+        }
+      else
+        {
+          prev = &(*prev)->next;
+        }
+    }
+
+  nxrmutex_unlock(&g_gcov_lock);
 }
diff --git a/libs/libc/elf/elf_insert.c b/libs/libc/elf/elf_insert.c
index 0c5c0185e03..f6a54f9b4b4 100644
--- a/libs/libc/elf/elf_insert.c
+++ b/libs/libc/elf/elf_insert.c
@@ -121,10 +121,8 @@ void libelf_dumpmodule(FAR struct module_s *modp)
   binfo("  modname:      %s\n", modp->modname);
 #endif
   binfo("  textalloc:    %08lx\n", (long)modp->textalloc);
-#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_MODULE)
   binfo("  dataalloc:    %08lx\n", (long)modp->dataalloc);
   binfo("  textsize:     %ld\n",   (long)modp->textsize);
-#endif
 
 #ifdef CONFIG_ARCH_USE_SEPARATED_SECTION
   binfo("  sectalloc:    %p\n", modp->sectalloc);
@@ -418,10 +416,8 @@ FAR void *libelf_insert(FAR struct list_node *registry,
   modp->nsect = loadinfo.ehdr.e_shnum;
 #endif
 
-#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_MODULE)
   modp->textsize  = loadinfo.textsize;
   modp->datasize  = loadinfo.datasize;
-#endif
 
   /* Call the module initializer.  An FDPIC object's constructors reach its
    * globals through its own data base, which the loading thread does not
diff --git a/libs/libc/elf/elf_remove.c b/libs/libc/elf/elf_remove.c
index 5623096182d..d9be2e288f4 100644
--- a/libs/libc/elf/elf_remove.c
+++ b/libs/libc/elf/elf_remove.c
@@ -26,6 +26,7 @@
 #include <assert.h>
 #include <nuttx/debug.h>
 #include <errno.h>
+#include <gcov.h>
 
 #include <nuttx/arch.h>
 #include <nuttx/fdpic.h>
@@ -115,6 +116,11 @@ int libelf_uninit(FAR struct module_s *modp)
 
   if (modp->textalloc != NULL || modp->dataalloc != NULL)
     {
+      /* The coverage data of the module must not outlive it */
+
+      __gcov_unload(modp->textalloc, modp->textsize);
+      __gcov_unload(modp->dataalloc, modp->datasize);
+
       /* Free the module memory and nullify so that the memory cannot
        * be freed again
        *
@@ -143,6 +149,8 @@ int libelf_uninit(FAR struct module_s *modp)
                 }
 #  endif
 
+              __gcov_unload(modp->sectalloc[i],
+                            lib_malloc_size(modp->sectalloc[i]));
               lib_free(modp->sectalloc[i]);
             }
 
@@ -195,10 +203,8 @@ int libelf_uninit(FAR struct module_s *modp)
 
       modp->textalloc = NULL;
       modp->dataalloc = NULL;
-#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_MODULE)
       modp->textsize  = 0;
       modp->datasize  = 0;
-#endif
     }
 
 #if CONFIG_LIBC_ELF_MAXDEPEND > 0

Reply via email to