gotindex named the .got section header, and every user then reached through
shdr[] for what it actually wanted. Only one of the five wanted the index.
gotbase and gotsize say it directly. gotsize is the extent of .got and is
also what says the object has one, and gotbase is where the GOT ended up:
the placed address of .got for an ordinary object, or DT_PLTGOT for an FDPIC
one, which libelf_bind() already reads. Both are set in libelf_loadfile(),
after the sections are placed, so gotbase is the address the object will be
read at rather than the one it was linked for.
The GOT walk in libelf_loadfile() now runs only when there is a base, which
also keeps it off an FDPIC object. An FDPIC object's sections are never
placed, so .got carried a link time sh_addr there, and the walk read and
wrote through it. Its GOT is relocated through its own relocations.
The check that gates libelf_xipacquire() runs before the load, when neither
field is set, so it looks the section up by name. It hands the index it
found to libelf_loadfile(), which is the only reason that function takes
one: the object is searched once, not twice.
One behaviour changes: a .got that exists but is empty now reads as no GOT.
There is nothing for any of the five users to do with an empty one.
Built for pimoroni-pico-2-plus with CONFIG_PIC, CONFIG_ELF and
CONFIG_LIBC_ELF, and for mps3-an547:bl, which is the board that read the
index. Run on QEMU with mps3-an547:picostest, which loads PIC ELF modules
from a romfs: hello prints, and ostest reaches the timed mutex test, the
same as before the change.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
The FDPIC work touches these files, and nxstyle reports errors on the lines
around every hunk, which fails the check job. The errors are older than
this series: a switch body indented two columns too deep in elf_symbols.c,
and declarations with no blank line after them.
Whitespace and one reworded comment, no change in behaviour.
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
A module that dlopen()s a library gets back function addresses from
dlsym() and calls them. Under FDPIC a bare code address is not enough:
the callee needs its own data base as well, so what dlsym() returns has
to be a function descriptor.
The exported symbol table carries no type information -- symtab_s is a
name and a value, and its own comment says typing would have to be added
to support anything but function pointers -- so by the time dlsym() is
asked there is no way to tell a function from an object.
libelf_insertsymtab() is the last point that can: st_info is still in
hand there. So an FDPIC object's exported functions are published as the
address of a descriptor carved from the module's pool, and dlopen(),
dlsym() and the module registry need no knowledge of FDPIC at all. The
pool is sized for the dynamic symbol table as well as the relocations,
since both can draw from it.
That leaves the symbol values themselves, which were wrong for any
ET_DYN object. libelf_loadsymtab() adds the symbol's section address to
its value, which is right for ET_REL, where the section address is where
the section was actually placed and the value is relative to it. In a
shared object both are already full link-time addresses, so adding them
counts the section twice. It needs translating onto wherever the object
was placed instead.
Library data is shared between everything that dlopen()s it, because the
registry holds one instance per name. Giving each user its own copy
would mean teaching the registry about instances, which is a much larger
change to shared code; an executable loaded through exec() already gets
its own data, since that path loads a fresh copy each time.
Built and run on lm3s6965-ek with the examples/elf ROMFS; the FDPIC
module continues to load, relocate and call through its own descriptors.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
libelf_relocatedyn() reads the handful of DT_* tags it needs to walk the
relocation tables and ignores the rest. Three more matter now.
DT_PLTGOT is where the object's data base lives. An FDPIC module runs
with that in the PIC base register, and every function descriptor built
for it names the same base as the one its callee should run with, so
without it there is nothing to put in a descriptor's second word.
The DT_*_ARRAY tags are the constructor and destructor tables. These are
already found through the section headers a few lines further down, and
that path is kept, but the dynamic tags are the authoritative copy and an
object is not obliged to carry section headers at all. Both paths now
translate through libelf_addr(), so they agree on the answer rather than
depending on which ran last. The tag values themselves were missing from
include/elf.h and are added.
Sizing the descriptor pool has to happen here rather than later.
R_ARM_FUNCDESC asks the loader to manufacture a descriptor and hand back
its address, which means the space must exist by the time the relocation
is applied, and by then the segment has been placed. So libelf_elfsize()
reserves it behind the writable data, bounded by the relocation count --
one relocation cannot ask for more than one descriptor. That bound has
slack in it, but a descriptor is two words and modules are small, which
is cheaper than walking every relocation twice to get an exact count.
Nothing here runs for a non-FDPIC object. Built and booted
mps3-an547:picostest with no change in behaviour.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
An ET_DYN object is loaded into one allocation with its data behind its
text, because its data references sit at a fixed distance from the code
that makes them. An FDPIC object does not work that way: it reaches its
data through a base register, so the two segments can be placed wherever
suits, and the point of the format is that the read-only one is left on
the media and executed there while only the writable one is copied. One
copy of the text then serves every instance.
So libelf_load() grows a second case. The object announces itself in the
OS/ABI byte, which is noted once in libelf_loadhdrs() rather than
re-derived; e_flags cannot be used for this, as an FDPIC object's are an
unremarkable EABI version and testing them would reject every valid
module. Text is taken from the media address plus the segment's own file
offset -- the same arithmetic the ET_REL path already does with
sh_offset -- and libelf_loadfile() does not read it. If the filesystem
cannot show its media, the loader copies the text to RAM instead. The
module then loses the shared text and the flash saving, but it runs.
Obtaining that address needs two mechanisms, and they are not
interchangeable. A compacting filesystem can move a file's blocks, so it
hands out an address only with a pin that holds them still and expects
the pin back; xipfs is the one in tree. A filesystem whose layout never
changes has nothing to hold and answers FIOC_XIPBASE with a bare address;
romfs and tmpfs are those. libelf_xipacquire() asks for the pin first,
because a filesystem that needs one is not safe without it, and
libelf_unload() gives it back. The loader asks for a pin only if it can
hold one, or the pin would stay for ever.
The pin is thus not specific to FDPIC. Any module that executes in place
from a compacting filesystem takes one, and gives it back at unload.
mmap() is not used, though both filesystems implement it. The mapping
would be recorded against whichever task called the loader, while the
release happens when the module's own task exits, which is a different
group -- so the pin would outlive the module and the extent would never
become movable again.
Unloading has to change with placement: the existing path frees only
textalloc because ET_DYN had a single allocation, which would leak an
FDPIC object's data and free media the filesystem only lent us.
Nothing here runs for a non-FDPIC object; every branch is behind the flag
and the single-allocation path is untouched. Built and booted
mps3-an547:picostest, which is CONFIG_ELF with CONFIG_PIC, with no change
in behaviour.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
libelf_elfsize() takes textalign and dataalign from the section headers,
which only the ET_REL path walks. An ET_DYN object is sized from its
program headers instead, so both fields stay at zero, and the allocation
a few lines later asks for that alignment:
loadinfo->textalloc = lib_memalign(loadinfo->textalign, ...);
Zero is not a valid alignment, and every path that receives it divides by
it. mm_memalign() accepts zero as a power of two, because 0 & -0 is 0,
then takes the "alignment <= MM_ALIGN" branch and evaluates
"((uintptr_t)ptr) % alignment" in a DEBUGASSERT. With
CONFIG_MM_HEAP_MEMPOOL and a pool that fits the request the object never
reaches that branch and gets ALIGN_UP(blk, 0) instead, which is
((blk - 1) / 0) * 0.
On Cortex-M this is usually invisible: UDIV returns zero for a division
by zero unless CCR.DIV_0_TRP is set, which NuttX does not set, so the
assertion compares zero against zero and passes. It is a SIGFPE on the
simulator, and the mempool path returns a null pointer wherever the
division yields zero, which the loader reports as -ENOMEM.
Ask for a natural word when the program headers gave nothing. p_align is
the linker's page granularity, not a section requirement, so honouring it
would cost a page per module for no gain, and the sections of a shared
object need no more than a word.
Built for mps3-an547:picostest, which is CONFIG_ELF with CONFIG_PIC.
Runtime evidence on hardware follows.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
debug.h is a NuttX-specific, non-POSIX header. Placing it in the
top-level include/ directory creates naming conflicts with external
projects that define their own debug.h.
This commit moves the canonical header to include/nuttx/debug.h,
following the NuttX convention for non-POSIX/non-standard headers,
and updates all in-tree references.
A backward-compatibility shim is left at include/debug.h that
emits a deprecation #warning and re-includes <nuttx/debug.h>,
allowing out-of-tree code to continue building while migrating.
Signed-off-by: Piyush Patle <piyushpatle228@gmail.com>
Renaming "modlib" to "libelf" is more in line with the implementation content,
which makes it easier for individual developers to understand the capabilities of this module.
CONFIG_LIBC_MODLIB -> CONFIG_LIBC_ELF
Signed-off-by: chao an <anchao.archer@bytedance.com>
2025-04-11 09:43:22 +08:00
Renamed from libs/libc/modlib/modlib_load.c (Browse further)