From 3a2ce9e2bdd14af18e7201607a6acfea89422e90 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Tue, 25 Aug 2026 11:14:11 +0200 Subject: [PATCH] binfmt/elf: Load FDPIC modules through the ELF loader. exec() of an FDPIC module now works. The loader already places such an object and binds it; what was missing is everything binfmt has to carry across from the load to the running task. The task needs the module's data base in its PIC base register. binfmt builds a D-Space for any object with a GOT, taking the base from the .got section address; an FDPIC object names it in DT_PLTGOT instead, which the loader has already translated, so the two are the same idea reached by different routes and both are what up_initial_state() installs. Constructors are not binfmt's business. A module carries its own crt0, which walks .init_array on the task that runs the module and then calls main, so they run in the module's own context and with its own data base. For a module that arrives through dlopen(), libelf_insert() walks the array instead, and it enters each entry through fdpic_invoke() because a descriptor resolved on the calling task carries the wrong base. The read-only segment of a module that executes in place is held by a filesystem pin. The load takes it, and the module owns it from the point where nothing can fail any more; it is given back when the task that runs the module exits. The pin is held through a reference to the file rather than a descriptor, because the descriptor belongs to the task that called the loader and the release happens on another one. libelf_remove() and libelf_uninit() give back what an FDPIC module holds: the pin, and the writable segment, while the read-only one is media rather than an allocation and must not be freed. Built for mps3-an547:picostest with CONFIG_FDPIC both ways. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- binfmt/elf.c | 11 +++++++++ include/nuttx/lib/elf.h | 12 ++++++++++ libs/libc/elf/elf_bind.c | 18 +++++++++++++++ libs/libc/elf/elf_insert.c | 37 +++++++++++++++++++++++++++++- libs/libc/elf/elf_remove.c | 47 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 1 deletion(-) diff --git a/binfmt/elf.c b/binfmt/elf.c index a4ad873a65add..3a6cdadc46137 100644 --- a/binfmt/elf.c +++ b/binfmt/elf.c @@ -252,6 +252,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.fdpic = loadinfo.fdpic; + binp->mod.gotbase = loadinfo.gotbase; # ifdef CONFIG_BINFMT_CONSTRUCTORS binp->mod.initarr = loadinfo.initarr; binp->mod.finiarr = loadinfo.finiarr; @@ -286,6 +288,15 @@ static int elf_loadbinary(FAR struct binary_s *binp, } #endif +#ifdef HAVE_LIBC_ELF_PIN + /* Past the last thing that can fail, so the module owns the pin: it is + * given back when the task that runs the module exits. + */ + + binp->mod.pinfile = loadinfo.pinfile; + loadinfo.pinfile = NULL; +#endif + libelf_uninitialize(&loadinfo); return OK; diff --git a/include/nuttx/lib/elf.h b/include/nuttx/lib/elf.h index e30399eede951..80c9d3fb6583e 100644 --- a/include/nuttx/lib/elf.h +++ b/include/nuttx/lib/elf.h @@ -190,6 +190,18 @@ struct module_s uint16_t nsect; /* Number of entries in sectalloc array */ #endif int dynamic; /* Module is a dynamic shared object */ + bool fdpic; /* Module is an FDPIC object: its two + * segments were placed separately and + * the text is media, not an allocation + */ + uintptr_t gotbase; /* An FDPIC object's data base, to + * enter its destructors with + */ +#ifdef HAVE_LIBC_ELF_PIN + FAR struct file *pinfile; /* Holds the XIP pin on the text until + * 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 */ diff --git a/libs/libc/elf/elf_bind.c b/libs/libc/elf/elf_bind.c index 4e0727222dd46..bb5e49b38015f 100644 --- a/libs/libc/elf/elf_bind.c +++ b/libs/libc/elf/elf_bind.c @@ -714,6 +714,24 @@ 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: + + /* Nothing loads DT_NEEDED yet, so refuse the module rather + * than let it fault on its first call into the library. + */ + + if (loadinfo->fdpic) + { + berr("ERROR: FDPIC module has a DT_NEEDED entry. Shared " + "libraries are not supported; link it statically.\n"); + lib_free(sym); + lib_free(rels); + lib_free(dyn); + return -ENOEXEC; + } + + break; + case DT_PLTGOT: /* The object's data base. Every function descriptor built diff --git a/libs/libc/elf/elf_insert.c b/libs/libc/elf/elf_insert.c index 09d1cb2623d23..fc1f58fc93445 100644 --- a/libs/libc/elf/elf_insert.c +++ b/libs/libc/elf/elf_insert.c @@ -29,6 +29,7 @@ #include #include +#include #include #include "elf.h" @@ -404,6 +405,12 @@ FAR void *libelf_insert(FAR const char *filename, FAR const char *modname) modp->textalloc = (FAR void *)loadinfo.textalloc; modp->dataalloc = (FAR void *)loadinfo.datastart; + modp->fdpic = loadinfo.fdpic; + modp->gotbase = loadinfo.gotbase; +#ifdef HAVE_LIBC_ELF_PIN + modp->pinfile = loadinfo.pinfile; + loadinfo.pinfile = NULL; +#endif #ifdef CONFIG_ARCH_USE_SEPARATED_SECTION modp->sectalloc = (FAR void **)loadinfo.sectalloc; modp->nsect = loadinfo.ehdr.e_shnum; @@ -421,11 +428,26 @@ FAR void *libelf_insert(FAR const char *filename, FAR const char *modname) case ET_REL : case ET_DYN : - /* Process any preinit_array entries */ + /* Process any preinit_array entries. An FDPIC object's + * constructors need its own data base, not the loading thread's. + */ array = (FAR void (**)(void))loadinfo.preiarr; for (i = 0; i < loadinfo.nprei; i++) { +#ifdef CONFIG_FDPIC + if (loadinfo.fdpic) + { + struct fdpic_desc_s desc; + + desc.entry = (uintptr_t)array[i]; + desc.got = loadinfo.gotbase; + + fdpic_invoke(0, &desc); + continue; + } +#endif + array[i](); } @@ -434,6 +456,19 @@ FAR void *libelf_insert(FAR const char *filename, FAR const char *modname) array = (FAR void (**)(void))loadinfo.initarr; for (i = 0; i < loadinfo.ninit; i++) { +#ifdef CONFIG_FDPIC + if (loadinfo.fdpic) + { + struct fdpic_desc_s desc; + + desc.entry = (uintptr_t)array[i]; + desc.got = loadinfo.gotbase; + + fdpic_invoke(0, &desc); + continue; + } +#endif + array[i](); } diff --git a/libs/libc/elf/elf_remove.c b/libs/libc/elf/elf_remove.c index 67722937b3302..85c9904898cf7 100644 --- a/libs/libc/elf/elf_remove.c +++ b/libs/libc/elf/elf_remove.c @@ -29,8 +29,11 @@ #include #include +#include #include +#include "elf/elf.h" + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -64,6 +67,24 @@ int libelf_uninit(FAR struct module_s *modp) array = (FAR void (**)(void))modp->finiarr; for (i = 0; i < modp->nfini; i++) { + /* Like the constructors, an FDPIC object's destructors reach its + * globals through its own data base, which the unloading thread does + * not carry. + */ + +#ifdef CONFIG_FDPIC + if (modp->fdpic) + { + struct fdpic_desc_s desc; + + desc.entry = (uintptr_t)array[i]; + desc.got = modp->gotbase; + + fdpic_invoke(0, &desc); + continue; + } +#endif + array[i](); } @@ -93,6 +114,14 @@ int libelf_uninit(FAR struct module_s *modp) #endif } +#ifdef HAVE_LIBC_ELF_PIN + /* Give the pin back before the text goes out of use. This does nothing if + * the loader took no pin. + */ + + libelf_pinrelease(&modp->pinfile); +#endif + /* Release resources held by the module */ if (modp->textalloc != NULL || modp->dataalloc != NULL) @@ -148,6 +177,24 @@ int libelf_uninit(FAR struct module_s *modp) # endif #endif } + else if (modp->fdpic) + { + /* An FDPIC object placed its two segments separately. Free each + * one. If the text stayed on the media, it was never allocated, + * thus leave it. + */ + + if (modp->xipbase == 0) + { +#if defined(CONFIG_ARCH_USE_TEXT_HEAP) + up_textheap_free((FAR void *)modp->textalloc); +#else + lib_free((FAR void *)modp->textalloc); +#endif + } + + lib_free((FAR void *)modp->dataalloc); + } else { lib_free((FAR void *)modp->textalloc);