From ea78802f8d8b434150b04f298be630ea1a5f4075 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Thu, 30 Jul 2026 13:41:37 +0200 Subject: [PATCH] binfmt/fdpic: Add an FDPIC ELF module loader. Lets a NOMMU target execute downloadable modules in place from memory-mapped NOR flash, so a module's text and rodata never consume RAM. It is the consumer of xipfs: the loader maps a module's read-only segment with MAP_XIP_STRICT, which resolves to a direct flash pointer or fails with -ENXIO rather than falling back to a RAM copy, and pins the extent for as long as the module is loaded so the defragmenter cannot relocate code that is executing. Only the writable segment is copied to RAM, once per running instance. The loader follows DT_NEEDED so a module can use shared libraries, each object getting its own GOT and its own data. FDPIC is what makes this possible: text is position-independent and each LOAD segment is placed independently, with the text-to-data offset communicated at load time through function descriptors and the GOT. A descriptor is a pair -- entry pointer plus data base -- so a module function handed back to the firmware carries the data base it needs. r9 holds that base at runtime, per the ARM FDPIC ABI. Reserving r9 across the base firmware is what allows a firmware routine to call back into module code and still arrive with the module's data base intact. Toolchain.defs puts --fixed-r9 in ARCHCPUFLAGS rather than CFLAGS, because almost every board Make.defs assigns CFLAGS with ':=' after including it, which would discard the flag; ARCHCPUFLAGS is re-expanded by that same assignment and so survives it. The CONFIG_PIC --fixed-r10 case is skipped under FDPIC, since reserving both registers would cost one for nothing. The DT_NEEDED walk is depth capped. fdpic_loaddepends() recursed once per link of a dependency chain with a path buffer on each frame and nothing to stop it, so a malformed module set overflowed the stack of whichever task called the loader instead of being rejected. A dependency *cycle* was never the hazard -- an object joins the load's list before its own dependencies are walked, so a library naming something already loaded finds it there and stops -- what was unbounded is a chain of distinct names, which the list cannot bound, hence an explicit cap rather than cycle detection. A module's .rofixup section is skipped, and the file header records why. .rofixup is the FDPIC self-relocation list a static executable's crt0 walks to derive its own GOT when no loader is present. A module links -shared -nostartfiles, so no crt0 runs, and the built objects hold exactly one entry there -- the address of the GOT itself, which this loader computes and installs at every entry into module code anyway. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Marco Casaroli --- .codespellrc | 1 + arch/arm/include/armv7-m/irq.h | 9 +- arch/arm/include/armv8-m/irq.h | 9 +- arch/arm/include/elf.h | 18 + arch/arm/src/common/Toolchain.defs | 31 + binfmt/CMakeLists.txt | 4 + binfmt/Kconfig | 49 ++ binfmt/Makefile | 4 + binfmt/binfmt.h | 32 + binfmt/binfmt_initialize.c | 8 + binfmt/fdpic.c | 1218 ++++++++++++++++++++++++++++ include/nuttx/binfmt/fdpic.h | 198 +++++ libs/libc/stdlib/lib_bsearch.c | 11 + libs/libc/stdlib/lib_qsort.c | 39 +- 14 files changed, 1626 insertions(+), 5 deletions(-) create mode 100644 binfmt/fdpic.c create mode 100644 include/nuttx/binfmt/fdpic.h diff --git a/.codespellrc b/.codespellrc index 14770824d32..3a1f6d04a97 100644 --- a/.codespellrc +++ b/.codespellrc @@ -47,6 +47,7 @@ ignore-words-list = mot, mis, nexted, + nneeded, numer, nwe, oen, diff --git a/arch/arm/include/armv7-m/irq.h b/arch/arm/include/armv7-m/irq.h index 1f37bb6fbb5..69d63984d56 100644 --- a/arch/arm/include/armv7-m/irq.h +++ b/arch/arm/include/armv7-m/irq.h @@ -182,9 +182,16 @@ /* The PIC register is usually R10. It can be R9 is stack checking is enabled * or if the user changes it with -mpic-register on the GCC command line. + * + * The ARM FDPIC ABI mandates R9, and GCC hardcodes it for -mfdpic, so an + * FDPIC build has no choice in the matter. */ -#define REG_PIC REG_R10 +#ifdef CONFIG_FDPIC +# define REG_PIC REG_R9 +#else +# define REG_PIC REG_R10 +#endif /* CONTROL register */ diff --git a/arch/arm/include/armv8-m/irq.h b/arch/arm/include/armv8-m/irq.h index 3d4290eeb7f..938e4c57283 100644 --- a/arch/arm/include/armv8-m/irq.h +++ b/arch/arm/include/armv8-m/irq.h @@ -188,9 +188,16 @@ /* The PIC register is usually R10. It can be R9 is stack checking is enabled * or if the user changes it with -mpic-register on the GCC command line. + * + * The ARM FDPIC ABI mandates R9, and GCC hardcodes it for -mfdpic, so an + * FDPIC build has no choice in the matter. */ -#define REG_PIC REG_R10 +#ifdef CONFIG_FDPIC +# define REG_PIC REG_R9 +#else +# define REG_PIC REG_R10 +#endif /* CONTROL register */ diff --git a/arch/arm/include/elf.h b/arch/arm/include/elf.h index e5c2780472c..44e3550d391 100644 --- a/arch/arm/include/elf.h +++ b/arch/arm/include/elf.h @@ -206,6 +206,24 @@ #define R_ARM_THM_TLS_DESCSEQ16 129 /* Thumb16 */ #define R_ARM_THM_TLS_DESCSEQ32 130 /* Thumb32 */ +/* FDPIC relocations. + * + * Under the FDPIC ABI each PT_LOAD segment is placed independently, so a + * function pointer cannot be a bare code address: it has to carry the data + * base its callee will need. A "function descriptor" is that pair, and + * these relocations are how the loader is asked to build and reference + * them. Values are from the ARM FDPIC ABI as implemented by binutils + * (include/elf/arm.h). + */ + +#define R_ARM_GOTFUNCDESC 161 /* Data GOT entry holding a descriptor */ +#define R_ARM_GOTOFFFUNCDESC 162 /* Data GOT-relative descriptor */ +#define R_ARM_FUNCDESC 163 /* Data Address of a descriptor */ +#define R_ARM_FUNCDESC_VALUE 164 /* Data The descriptor itself: {code, GOT} */ +#define R_ARM_TLS_GD32_FDPIC 165 /* Data */ +#define R_ARM_TLS_LDM32_FDPIC 166 /* Data */ +#define R_ARM_TLS_IE32_FDPIC 167 /* Data */ + /* Processor specific values for the Phdr p_type field. */ #define PT_ARM_EXIDX (PT_LOPROC + 1) /* ARM unwind segment. */ diff --git a/arch/arm/src/common/Toolchain.defs b/arch/arm/src/common/Toolchain.defs index c535bde05aa..66d5147233e 100644 --- a/arch/arm/src/common/Toolchain.defs +++ b/arch/arm/src/common/Toolchain.defs @@ -612,11 +612,42 @@ CELFFLAGS = $(filter-out --fixed-r10,$(CFLAGS)) -fvisibility=hidden \ CXXELFFLAGS = $(filter-out --fixed-r10,$(CXXFLAGS)) -fvisibility=hidden \ -mlong-calls +ifeq ($(CONFIG_FDPIC),y) + # An FDPIC module reaches its own data through r9, and that register has + # to survive a call into the base firmware. A base firmware routine that + # calls back into module code -- qsort() with a module comparison + # function is the standard case -- must arrive there with the module's + # GOT still in r9, which it will not if the compiler was free to allocate + # r9 here. + # + # This goes in ARCHCPUFLAGS rather than CFLAGS because almost every board + # Make.defs assigns + # + # CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ... + # + # with ':=' after including this file, which discards anything added to + # CFLAGS here but re-expands ARCHCPUFLAGS. The failure is silent and the + # symptom is remote from the cause: the build succeeds and only a callback + # from firmware into module code misbehaves, reading its data through a + # register the firmware has since reused. + # + # CPICFLAGS derives from CFLAGS, so this reaches module compiles too. + # That is harmless: GCC only rejects --fixed-rN when it names the same + # register as -mpic-register, and PIC modules use r10. + + ARCHCPUFLAGS += --fixed-r9 +endif + ifeq ($(CONFIG_PIC),y) +ifneq ($(CONFIG_FDPIC),y) # ARCHCFLAGS, not CFLAGS: board Make.defs reassign CFLAGS with ':=' # after including this file, which would discard the flag. + # + # FDPIC reserves r9 above instead; reserving both would cost a register + # for nothing. ARCHCFLAGS += --fixed-r10 +endif CELFFLAGS += $(PICFLAGS) -mpic-register=r10 CXXELFFLAGS += $(PICFLAGS) -mpic-register=r10 diff --git a/binfmt/CMakeLists.txt b/binfmt/CMakeLists.txt index 1af4cd3042b..451eba1fb74 100644 --- a/binfmt/CMakeLists.txt +++ b/binfmt/CMakeLists.txt @@ -61,6 +61,10 @@ if(CONFIG_NXFLAT) list(APPEND SRCS nxflat.c) endif() +if(CONFIG_FDPIC) + list(APPEND SRCS fdpic.c) +endif() + # Builtin application interfaces if(CONFIG_BUILTIN) diff --git a/binfmt/Kconfig b/binfmt/Kconfig index 93844898da0..ec5aa6f1ad6 100644 --- a/binfmt/Kconfig +++ b/binfmt/Kconfig @@ -46,6 +46,55 @@ if NXFLAT source "binfmt/libnxflat/Kconfig" endif +config FDPIC + bool "Enable the FDPIC ELF Binary Format" + default n + select BINFMT_LOADABLE + select PIC + depends on ARCH_ARM + ---help--- + Enable support for FDPIC ELF modules. + + An FDPIC module has independently placeable read-only and writable + segments, so its text can be mapped and executed directly out of + flash while only the writable segment is copied to RAM -- once per + running instance. This requires a filesystem that can expose the + media, such as XIPFS or ROMFS, and an arm-uclinuxfdpiceabi + toolchain to build the modules. + + The base firmware must reserve the FDPIC register (r9 on ARM) or + callbacks from the base firmware into module code will fail. + +if FDPIC + +config FDPIC_LIBPATH + string "Directory searched for shared libraries" + default "/mnt/xipfs" + ---help--- + Where the loader looks for the objects a module names in its + DT_NEEDED entries. For execute-in-place this should be a + filesystem that can expose its media, so a library's code is + mapped rather than copied. + +config FDPIC_STACKSIZE + int "FDPIC module stack size" + default 2048 + ---help--- + Size of the stack given to a task created from an FDPIC module. + +config FDPIC_ALLOW_COPY + bool "Allow copying text to RAM when execute-in-place is impossible" + default n + ---help--- + By default a module whose text cannot be mapped directly onto the + media fails to load, because copying it to RAM silently forfeits + the memory saving that FDPIC was chosen for. + + Enable this during board bring-up to keep modules loading from a + filesystem that does not support execute-in-place. + +endif # FDPIC + config ELF bool "Enable the ELF Binary Format" default n diff --git a/binfmt/Makefile b/binfmt/Makefile index 6ebd09632f3..9c170c4cddb 100644 --- a/binfmt/Makefile +++ b/binfmt/Makefile @@ -58,6 +58,10 @@ ifeq ($(CONFIG_NXFLAT),y) CSRCS += nxflat.c endif +ifeq ($(CONFIG_FDPIC),y) +CSRCS += fdpic.c +endif + # Add configured binary modules include libnxflat/Make.defs diff --git a/binfmt/binfmt.h b/binfmt/binfmt.h index c362bf041c6..73fa5a36c42 100644 --- a/binfmt/binfmt.h +++ b/binfmt/binfmt.h @@ -327,6 +327,38 @@ int nxflat_initialize(void); void nxflat_uninitialize(void); #endif +#ifdef CONFIG_FDPIC +/**************************************************************************** + * Name: fdpic_initialize + * + * Description: + * FDPIC support is built unconditionally. However, in order to + * use this binary format, this function must be called during system + * initialization in order to register the FDPIC binary format. + * + * Returned Value: + * This is a NuttX internal function so it follows the convention that + * 0 (OK) is returned on success and a negated errno is returned on + * failure. + * + ****************************************************************************/ + +int fdpic_initialize(void); + +/**************************************************************************** + * Name: fdpic_uninitialize + * + * Description: + * Unregister the FDPIC binary loader + * + * Returned Value: + * None + * + ****************************************************************************/ + +void fdpic_uninitialize(void); +#endif + #undef EXTERN #if defined(__cplusplus) } diff --git a/binfmt/binfmt_initialize.c b/binfmt/binfmt_initialize.c index ef110e898ba..bad2ec08d52 100644 --- a/binfmt/binfmt_initialize.c +++ b/binfmt/binfmt_initialize.c @@ -74,6 +74,14 @@ void binfmt_initialize(void) } #endif +#ifdef CONFIG_FDPIC + ret = fdpic_initialize(); + if (ret < 0) + { + berr("ERROR: fdpic_initialize failed: %d\n", ret); + } +#endif + UNUSED(ret); } diff --git a/binfmt/fdpic.c b/binfmt/fdpic.c new file mode 100644 index 00000000000..97c8cbb942c --- /dev/null +++ b/binfmt/fdpic.c @@ -0,0 +1,1218 @@ +/**************************************************************************** + * binfmt/fdpic.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Loader for FDPIC ELF modules, for executing code in place out of flash. + * + * An FDPIC module has two loadable segments that may be placed completely + * independently of one another. That is the property this loader exists to + * exploit: the read-only segment is *mapped* rather than copied, so on a + * filesystem that can hand out a direct pointer to the media -- xipfs, or + * ROMFS -- a module's text and rodata are executed and read where they + * already sit in flash, and never occupy RAM. Only the writable segment is + * copied, once per running instance. + * + * Because the two segments are unrelated at run time, a function's address + * is not enough to call it: the callee also needs its data base. FDPIC + * therefore represents a function pointer as a *descriptor*, the pair + * {entry, GOT}, and the caller installs the GOT half into the FDPIC + * register before branching. Building those descriptors is most of what + * this loader does. + * + * The relocation set is small because the static link has already folded + * the GOT-relative work away. Only three kinds survive into the module: + * + * R_ARM_RELATIVE an address that needs its segment's base added + * R_ARM_FUNCDESC_VALUE a descriptor to fill in, usually for an import + * R_ARM_FUNCDESC a pointer to a descriptor the loader must supply + * + * A module's '.rofixup' section is deliberately ignored: it exists for a + * static executable's crt0, which a module never runs, and holds only the + * GOT self-pointer that this loader supplies from the register instead. + * + * The base firmware must be built with the FDPIC register reserved + * (-ffixed-r9 on ARM); a well behaved module is not enough, because a kernel + * routine calling back into module code has to arrive with the module's GOT + * still in the register. + * + * Documentation/components/fdpic.rst has the reasoning behind all of this in + * full, including why ignoring '.rofixup' is correct rather than tolerated. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_FDPIC + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifndef ELFOSABI_ARM_FDPIC +# define ELFOSABI_ARM_FDPIC 65 +#endif + +/* The Thumb bit lives in the low bit of a code address. Descriptors and + * entry points must keep it or the core faults on the branch. + */ + +#define FDPIC_THUMB_BIT 1 + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int fdpic_loadbinary(FAR struct binary_s *binp, + FAR const char *filename, + FAR const struct symtab_s *exports, + int nexports); +static int fdpic_unloadbinary(FAR struct binary_s *binp); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct binfmt_s g_fdpicbinfmt = +{ + NULL, /* next */ + fdpic_loadbinary, /* load */ + fdpic_unloadbinary, /* unload */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: fdpic_read + * + * Description: + * Read from the module file at an absolute offset. + * + ****************************************************************************/ + +static int fdpic_read(FAR struct fdpic_loadinfo_s *loadinfo, + FAR void *buffer, size_t nbytes, off_t offset) +{ + FAR uint8_t *dest = buffer; + ssize_t nread; + + if (file_seek(&loadinfo->file, offset, SEEK_SET) < 0) + { + return -EIO; + } + + while (nbytes > 0) + { + nread = file_read(&loadinfo->file, dest, nbytes); + if (nread < 0) + { + if (nread == -EINTR) + { + continue; + } + + return (int)nread; + } + + if (nread == 0) + { + return -ENODATA; + } + + dest += nread; + nbytes -= nread; + } + + return OK; +} + +/**************************************************************************** + * Name: fdpic_addr + * + * Description: + * Translate a link-time virtual address into the address the segment + * actually landed at. This is the whole of the FDPIC "load map": two + * segments, each with its own independent bias. + * + * Returned Value: + * The run-time address, or 0 if the address belongs to neither segment. + * + ****************************************************************************/ + +static uintptr_t fdpic_addr(FAR struct fdpic_loadinfo_s *loadinfo, + uintptr_t vaddr) +{ + if (vaddr >= loadinfo->textvaddr && + vaddr < loadinfo->textvaddr + loadinfo->textsize) + { + return loadinfo->textaddr + (vaddr - loadinfo->textvaddr); + } + + if (vaddr >= loadinfo->datavaddr && + vaddr < loadinfo->datavaddr + loadinfo->datamemsz) + { + return loadinfo->dataaddr + (vaddr - loadinfo->datavaddr); + } + + return 0; +} + +/**************************************************************************** + * Name: fdpic_verify + * + * Description: + * Check that this really is an FDPIC module for this machine. + * + * The FDPIC marker is the OS/ABI byte, not e_flags: a module's e_flags + * are an unremarkable EABI version, so testing them would reject every + * valid module. + * + ****************************************************************************/ + +static int fdpic_verify(FAR const Elf32_Ehdr *ehdr) +{ + if (memcmp(ehdr->e_ident, ELFMAG, EI_MAGIC_SIZE) != 0) + { + return -ENOEXEC; + } + + if (ehdr->e_ident[EI_CLASS] != ELFCLASS32 || + ehdr->e_ident[EI_DATA] != ELFDATA2LSB) + { + berr("ERROR: Not a little-endian 32-bit object\n"); + return -ENOEXEC; + } + + if (ehdr->e_ident[EI_OSABI] != ELFOSABI_ARM_FDPIC) + { + /* Not FDPIC. Quietly decline so another binfmt handler can try. */ + + return -ENOEXEC; + } + + if (ehdr->e_type != ET_DYN) + { + berr("ERROR: FDPIC module must be ET_DYN, got %u\n", ehdr->e_type); + return -ENOEXEC; + } + + if (ehdr->e_machine != EM_ARM) + { + berr("ERROR: Not an ARM module (e_machine=%u)\n", ehdr->e_machine); + return -ENOEXEC; + } + + return OK; +} + +/**************************************************************************** + * Name: fdpic_readdynamic + * + * Description: + * Pull the handful of dynamic tags the loader needs out of PT_DYNAMIC. + * + * This is read straight from the file rather than from a loaded segment, + * because it has to be known before the writable segment can be sized: + * the relocation count bounds how many descriptors might be requested. + * + ****************************************************************************/ + +static int fdpic_readdynamic(FAR struct fdpic_loadinfo_s *loadinfo, + FAR const Elf32_Phdr *phdr) +{ + FAR Elf32_Dyn *dyn; + size_t ndyn; + size_t i; + int ret; + + loadinfo->nneeded = 0; + + if (phdr->p_filesz == 0 || phdr->p_filesz > 4096) + { + return -ENOEXEC; + } + + dyn = kmm_malloc(phdr->p_filesz); + if (dyn == NULL) + { + return -ENOMEM; + } + + ret = fdpic_read(loadinfo, dyn, phdr->p_filesz, phdr->p_offset); + if (ret < 0) + { + kmm_free(dyn); + return ret; + } + + ndyn = phdr->p_filesz / sizeof(Elf32_Dyn); + + for (i = 0; i < ndyn && dyn[i].d_tag != DT_NULL; i++) + { + switch (dyn[i].d_tag) + { + case DT_REL: + loadinfo->relvaddr = dyn[i].d_un.d_ptr; + break; + + case DT_RELSZ: + loadinfo->relsize = dyn[i].d_un.d_val; + break; + + case DT_SYMTAB: + loadinfo->symtabvaddr = dyn[i].d_un.d_ptr; + break; + + case DT_STRTAB: + loadinfo->strtabvaddr = dyn[i].d_un.d_ptr; + break; + + case DT_PLTGOT: + loadinfo->gotaddr = dyn[i].d_un.d_ptr; + break; + + case DT_NEEDED: + /* An offset into DT_STRTAB. The string itself lives in the + * read-only segment, which is not mapped yet, so only the + * offset is recorded here. + */ + + if (loadinfo->nneeded < FDPIC_MAX_NEEDED) + { + loadinfo->needed[loadinfo->nneeded++] = dyn[i].d_un.d_val; + } + else + { + berr("ERROR: More than %d DT_NEEDED entries\n", + FDPIC_MAX_NEEDED); + kmm_free(dyn); + return -ENOEXEC; + } + break; + + default: + break; + } + } + + kmm_free(dyn); + + if (loadinfo->gotaddr == 0) + { + /* DT_PLTGOT is only emitted when the object has a PLT, which a leaf + * library calling nothing external does not. It still has a GOT -- + * its own data is reached through it, so the FDPIC register has to + * point at it -- and the linker places that GOT immediately after + * the dynamic section. + */ + + loadinfo->gotaddr = phdr->p_vaddr + phdr->p_memsz; + + binfo("fdpic: %s has no DT_PLTGOT, taking GOT at %08lx\n", + loadinfo->name, (unsigned long)loadinfo->gotaddr); + } + + return OK; +} + +/**************************************************************************** + * Name: fdpic_load + * + * Description: + * Place both segments: map the read-only one, copy the writable one. + * + ****************************************************************************/ + +static int fdpic_load(FAR struct fdpic_loadinfo_s *loadinfo) +{ + FAR Elf32_Phdr *phdrs; + FAR Elf32_Phdr *text = NULL; + FAR Elf32_Phdr *data = NULL; + FAR Elf32_Phdr *dynamic = NULL; + uintptr_t mapped = 0; + size_t phdrsize; + size_t i; + int ret; + + if (loadinfo->ehdr.e_phnum == 0 || + loadinfo->ehdr.e_phentsize != sizeof(Elf32_Phdr)) + { + return -ENOEXEC; + } + + phdrsize = (size_t)loadinfo->ehdr.e_phnum * sizeof(Elf32_Phdr); + phdrs = kmm_malloc(phdrsize); + if (phdrs == NULL) + { + return -ENOMEM; + } + + ret = fdpic_read(loadinfo, phdrs, phdrsize, loadinfo->ehdr.e_phoff); + if (ret < 0) + { + goto errout_with_phdrs; + } + + for (i = 0; i < loadinfo->ehdr.e_phnum; i++) + { + if (phdrs[i].p_type == PT_DYNAMIC) + { + dynamic = &phdrs[i]; + } + else if (phdrs[i].p_type == PT_LOAD) + { + if ((phdrs[i].p_flags & PF_W) != 0) + { + data = &phdrs[i]; + } + else + { + text = &phdrs[i]; + } + } + } + + if (text == NULL || data == NULL || dynamic == NULL) + { + berr("ERROR: Expected one RX and one RW PT_LOAD plus PT_DYNAMIC\n"); + ret = -ENOEXEC; + goto errout_with_phdrs; + } + + loadinfo->textvaddr = text->p_vaddr; + loadinfo->textsize = text->p_memsz; + loadinfo->datavaddr = data->p_vaddr; + loadinfo->datafilesz = data->p_filesz; + loadinfo->datamemsz = data->p_memsz; + + ret = fdpic_readdynamic(loadinfo, dynamic); + if (ret < 0) + { + goto errout_with_phdrs; + } + + /* Map the read-only segment. + * + * MAP_XIP_STRICT is the point of the exercise: it tells the filesystem + * to resolve the mapping onto the media or fail, rather than quietly + * copying the file into RAM. A silent copy would still work, which is + * exactly why it must be refused -- it would cost the RAM this loader + * exists to save, and nothing would report it. + */ + + ret = file_ioctl(&loadinfo->file, XIPFSIOC_PIN, + (unsigned long)(uintptr_t)&mapped); + if (ret >= 0) + { + /* The pin is held until unload, which happens when the spawned task + * exits. That is a different task from the one running the loader, + * so this deliberately does not go through mmap: an mm_map entry + * would be recorded against whoever called the loader and would + * never be released by the module's own exit. + */ + + loadinfo->textaddr = (uintptr_t)mapped + text->p_offset; + loadinfo->textmapped = true; + } +#ifdef CONFIG_FDPIC_ALLOW_COPY + else + { + /* Bring-up escape hatch: keep working on a filesystem that cannot + * expose its media, at the cost of the RAM saving. + */ + + fwarn("fdpic: cannot execute in place (%d), copying text to RAM\n", + ret); + + loadinfo->textaddr = (uintptr_t)kmm_malloc(text->p_memsz); + if (loadinfo->textaddr == 0) + { + ret = -ENOMEM; + goto errout_with_phdrs; + } + + ret = fdpic_read(loadinfo, (FAR void *)loadinfo->textaddr, + text->p_filesz, text->p_offset); + if (ret < 0) + { + goto errout_with_text; + } + } +#else + else + { + berr("ERROR: Module cannot be executed in place: %d\n", ret); + goto errout_with_phdrs; + } +#endif + + /* Allocate the writable segment, with room after it for any descriptors + * the relocations ask us to manufacture. Bounding that by the total + * relocation count costs a few bytes and avoids a second pass over the + * relocation table. + */ + + loadinfo->ndesc = loadinfo->relsize / sizeof(Elf32_Rel); + loadinfo->dataalloc = loadinfo->datamemsz + + (size_t)loadinfo->ndesc * + sizeof(struct fdpic_desc_s); + + loadinfo->dataaddr = (uintptr_t)kumm_zalloc(loadinfo->dataalloc); + if (loadinfo->dataaddr == 0) + { + ret = -ENOMEM; + goto errout_with_text; + } + + loadinfo->descpool = loadinfo->dataaddr + loadinfo->datamemsz; + + ret = fdpic_read(loadinfo, (FAR void *)loadinfo->dataaddr, + loadinfo->datafilesz, data->p_offset); + if (ret < 0) + { + goto errout_with_data; + } + + /* Everything past p_filesz is .bss, already zeroed by kumm_zalloc */ + + loadinfo->gotaddr = fdpic_addr(loadinfo, loadinfo->gotaddr); + loadinfo->entry = fdpic_addr(loadinfo, + loadinfo->ehdr.e_entry & ~FDPIC_THUMB_BIT); + + if (loadinfo->gotaddr == 0 || loadinfo->entry == 0) + { + berr("ERROR: GOT or entry point outside any segment\n"); + ret = -ENOEXEC; + goto errout_with_data; + } + + loadinfo->entry |= (loadinfo->ehdr.e_entry & FDPIC_THUMB_BIT); + + binfo("fdpic: text %08lx (%s) data %08lx got %08lx entry %08lx\n", + (unsigned long)loadinfo->textaddr, + loadinfo->textmapped ? "in place" : "copied", + (unsigned long)loadinfo->dataaddr, + (unsigned long)loadinfo->gotaddr, + (unsigned long)loadinfo->entry); + + kmm_free(phdrs); + return OK; + +errout_with_data: + kumm_free((FAR void *)loadinfo->dataaddr); + loadinfo->dataaddr = 0; + +errout_with_text: + if (loadinfo->textmapped) + { + file_ioctl(&loadinfo->file, XIPFSIOC_UNPIN, 0); + } + else if (loadinfo->textaddr != 0) + { + kmm_free((FAR void *)loadinfo->textaddr); + } + + loadinfo->textaddr = 0; + +errout_with_phdrs: + kmm_free(phdrs); + return ret; +} + +/**************************************************************************** + * Name: fdpic_release + * + * Description: + * Release every object of a load. Dropping a text pin is what lets a + * compacting filesystem move those blocks again, now that nothing is + * executing from them. + * + ****************************************************************************/ + +static void fdpic_release(FAR struct fdpic_loadinfo_s *head) +{ + FAR struct fdpic_loadinfo_s *obj; + FAR struct fdpic_loadinfo_s *next; + + for (obj = head; obj != NULL; obj = next) + { + next = obj->flink; + + if (obj->dataaddr != 0) + { + kumm_free((FAR void *)obj->dataaddr); + } + + if (obj->textmapped) + { + file_ioctl(&obj->file, XIPFSIOC_UNPIN, 0); + } + else if (obj->textaddr != 0) + { + kmm_free((FAR void *)obj->textaddr); + } + + file_close(&obj->file); + kmm_free(obj); + } +} + +/**************************************************************************** + * Name: fdpic_loadobject + * + * Description: + * Open one object, place its segments, and append it to the load's list. + * + ****************************************************************************/ + +static int fdpic_loadobject(FAR const char *path, FAR const char *name, + FAR struct fdpic_loadinfo_s **head, + FAR struct fdpic_loadinfo_s **out) +{ + FAR struct fdpic_loadinfo_s *obj; + FAR struct fdpic_loadinfo_s *tail; + int ret; + + obj = kmm_zalloc(sizeof(struct fdpic_loadinfo_s)); + if (obj == NULL) + { + return -ENOMEM; + } + + strlcpy(obj->name, name, sizeof(obj->name)); + + ret = file_open(&obj->file, path, O_RDONLY); + if (ret < 0) + { + /* Not an error worth shouting about: binfmt offers every candidate + * name to every loader, including builtin command names that are + * not files at all. + */ + + binfo("fdpic: cannot open %s: %d\n", path, ret); + kmm_free(obj); + return ret; + } + + ret = fdpic_read(obj, &obj->ehdr, sizeof(Elf32_Ehdr), 0); + if (ret >= 0) + { + ret = fdpic_verify(&obj->ehdr); + } + + if (ret >= 0) + { + ret = fdpic_load(obj); + } + + if (ret < 0) + { + file_close(&obj->file); + kmm_free(obj); + return ret; + } + + /* Append, so the module stays first and dependencies follow it */ + + if (*head == NULL) + { + *head = obj; + } + else + { + for (tail = *head; tail->flink != NULL; tail = tail->flink) + { + } + + tail->flink = obj; + } + + if (out != NULL) + { + *out = obj; + } + + return OK; +} + +/**************************************************************************** + * Name: fdpic_loaddepends + * + * Description: + * Load whatever this object names in DT_NEEDED. + * + * The names live in the string table inside the read-only segment, so + * this can only run once that segment is mapped. Anything already + * loaded for this module is reused rather than loaded twice. + * + * That reuse is also what makes a dependency cycle harmless: an object is + * on the list before its own dependencies are walked, so a library naming + * something already loaded finds it and stops. What the list cannot + * bound is a chain of distinct names, which is why the depth is capped + * explicitly. + * + ****************************************************************************/ + +static int fdpic_loaddepends(FAR struct fdpic_loadinfo_s *obj, + FAR struct fdpic_loadinfo_s **head, + int depth) +{ + FAR const char *strtab; + FAR struct fdpic_loadinfo_s *o; + FAR struct fdpic_loadinfo_s *dep; + char path[128]; + bool seen; + int ret; + int i; + + if (obj->nneeded == 0) + { + return OK; + } + + if (depth >= FDPIC_MAX_DEPTH) + { + berr("ERROR: %s exceeds the %d level DT_NEEDED depth limit\n", + obj->name, FDPIC_MAX_DEPTH); + return -ELOOP; + } + + strtab = (FAR const char *)fdpic_addr(obj, obj->strtabvaddr); + if (strtab == NULL) + { + return -ENOEXEC; + } + + for (i = 0; i < obj->nneeded; i++) + { + FAR const char *name = &strtab[obj->needed[i]]; + + seen = false; + for (o = *head; o != NULL; o = o->flink) + { + if (strcmp(o->name, name) == 0) + { + seen = true; + break; + } + } + + if (seen) + { + continue; + } + + snprintf(path, sizeof(path), "%s/%s", CONFIG_FDPIC_LIBPATH, name); + + binfo("fdpic: %s needs %s\n", obj->name, name); + + ret = fdpic_loadobject(path, name, head, &dep); + if (ret < 0) + { + berr("ERROR: Cannot load %s needed by %s: %d\n", + name, obj->name, ret); + return ret; + } + + /* Libraries may depend on libraries */ + + ret = fdpic_loaddepends(dep, head, depth + 1); + if (ret < 0) + { + return ret; + } + } + + return OK; +} + +/**************************************************************************** + * Name: fdpic_symvalue + * + * Description: + * Resolve one relocation's symbol. A symbol defined inside the module is + * translated through the load map; an undefined one is an import and is + * looked up in the symbol table the base firmware exports. + * + ****************************************************************************/ + +static int fdpic_symvalue(FAR struct fdpic_loadinfo_s *obj, + FAR struct fdpic_loadinfo_s *head, + uint32_t symidx, + FAR const struct symtab_s *exports, int nexports, + FAR uintptr_t *value, + FAR struct fdpic_loadinfo_s **owner) +{ + FAR const Elf32_Sym *symtab; + FAR const Elf32_Sym *sym; + FAR const char *strtab; + FAR const char *name; + FAR const struct symtab_s *found; + FAR struct fdpic_loadinfo_s *o; + + symtab = (FAR const Elf32_Sym *)fdpic_addr(obj, obj->symtabvaddr); + strtab = (FAR const char *)fdpic_addr(obj, obj->strtabvaddr); + + if (symtab == NULL || strtab == NULL) + { + return -ENOEXEC; + } + + sym = &symtab[symidx]; + name = &strtab[sym->st_name]; + + if (sym->st_shndx != SHN_UNDEF) + { + /* Defined right here */ + + *value = fdpic_addr(obj, sym->st_value & ~FDPIC_THUMB_BIT); + if (*value == 0) + { + berr("ERROR: '%s' outside any segment of %s\n", name, obj->name); + return -ENOEXEC; + } + + *value |= (sym->st_value & FDPIC_THUMB_BIT); + *owner = obj; + return OK; + } + + /* Undefined here. Try the other objects in this load before falling + * back to the firmware, so a module linked against a library binds to + * the library rather than to a same-named firmware symbol. + */ + + for (o = head; o != NULL; o = o->flink) + { + FAR const Elf32_Sym *osym; + FAR const Elf32_Sym *otab; + FAR const char *ostr; + uint32_t n; + uint32_t i; + + if (o == obj || o->symtabvaddr == 0) + { + continue; + } + + otab = (FAR const Elf32_Sym *)fdpic_addr(o, o->symtabvaddr); + ostr = (FAR const char *)fdpic_addr(o, o->strtabvaddr); + if (otab == NULL || ostr == NULL) + { + continue; + } + + /* The dynamic symbol table runs from its start to the string table, + * which the linker places immediately after it. + */ + + n = (o->strtabvaddr - o->symtabvaddr) / sizeof(Elf32_Sym); + + for (i = 0; i < n; i++) + { + osym = &otab[i]; + + if (osym->st_shndx == SHN_UNDEF || osym->st_name == 0) + { + continue; + } + + if (strcmp(&ostr[osym->st_name], name) != 0) + { + continue; + } + + *value = fdpic_addr(o, osym->st_value & ~FDPIC_THUMB_BIT); + if (*value == 0) + { + continue; + } + + *value |= (osym->st_value & FDPIC_THUMB_BIT); + *owner = o; + + binfo("fdpic: '%s' resolved in %s\n", name, o->name); + return OK; + } + } + + /* Imported from the base firmware */ + + if (exports == NULL) + { + berr("ERROR: '%s' imported but no symbol table was provided\n", name); + return -ENOENT; + } + + found = symtab_findbyname(exports, name, nexports); + if (found == NULL) + { + berr("ERROR: Imported symbol '%s' not found\n", name); + return -ENOENT; + } + + *value = (uintptr_t)found->sym_value; + *owner = NULL; /* Firmware: no GOT of its own */ + return OK; +} + +/**************************************************************************** + * Name: fdpic_bind + * + * Description: + * Apply the module's dynamic relocations. + * + ****************************************************************************/ + +static int fdpic_bind(FAR struct fdpic_loadinfo_s *loadinfo, + FAR struct fdpic_loadinfo_s *head, + FAR const struct symtab_s *exports, int nexports) +{ + FAR const Elf32_Rel *rels; + size_t nrels; + size_t i; + int ret; + + if (loadinfo->relsize == 0) + { + return OK; + } + + rels = (FAR const Elf32_Rel *)fdpic_addr(loadinfo, loadinfo->relvaddr); + if (rels == NULL) + { + berr("ERROR: Relocations outside any segment\n"); + return -ENOEXEC; + } + + nrels = loadinfo->relsize / sizeof(Elf32_Rel); + + for (i = 0; i < nrels; i++) + { + uint32_t type = ELF32_R_TYPE(rels[i].r_info); + uint32_t symidx = ELF32_R_SYM(rels[i].r_info); + FAR struct fdpic_loadinfo_s *owner = NULL; + FAR uint32_t *where; + uintptr_t value; + + where = (FAR uint32_t *)fdpic_addr(loadinfo, rels[i].r_offset); + if (where == NULL) + { + berr("ERROR: Relocation %zu targets %08lx, outside any segment\n", + i, (unsigned long)rels[i].r_offset); + return -ENOEXEC; + } + + /* Everything relocated must land in the writable segment. A module + * that asked us to patch its text could not be executed in place by + * a second instance, so refuse rather than silently give up sharing. + */ + + if ((uintptr_t)where < loadinfo->dataaddr || + (uintptr_t)where >= loadinfo->dataaddr + loadinfo->dataalloc) + { + berr("ERROR: Relocation %zu would write outside the RW segment\n", + i); + return -ENOEXEC; + } + + switch (type) + { + case R_ARM_NONE: + break; + + case R_ARM_RELATIVE: + { + /* REL format: the addend is the link-time address already + * sitting at the target. + */ + + uintptr_t addr = fdpic_addr(loadinfo, *where); + + if (addr == 0) + { + berr("ERROR: RELATIVE target %08lx outside any segment\n", + (unsigned long)*where); + return -ENOEXEC; + } + + *where = (uint32_t)addr; + } + break; + + case R_ARM_FUNCDESC_VALUE: + { + /* The target *is* the descriptor: two words, entry then the + * data base to install before branching. + * + * The GOT written here is this module's own, including for + * imported functions. That is deliberate and is what makes + * a callback work: when the base firmware's qsort() calls + * back into the module's comparison function, the module + * needs its own data base in the FDPIC register. + */ + + FAR struct fdpic_desc_s *desc = + (FAR struct fdpic_desc_s *)where; + uint32_t addend; + + ret = fdpic_symvalue(loadinfo, head, symidx, exports, + nexports, &value, &owner); + if (ret < 0) + { + return ret; + } + + /* REL format keeps the addend in place, in the word that is + * about to become the entry point. It matters: a static + * function is referenced through its *section* symbol, whose + * value is the section base, with the offset -- and the + * Thumb bit -- carried entirely by the addend. Dropping it + * yields an even address and the core faults trying to + * execute it as ARM code. + */ + + addend = desc->entry; + + desc->entry = value + addend; + + /* The GOT half must be the *defining* object's, not this + * one's. Calling into a shared library installs that + * library's data base, which is how it reaches its own + * globals -- and is the entire reason FDPIC exists. A + * firmware symbol has no GOT, so leave this object's in + * place: the firmware ignores the register, and anything it + * calls back into belongs to this object. + */ + + desc->got = (owner != NULL) ? owner->gotaddr + : loadinfo->gotaddr; + } + 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. + */ + + FAR struct fdpic_desc_s *desc; + + if (loadinfo->usedesc >= loadinfo->ndesc) + { + berr("ERROR: Out of function descriptors\n"); + return -ENOMEM; + } + + ret = fdpic_symvalue(loadinfo, head, symidx, exports, + nexports, &value, &owner); + if (ret < 0) + { + return ret; + } + + desc = (FAR struct fdpic_desc_s *)loadinfo->descpool + + loadinfo->usedesc++; + + desc->entry = value + *where; /* addend, as above */ + desc->got = (owner != NULL) ? owner->gotaddr + : loadinfo->gotaddr; + + *where = (uint32_t)(uintptr_t)desc; + } + break; + + default: + berr("ERROR: Unsupported relocation type %" PRIu32 "\n", type); + return -ENOSYS; + } + } + + binfo("fdpic: applied %zu relocations, %u descriptors created\n", + nrels, loadinfo->usedesc); + + return OK; +} + +/**************************************************************************** + * Name: fdpic_loadbinary + ****************************************************************************/ + +static int fdpic_loadbinary(FAR struct binary_s *binp, + FAR const char *filename, + FAR const struct symtab_s *exports, + int nexports) +{ + FAR struct fdpic_loadinfo_s *head = NULL; + FAR struct fdpic_loadinfo_s *main_obj = NULL; + FAR struct fdpic_loadinfo_s *obj; + FAR const char *base; + int ret; + + binfo("Loading FDPIC module: %s\n", filename); + + base = strrchr(filename, '/'); + base = (base != NULL) ? base + 1 : filename; + + ret = fdpic_loadobject(filename, base, &head, &main_obj); + if (ret < 0) + { + return ret; + } + + /* Pull in whatever it needs before binding, so that cross-object + * references have something to resolve against. + */ + + ret = fdpic_loaddepends(main_obj, &head, 0); + if (ret < 0) + { + goto errout; + } + + /* Bind every object, not just the module. A library has relocations of + * its own, and its imports resolve against the same set. + */ + + for (obj = head; obj != NULL; obj = obj->flink) + { + ret = fdpic_bind(obj, head, exports, nexports); + if (ret < 0) + { + berr("ERROR: Failed to bind %s: %d\n", obj->name, ret); + goto errout; + } + } + + binp->entrypt = (main_t)main_obj->entry; + binp->mapsize = 0; + binp->stacksize = CONFIG_FDPIC_STACKSIZE; + binp->unload = fdpic_unloadbinary; + +#ifdef CONFIG_PIC + /* The scheduler expects a reference counted container here, not a bare + * address: up_initial_state() installs dspace->region into the FDPIC + * register. Threads of the task share it via crefs, and + * sched_releasetcb() frees the container -- but not the region, which is + * this loader's to release. + * + * The module's own GOT goes in. A call into a library switches the + * register to that library's GOT via the descriptor, and switches back + * on return. + */ + + main_obj->dspace = kmm_malloc(sizeof(struct dspace_s)); + if (main_obj->dspace == NULL) + { + ret = -ENOMEM; + goto errout; + } + + main_obj->dspace->crefs = 1; + main_obj->dspace->region = (FAR uint8_t *)main_obj->gotaddr; + + binp->picbase = main_obj->dspace; +#endif + + binp->mapped = head; + return OK; + +errout: + fdpic_release(head); + return ret; +} + +/**************************************************************************** + * Name: fdpic_unloadbinary + * + * Description: + * Release the module. Unmapping the text drops the filesystem's pin on + * it, which is what allows a compacting filesystem to move those blocks + * again once no instance is executing from them. + * + ****************************************************************************/ + +static int fdpic_unloadbinary(FAR struct binary_s *binp) +{ + fdpic_release(binp->mapped); + binp->mapped = NULL; + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: fdpic_initialize + ****************************************************************************/ + +int fdpic_initialize(void) +{ + int ret; + + ret = register_binfmt(&g_fdpicbinfmt); + if (ret < 0) + { + berr("ERROR: Failed to register FDPIC binfmt: %d\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: fdpic_uninitialize + ****************************************************************************/ + +void fdpic_uninitialize(void) +{ + unregister_binfmt(&g_fdpicbinfmt); +} + +#endif /* CONFIG_FDPIC */ diff --git a/include/nuttx/binfmt/fdpic.h b/include/nuttx/binfmt/fdpic.h new file mode 100644 index 00000000000..207fae8ffaa --- /dev/null +++ b/include/nuttx/binfmt/fdpic.h @@ -0,0 +1,198 @@ +/**************************************************************************** + * include/nuttx/binfmt/fdpic.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_BINFMT_FDPIC_H +#define __INCLUDE_NUTTX_BINFMT_FDPIC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* A function descriptor is the pair that makes FDPIC work: a callee cannot + * find its own data from its code address alone, because the two segments + * are placed independently, so every function pointer carries the data base + * to install alongside the entry point. + */ + +#define FDPIC_DESC_WORDS 2 + +/* Shared libraries a single object may name in DT_NEEDED */ + +#define FDPIC_MAX_NEEDED 8 + +/* How deep a chain of libraries depending on libraries may go. + * + * A cycle cannot run away -- an object joins the load's list before its own + * dependencies are walked, so coming back around finds it already there -- + * but a chain of distinct names has nothing to stop it. The walk recurses + * once per link with a path buffer on each frame, so without a bound a + * malformed module set overflows the stack of whichever task called the + * loader rather than being rejected. + */ + +#define FDPIC_MAX_DEPTH 8 + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct fdpic_desc_s +{ + uintptr_t entry; /* Address of the code */ + uintptr_t got; /* Data/GOT base to install in the FDPIC register */ +}; + +/* One loaded object: the module itself, or a shared library it needs. + * + * A load produces a list of these -- the module first, then whatever its + * DT_NEEDED entries pulled in. Text is mapped and therefore shared between + * every instance; the writable segment is private to this load, so two + * tasks using the same library each get their own copy of its data, which + * is the behaviour a library with state has to have. + */ + +struct fdpic_loadinfo_s +{ + FAR struct fdpic_loadinfo_s *flink; /* Next object in this load */ + char name[32]; /* For diagnostics and DT_NEEDED */ + + struct file file; /* The module being loaded */ + Elf32_Ehdr ehdr; /* Copy of the ELF header */ + + /* Read-only segment. This is mapped, not copied: on a filesystem that + * can expose the media directly this pointer is the flash address and + * the text is never in RAM at all. + */ + + uintptr_t textaddr; /* Where the RX segment actually is */ + uintptr_t textvaddr; /* p_vaddr it was linked at */ + size_t textsize; + bool textmapped; /* True if it came from mmap */ + + /* Read-write segment. Always a private RAM copy, one per instance. */ + + uintptr_t dataaddr; /* Where the RW segment was placed */ + uintptr_t datavaddr; /* p_vaddr it was linked at */ + size_t datafilesz; /* Bytes to read from the file */ + size_t datamemsz; /* Including .bss */ + size_t dataalloc; /* Total allocation incl. descriptors */ + + uintptr_t gotaddr; /* Runtime address of the GOT */ + uintptr_t entry; /* Runtime entry point */ + + /* Reference counted container the scheduler installs into the FDPIC + * register on every context switch. Allocated here; freed by + * sched_releasetcb() once the last thread using it is gone. + */ + + FAR struct dspace_s *dspace; + + /* Pool used to satisfy R_ARM_FUNCDESC, which asks the loader to + * manufacture a descriptor and hand back its address. + */ + + uintptr_t descpool; + uint16_t ndesc; /* Capacity */ + uint16_t usedesc; /* Next free slot */ + + /* Dynamic information, all as link-time virtual addresses */ + + /* DT_NEEDED entries, as offsets into DT_STRTAB. Resolved to names once + * the read-only segment holding the string table is mapped. + */ + + uint32_t needed[FDPIC_MAX_NEEDED]; + uint8_t nneeded; + + uintptr_t relvaddr; + size_t relsize; + uintptr_t symtabvaddr; + uintptr_t strtabvaddr; +}; + +/**************************************************************************** + * Inline Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: fdpic_callback + * + * Description: + * Resolve a function pointer that arrived from a caller which may be an + * FDPIC module. + * + * This exists because the two sides disagree about what a function + * pointer *is*. Base firmware is not built FDPIC, so to it a function + * pointer is a code address and it simply branches to it. An FDPIC + * module passes the address of a two-word descriptor instead. A base + * firmware routine that takes a callback -- qsort() and bsearch() are the + * ones that matter -- therefore branches straight into the module's data + * segment and faults. + * + * The caller is identified by the FDPIC register: a module task runs with + * its GOT there, and a plain kernel task runs with zero, because + * up_initial_state() only installs a value when the task has a D-Space. + * + * Only the entry point is taken from the descriptor. The data base is + * already correct in the register: the base firmware is built with that + * register reserved, so the module's GOT survives the call in. + * + * Input Parameters: + * fn - The pointer as it was received. + * + * Returned Value: + * An address that can be branched to directly. + * + ****************************************************************************/ + +#if defined(CONFIG_FDPIC) && defined(__thumb__) +static inline FAR void *fdpic_callback(FAR void *fn) +{ + uintptr_t base; + + __asm__ __volatile__ ("mov %0, r9" : "=r"(base)); + + if (base != 0 && fn != NULL) + { + return (FAR void *)((FAR struct fdpic_desc_s *)fn)->entry; + } + + return fn; +} +#else +# define fdpic_callback(fn) (fn) +#endif + +#endif /* __INCLUDE_NUTTX_BINFMT_FDPIC_H */ diff --git a/libs/libc/stdlib/lib_bsearch.c b/libs/libc/stdlib/lib_bsearch.c index a4e3047bf90..141c16ef12a 100644 --- a/libs/libc/stdlib/lib_bsearch.c +++ b/libs/libc/stdlib/lib_bsearch.c @@ -37,6 +37,10 @@ ****************************************************************************/ #include + +#ifdef CONFIG_FDPIC +# include +#endif #include /**************************************************************************** @@ -114,6 +118,13 @@ FAR void *bsearch(FAR const void *key, FAR const void *base, size_t nel, DEBUGASSERT(base != NULL || nel == 0); DEBUGASSERT(compar != NULL); +#ifdef CONFIG_FDPIC + /* See qsort(): an FDPIC caller passes a descriptor, not a code address */ + + compar = (CODE int (*)(FAR const void *, FAR const void *)) + fdpic_callback((FAR void *)compar); +#endif + for (lim = nel, lower = (const char *)base; lim != 0; lim >>= 1) { middle = lower + (lim >> 1) * width; diff --git a/libs/libc/stdlib/lib_qsort.c b/libs/libc/stdlib/lib_qsort.c index 5646452388f..3965058bdf2 100644 --- a/libs/libc/stdlib/lib_qsort.c +++ b/libs/libc/stdlib/lib_qsort.c @@ -45,6 +45,10 @@ #include #include +#ifdef CONFIG_FDPIC +# include +#endif + /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ @@ -156,8 +160,9 @@ static inline FAR char *med3(FAR char *a, FAR char *b, FAR char *c, * ****************************************************************************/ -void qsort(FAR void *base, size_t nel, size_t width, - CODE int(*compar)(FAR const void *, FAR const void *)) +static void qsort_internal(FAR void *base, size_t nel, size_t width, + CODE int(*compar)(FAR const void *, + FAR const void *)) { FAR char *pa; FAR char *pb; @@ -277,7 +282,7 @@ loop: if ((r = pb - pa) > width) { - qsort(base, r / width, width, compar); + qsort_internal(base, r / width, width, compar); } if ((r = pd - pc) > width) @@ -289,3 +294,31 @@ loop: goto loop; } } + +/**************************************************************************** + * Name: qsort + * + * Description: + * Public entry point. Resolves the comparison function once and then + * hands an ordinary pointer to the implementation. + * + * The split matters: qsort_internal() recurses, and resolving on every + * entry would treat an already-resolved code address as a descriptor the + * second time round and branch somewhere meaningless. + * + ****************************************************************************/ + +void qsort(FAR void *base, size_t nel, size_t width, + CODE int(*compar)(FAR const void *, FAR const void *)) +{ +#ifdef CONFIG_FDPIC + /* An FDPIC module passes the address of a function descriptor, not a + * code address. + */ + + compar = (CODE int (*)(FAR const void *, FAR const void *)) + fdpic_callback((FAR void *)compar); +#endif + + qsort_internal(base, nel, width, compar); +}