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


The following commit(s) were added to refs/heads/master by this push:
     new 50a735bf867 libs/libc/machine/arm: Relocate FDPIC function descriptors.
50a735bf867 is described below

commit 50a735bf8672fd13ed68392bba6cf7c6b7d85303
Author: Marco Casaroli <[email protected]>
AuthorDate: Mon Aug 3 08:58:41 2026 +0200

    libs/libc/machine/arm: Relocate FDPIC function descriptors.
    
    A function pointer under FDPIC is not a code address.  Because each
    PT_LOAD segment is placed independently, a pointer has to carry the data
    base its callee will need, so it is a two-word descriptor: the entry
    point, and the base to install in the PIC register before branching.
    R_ARM_FUNCDESC_VALUE says "the thing you are patching is such a
    descriptor", and R_ARM_FUNCDESC says "manufacture one and give me its
    address".
    
    Both need state a relocation cannot carry.  A descriptor's second word is
    the *object's* data base, from DT_PLTGOT, and R_ARM_FUNCDESC carves
    descriptors from a pool whose cursor has to survive from one relocation
    to the next.  up_relocate() is handed only a relocation, a resolved
    symbol and an address to patch.
    
    arch_data is the existing channel for exactly this -- RISC-V already uses
    it to remember a HI20 relocation while its LO12 partner is processed --
    but nothing has ever put loader state into it: it is declared zeroed and
    written only by up_relocate() itself.  So ARCH_ELFDATA_INIT and
    ARCH_ELFDATA_FINI are added, seeding the block from the loadinfo before
    the relocation loop and reading the cursor back after.  Both default to
    nothing, so an architecture that does not define them is unaffected, and
    RISC-V's use of arch_data is untouched.  libelf_relocatedyn() walks both
    dynamic tables under one arch_data, so the cursor spans the whole object.
    
    The addend handling is the part that is easy to get wrong.  REL format
    keeps the addend in place, in the word about to become the entry point,
    and a pointer to a static function is referenced through its *section*
    symbol -- the value is the section base and the offset, including the
    Thumb bit, is entirely in the addend.  Dropping it yields an even address
    and the core faults trying to execute it as ARM code.
    
    The GOT written into a descriptor is the loading object's own, even for
    an imported function, which is what makes a callback work: when the base
    firmware's qsort() calls back into a module's comparison function, the
    module needs its own data base in the PIC register.
    
    libelf_relocatedyn()'s imported-symbol path needed a change to suit.  It
    stores the resolved address directly and never calls up_relocate(), which
    cannot produce a two-word descriptor, so under FDPIC the resolved value
    now goes through up_relocate() and the relocation type decides what to
    write.
    
    Implemented for armv7-m and armv8-m, the profiles FDPIC targets; the
    other ARM variants gain the arch_data block but no new relocations.
    Built and booted mps3-an547:picostest and lm3s6965-ek:qemu-nxflat, the
    ELF PIC and NXFLAT users of this code, both unchanged.
    
    Assisted-by: Claude Opus 5 (1M context) <[email protected]>
    Signed-off-by: Marco Casaroli <[email protected]>
---
 arch/arm/include/elf.h                   | 62 +++++++++++++++++++++
 libs/libc/elf/elf_bind.c                 | 64 ++++++++++++++++++++-
 libs/libc/machine/arm/armv7-m/arch_elf.c | 96 +++++++++++++++++++++++++++++++-
 libs/libc/machine/arm/armv8-m/arch_elf.c | 96 +++++++++++++++++++++++++++++++-
 4 files changed, 314 insertions(+), 4 deletions(-)

diff --git a/arch/arm/include/elf.h b/arch/arm/include/elf.h
index 460cb34ad4e..b0bcf2c676c 100644
--- a/arch/arm/include/elf.h
+++ b/arch/arm/include/elf.h
@@ -264,10 +264,72 @@
 #define DT_ARM_PREEMPTMAP        0x70000002
 #define DT_ARM_RESERVED2         0x70000003
 
+/* Loader state the FDPIC relocations need: the object's data base, and the
+ * descriptor pool cursor, which must survive from one relocation to the
+ * next.  It arrives through the arch_data channel.
+ */
+
+#define ARCH_ELFDATA             1
+
+#define ARCH_ELFDATA_INIT(d, l)          \
+  do                                     \
+    {                                    \
+      (d)->fdpic    = (l)->fdpic;        \
+      (d)->gotbase  = (l)->gotbase;      \
+      (d)->descpool = (l)->descpool;     \
+      (d)->ndesc    = (l)->ndesc;        \
+      (d)->usedesc  = (l)->usedesc;      \
+    }                                    \
+  while (0)
+
+#define ARCH_ELFDATA_FINI(d, l)          \
+  do                                     \
+    {                                    \
+      (l)->usedesc = (d)->usedesc;       \
+    }                                    \
+  while (0)
+
+/* Which relocation table is being walked.  A relocation out of DT_JMPREL
+ * overwrites a word the linker pre-loaded with a lazy binding stub, which is
+ * not an addend and must not be added to.
+ */
+
+#define ARCH_ELFDATA_SET_PLTREL(d, v)    (d)->pltrel = (v)
+
 /****************************************************************************
  * Public Types
  ****************************************************************************/
 
+#ifndef __ASSEMBLY__
+
+/* A function descriptor is what an FDPIC function pointer is.  Its shape is
+ * common, so struct fdpic_desc_s from include/nuttx/fdpic.h serves; only a
+ * pointer to one is kept here, thus the tag is enough.
+ */
+
+struct fdpic_desc_s;
+
+struct arch_elfdata_s
+{
+  uint8_t   fdpic;         /* The object is an FDPIC one */
+  uintptr_t gotbase;       /* DT_PLTGOT: this object's data base */
+  uint16_t  ndesc;         /* Capacity, in descriptors */
+  uint16_t  usedesc;       /* Next free slot */
+
+  /* The pool the descriptors are taken from */
+
+  FAR struct fdpic_desc_s *descpool;
+
+  uint8_t   pltrel;        /* Relocation comes from DT_JMPREL, so the word
+                            * it overwrites is a lazy binding stub and not
+                            * an addend
+                            */
+};
+
+typedef struct arch_elfdata_s arch_elfdata_t;
+
+#endif /* __ASSEMBLY__ */
+
 typedef struct __EIT_entry
 {
   unsigned long fnoffset;
diff --git a/libs/libc/elf/elf_bind.c b/libs/libc/elf/elf_bind.c
index 4e0727222dd..fae5b5f3cec 100644
--- a/libs/libc/elf/elf_bind.c
+++ b/libs/libc/elf/elf_bind.c
@@ -58,6 +58,25 @@
 #  define ARCH_ELFDATA_PARM NULL
 #endif
 
+/* Move loader state in and out of the arch_data block, and say which
+ * relocation table is being walked.  Nothing for an architecture whose
+ * relocations do not need any of it.
+ */
+
+#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)
+#endif
+
+#if defined(ARCH_ELFDATA) && defined(ARCH_ELFDATA_INIT)
+#  define ARCH_ELFDATA_SETUP(l)    ARCH_ELFDATA_INIT(&arch_data, l)
+#  define ARCH_ELFDATA_TEARDOWN(l) ARCH_ELFDATA_FINI(&arch_data, l)
+#else
+#  define ARCH_ELFDATA_SETUP(l)
+#  define ARCH_ELFDATA_TEARDOWN(l)
+#endif
+
 /****************************************************************************
  * Private Types
  ****************************************************************************/
@@ -720,7 +739,8 @@ static int libelf_relocatedyn(FAR struct module_s *modp,
              * for it names this base.
              */
 
-            loadinfo->gotbase = dyn[i].d_un.d_ptr;
+            loadinfo->gotbase = libelf_addr(loadinfo,
+                                            dyn[i].d_un.d_ptr);
             break;
 
           /* The constructor and destructor tables.  Section headers are
@@ -778,6 +798,13 @@ static int libelf_relocatedyn(FAR struct module_s *modp,
         }
     }
 
+  /* 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.
+   */
+
+  ARCH_ELFDATA_SETUP(loadinfo);
+
   symhdr = &loadinfo->shdr[loadinfo->dsymtabidx];
   sym = lib_malloc(symhdr->sh_size);
   if (!sym)
@@ -815,6 +842,10 @@ static int libelf_relocatedyn(FAR struct module_s *modp,
       ret = OK;
       lrelent = reldata.relsz[idx_rel] / reldata.relentsz[idx_rel];
 
+      /* Say which table this is, for an architecture that cares. */
+
+      ARCH_ELFDATA_PLTREL(idx_rel == I_PLT);
+
       for (i = 0; i < lrelent; i++)
         {
           /* Process each relocation entry
@@ -909,7 +940,30 @@ static int libelf_relocatedyn(FAR struct module_s *modp,
                       addr += rela->r_addend;
                     }
 
-                  *(FAR uintptr_t *)addr = (uintptr_t)ep;
+                  /* An import may be a descriptor under FDPIC, which is
+                   * built rather than assigned, so the relocation type
+                   * decides what to write.  Everything else stores the
+                   * resolved address, which R_ARM_JUMP_SLOT and
+                   * R_ARM_GLOB_DAT do, so one path serves both.
+                   */
+
+                  Elf_Sym extsym =
+                  {
+                    0
+                  };
+
+                  extsym.st_value = (uintptr_t)ep;
+
+                  ret = up_relocate(rel, &extsym, addr, ARCH_ELFDATA_PARM);
+                  if (ret < 0)
+                    {
+                      berr("ERROR: Section %d reloc %d: "
+                           "Relocation failed: %d\n", relidx, i, ret);
+                      lib_free(sym);
+                      lib_free(rels);
+                      lib_free(dyn);
+                      return ret;
+                    }
                 }
               else if (loadinfo->fdpic)
                 {
@@ -975,6 +1029,12 @@ 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.
+   */
+
+  ARCH_ELFDATA_TEARDOWN(loadinfo);
+
   lib_free(sym);
   lib_free(rels);
   lib_free(dyn);
diff --git a/libs/libc/machine/arm/armv7-m/arch_elf.c 
b/libs/libc/machine/arm/armv7-m/arch_elf.c
index f167b8c3daf..e0468c71ede 100644
--- a/libs/libc/machine/arm/armv7-m/arch_elf.c
+++ b/libs/libc/machine/arm/armv7-m/arch_elf.c
@@ -32,6 +32,7 @@
 #include <nuttx/debug.h>
 
 #include <nuttx/elf.h>
+#include <nuttx/fdpic.h>
 
 /****************************************************************************
  * Public Functions
@@ -126,7 +127,8 @@ int up_relocate(const Elf32_Rel *rel, const Elf32_Sym *sym, 
uintptr_t addr,
 
   relotype = ELF32_R_TYPE(rel->r_info);
   if (sym == NULL && relotype != R_ARM_NONE && relotype != R_ARM_V4BX &&
-      relotype != R_ARM_RELATIVE && relotype != R_ARM_JUMP_SLOT)
+      relotype != R_ARM_RELATIVE && relotype != R_ARM_JUMP_SLOT &&
+      relotype != R_ARM_GLOB_DAT)
     {
       return -EINVAL;
     }
@@ -175,6 +177,97 @@ int up_relocate(const Elf32_Rel *rel, const Elf32_Sym 
*sym, uintptr_t addr,
         }
         break;
 
+      case R_ARM_FUNCDESC_VALUE:
+        {
+          /* The target is a descriptor: entry point and data base.  The
+           * addend sits in the word that becomes the entry point and
+           * carries the Thumb bit, so it must be kept.  The base written is
+           * this object's own, which is what makes a callback work.
+           */
+
+          struct fdpic_desc_s *desc =
+            (struct fdpic_desc_s *)addr;
+          arch_elfdata_t *data = (arch_elfdata_t *)arch_data;
+
+          if (data == NULL)
+            {
+              berr("ERROR: FUNCDESC_VALUE without loader state\n");
+              return -EINVAL;
+            }
+
+          /* A descriptor only means anything in an FDPIC object.  An object
+           * that carries these relocations without saying it is FDPIC cannot
+           * be run: nothing would install its data base.
+           */
+
+          if (!data->fdpic)
+            {
+              berr("ERROR: FUNCDESC_VALUE in a non-FDPIC object\n");
+              return -ENOEXEC;
+            }
+
+          binfo("Performing FUNCDESC_VALUE link "
+                "at addr=%08" PRIxPTR " to sym=%p st_value=%08" PRIx32 "\n",
+                addr, sym, sym->st_value);
+
+          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;
+            }
+          else
+            {
+              desc->entry = sym->st_value + desc->entry;
+            }
+
+          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.
+           */
+
+          struct fdpic_desc_s *desc;
+          arch_elfdata_t *data = (arch_elfdata_t *)arch_data;
+
+          if (data == NULL)
+            {
+              berr("ERROR: FUNCDESC without loader state\n");
+              return -EINVAL;
+            }
+
+          if (!data->fdpic)
+            {
+              berr("ERROR: FUNCDESC in a non-FDPIC object\n");
+              return -ENOEXEC;
+            }
+
+          if (data->usedesc >= data->ndesc)
+            {
+              berr("ERROR: Out of function descriptors\n");
+              return -ENOMEM;
+            }
+
+          desc = data->descpool + data->usedesc++;
+
+          binfo("Performing FUNCDESC link "
+                "at addr=%08" PRIxPTR " to sym=%p st_value=%08" PRIx32 "\n",
+                addr, sym, sym->st_value);
+
+          desc->entry = sym->st_value + *(uint32_t *)addr;
+          desc->got   = data->gotbase;
+
+          *(uint32_t *)addr = (uint32_t)(uintptr_t)desc;
+        }
+        break;
+
       case R_ARM_ABS32:
       case R_ARM_TARGET1:  /* New ABI:  TARGET1 always treated as ABS32 */
         {
@@ -501,6 +594,7 @@ int up_relocate(const Elf32_Rel *rel, const Elf32_Sym *sym, 
uintptr_t addr,
         break;
 
       case R_ARM_RELATIVE:
+      case R_ARM_GLOB_DAT:
       case R_ARM_JUMP_SLOT:
         {
           *(uint32_t *)addr = (uint32_t)sym->st_value;
diff --git a/libs/libc/machine/arm/armv8-m/arch_elf.c 
b/libs/libc/machine/arm/armv8-m/arch_elf.c
index 27a6e673f22..a0e0ec998c3 100644
--- a/libs/libc/machine/arm/armv8-m/arch_elf.c
+++ b/libs/libc/machine/arm/armv8-m/arch_elf.c
@@ -32,6 +32,7 @@
 #include <nuttx/debug.h>
 
 #include <nuttx/elf.h>
+#include <nuttx/fdpic.h>
 
 /****************************************************************************
  * Public Functions
@@ -126,7 +127,8 @@ int up_relocate(const Elf32_Rel *rel, const Elf32_Sym *sym, 
uintptr_t addr,
 
   relotype = ELF32_R_TYPE(rel->r_info);
   if (sym == NULL && relotype != R_ARM_NONE && relotype != R_ARM_V4BX &&
-      relotype != R_ARM_RELATIVE && relotype != R_ARM_JUMP_SLOT)
+      relotype != R_ARM_RELATIVE && relotype != R_ARM_JUMP_SLOT &&
+      relotype != R_ARM_GLOB_DAT)
     {
       return -EINVAL;
     }
@@ -175,6 +177,97 @@ int up_relocate(const Elf32_Rel *rel, const Elf32_Sym 
*sym, uintptr_t addr,
         }
         break;
 
+      case R_ARM_FUNCDESC_VALUE:
+        {
+          /* The target is a descriptor: entry point and data base.  The
+           * addend sits in the word that becomes the entry point and
+           * carries the Thumb bit, so it must be kept.  The base written is
+           * this object's own, which is what makes a callback work.
+           */
+
+          struct fdpic_desc_s *desc =
+            (struct fdpic_desc_s *)addr;
+          arch_elfdata_t *data = (arch_elfdata_t *)arch_data;
+
+          if (data == NULL)
+            {
+              berr("ERROR: FUNCDESC_VALUE without loader state\n");
+              return -EINVAL;
+            }
+
+          /* A descriptor only means anything in an FDPIC object.  An object
+           * that carries these relocations without saying it is FDPIC cannot
+           * be run: nothing would install its data base.
+           */
+
+          if (!data->fdpic)
+            {
+              berr("ERROR: FUNCDESC_VALUE in a non-FDPIC object\n");
+              return -ENOEXEC;
+            }
+
+          binfo("Performing FUNCDESC_VALUE link "
+                "at addr=%08" PRIxPTR " to sym=%p st_value=%08" PRIx32 "\n",
+                addr, sym, sym->st_value);
+
+          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;
+            }
+          else
+            {
+              desc->entry = sym->st_value + desc->entry;
+            }
+
+          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.
+           */
+
+          struct fdpic_desc_s *desc;
+          arch_elfdata_t *data = (arch_elfdata_t *)arch_data;
+
+          if (data == NULL)
+            {
+              berr("ERROR: FUNCDESC without loader state\n");
+              return -EINVAL;
+            }
+
+          if (!data->fdpic)
+            {
+              berr("ERROR: FUNCDESC in a non-FDPIC object\n");
+              return -ENOEXEC;
+            }
+
+          if (data->usedesc >= data->ndesc)
+            {
+              berr("ERROR: Out of function descriptors\n");
+              return -ENOMEM;
+            }
+
+          desc = data->descpool + data->usedesc++;
+
+          binfo("Performing FUNCDESC link "
+                "at addr=%08" PRIxPTR " to sym=%p st_value=%08" PRIx32 "\n",
+                addr, sym, sym->st_value);
+
+          desc->entry = sym->st_value + *(uint32_t *)addr;
+          desc->got   = data->gotbase;
+
+          *(uint32_t *)addr = (uint32_t)(uintptr_t)desc;
+        }
+        break;
+
       case R_ARM_ABS32:
       case R_ARM_TARGET1:  /* New ABI:  TARGET1 always treated as ABS32 */
         {
@@ -501,6 +594,7 @@ int up_relocate(const Elf32_Rel *rel, const Elf32_Sym *sym, 
uintptr_t addr,
         break;
 
       case R_ARM_RELATIVE:
+      case R_ARM_GLOB_DAT:
       case R_ARM_JUMP_SLOT:
         {
           *(uint32_t *)addr = (uint32_t)sym->st_value;

Reply via email to