diff --git a/arch/arm/include/elf.h b/arch/arm/include/elf.h index e5c2780472c..460cb34ad4e 100644 --- a/arch/arm/include/elf.h +++ b/arch/arm/include/elf.h @@ -41,6 +41,11 @@ #define EM_ARCH EM_ARM +/* An object built for the FDPIC ABI says so in the OS/ABI byte. */ + +#define ELF_IS_FDPIC(ehdr) \ + ((ehdr)->e_ident[EI_OSABI] == ELFOSABI_ARM_FDPIC) + /* Table 4-2, ARM-specific e_flags */ #define EF_ARM_EABI_MASK 0xff000000 @@ -206,6 +211,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/fs/Kconfig b/fs/Kconfig index b2aef67bb81..b42c5d513d8 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -94,6 +94,17 @@ config PSEUDOFS_ATTRIBUTES Enable support for attributes(e.g. mode, uid, gid and time) in the pseudo file system. +config FS_PIN + bool + default n + ---help--- + Selected by a file system that can pin a file's blocks in place and + hand out their media address, so that a module's read-only segment + can be executed where it already lies instead of being copied. + + The pin is held for as long as the module is loaded and given back + when it is unloaded. + config FS_PERMISSION bool "Enable UNIX Filesystem Permission Support" default n diff --git a/fs/xipfs/Kconfig b/fs/xipfs/Kconfig index fbdad9240a0..73351cf7ea2 100644 --- a/fs/xipfs/Kconfig +++ b/fs/xipfs/Kconfig @@ -7,6 +7,7 @@ config FS_XIPFS bool "XIPFS contiguous execute-in-place file system" default n depends on !DISABLE_MOUNTPOINT && MTD + select FS_PIN ---help--- Enable support for XIPFS, a file system that stores each file as a single physically contiguous, erase-block aligned extent on memory diff --git a/include/elf.h b/include/elf.h index a3d6dc8f927..f940dbabb67 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 */ diff --git a/include/nuttx/elf.h b/include/nuttx/elf.h index abb3c2fa6dc..ab3a767d87d 100644 --- a/include/nuttx/elf.h +++ b/include/nuttx/elf.h @@ -37,6 +37,12 @@ #define ELF_PRARGSZ (80) /* Number of chars for args */ +/* An architecture that has no FDPIC ABI recognises no FDPIC object. */ + +#ifndef ELF_IS_FDPIC +# define ELF_IS_FDPIC(ehdr) false +#endif + /**************************************************************************** * Public Types ****************************************************************************/ diff --git a/include/nuttx/lib/elf.h b/include/nuttx/lib/elf.h index bcb8a039c80..1648da450e5 100644 --- a/include/nuttx/lib/elf.h +++ b/include/nuttx/lib/elf.h @@ -44,6 +44,19 @@ # 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. + * + * That reference is taken with file_get() and file_dup2(), which are kernel + * side, so this is for the copy of libc that runs there. + */ + +#if defined(CONFIG_FS_PIN) && \ + (defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__)) +# define HAVE_LIBC_ELF_PIN 1 +#endif + #ifndef CONFIG_LIBC_ELF_ALIGN_LOG2 # define CONFIG_LIBC_ELF_ALIGN_LOG2 2 #endif @@ -123,6 +136,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 +266,16 @@ struct mod_loadinfo_s * skip the copy. */ + /* True if e_ident[EI_OSABI] marked this an FDPIC object. */ + + bool fdpic; + +#ifdef HAVE_LIBC_ELF_PIN + /* The file the pin is held through, handed to the module once it loads. */ + + FAR struct file *pinfile; +#endif + /* 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 7d5a7bcd671..fcf3227cf11 100644 --- a/libs/libc/elf/elf.h +++ b/libs/libc/elf/elf.h @@ -349,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_load.c b/libs/libc/elf/elf_load.c index e01e2fd7489..ceea2eb2d59 100644 --- a/libs/libc/elf/elf_load.c +++ b/libs/libc/elf/elf_load.c @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,7 @@ #include #include +#include #include #include "libc.h" @@ -364,6 +366,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); @@ -539,10 +548,113 @@ skipload: 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; +} + +#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; + binfo("pinned xipbase %" PRIxPTR "\n", loadinfo->xipbase); + return OK; + } +#endif + + if (ioctl(loadinfo->filfd, FIOC_XIPBASE, (unsigned long)&base) >= 0) + { + loadinfo->xipbase = base; + binfo("can use xipbase %" PRIxPTR "\n", loadinfo->xipbase); + return OK; + } + + return -ENOTTY; +} + /**************************************************************************** * Public Functions ****************************************************************************/ +#ifdef HAVE_LIBC_ELF_PIN +/**************************************************************************** + * 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 + /**************************************************************************** * Name: libelf_load * @@ -559,6 +671,7 @@ skipload: int libelf_load(FAR struct mod_loadinfo_s *loadinfo) { int ret; + int i; binfo("loadinfo: %p\n", loadinfo); DEBUGASSERT(loadinfo && loadinfo->filfd >= 0); @@ -576,11 +689,7 @@ int libelf_load(FAR struct mod_loadinfo_s *loadinfo) if (loadinfo->gotindex >= 0) { 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 +756,96 @@ 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. + */ - loadinfo->datastart = loadinfo->textalloc + - loadinfo->textsize + - loadinfo->segpad; + 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; + } + } + } + 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; + } } #endif /* CONFIG_LIBC_ELF_LOADTO_LMA */ @@ -732,11 +916,7 @@ int libelf_load_with_addrenv(FAR struct mod_loadinfo_s *loadinfo) if (loadinfo->gotindex >= 0) { 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 6e3e3afb041..1bc929027c8 100644 --- a/libs/libc/elf/elf_loadhdrs.c +++ b/libs/libc/elf/elf_loadhdrs.c @@ -29,6 +29,7 @@ #include #include #include +#include #include @@ -66,6 +67,23 @@ int libelf_loadhdrs(FAR struct mod_loadinfo_s *loadinfo) /* Verify that there are sections */ + /* The architecture knows how an FDPIC object announces itself. Ask it + * once. + */ + + loadinfo->fdpic = ELF_IS_FDPIC(&loadinfo->ehdr); + + /* 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_unload.c b/libs/libc/elf/elf_unload.c index c1c7609bf2f..9754a4da00f 100644 --- a/libs/libc/elf/elf_unload.c +++ b/libs/libc/elf/elf_unload.c @@ -59,6 +59,17 @@ 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->pinfile != NULL) + { + libelf_pinrelease(&loadinfo->pinfile); + } +#endif + #ifdef CONFIG_ARCH_ADDRENV if (loadinfo->addrenv != NULL) { @@ -68,9 +79,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->ehdr.e_type != ET_DYN) + 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 */ + + else if (loadinfo->ehdr.e_type != ET_DYN) { #ifdef CONFIG_ARCH_USE_SEPARATED_SECTION int i;