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