diff --git a/arch/arm/src/cmake/elf.cmake b/arch/arm/src/cmake/elf.cmake index 7108aa4c17485..ab09828847c8b 100644 --- a/arch/arm/src/cmake/elf.cmake +++ b/arch/arm/src/cmake/elf.cmake @@ -27,17 +27,59 @@ nuttx_mod_compile_options(-fvisibility=hidden -mlong-calls) nuttx_elf_compile_options_ifdef(CONFIG_UNWINDER_ARM -fno-unwind-tables -fno-asynchronous-unwind-tables) -# An ELF module needs r9 as its PIC base, so it must not also have the register -# fixed: GCC rejects that pair with "unable to use 'r9' for PIC register". This -# mirrors CELFFLAGS in common/Toolchain.defs, which filters --fixed-r9 back out -# of the inherited CFLAGS for the same reason. +if(CONFIG_FDPIC) -nuttx_elf_compile_options_ifdef(CONFIG_PIC -mpic-register=r9) + # An FDPIC module is a shared object whose two segments the loader places + # independently. The stock compiler emits correct FDPIC objects for both C + # and C++, so only the link needs the arm-uclinuxfdpiceabi linker: the stock + # one carries the armelf emulation alone and would turn every import into a + # jump slot where the ABI wants a function descriptor. -nuttx_elf_link_options_ifdef( - CONFIG_PIC --unresolved-symbols=ignore-in-object-files --emit-relocs) + if(NOT FDPIC_CROSSDEV) + set(FDPIC_CROSSDEV arm-uclinuxfdpiceabi-) + endif() + + # Say which linker is missing rather than failing later with a command that + # cannot be run. + + find_program(FDPIC_LD "${FDPIC_CROSSDEV}ld") + + if(NOT FDPIC_LD) + message( + FATAL_ERROR + "CONFIG_FDPIC needs ${FDPIC_CROSSDEV}ld, which is not on PATH. " + "It is in the NuttX CI image, and tools/ci/docker/linux/Dockerfile " + "shows how it is built. Set FDPIC_CROSSDEV to use a different prefix") + endif() + + set(CMAKE_ELF_LD + "${FDPIC_LD}" + CACHE INTERNAL "Linker for FDPIC modules") + + nuttx_elf_compile_options(-mfdpic -fPIC -Wa,--noexecstack) + + nuttx_elf_link_options(-m armelf_linux_fdpiceabi -shared -z now) -nuttx_elf_link_options_ifdef(CONFIG_BINFMT_ELF_RELOCATABLE -r) +elseif(CONFIG_PIC) + + # An ELF module needs r9 as its PIC base, so it must not also have the + # register fixed: GCC rejects that pair with "unable to use 'r9' for PIC + # register". This mirrors CELFFLAGS in common/Toolchain.defs, which filters + # --fixed-r9 back out of the inherited CFLAGS for the same reason. + + nuttx_elf_compile_options(-mpic-register=r9) + + nuttx_elf_link_options(--unresolved-symbols=ignore-in-object-files + --emit-relocs) + +endif() + +# Not with CONFIG_PIC: there the module is linked as an executable, which is +# what common/Toolchain.defs does too. + +if(CONFIG_BINFMT_ELF_RELOCATABLE AND NOT CONFIG_PIC) + nuttx_elf_link_options(-r) +endif() nuttx_mod_link_options(-r) diff --git a/arch/arm/src/common/Toolchain.defs b/arch/arm/src/common/Toolchain.defs index 155ef3f3d7ce3..a1d6580326384 100644 --- a/arch/arm/src/common/Toolchain.defs +++ b/arch/arm/src/common/Toolchain.defs @@ -642,11 +642,45 @@ ifeq ($(CONFIG_PIC),y) # after including this file, which would discard the flag. ARCHCFLAGS += --fixed-r9 + +ifeq ($(CONFIG_FDPIC),y) + # An FDPIC module is a shared object whose two segments the loader places + # independently. The stock compiler emits correct FDPIC objects for both C + # and C++, so only the link needs the arm-uclinuxfdpiceabi linker: the + # stock one carries the armelf emulation alone and would turn every import + # into a jump slot where the ABI wants a function descriptor. + + FDPIC_CROSSDEV ?= arm-uclinuxfdpiceabi- + MODULELD = $(FDPIC_CROSSDEV)ld + + # Say which linker is missing rather than letting make report a command it + # cannot run. The report is deferred to the link itself rather than made + # here, so that a tree configured for FDPIC on a host without the linker can + # still be cleaned and reconfigured. + + FDPIC_LD_FOUND := $(shell command -v $(MODULELD) 2> /dev/null) + + ifeq ($(FDPIC_LD_FOUND),) + FDPIC_NO_LD_MSG = CONFIG_FDPIC needs $(FDPIC_CROSSDEV)ld, which is not \ + on PATH. It is in the NuttX CI image, and \ + tools/ci/docker/linux/Dockerfile shows how it is built. Set \ + FDPIC_CROSSDEV to use a different prefix. + + MODULELD = $(SHELL) -c 'echo "ERROR: $(FDPIC_NO_LD_MSG)" 1>&2; exit 1' -- + endif + + CELFFLAGS += -mfdpic -fPIC -Wa,--noexecstack + CXXELFFLAGS += -mfdpic -fPIC -Wa,--noexecstack + + LDELFFLAGS += -m armelf_linux_fdpiceabi -shared -z now +else CELFFLAGS += $(PICFLAGS) -mpic-register=r9 CXXELFFLAGS += $(PICFLAGS) -mpic-register=r9 # Generate an executable elf, need to ignore undefined symbols LDELFFLAGS += --unresolved-symbols=ignore-in-object-files --emit-relocs +endif + else ifneq ($(CONFIG_BINFMT_ELF_EXECUTABLE),y) LDELFFLAGS += -r diff --git a/cmake/nuttx_add_application.cmake b/cmake/nuttx_add_application.cmake index 4864eaedbd683..bf4b626ce93cc 100644 --- a/cmake/nuttx_add_application.cmake +++ b/cmake/nuttx_add_application.cmake @@ -149,7 +149,15 @@ function(nuttx_add_application) if(TARGET STARTUP_OBJS) add_dependencies(${TARGET} STARTUP_OBJS) endif() - if(NOT "${CMAKE_LD}" MATCHES "gcc$") + # A module may need a different linker from the one that links the + # firmware: an FDPIC module does, because the stock linker cannot + # produce one. CMAKE_ELF_LD is that linker, and it is the ordinary one + # unless the architecture says otherwise. + + if(NOT CMAKE_ELF_LD) + set(CMAKE_ELF_LD ${CMAKE_LD}) + endif() + if(NOT "${CMAKE_ELF_LD}" MATCHES "gcc$") set(USE_LINKER True) endif() if(STACKSIZE) @@ -179,7 +187,7 @@ function(nuttx_add_application) POST_BUILD COMMAND # add default link option - ${CMAKE_LD} -T ${NUTTX_BINARY_DIR}/gnu-elf.ld + ${CMAKE_ELF_LD} -T ${NUTTX_BINARY_DIR}/gnu-elf.ld # add global MOD link option if dynlib link $<$:$> # add global ELF link option if m&kernel link diff --git a/libs/libc/elf/gnu-elf.ld.in b/libs/libc/elf/gnu-elf.ld.in index 340a91df5a207..be0c87760501f 100644 --- a/libs/libc/elf/gnu-elf.ld.in +++ b/libs/libc/elf/gnu-elf.ld.in @@ -39,6 +39,34 @@ # define SECTIONS_ALIGN 4 #endif +/* An FDPIC module is a shared object whose read-only and writable segments + * the loader places independently: the read-only one runs where the + * filesystem already holds it and only the writable one is copied to RAM, + * once per running instance. So the two go into segments of their own, and + * .dynamic is named, because a shared object is bound through it. + * + * Everything else is common, the symbols crt0.c walks included, so a module + * is built and entered the same way whichever this is. + */ + +#ifdef CONFIG_FDPIC +# define PHDR_TEXT :text +# define PHDR_DATA :data +# define DATA_ALIGN . = ALIGN(0x1000); + +PHDRS +{ + text PT_LOAD FLAGS(5); /* Read and execute */ + data PT_LOAD FLAGS(6); /* Read and write */ + dynamic PT_DYNAMIC FLAGS(6); +} + +#else +# define PHDR_TEXT +# define PHDR_DATA +# define DATA_ALIGN +#endif + SECTIONS { .text TEXT : @@ -53,7 +81,7 @@ SECTIONS *(.jcr) . = ALIGN(SECTIONS_ALIGN); _etext = . ; - } + } PHDR_TEXT .rodata : { @@ -64,7 +92,9 @@ SECTIONS *(.gnu.linkonce.r*) . = ALIGN(SECTIONS_ALIGN); _erodata = . ; - } + } PHDR_TEXT + + DATA_ALIGN .data DATA : { @@ -75,7 +105,7 @@ SECTIONS *(.gnu.linkonce.d*) . = ALIGN(SECTIONS_ALIGN); _edata = . ; - } + } PHDR_DATA .init_array : { @@ -86,7 +116,7 @@ SECTIONS . = ALIGN(SECTIONS_ALIGN); _einit = .; _ectors = .; - } + } PHDR_DATA .fini_array : { @@ -98,7 +128,24 @@ SECTIONS . = ALIGN(SECTIONS_ALIGN); _efini = .; _edtors = .; - } + } PHDR_DATA + +#ifdef CONFIG_FDPIC + .dynamic : + { + *(.dynamic) + } :data :dynamic +#endif + + .got : + { + *(.got*) + } PHDR_DATA + + /* .bss holds no file content, so it comes last. Everything ahead of it + * has content, thus the writable segment's p_filesz stops where .bss + * starts and the file does not carry it. + */ .bss : { @@ -111,12 +158,7 @@ SECTIONS *(COMMON) . = ALIGN(SECTIONS_ALIGN); _ebss = . ; - } - - .got : - { - *(.got*) - } + } PHDR_DATA /* Stabs debugging sections. */