arch/arm, libs/libc/elf: Build FDPIC modules in the normal ELF build.

With CONFIG_FDPIC selected, a module built by apps/Application.mk is now an
FDPIC shared object.  Nothing about how a module is written or built
changes: the same MODULE = m in the same Makefile, the same crt0 and the
same linker script.

Two things differ from the position independent build beside it.  The
compiler is told -mfdpic -fPIC, and the link is done by 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: the stock
linker carries the armelf emulation alone and would turn every import into
an R_ARM_JUMP_SLOT, one word, where the ABI wants an R_ARM_FUNCDESC_VALUE,
which is two, a code address and the data base that goes with it.  Such a
module links cleanly and then calls out of itself with the caller's data
base still in r9.  That linker is in the CI image.

gnu-elf.ld.in gains the two segments an FDPIC module needs, under
CONFIG_FDPIC, because the loader places its read-only and writable segments
independently, and names .dynamic, because a shared object is bound through
it.  The sections themselves are untouched and so are the symbols crt0.c
walks, so one script serves both and both build systems get it.

.bss moves to the end of the script, for every configuration and not only
FDPIC.  It held no file content but sat ahead of .got and .dynamic, which
do, so the writable segment's p_filesz had to span it and the module file
carried the whole of .bss.  A module with 16 KiB of .bss went from 26724 to
10340 bytes, and its writable segment from p_filesz 0x40ac to 0xac against
an unchanged p_memsz.  The loader reads p_filesz off the media, so it read
those bytes too.

Built for mps3-an547:picostest with apps/examples/elf, CONFIG_FDPIC both
ways.  With it on, every module in apps/bin is ARM FDPIC with two PT_LOAD
segments and enters at _start; hello++3, which has a static C++ object,
carries DT_INIT_ARRAY and DT_FINI_ARRAY.  With it off the generated script
has no PHDRS and the modules are what they were.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
This commit is contained in:
Marco Casaroli 2026-08-28 18:22:08 +02:00 committed by Xiang Xiao
parent a60ee84197
commit 4cdd3cdbda
2 changed files with 71 additions and 11 deletions

View file

@ -642,11 +642,29 @@ 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
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

View file

@ -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. */