diff --git a/arch/Kconfig b/arch/Kconfig index 95a74a3f63bbc..1590575654446 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -724,6 +724,13 @@ config ARCH_HAVE_ELF_EXECUTABLE bool default n +config ARCH_HAVE_ELF_FDPIC + bool + default n + ---help--- + The architecture has a PIC base register and the ELF relocations + that an FDPIC object uses. + config ARCH_HAVE_TRUSTZONE bool default n diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 5721c73b3c7bf..bcd25f223c028 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1096,6 +1096,7 @@ config ARCH_ARMV7M default n select ARCH_HAVE_CPUINFO select ARCH_HAVE_DEBUG + select ARCH_HAVE_ELF_FDPIC select ARCH_HAVE_PERF_EVENTS config ARCH_CORTEXM3 @@ -1245,6 +1246,7 @@ config ARCH_ARMV8M default n select ARCH_HAVE_CPUINFO select ARCH_HAVE_DEBUG + select ARCH_HAVE_ELF_FDPIC select ARCH_HAVE_PERF_EVENTS config ARCH_CORTEXM23 diff --git a/arch/arm/include/arch.h b/arch/arm/include/arch.h index 786f7e6cbd2ca..457057b489b4a 100644 --- a/arch/arm/include/arch.h +++ b/arch/arm/include/arch.h @@ -79,6 +79,45 @@ do { \ ); \ } while (0) +#ifdef CONFIG_FDPIC + +/**************************************************************************** + * Name: up_fdpic_invoke + * + * Description: + * Call a module entry point with the module data base in the PIC base + * register. Put the caller data base back after the call. + * + * Input Parameters: + * entry - The code address to enter. + * arg - The one word argument, passed in r0. + * got - The module data base to install. + * + ****************************************************************************/ + +static inline void up_fdpic_invoke(uintptr_t entry, uintptr_t arg, + uintptr_t got) +{ + register uintptr_t r0v __asm__ ("r0") = arg; + + /* arg is already in r0. r4 goes on the stack with the PIC register to + * keep the push aligned to 8 bytes. + */ + + __asm__ __volatile__ + ( + "push {r4, " PIC_REG_STRING "}\n" /* Save the caller's base */ + "mov " PIC_REG_STRING ", %[got]\n" /* Install the module's base */ + "blx %[entry]\n" /* Enter the module */ + "pop {r4, " PIC_REG_STRING "}\n" /* Restore the caller's base */ + : "+r" (r0v) + : [entry] "r" (entry), [got] "r" (got) + : "r1", "r2", "r3", "r12", "lr", "cc", "memory" + ); +} + +#endif /* CONFIG_FDPIC */ + #endif /* CONFIG_PIC */ #ifdef CONFIG_ARCH_ADDRENV diff --git a/arch/arm/include/elf.h b/arch/arm/include/elf.h index e5c2780472c18..29e72a1e8f946 100644 --- a/arch/arm/include/elf.h +++ b/arch/arm/include/elf.h @@ -206,6 +206,18 @@ #define R_ARM_THM_TLS_DESCSEQ16 129 /* Thumb16 */ #define R_ARM_THM_TLS_DESCSEQ32 130 /* Thumb32 */ +/* FDPIC relocations. Values 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/binfmt/Kconfig b/binfmt/Kconfig index 93844898da0d4..eb3e23bad3cf5 100644 --- a/binfmt/Kconfig +++ b/binfmt/Kconfig @@ -60,6 +60,38 @@ config ELF_STACKSIZE default DEFAULT_TASK_STACKSIZE ---help--- This is the default stack size that will be used when starting ELF binaries. + +config FDPIC + bool "FDPIC modules" + default n + select PIC + depends on ARCH_HAVE_ELF_FDPIC + ---help--- + Load ELF modules built for the FDPIC ABI. + + An FDPIC module places its read-only and writable segments + independently, so its text can be executed directly out of flash + while only the writable segment is copied to RAM, once per running + instance. A filesystem that can show its media, such as XIPFS or + ROMFS, gives that result. On any other filesystem the loader copies + the text to RAM, and the module runs but shares nothing. + + Building a module needs an arm-uclinuxfdpiceabi linker. The stock + arm-none-eabi compiler emits correct FDPIC objects for both C and + C++, so only the link needs it. + + What this adds over the position independent ELF support already + present is a function pointer that carries its own data base, as a + two word descriptor rather than a bare code address. That is what + lets a module be called back on a thread it did not create, such as + the work queue worker that runs a SIGEV_THREAD notification. + + Selecting this makes ten libc and sched entry points that can + accept a callback from a module resolve such a descriptor before + storing or branching to it. Each costs a register read and a + branch on a path that is not hot. + + FDPIC is specified only for ARM Thumb-2. endif endif diff --git a/include/elf.h b/include/elf.h index a3d6dc8f927fa..fdecf44c075c4 100644 --- a/include/elf.h +++ b/include/elf.h @@ -149,6 +149,7 @@ #define ELFOSABI_MODESTO 11 /* Novell Modesto. */ #define ELFOSABI_OPENBSD 12 /* OpenBSD. */ #define ELFOSABI_ARM_AEABI 64 /* ARM EABI */ +#define ELFOSABI_ARM_FDPIC 65 /* ARM FDPIC */ #define ELFOSABI_ARM 97 /* ARM */ #define ELFOSABI_STANDALONE 255 /* Standalone (embedded) application */ @@ -278,6 +279,12 @@ #define DT_TEXTREL 22 /* d_un=ignored */ #define DT_JMPREL 23 /* d_un=d_ptr */ #define DT_BINDNOW 24 /* d_un=ignored */ +#define DT_INIT_ARRAY 25 /* d_un=d_ptr */ +#define DT_FINI_ARRAY 26 /* d_un=d_ptr */ +#define DT_INIT_ARRAYSZ 27 /* d_un=d_val */ +#define DT_FINI_ARRAYSZ 28 /* d_un=d_val */ +#define DT_PREINIT_ARRAY 32 /* d_un=d_ptr */ +#define DT_PREINIT_ARRAYSZ 33 /* d_un=d_val */ #define DT_LOPROC 0x70000000 /* d_un=unspecified */ #define DT_HIPROC 0x7fffffff /* d_un= unspecified */ diff --git a/include/nuttx/fdpic.h b/include/nuttx/fdpic.h new file mode 100644 index 0000000000000..afd0ebd2d6975 --- /dev/null +++ b/include/nuttx/fdpic.h @@ -0,0 +1,129 @@ +/**************************************************************************** + * include/nuttx/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_FDPIC_H +#define __INCLUDE_NUTTX_FDPIC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#include +#include + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* A function descriptor: what a function pointer is under FDPIC. The + * firmware branches to a code address; a module passes one of these. + */ + +struct fdpic_desc_s +{ + uintptr_t entry; /* Address of the code */ + uintptr_t got; /* Data base to install before branching */ +}; + +/**************************************************************************** + * Inline Functions + ****************************************************************************/ + +#ifdef CONFIG_FDPIC + +/**************************************************************************** + * Name: fdpic_base + * + * Description: + * The data base of the calling context, from the PIC base register. + * Non-zero means the caller is an FDPIC module, zero means firmware. + * + ****************************************************************************/ + +static inline uintptr_t fdpic_base(void) +{ + uintptr_t base; + + up_getpicbase(&base); + return base; +} + +/**************************************************************************** + * Name: fdpic_callback + * + * Description: + * Resolve a function pointer from a caller that may be an FDPIC module. + * Only the entry point is taken: the data base is already in the register. + * + * Input Parameters: + * fn - The pointer as it was received. + * + * Returned Value: + * An address that can be branched to directly. + * + ****************************************************************************/ + +static inline FAR void *fdpic_callback(FAR void *fn) +{ + if (fn != NULL && fdpic_base() != 0) + { + return (FAR void *)((FAR struct fdpic_desc_s *)fn)->entry; + } + + return fn; +} + +/**************************************************************************** + * Name: fdpic_invoke + * + * Description: + * Call a resolved module entry point with the module data base in the PIC + * base register. For a callback that runs on a shared thread, which + * carries no module base. Elsewhere fdpic_callback() is enough. + * + * Input Parameters: + * entry - The code address to enter, already resolved from the descriptor. + * arg - The one word argument. + * got - The module data base to install. + * + ****************************************************************************/ + +static inline void fdpic_invoke(uintptr_t entry, uintptr_t arg, + uintptr_t got) +{ + up_fdpic_invoke(entry, arg, got); +} + +#else + +# define fdpic_base() (0) +# define fdpic_callback(fn) (fn) +# define fdpic_invoke(entry, arg, got) \ + ((void)(got), (((CODE void (*)(uintptr_t))(uintptr_t)(entry))(arg))) + +#endif /* CONFIG_FDPIC */ + +#endif /* __INCLUDE_NUTTX_FDPIC_H */ diff --git a/include/nuttx/lib/elf.h b/include/nuttx/lib/elf.h index bcb8a039c8090..a9a9918704a10 100644 --- a/include/nuttx/lib/elf.h +++ b/include/nuttx/lib/elf.h @@ -44,6 +44,15 @@ # define CONFIG_LIBC_ELF_MAXDEPEND 0 #endif +/* A compacting filesystem gives its media address with a pin that holds the + * blocks in place. The loader holds the pin through a file reference, + * because the unload runs on another task. Flat build only. + */ + +#if defined(CONFIG_FS_XIPFS) && defined(CONFIG_BUILD_FLAT) +# define HAVE_LIBC_ELF_PIN 1 +#endif + #ifndef CONFIG_LIBC_ELF_ALIGN_LOG2 # define CONFIG_LIBC_ELF_ALIGN_LOG2 2 #endif @@ -123,6 +132,7 @@ typedef CODE int (*mod_uninitializer_t)(FAR void *arg); * nexports - The number of symbols in the exported symbol table. */ +struct file; struct symtab_s; struct mod_info_s { @@ -252,6 +262,36 @@ struct mod_loadinfo_s * skip the copy. */ + /* FDPIC state. + * + * fdpic - True if e_ident[EI_OSABI] marked this an FDPIC object. + * textpin - True if a filesystem pin holds the read-only segment, which + * the loader gives back at unload. + */ + + bool fdpic; + bool textpin; + +#ifdef HAVE_LIBC_ELF_PIN + /* The file the pin is held through, handed to the module once it loads. */ + + FAR struct file *pinfile; +#endif + + /* The object's data base, from DT_PLTGOT. An FDPIC module runs with this + * in the PIC base register. + */ + + uintptr_t gotaddr; + + /* Pool of function descriptors behind the writable segment. Reserved + * when the segment is sized, and bounded by the relocation count. + */ + + uintptr_t descpool; + uint16_t ndesc; /* Capacity */ + uint16_t usedesc; /* Next free slot */ + /* Address environment. * * addrenv - This is the handle created by addrenv_allocate() that can be diff --git a/libs/libc/elf/elf.h b/libs/libc/elf/elf.h index 986a3fa5803a9..fcf3227cf112a 100644 --- a/libs/libc/elf/elf.h +++ b/libs/libc/elf/elf.h @@ -238,6 +238,34 @@ int libelf_reallocbuffer(FAR struct mod_loadinfo_s *loadinfo, int libelf_freebuffers(FAR struct mod_loadinfo_s *loadinfo); +/**************************************************************************** + * Name: libelf_addr + * + * Description: + * Translate a link-time address in a loaded object to the address it + * occupies now. An address below the data segment's link-time base + * belongs to text, anything at or above it to data. + * + * Input Parameters: + * loadinfo - Load state information + * vaddr - The link-time address to translate + * + * Returned Value: + * The run-time address. + * + ****************************************************************************/ + +static inline uintptr_t libelf_addr(FAR struct mod_loadinfo_s *loadinfo, + uintptr_t vaddr) +{ + if (loadinfo->datasec != 0 && vaddr >= loadinfo->datasec) + { + return loadinfo->datastart + (vaddr - loadinfo->datasec); + } + + return loadinfo->textalloc + vaddr; +} + #ifdef CONFIG_ARCH_ADDRENV /**************************************************************************** @@ -321,4 +349,26 @@ int libelf_addrenv_restore(FAR struct mod_loadinfo_s *loadinfo); void libelf_addrenv_free(FAR struct mod_loadinfo_s *loadinfo); #endif /* CONFIG_ARCH_ADDRENV */ + +#ifdef HAVE_LIBC_ELF_PIN + +/**************************************************************************** + * Name: libelf_pinrelease + * + * Description: + * Give back an XIP pin that the loader took, and the file that holds it. + * Does nothing if the loader took no pin. + * + * Input Parameters: + * pinfile - The held file. Cleared on return. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +void libelf_pinrelease(FAR struct file **pinfile); + +#endif + #endif /* __LIBS_LIBC_LIBC_ELF_LIBC_ELF_H */ diff --git a/libs/libc/elf/elf_bind.c b/libs/libc/elf/elf_bind.c index 34f3fddf79d14..9702ff9fdf399 100644 --- a/libs/libc/elf/elf_bind.c +++ b/libs/libc/elf/elf_bind.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "libc.h" @@ -637,7 +638,9 @@ static int libelf_relocateadd(FAR struct module_s *modp, static int libelf_relocatedyn(FAR struct module_s *modp, FAR struct mod_loadinfo_s *loadinfo, - int relidx) + int relidx, + FAR const struct symtab_s *exports, + int nexports) { FAR Elf_Shdr *shdr = &loadinfo->shdr[relidx]; FAR Elf_Shdr *symhdr; @@ -712,12 +715,61 @@ static int libelf_relocatedyn(FAR struct module_s *modp, case DT_PLTRELSZ: reldata.relsz[I_PLT] = dyn[i].d_un.d_val; break; + case DT_PLTGOT: + + /* The object's data base. Every function descriptor built + * for it names this base. + */ + + loadinfo->gotaddr = dyn[i].d_un.d_ptr; + break; + + /* The constructor and destructor tables. Section headers are + * optional, so the dynamic tags are the authoritative copy. + */ + + case DT_INIT_ARRAY: + loadinfo->initarr = libelf_addr(loadinfo, dyn[i].d_un.d_ptr); + break; + + case DT_INIT_ARRAYSZ: + loadinfo->ninit = dyn[i].d_un.d_val / sizeof(uintptr_t); + break; + + case DT_FINI_ARRAY: + loadinfo->finiarr = libelf_addr(loadinfo, dyn[i].d_un.d_ptr); + break; + + case DT_FINI_ARRAYSZ: + loadinfo->nfini = dyn[i].d_un.d_val / sizeof(uintptr_t); + break; + + case DT_PREINIT_ARRAY: + loadinfo->preiarr = libelf_addr(loadinfo, dyn[i].d_un.d_ptr); + break; + + case DT_PREINIT_ARRAYSZ: + loadinfo->nprei = dyn[i].d_un.d_val / sizeof(uintptr_t); + break; + case DT_PLTREL: if (dyn[i].d_un.d_val == DT_REL) { reldata.relentsz[I_PLT] = sizeof(Elf_Rel); reldata.relrela[I_PLT] = 0; } + else if (loadinfo->fdpic) + { + /* The ARM FDPIC ABI is REL throughout. RELA entries are + * longer, so walking them as REL reads the wrong place. + */ + + berr("ERROR: FDPIC object claims RELA PLT relocations\n"); + lib_free(sym); + lib_free(rels); + lib_free(dyn); + return -ENOEXEC; + } else { reldata.relentsz[I_PLT] = sizeof(Elf_Rela); @@ -815,56 +867,99 @@ static int libelf_relocatedyn(FAR struct module_s *modp, if (sym[idx_sym].st_shndx == SHN_UNDEF) { - FAR void *ep; - - ep = libelf_findglobal(modp, loadinfo, symhdr, - &sym[idx_sym]); - if ((ep == NULL) && (ELF_ST_BIND(sym[idx_sym].st_info) - != STB_WEAK)) - { - berr("ERROR: Unable to resolve addr of ext ref %s\n", - loadinfo->iobuffer); - ret = -EINVAL; - lib_free(sym); - lib_free(rels); - lib_free(dyn); - return ret; - } - - addr = rel->r_offset + loadinfo->textalloc; - - if (reldata.relrela[idx_rel] == 1) - { - addr += rela->r_addend; - } - - *(FAR uintptr_t *)addr = (uintptr_t)ep; + FAR void *ep; + + ep = libelf_findglobal(modp, loadinfo, symhdr, + &sym[idx_sym]); + + /* libelf_findglobal() searches only the registered + * symbols. A module from exec() has its own export + * table, and an FDPIC module imports its libc there. + */ + + if (ep == NULL && exports != NULL) + { + FAR const struct symtab_s *sm; + + sm = symtab_findbyname(exports, + (FAR char *) + loadinfo->iobuffer, + nexports); + if (sm != NULL) + { + ep = (FAR void *)sm->sym_value; + } + } + + if ((ep == NULL) && (ELF_ST_BIND(sym[idx_sym].st_info) + != STB_WEAK)) + { + berr("ERROR: Unable to resolve addr of ext ref %s\n", + loadinfo->iobuffer); + ret = -EINVAL; + lib_free(sym); + lib_free(rels); + lib_free(dyn); + return ret; + } + + addr = libelf_addr(loadinfo, rel->r_offset); + + if (reldata.relrela[idx_rel] == 1) + { + addr += rela->r_addend; + } + + *(FAR uintptr_t *)addr = (uintptr_t)ep; + } + else if (loadinfo->fdpic) + { + /* A relocation naming a symbol inside this object. A + * pointer to a static function is emitted against the + * section symbol, so the offset, Thumb bit included, is + * the addend and must not come from the patched word. + */ + + Elf_Sym defsym = sym[idx_sym]; + + defsym.st_value = libelf_addr(loadinfo, + sym[idx_sym].st_value); + + addr = libelf_addr(loadinfo, rel->r_offset); + + if (reldata.relrela[idx_rel] == 1) + { + addr += rela->r_addend; + } + + ret = up_relocate(rel, &defsym, addr, ARCH_ELFDATA_PARM); + if (ret < 0) + { + berr("ERROR: Section %d reloc %d: " + "Relocation failed: %d\n", relidx, i, ret); + lib_free(sym); + lib_free(rels); + lib_free(dyn); + return ret; + } } } else { Elf_Sym dynsym = - { - 0 - }; + { + 0 + }; - addr = rel->r_offset - loadinfo->datasec + loadinfo->datastart; + addr = libelf_addr(loadinfo, rel->r_offset); if (reldata.relrela[idx_rel] == 1) { addr += rela->r_addend; } - if ((*(FAR uint32_t *)addr) < loadinfo->datasec) - { - dynsym.st_value = *(FAR uint32_t *)addr + - loadinfo->textalloc; - } - else - { - dynsym.st_value = *(FAR uint32_t *)addr - - loadinfo->datasec + loadinfo->datastart; - } + dynsym.st_value = libelf_addr(loadinfo, + *(FAR uint32_t *)addr); ret = up_relocate(rel, &dynsym, addr, ARCH_ELFDATA_PARM); } @@ -951,6 +1046,7 @@ int libelf_bind(FAR struct module_s *modp, /* Get the index to the relocation section */ int infosec = loadinfo->shdr[i].sh_info; + if (infosec >= loadinfo->ehdr.e_shnum) { continue; @@ -962,29 +1058,27 @@ int libelf_bind(FAR struct module_s *modp, switch (loadinfo->shdr[i].sh_type) { case SHT_DYNAMIC: - ret = libelf_relocatedyn(modp, loadinfo, i); + ret = libelf_relocatedyn(modp, loadinfo, i, + exports, nexports); break; case SHT_DYNSYM: loadinfo->dsymtabidx = i; break; case SHT_INIT_ARRAY: - loadinfo->initarr = loadinfo->shdr[i].sh_addr - - loadinfo->datasec + - loadinfo->datastart; + loadinfo->initarr = libelf_addr(loadinfo, + loadinfo->shdr[i].sh_addr); loadinfo->ninit = loadinfo->shdr[i].sh_size / sizeof(uintptr_t); break; case SHT_FINI_ARRAY: - loadinfo->finiarr = loadinfo->shdr[i].sh_addr - - loadinfo->datasec + - loadinfo->datastart; + loadinfo->finiarr = libelf_addr(loadinfo, + loadinfo->shdr[i].sh_addr); loadinfo->nfini = loadinfo->shdr[i].sh_size / sizeof(uintptr_t); break; case SHT_PREINIT_ARRAY: - loadinfo->preiarr = loadinfo->shdr[i].sh_addr - - loadinfo->datasec + - loadinfo->datastart; + loadinfo->preiarr = libelf_addr(loadinfo, + loadinfo->shdr[i].sh_addr); loadinfo->nprei = loadinfo->shdr[i].sh_size / sizeof(uintptr_t); break; @@ -1085,6 +1179,7 @@ int libelf_bind(FAR struct module_s *modp, if (loadinfo->addrenv != NULL) { int status = libelf_addrenv_restore(loadinfo); + if (status < 0) { berr("ERROR: libelf_addrenv_restore() failed: %d\n", status); diff --git a/libs/libc/elf/elf_insert.c b/libs/libc/elf/elf_insert.c index 5b646115248bf..09d1cb2623d23 100644 --- a/libs/libc/elf/elf_insert.c +++ b/libs/libc/elf/elf_insert.c @@ -85,6 +85,7 @@ void libelf_dumploadinfo(FAR struct mod_loadinfo_s *loadinfo) for (i = 0; i < loadinfo->ehdr.e_shnum; i++) { FAR Elf_Shdr *shdr = &loadinfo->shdr[i]; + binfo("Sections %d:\n", i); # ifdef CONFIG_ARCH_USE_SEPARATED_SECTION if (loadinfo->ehdr.e_type == ET_REL) @@ -247,9 +248,21 @@ static int libelf_loadsymtab(FAR struct module_s *modp, if (sym[i].st_shndx != SHN_UNDEF && sym[i].st_shndx < loadinfo->ehdr.e_shnum) { - FAR Elf_Shdr *s = &loadinfo->shdr[sym[i].st_shndx]; + if (loadinfo->ehdr.e_type == ET_DYN) + { + /* A shared object's symbol value is already the full + * link-time address. It only needs translating onto where + * the object was placed. + */ + + sym[i].st_value = libelf_addr(loadinfo, sym[i].st_value); + } + else + { + FAR Elf_Shdr *s = &loadinfo->shdr[sym[i].st_shndx]; - sym[i].st_value = sym[i].st_value + s->sh_addr; + sym[i].st_value = sym[i].st_value + s->sh_addr; + } } } @@ -408,27 +421,27 @@ FAR void *libelf_insert(FAR const char *filename, FAR const char *modname) case ET_REL : case ET_DYN : - /* Process any preinit_array entries */ + /* Process any preinit_array entries */ - array = (FAR void (**)(void))loadinfo.preiarr; - for (i = 0; i < loadinfo.nprei; i++) - { - array[i](); - } + array = (FAR void (**)(void))loadinfo.preiarr; + for (i = 0; i < loadinfo.nprei; i++) + { + array[i](); + } - /* Process any init_array entries */ + /* Process any init_array entries */ - array = (FAR void (**)(void))loadinfo.initarr; - for (i = 0; i < loadinfo.ninit; i++) - { - array[i](); - } + array = (FAR void (**)(void))loadinfo.initarr; + for (i = 0; i < loadinfo.ninit; i++) + { + array[i](); + } - modp->initarr = loadinfo.initarr; - modp->ninit = loadinfo.ninit; - modp->finiarr = loadinfo.finiarr; - modp->nfini = loadinfo.nfini; - break; + modp->initarr = loadinfo.initarr; + modp->ninit = loadinfo.ninit; + modp->finiarr = loadinfo.finiarr; + modp->nfini = loadinfo.nfini; + break; } /* Add the new module entry to the registry */ diff --git a/libs/libc/elf/elf_load.c b/libs/libc/elf/elf_load.c index e01e2fd748982..3f62a287d46fe 100644 --- a/libs/libc/elf/elf_load.c +++ b/libs/libc/elf/elf_load.c @@ -40,6 +40,7 @@ #include #include +#include #include #include "libc.h" @@ -239,6 +240,46 @@ static void libelf_elfsize(FAR struct mod_loadinfo_s *loadinfo, bool alloc) } } + /* Reserve the descriptor pool. R_ARM_FUNCDESC asks the loader to + * manufacture a descriptor after the segment is placed, and the + * relocation count bounds how many. + */ + + if (loadinfo->fdpic) + { + size_t nrels = 0; + + for (i = 0; i < loadinfo->ehdr.e_shnum; i++) + { + FAR Elf_Shdr *shdr = &loadinfo->shdr[i]; + + if (shdr->sh_type == SHT_REL && shdr->sh_entsize != 0) + { + nrels += shdr->sh_size / shdr->sh_entsize; + } + } + + /* A library also publishes a descriptor per exported function, for + * dlsym(). The dynamic symbol table bounds how many. + */ + + for (i = 0; i < loadinfo->ehdr.e_shnum; i++) + { + FAR Elf_Shdr *shdr = &loadinfo->shdr[i]; + + if (shdr->sh_type == SHT_DYNSYM && shdr->sh_entsize != 0) + { + nrels += shdr->sh_size / shdr->sh_entsize; + } + } + + loadinfo->ndesc = nrels; + loadinfo->descpool = datasize; + datasize += nrels * 2 * sizeof(uintptr_t); + + binfo("fdpic: reserving %zu descriptors behind the data\n", nrels); + } + /* An ET_DYN object is sized from its program headers, which give no * section alignment. A word is enough. */ @@ -364,6 +405,13 @@ static inline int libelf_loadfile(FAR struct mod_loadinfo_s *loadinfo) { if (phdr->p_flags & PF_X) { + if (loadinfo->fdpic && loadinfo->xipbase != 0) + { + /* Mapped, not copied. */ + + continue; + } + ret = libelf_read(loadinfo, buffer_data_address(text), phdr->p_filesz, phdr->p_offset); @@ -371,6 +419,7 @@ static inline int libelf_loadfile(FAR struct mod_loadinfo_s *loadinfo) else { size_t bsssize = phdr->p_memsz - phdr->p_filesz; + ret = libelf_read(loadinfo, data, phdr->p_filesz, phdr->p_offset); memset(data + phdr->p_filesz, 0, bsssize); @@ -539,6 +588,107 @@ static inline int libelf_loadfile(FAR struct mod_loadinfo_s *loadinfo) return OK; } +/**************************************************************************** + * Name: libelf_xipacquire + * + * Description: + * Ask the filesystem for the address of this file on its media, so the + * read-only part of the object can run where it lies. Ask for a pin + * first: a compacting filesystem is not safe without one. Do not ask at + * all if this build cannot hold a pin. + * + * Returned Value: + * Zero if an address was obtained, a negated errno otherwise. Callers + * that can live without one may ignore the failure. + * + ****************************************************************************/ + +#ifdef HAVE_LIBC_ELF_PIN +static int libelf_pinhold(FAR struct mod_loadinfo_s *loadinfo) +{ + FAR struct file *filep; + int ret; + + /* The descriptor belongs to the task that called the loader, and the + * unload runs on another task. Hold the file instead. + */ + + loadinfo->pinfile = lib_zalloc(sizeof(struct file)); + if (loadinfo->pinfile == NULL) + { + return -ENOMEM; + } + + ret = file_get(loadinfo->filfd, &filep); + if (ret >= 0) + { + ret = file_dup2(filep, loadinfo->pinfile); + file_put(filep); + } + + if (ret < 0) + { + lib_free(loadinfo->pinfile); + loadinfo->pinfile = NULL; + } + + return ret; +} + +/**************************************************************************** + * Name: libelf_pinrelease + * + * Description: + * Give back an XIP pin and the file it was held through, so the + * filesystem can reclaim the extent. + * + ****************************************************************************/ + +void libelf_pinrelease(FAR struct file **pinfile) +{ + if (*pinfile != NULL) + { + file_ioctl(*pinfile, XIPFSIOC_UNPIN, 0); + file_close(*pinfile); + lib_free(*pinfile); + *pinfile = NULL; + } +} +#endif + +static int libelf_xipacquire(FAR struct mod_loadinfo_s *loadinfo) +{ + uintptr_t base = 0; + +#ifdef HAVE_LIBC_ELF_PIN + if (ioctl(loadinfo->filfd, XIPFSIOC_PIN, (unsigned long)&base) >= 0) + { + int ret = libelf_pinhold(loadinfo); + + if (ret < 0) + { + berr("ERROR: Failed to hold the pinned file: %d\n", ret); + ioctl(loadinfo->filfd, XIPFSIOC_UNPIN, 0); + return ret; + } + + loadinfo->xipbase = base; + loadinfo->textpin = true; + binfo("pinned xipbase %zx\n", (size_t)loadinfo->xipbase); + return OK; + } +#endif + + if (ioctl(loadinfo->filfd, FIOC_XIPBASE, (unsigned long)&base) >= 0) + { + loadinfo->xipbase = base; + binfo("can use xipbase %zx\n", (size_t)loadinfo->xipbase); + return OK; + } + + return -ENOTTY; +} + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -559,6 +709,7 @@ static inline int libelf_loadfile(FAR struct mod_loadinfo_s *loadinfo) int libelf_load(FAR struct mod_loadinfo_s *loadinfo) { int ret; + int i; binfo("loadinfo: %p\n", loadinfo); DEBUGASSERT(loadinfo && loadinfo->filfd >= 0); @@ -573,14 +724,10 @@ int libelf_load(FAR struct mod_loadinfo_s *loadinfo) } loadinfo->gotindex = libelf_findsection(loadinfo, ".got"); - if (loadinfo->gotindex >= 0) + if (loadinfo->gotindex >= 0 || loadinfo->fdpic) { binfo("GOT section found! index %d\n", loadinfo->gotindex); - if (ioctl(loadinfo->filfd, FIOC_XIPBASE, - (unsigned long)&loadinfo->xipbase) >= 0) - { - binfo("can use xipbase %zu\n", loadinfo->xipbase); - } + libelf_xipacquire(loadinfo); } /* Determine total size to allocate */ @@ -647,21 +794,102 @@ int libelf_load(FAR struct mod_loadinfo_s *loadinfo) } else if (loadinfo->ehdr.e_type == ET_DYN) { - loadinfo->textalloc = (uintptr_t)lib_memalign(loadinfo->textalign, - loadinfo->textsize + - loadinfo->datasize + - loadinfo->segpad); - - if (!loadinfo->textalloc) + if (loadinfo->fdpic) { - berr("ERROR: Failed to allocate memory for the module\n"); - ret = -ENOMEM; - goto errout_with_buffers; + /* The two segments are placed independently, thus only the + * writable segment is allocated, once per instance. + */ + + if (loadinfo->xipbase != 0) + { + /* The text stays on the media. The media address is the base + * of the file, thus add the file offset of the segment. + */ + + for (i = 0; i < loadinfo->ehdr.e_phnum; i++) + { + FAR Elf_Phdr *phdr = &loadinfo->phdr[i]; + + if (phdr->p_type == PT_LOAD && + (phdr->p_flags & PF_X) != 0) + { + loadinfo->textalloc = loadinfo->xipbase + + phdr->p_offset; + break; + } + } + } + else if (loadinfo->textsize > 0) + { + /* The filesystem cannot show its media, thus copy the text + * to RAM. The instances no longer share it. + */ + +# if defined(CONFIG_ARCH_USE_TEXT_HEAP) && \ + defined(CONFIG_ARCH_USE_SEPARATED_SECTION) + loadinfo->textalloc = (uintptr_t) + up_textheap_memalign(".text", + loadinfo->textalign, + loadinfo->textsize); +# elif defined(CONFIG_ARCH_USE_TEXT_HEAP) + loadinfo->textalloc = (uintptr_t) + up_textheap_memalign(loadinfo->textalign, + loadinfo->textsize); +# else + loadinfo->textalloc = (uintptr_t) + lib_memalign(loadinfo->textalign, + loadinfo->textsize); +# endif + if (loadinfo->textalloc == 0) + { + berr("ERROR: Failed to allocate the module's text\n"); + ret = -ENOMEM; + goto errout_with_buffers; + } + } + + if (loadinfo->datasize > 0) + { + loadinfo->datastart = + (uintptr_t)lib_memalign(loadinfo->dataalign, + loadinfo->datasize); + if (!loadinfo->datastart) + { + berr("ERROR: Failed to allocate the module's data\n"); + ret = -ENOMEM; + goto errout_with_buffers; + } + } + + /* The pool was sized as an offset past the end of the real + * data; now that the segment has an address, make it one. + */ + + loadinfo->descpool += loadinfo->datastart; } + else + { + /* Everything else keeps text and data adjacent: one allocation, + * data behind text. + */ + + loadinfo->textalloc = (uintptr_t) + lib_memalign(loadinfo->textalign, + loadinfo->textsize + + loadinfo->datasize + + loadinfo->segpad); + + if (!loadinfo->textalloc) + { + berr("ERROR: Failed to allocate memory for the module\n"); + ret = -ENOMEM; + goto errout_with_buffers; + } - loadinfo->datastart = loadinfo->textalloc + - loadinfo->textsize + - loadinfo->segpad; + loadinfo->datastart = loadinfo->textalloc + + loadinfo->textsize + + loadinfo->segpad; + } } #endif /* CONFIG_LIBC_ELF_LOADTO_LMA */ @@ -729,14 +957,10 @@ int libelf_load_with_addrenv(FAR struct mod_loadinfo_s *loadinfo) } loadinfo->gotindex = libelf_findsection(loadinfo, ".got"); - if (loadinfo->gotindex >= 0) + if (loadinfo->gotindex >= 0 || loadinfo->fdpic) { binfo("GOT section found! index %d\n", loadinfo->gotindex); - if (ioctl(loadinfo->filfd, FIOC_XIPBASE, - (unsigned long)&loadinfo->xipbase) >= 0) - { - binfo("can use xipbase %zu\n", loadinfo->xipbase); - } + libelf_xipacquire(loadinfo); } /* Determine total size to allocate */ diff --git a/libs/libc/elf/elf_loadhdrs.c b/libs/libc/elf/elf_loadhdrs.c index 6e3e3afb0410d..21fbfe1a7629e 100644 --- a/libs/libc/elf/elf_loadhdrs.c +++ b/libs/libc/elf/elf_loadhdrs.c @@ -66,6 +66,21 @@ int libelf_loadhdrs(FAR struct mod_loadinfo_s *loadinfo) /* Verify that there are sections */ + /* An FDPIC object announces itself in the OS/ABI byte. Note it once. */ + + loadinfo->fdpic = (loadinfo->ehdr.e_ident[EI_OSABI] == ELFOSABI_ARM_FDPIC); + + /* A module is a shared object. An FDPIC object that is anything else + * would be placed through the wrong path. + */ + + if (loadinfo->fdpic && loadinfo->ehdr.e_type != ET_DYN) + { + berr("ERROR: FDPIC object is not a shared object: e_type=%u\n", + loadinfo->ehdr.e_type); + return -ENOEXEC; + } + if (loadinfo->ehdr.e_shnum < 1) { berr("ERROR: No sections(?)\n"); diff --git a/libs/libc/elf/elf_symbols.c b/libs/libc/elf/elf_symbols.c index 39ad66f085853..55725af775ad3 100644 --- a/libs/libc/elf/elf_symbols.c +++ b/libs/libc/elf/elf_symbols.c @@ -34,6 +34,7 @@ #include #include +#include #include #include "libc.h" @@ -107,7 +108,11 @@ static int libelf_symname(FAR struct mod_loadinfo_s *loadinfo, if (sym->st_name == 0) { - berr("ERROR: Symbol has no name\n"); + /* Not a failure. A section symbol has no name, and + * libelf_findsymbol() meets these routinely and checks for -ESRCH. + */ + + binfo("Symbol has no name\n"); return -ESRCH; } @@ -215,6 +220,7 @@ static int libelf_symcallback(FAR struct module_s *modp, FAR void *arg) #if CONFIG_LIBC_ELF_MAXDEPEND > 0 int ret = libelf_depend(exportinfo->modp, modp); + if (ret < 0) { berr("ERROR: libelf_depend failed: %d\n", ret); @@ -349,108 +355,108 @@ int libelf_symvalue(FAR struct module_s *modp, switch (sym->st_shndx) { - case SHN_COMMON: - { - /* NuttX ELF modules should be compiled with -fno-common. */ - - berr("ERROR: SHN_COMMON: Re-compile with -fno-common\n"); - return -ENOSYS; - } - - case SHN_ABS: - { - /* st_value already holds the correct value */ - - binfo("SHN_ABS: st_value=%08lx\n", (long)sym->st_value); - return OK; - } - - case SHN_UNDEF: - { - /* Get the name of the undefined symbol */ - - ret = libelf_symname(loadinfo, sym, sh_offset); - if (ret < 0) - { - /* There are a few relocations for a few architectures that do - * no depend upon a named symbol. We don't know if that is the - * case here, but return and special error to the caller to - * indicate the nameless symbol. - */ - - berr("ERROR: SHN_UNDEF: Failed to get symbol name: %d\n", ret); - return ret; - } - - /* First check if the symbol is exported by an installed module. - * Newest modules are installed at the head of the list. Therefore, - * if the symbol is exported by numerous modules, then the most - * recently installed will take precedence. - */ - - exportinfo.name = (FAR const char *)loadinfo->iobuffer; - exportinfo.modp = modp; - exportinfo.symbol = NULL; - - ret = libelf_registry_foreach(libelf_symcallback, - (FAR void *)&exportinfo); - if (ret < 0) - { - berr("ERROR: libelf_symcallback failed: %d\n", ret); - return ret; - } - - symbol = exportinfo.symbol; - - /* If the symbol is not exported by any module, then check if the - * base code exports a symbol of this name. - */ - - if (symbol == NULL) - { - symbol = symtab_findbyname(exports, exportinfo.name, - nexports); - } - - /* Was the symbol found from any exporter? */ - - if (symbol == NULL) - { - berr("ERROR: SHN_UNDEF: Exported symbol \"%s\" not found\n", - loadinfo->iobuffer); - return -ENOENT; - } - - /* Yes... add the exported symbol value to the ELF symbol tablei - * entry - */ - - binfo("SHN_UNDEF: name=%s " - "%08" PRIxPTR "+%08" PRIxPTR "=%08" PRIxPTR "\n", - loadinfo->iobuffer, - (uintptr_t)sym->st_value, (uintptr_t)symbol->sym_value, - (uintptr_t)(sym->st_value + (uintptr_t)symbol->sym_value)); - - sym->st_value += ((uintptr_t)symbol->sym_value); - } - break; - - default: - { - secbase = loadinfo->shdr[sym->st_shndx].sh_addr; - - binfo("Other[%d]: %08" PRIxPTR "+%08" PRIxPTR "=%08" PRIxPTR "\n", - sym->st_shndx, - (uintptr_t)sym->st_value, secbase, - (uintptr_t)(sym->st_value + secbase)); - - sym->st_value += secbase; - if (loadinfo->gotindex >= 0) - { - sym->st_value -= loadinfo->shdr[sym->st_shndx].sh_offset; - } - } - break; + case SHN_COMMON: + { + /* NuttX ELF modules should be compiled with -fno-common. */ + + berr("ERROR: SHN_COMMON: Re-compile with -fno-common\n"); + return -ENOSYS; + } + + case SHN_ABS: + { + /* st_value already holds the correct value */ + + binfo("SHN_ABS: st_value=%08lx\n", (long)sym->st_value); + return OK; + } + + case SHN_UNDEF: + { + /* Get the name of the undefined symbol */ + + ret = libelf_symname(loadinfo, sym, sh_offset); + if (ret < 0) + { + /* There are a few relocations for a few architectures that do + * no depend upon a named symbol. We don't know if that is the + * case here, but return and special error to the caller to + * indicate the nameless symbol. + */ + + berr("ERROR: SHN_UNDEF: Failed to get symbol name: %d\n", ret); + return ret; + } + + /* First check if the symbol is exported by an installed module. + * Newest modules are installed at the head of the list. So if + * the symbol is exported by numerous modules, then the most + * recently installed will take precedence. + */ + + exportinfo.name = (FAR const char *)loadinfo->iobuffer; + exportinfo.modp = modp; + exportinfo.symbol = NULL; + + ret = libelf_registry_foreach(libelf_symcallback, + (FAR void *)&exportinfo); + if (ret < 0) + { + berr("ERROR: libelf_symcallback failed: %d\n", ret); + return ret; + } + + symbol = exportinfo.symbol; + + /* If the symbol is not exported by any module, then check if the + * base code exports a symbol of this name. + */ + + if (symbol == NULL) + { + symbol = symtab_findbyname(exports, exportinfo.name, + nexports); + } + + /* Was the symbol found from any exporter? */ + + if (symbol == NULL) + { + berr("ERROR: SHN_UNDEF: Exported symbol \"%s\" not found\n", + loadinfo->iobuffer); + return -ENOENT; + } + + /* Yes... add the exported symbol value to the ELF symbol tablei + * entry + */ + + binfo("SHN_UNDEF: name=%s " + "%08" PRIxPTR "+%08" PRIxPTR "=%08" PRIxPTR "\n", + loadinfo->iobuffer, + (uintptr_t)sym->st_value, (uintptr_t)symbol->sym_value, + (uintptr_t)(sym->st_value + (uintptr_t)symbol->sym_value)); + + sym->st_value += ((uintptr_t)symbol->sym_value); + } + break; + + default: + { + secbase = loadinfo->shdr[sym->st_shndx].sh_addr; + + binfo("Other[%d]: %08" PRIxPTR "+%08" PRIxPTR "=%08" PRIxPTR "\n", + sym->st_shndx, + (uintptr_t)sym->st_value, secbase, + (uintptr_t)(sym->st_value + secbase)); + + sym->st_value += secbase; + if (loadinfo->gotindex >= 0) + { + sym->st_value -= loadinfo->shdr[sym->st_shndx].sh_offset; + } + } + break; } return OK; @@ -538,6 +544,26 @@ int libelf_insertsymtab(FAR struct module_s *modp, strdup((FAR char *)loadinfo->iobuffer); symbol[j].sym_value = (FAR const void *)(uintptr_t)sym[i].st_value; + + /* Publish an FDPIC function as a descriptor, so dlsym() + * hands back something callable. Only here does st_info + * still say what is a function. + */ + + if (loadinfo->fdpic && + ELF_ST_TYPE(sym[i].st_info) == STT_FUNC && + loadinfo->usedesc < loadinfo->ndesc) + { + FAR struct fdpic_desc_s *desc = + (FAR struct fdpic_desc_s *)loadinfo->descpool + + loadinfo->usedesc++; + + desc->entry = sym[i].st_value; + desc->got = loadinfo->gotaddr; + + symbol[j].sym_value = (FAR const void *)desc; + } + j++; } } @@ -572,6 +598,7 @@ static int findep(FAR const void *c1, FAR const void *c2) { FAR const struct eptable_s *m1 = (FAR const struct eptable_s *)c1; FAR const struct eptable_s *m2 = (FAR const struct eptable_s *)c2; + return strcmp((FAR const char *)m1->epname, (FAR const char *)m2->epname); } diff --git a/libs/libc/elf/elf_unload.c b/libs/libc/elf/elf_unload.c index c1c7609bf2f6c..63111cd15b3be 100644 --- a/libs/libc/elf/elf_unload.c +++ b/libs/libc/elf/elf_unload.c @@ -59,6 +59,18 @@ int libelf_unload(FAR struct mod_loadinfo_s *loadinfo) libelf_freebuffers(loadinfo); +#ifdef HAVE_LIBC_ELF_PIN + /* Give the pin back if the loader took one, so the filesystem can + * reclaim the extent. + */ + + if (loadinfo->textpin) + { + libelf_pinrelease(&loadinfo->pinfile); + loadinfo->textpin = false; + } +#endif + #ifdef CONFIG_ARCH_ADDRENV if (loadinfo->addrenv != NULL) { @@ -68,9 +80,35 @@ int libelf_unload(FAR struct mod_loadinfo_s *loadinfo) #endif /* Release memory holding the relocated ELF image */ - /* ET_DYN has a single allocation so we only free textalloc */ + /* An FDPIC object placed its two segments separately. Free each one. If + * the text stayed on the media, it was never allocated, thus leave it. + */ + + if (loadinfo->fdpic) + { + if (loadinfo->textalloc != 0 && loadinfo->xipbase == 0) + { +#ifdef CONFIG_ARCH_USE_TEXT_HEAP + up_textheap_free((FAR void *)loadinfo->textalloc); +#else + lib_free((FAR void *)loadinfo->textalloc); +#endif + } + + if (loadinfo->datastart != 0) + { + lib_free((FAR void *)loadinfo->datastart); + loadinfo->datastart = 0; + } + + loadinfo->textalloc = 0; + loadinfo->textsize = 0; + loadinfo->datasize = 0; + } + + /* Any other ET_DYN has a single allocation so we only free textalloc */ - if (loadinfo->ehdr.e_type != ET_DYN) + else if (loadinfo->ehdr.e_type != ET_DYN) { #ifdef CONFIG_ARCH_USE_SEPARATED_SECTION int i;