2024-04-29 19:59:34 +08:00
|
|
|
/****************************************************************************
|
2025-04-10 09:51:25 +08:00
|
|
|
* libs/libc/elf/elf_remove.c
|
2024-04-29 19:59:34 +08:00
|
|
|
*
|
|
|
|
|
* 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.
|
|
|
|
|
*
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
|
* Included Files
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
|
#include <assert.h>
|
2026-04-01 05:11:15 +05:30
|
|
|
#include <nuttx/debug.h>
|
2024-04-29 19:59:34 +08:00
|
|
|
#include <errno.h>
|
|
|
|
|
|
|
|
|
|
#include <nuttx/arch.h>
|
binfmt/elf: Load FDPIC modules through the ELF loader.
exec() of an FDPIC module now works. The loader already places such an
object and binds it; what was missing is everything binfmt has to carry
across from the load to the running task.
The task needs the module's data base in its PIC base register. binfmt
builds a D-Space for any object with a GOT, taking the base from the .got
section address; an FDPIC object names it in DT_PLTGOT instead, which the
loader has already translated, so the two are the same idea reached by
different routes and both are what up_initial_state() installs.
Constructors are not binfmt's business. A module carries its own crt0,
which walks .init_array on the task that runs the module and then calls
main, so they run in the module's own context and with its own data base.
For a module that arrives through dlopen(), libelf_insert() walks the array
instead, and it enters each entry through fdpic_invoke() because a
descriptor resolved on the calling task carries the wrong base.
The read-only segment of a module that executes in place is held by a
filesystem pin. The load takes it, and the module owns it from the point
where nothing can fail any more; it is given back when the task that runs
the module exits. The pin is held through a reference to the file rather
than a descriptor, because the descriptor belongs to the task that called
the loader and the release happens on another one.
libelf_remove() and libelf_uninit() give back what an FDPIC module holds:
the pin, and the writable segment, while the read-only one is media rather
than an allocation and must not be freed.
Built for mps3-an547:picostest with CONFIG_FDPIC both ways.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-25 11:14:11 +02:00
|
|
|
#include <nuttx/fdpic.h>
|
2024-04-29 19:59:34 +08:00
|
|
|
#include <nuttx/lib/lib.h>
|
2025-04-10 09:51:25 +08:00
|
|
|
#include <nuttx/lib/elf.h>
|
2024-04-29 19:59:34 +08:00
|
|
|
|
binfmt/elf: Load FDPIC modules through the ELF loader.
exec() of an FDPIC module now works. The loader already places such an
object and binds it; what was missing is everything binfmt has to carry
across from the load to the running task.
The task needs the module's data base in its PIC base register. binfmt
builds a D-Space for any object with a GOT, taking the base from the .got
section address; an FDPIC object names it in DT_PLTGOT instead, which the
loader has already translated, so the two are the same idea reached by
different routes and both are what up_initial_state() installs.
Constructors are not binfmt's business. A module carries its own crt0,
which walks .init_array on the task that runs the module and then calls
main, so they run in the module's own context and with its own data base.
For a module that arrives through dlopen(), libelf_insert() walks the array
instead, and it enters each entry through fdpic_invoke() because a
descriptor resolved on the calling task carries the wrong base.
The read-only segment of a module that executes in place is held by a
filesystem pin. The load takes it, and the module owns it from the point
where nothing can fail any more; it is given back when the task that runs
the module exits. The pin is held through a reference to the file rather
than a descriptor, because the descriptor belongs to the task that called
the loader and the release happens on another one.
libelf_remove() and libelf_uninit() give back what an FDPIC module holds:
the pin, and the writable segment, while the read-only one is media rather
than an allocation and must not be freed.
Built for mps3-an547:picostest with CONFIG_FDPIC both ways.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-25 11:14:11 +02:00
|
|
|
#include "elf/elf.h"
|
|
|
|
|
|
2024-04-29 19:59:34 +08:00
|
|
|
/****************************************************************************
|
|
|
|
|
* Public Functions
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
/****************************************************************************
|
2025-04-10 09:51:25 +08:00
|
|
|
* Name: libelf_uninit
|
2024-04-29 19:59:34 +08:00
|
|
|
*
|
|
|
|
|
* Description:
|
libs/libc/elf: Load the libraries a module names in DT_NEEDED.
A module that names a shared library in DT_NEEDED now gets it loaded and
its imports bound against it, rather than being refused.
libelf_insert() does the loading, which is what dlopen() calls anyway: the
library lands in the module registry like anything else, its exports come
back through libelf_getsymbol() -- the same call dlsym() uses -- and a
library named by two modules is loaded once. A bare name is looked for
along LD_LIBRARY_PATH, where dlopen() looks for it. Undefined symbols
resolve against the globally registered symbols first, then the modules
this one depends on, then the table exec() supplied. Nothing here calls
into dlfcn, because this loader is also the kernel's module loader, which
has none.
Each library becomes one of the module's dependencies[], and the dependency
holds it in place of the reference libelf_insert() took. So a library
loaded only for DT_NEEDED is kept by the modules that depend on it, and
libelf_undepend() unloads it with the last of them; one that dlopen() or
insmod also opened stays until that reference goes too.
CONFIG_LIBC_ELF_MAXDEPEND bounds how many libraries a module may name,
which is what it already meant.
Six things had to be fixed to make it work, none of which a build shows.
reldata was a file-scope global. Loading a library from inside
libelf_relocatedyn() makes that function reentrant, so the nested load
overwrote the outer one's relocation offsets and the module resumed binding
with the library's DT_REL. It is now per call.
A cross-object call needs the callee's data base, not the caller's. A
symbol resolved from an FDPIC library comes back as a descriptor, and
R_ARM_FUNCDESC_VALUE was treating it as a code address and pairing it with
the importing module's GOT. It now copies both words, so the library runs
with its own.
An object with no imports has no PLT and so no DT_PLTGOT, but it still has
a GOT and still has to be entered with it. Without the fallback its
descriptors carried a data base of zero and the library read its globals
through a null pointer.
R_ARM_FUNCDESC, a pointer to a descriptor, wrapped a library's descriptor
in a second one. It now stores the library's descriptor as it is.
The flag that says a resolved value is a descriptor was set only for an
import and never cleared, so the next relocation against a symbol of the
module itself took that symbol for a descriptor too. It is cleared there.
libelf_symname() was static, and reading a DT_NEEDED name needs it.
A module with DT_NEEDED is refused where CONFIG_LIBC_ELF_MAXDEPEND is zero,
since that is where the dependency logic is compiled out.
A DT_NEEDED library is one shared instance, its data included, because the
loader returns the object already in the registry. A module started with
exec() is different: that path loads the module afresh each time, so two
running instances have separate data while sharing one copy of the text.
Built for mps3-an547:picostest with CONFIG_FDPIC both ways. Run on
mps2-an500:xipfs under QEMU: fdpicxip solib loads libcounter.so by name out
of DT_NEEDED, two instances share one pinned copy of its text, and the
library is unloaded, and its pin given back, when the second one exits. A
library also opened with dlopen() stays loaded after its DT_NEEDED user
exits, and dlclose() unloads it.
With CONFIG_ARCH_ADDRENV the program runs in its own address space, which
a library libelf_insert() loads cannot reach, so DT_NEEDED is refused
there as before.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-25 11:18:55 +02:00
|
|
|
* Uninitialize module resources. Gives up everything the module holds,
|
|
|
|
|
* the DT_NEEDED libraries included, so the caller must hold the last
|
|
|
|
|
* reference.
|
2024-04-29 19:59:34 +08:00
|
|
|
*
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
2025-04-10 09:51:25 +08:00
|
|
|
int libelf_uninit(FAR struct module_s *modp)
|
2024-04-29 19:59:34 +08:00
|
|
|
{
|
2024-04-28 18:16:07 +08:00
|
|
|
FAR void (**array)(void);
|
2024-07-10 20:49:28 +08:00
|
|
|
int ret = OK;
|
2024-04-28 18:16:07 +08:00
|
|
|
int i;
|
2024-04-29 19:59:34 +08:00
|
|
|
|
2025-04-10 09:51:25 +08:00
|
|
|
#if CONFIG_LIBC_ELF_MAXDEPEND > 0
|
2024-04-29 19:59:34 +08:00
|
|
|
/* Refuse to remove any module that other modules may depend upon. */
|
|
|
|
|
|
|
|
|
|
if (modp->dependents > 0)
|
|
|
|
|
{
|
|
|
|
|
berr("ERROR: Module has dependents: %d\n", modp->dependents);
|
2024-07-10 20:49:28 +08:00
|
|
|
return -EBUSY;
|
2024-04-29 19:59:34 +08:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
binfmt/elf: Load FDPIC modules through the ELF loader.
exec() of an FDPIC module now works. The loader already places such an
object and binds it; what was missing is everything binfmt has to carry
across from the load to the running task.
The task needs the module's data base in its PIC base register. binfmt
builds a D-Space for any object with a GOT, taking the base from the .got
section address; an FDPIC object names it in DT_PLTGOT instead, which the
loader has already translated, so the two are the same idea reached by
different routes and both are what up_initial_state() installs.
Constructors are not binfmt's business. A module carries its own crt0,
which walks .init_array on the task that runs the module and then calls
main, so they run in the module's own context and with its own data base.
For a module that arrives through dlopen(), libelf_insert() walks the array
instead, and it enters each entry through fdpic_invoke() because a
descriptor resolved on the calling task carries the wrong base.
The read-only segment of a module that executes in place is held by a
filesystem pin. The load takes it, and the module owns it from the point
where nothing can fail any more; it is given back when the task that runs
the module exits. The pin is held through a reference to the file rather
than a descriptor, because the descriptor belongs to the task that called
the loader and the release happens on another one.
libelf_remove() and libelf_uninit() give back what an FDPIC module holds:
the pin, and the writable segment, while the read-only one is media rather
than an allocation and must not be freed.
Built for mps3-an547:picostest with CONFIG_FDPIC both ways.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-25 11:14:11 +02:00
|
|
|
/* Is there an uninitializer? Like the constructors, an FDPIC object's
|
|
|
|
|
* destructors reach its globals through its own data base, which the
|
|
|
|
|
* unloading thread does not carry.
|
|
|
|
|
*/
|
2024-04-29 19:59:34 +08:00
|
|
|
|
2024-04-28 18:16:07 +08:00
|
|
|
array = (FAR void (**)(void))modp->finiarr;
|
|
|
|
|
for (i = 0; i < modp->nfini; i++)
|
|
|
|
|
{
|
binfmt/elf: Load FDPIC modules through the ELF loader.
exec() of an FDPIC module now works. The loader already places such an
object and binds it; what was missing is everything binfmt has to carry
across from the load to the running task.
The task needs the module's data base in its PIC base register. binfmt
builds a D-Space for any object with a GOT, taking the base from the .got
section address; an FDPIC object names it in DT_PLTGOT instead, which the
loader has already translated, so the two are the same idea reached by
different routes and both are what up_initial_state() installs.
Constructors are not binfmt's business. A module carries its own crt0,
which walks .init_array on the task that runs the module and then calls
main, so they run in the module's own context and with its own data base.
For a module that arrives through dlopen(), libelf_insert() walks the array
instead, and it enters each entry through fdpic_invoke() because a
descriptor resolved on the calling task carries the wrong base.
The read-only segment of a module that executes in place is held by a
filesystem pin. The load takes it, and the module owns it from the point
where nothing can fail any more; it is given back when the task that runs
the module exits. The pin is held through a reference to the file rather
than a descriptor, because the descriptor belongs to the task that called
the loader and the release happens on another one.
libelf_remove() and libelf_uninit() give back what an FDPIC module holds:
the pin, and the writable segment, while the read-only one is media rather
than an allocation and must not be freed.
Built for mps3-an547:picostest with CONFIG_FDPIC both ways.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-25 11:14:11 +02:00
|
|
|
fdpic_call(0, array[i], modp->gotbase);
|
2024-04-28 18:16:07 +08:00
|
|
|
}
|
|
|
|
|
|
2024-04-29 19:59:34 +08:00
|
|
|
if (modp->modinfo.uninitializer != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* Try to uninitialize the module */
|
|
|
|
|
|
|
|
|
|
ret = modp->modinfo.uninitializer(modp->modinfo.arg);
|
|
|
|
|
|
|
|
|
|
/* Did the module successfully uninitialize? */
|
|
|
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
{
|
|
|
|
|
berr("ERROR: Failed to uninitialize the module: %d\n", ret);
|
2024-07-10 20:49:28 +08:00
|
|
|
return ret;
|
2024-04-29 19:59:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Nullify so that the uninitializer cannot be called again */
|
|
|
|
|
|
|
|
|
|
modp->modinfo.uninitializer = NULL;
|
|
|
|
|
modp->modinfo.arg = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 15:51:46 +08:00
|
|
|
/* Free the symbol table that the module exports. It is built for
|
|
|
|
|
* every loaded module, whether or not it has an uninitializer.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
libelf_freesymtab(modp);
|
|
|
|
|
modp->modinfo.exports = NULL;
|
|
|
|
|
modp->modinfo.nexports = 0;
|
|
|
|
|
|
binfmt/elf: Load FDPIC modules through the ELF loader.
exec() of an FDPIC module now works. The loader already places such an
object and binds it; what was missing is everything binfmt has to carry
across from the load to the running task.
The task needs the module's data base in its PIC base register. binfmt
builds a D-Space for any object with a GOT, taking the base from the .got
section address; an FDPIC object names it in DT_PLTGOT instead, which the
loader has already translated, so the two are the same idea reached by
different routes and both are what up_initial_state() installs.
Constructors are not binfmt's business. A module carries its own crt0,
which walks .init_array on the task that runs the module and then calls
main, so they run in the module's own context and with its own data base.
For a module that arrives through dlopen(), libelf_insert() walks the array
instead, and it enters each entry through fdpic_invoke() because a
descriptor resolved on the calling task carries the wrong base.
The read-only segment of a module that executes in place is held by a
filesystem pin. The load takes it, and the module owns it from the point
where nothing can fail any more; it is given back when the task that runs
the module exits. The pin is held through a reference to the file rather
than a descriptor, because the descriptor belongs to the task that called
the loader and the release happens on another one.
libelf_remove() and libelf_uninit() give back what an FDPIC module holds:
the pin, and the writable segment, while the read-only one is media rather
than an allocation and must not be freed.
Built for mps3-an547:picostest with CONFIG_FDPIC both ways.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-25 11:14:11 +02:00
|
|
|
#ifdef HAVE_LIBC_ELF_PIN
|
|
|
|
|
/* Give the pin back before the text goes out of use. This does nothing if
|
|
|
|
|
* the loader took no pin.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
libelf_pinrelease(&modp->pinfile);
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-04-29 19:59:34 +08:00
|
|
|
/* Release resources held by the module */
|
|
|
|
|
|
|
|
|
|
if (modp->textalloc != NULL || modp->dataalloc != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* Free the module memory and nullify so that the memory cannot
|
|
|
|
|
* be freed again
|
|
|
|
|
*
|
|
|
|
|
* NOTE: For dynamic shared objects there is only a single
|
|
|
|
|
* allocation: the text/data were allocated in one operation
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (!modp->dynamic)
|
|
|
|
|
{
|
|
|
|
|
#ifdef CONFIG_ARCH_USE_SEPARATED_SECTION
|
|
|
|
|
for (i = 0; i < modp->nsect && modp->sectalloc[i] != NULL; i++)
|
|
|
|
|
{
|
|
|
|
|
# ifdef CONFIG_ARCH_USE_TEXT_HEAP
|
|
|
|
|
if (up_textheap_heapmember(modp->sectalloc[i]))
|
|
|
|
|
{
|
|
|
|
|
up_textheap_free(modp->sectalloc[i]);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
# endif
|
|
|
|
|
|
|
|
|
|
# ifdef CONFIG_ARCH_USE_DATA_HEAP
|
|
|
|
|
if (up_dataheap_heapmember(modp->sectalloc[i]))
|
|
|
|
|
{
|
|
|
|
|
up_dataheap_free(modp->sectalloc[i]);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
# endif
|
|
|
|
|
|
|
|
|
|
lib_free(modp->sectalloc[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lib_free(modp->sectalloc);
|
|
|
|
|
modp->sectalloc = NULL;
|
|
|
|
|
modp->nsect = 0;
|
|
|
|
|
#else
|
2024-07-03 19:45:27 +08:00
|
|
|
if (modp->xipbase == 0)
|
|
|
|
|
{
|
2024-04-29 19:59:34 +08:00
|
|
|
# if defined(CONFIG_ARCH_USE_TEXT_HEAP)
|
2024-07-03 19:45:27 +08:00
|
|
|
up_textheap_free((FAR void *)modp->textalloc);
|
2024-04-29 19:59:34 +08:00
|
|
|
# else
|
2024-07-03 19:45:27 +08:00
|
|
|
lib_free((FAR void *)modp->textalloc);
|
2024-04-29 19:59:34 +08:00
|
|
|
# endif
|
2024-07-03 19:45:27 +08:00
|
|
|
}
|
|
|
|
|
|
2024-04-29 19:59:34 +08:00
|
|
|
# if defined(CONFIG_ARCH_USE_DATA_HEAP)
|
|
|
|
|
up_dataheap_free((FAR void *)modp->dataalloc);
|
|
|
|
|
# else
|
|
|
|
|
lib_free((FAR void *)modp->dataalloc);
|
|
|
|
|
# endif
|
|
|
|
|
#endif
|
|
|
|
|
}
|
binfmt/elf: Load FDPIC modules through the ELF loader.
exec() of an FDPIC module now works. The loader already places such an
object and binds it; what was missing is everything binfmt has to carry
across from the load to the running task.
The task needs the module's data base in its PIC base register. binfmt
builds a D-Space for any object with a GOT, taking the base from the .got
section address; an FDPIC object names it in DT_PLTGOT instead, which the
loader has already translated, so the two are the same idea reached by
different routes and both are what up_initial_state() installs.
Constructors are not binfmt's business. A module carries its own crt0,
which walks .init_array on the task that runs the module and then calls
main, so they run in the module's own context and with its own data base.
For a module that arrives through dlopen(), libelf_insert() walks the array
instead, and it enters each entry through fdpic_invoke() because a
descriptor resolved on the calling task carries the wrong base.
The read-only segment of a module that executes in place is held by a
filesystem pin. The load takes it, and the module owns it from the point
where nothing can fail any more; it is given back when the task that runs
the module exits. The pin is held through a reference to the file rather
than a descriptor, because the descriptor belongs to the task that called
the loader and the release happens on another one.
libelf_remove() and libelf_uninit() give back what an FDPIC module holds:
the pin, and the writable segment, while the read-only one is media rather
than an allocation and must not be freed.
Built for mps3-an547:picostest with CONFIG_FDPIC both ways.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-25 11:14:11 +02:00
|
|
|
else if (modp->gotbase != 0)
|
|
|
|
|
{
|
|
|
|
|
/* An FDPIC object, which placed its two segments separately. Free
|
|
|
|
|
* each one. If the text stayed on the media, it was never
|
|
|
|
|
* allocated, thus leave it.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (modp->xipbase == 0)
|
|
|
|
|
{
|
|
|
|
|
#ifdef CONFIG_ARCH_USE_TEXT_HEAP
|
|
|
|
|
up_textheap_free((FAR void *)modp->textalloc);
|
|
|
|
|
#else
|
|
|
|
|
lib_free((FAR void *)modp->textalloc);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
libs/libc/elf: Load an FDPIC object's data into the data heap.
An architecture that sets CONFIG_ARCH_USE_DATA_HEAP gives a loaded module
its data from up_dataheap_memalign(), because the ordinary heap is not where
that data belongs there. The ELF loader honours it for every object but an
FDPIC one: an FDPIC object places its writable segment on its own, and that
allocation, and the two places that free it, still use lib_memalign() and
lib_free(). Its text already comes from the text heap.
So an FDPIC module's data goes to the data heap too, and back to it when the
module is unloaded or removed.
On mps3-an547, which sets both heaps, fdpicxip loaded the data of its two
instances at 0x1007220 and 0x104e480, in the ordinary heap. With this change
they are at 0x21000000 and 0x21000180, in the SRAM2 data heap, and both
instances run. In a protected build the difference matters: there the
ordinary heap is kernel memory, and the module takes a data access violation
on its first access to its data.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-09-28 13:15:28 +02:00
|
|
|
#ifdef CONFIG_ARCH_USE_DATA_HEAP
|
|
|
|
|
up_dataheap_free((FAR void *)modp->dataalloc);
|
|
|
|
|
#else
|
binfmt/elf: Load FDPIC modules through the ELF loader.
exec() of an FDPIC module now works. The loader already places such an
object and binds it; what was missing is everything binfmt has to carry
across from the load to the running task.
The task needs the module's data base in its PIC base register. binfmt
builds a D-Space for any object with a GOT, taking the base from the .got
section address; an FDPIC object names it in DT_PLTGOT instead, which the
loader has already translated, so the two are the same idea reached by
different routes and both are what up_initial_state() installs.
Constructors are not binfmt's business. A module carries its own crt0,
which walks .init_array on the task that runs the module and then calls
main, so they run in the module's own context and with its own data base.
For a module that arrives through dlopen(), libelf_insert() walks the array
instead, and it enters each entry through fdpic_invoke() because a
descriptor resolved on the calling task carries the wrong base.
The read-only segment of a module that executes in place is held by a
filesystem pin. The load takes it, and the module owns it from the point
where nothing can fail any more; it is given back when the task that runs
the module exits. The pin is held through a reference to the file rather
than a descriptor, because the descriptor belongs to the task that called
the loader and the release happens on another one.
libelf_remove() and libelf_uninit() give back what an FDPIC module holds:
the pin, and the writable segment, while the read-only one is media rather
than an allocation and must not be freed.
Built for mps3-an547:picostest with CONFIG_FDPIC both ways.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-25 11:14:11 +02:00
|
|
|
lib_free((FAR void *)modp->dataalloc);
|
libs/libc/elf: Load an FDPIC object's data into the data heap.
An architecture that sets CONFIG_ARCH_USE_DATA_HEAP gives a loaded module
its data from up_dataheap_memalign(), because the ordinary heap is not where
that data belongs there. The ELF loader honours it for every object but an
FDPIC one: an FDPIC object places its writable segment on its own, and that
allocation, and the two places that free it, still use lib_memalign() and
lib_free(). Its text already comes from the text heap.
So an FDPIC module's data goes to the data heap too, and back to it when the
module is unloaded or removed.
On mps3-an547, which sets both heaps, fdpicxip loaded the data of its two
instances at 0x1007220 and 0x104e480, in the ordinary heap. With this change
they are at 0x21000000 and 0x21000180, in the SRAM2 data heap, and both
instances run. In a protected build the difference matters: there the
ordinary heap is kernel memory, and the module takes a data access violation
on its first access to its data.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-09-28 13:15:28 +02:00
|
|
|
#endif
|
binfmt/elf: Load FDPIC modules through the ELF loader.
exec() of an FDPIC module now works. The loader already places such an
object and binds it; what was missing is everything binfmt has to carry
across from the load to the running task.
The task needs the module's data base in its PIC base register. binfmt
builds a D-Space for any object with a GOT, taking the base from the .got
section address; an FDPIC object names it in DT_PLTGOT instead, which the
loader has already translated, so the two are the same idea reached by
different routes and both are what up_initial_state() installs.
Constructors are not binfmt's business. A module carries its own crt0,
which walks .init_array on the task that runs the module and then calls
main, so they run in the module's own context and with its own data base.
For a module that arrives through dlopen(), libelf_insert() walks the array
instead, and it enters each entry through fdpic_invoke() because a
descriptor resolved on the calling task carries the wrong base.
The read-only segment of a module that executes in place is held by a
filesystem pin. The load takes it, and the module owns it from the point
where nothing can fail any more; it is given back when the task that runs
the module exits. The pin is held through a reference to the file rather
than a descriptor, because the descriptor belongs to the task that called
the loader and the release happens on another one.
libelf_remove() and libelf_uninit() give back what an FDPIC module holds:
the pin, and the writable segment, while the read-only one is media rather
than an allocation and must not be freed.
Built for mps3-an547:picostest with CONFIG_FDPIC both ways.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-25 11:14:11 +02:00
|
|
|
}
|
2024-04-29 19:59:34 +08:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
lib_free((FAR void *)modp->textalloc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
modp->textalloc = NULL;
|
|
|
|
|
modp->dataalloc = NULL;
|
|
|
|
|
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_MODULE)
|
|
|
|
|
modp->textsize = 0;
|
|
|
|
|
modp->datasize = 0;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-10 09:51:25 +08:00
|
|
|
#if CONFIG_LIBC_ELF_MAXDEPEND > 0
|
2024-07-10 20:49:28 +08:00
|
|
|
/* Eliminate any dependencies that this module has on other modules */
|
|
|
|
|
|
2025-04-10 09:51:25 +08:00
|
|
|
libelf_undepend(modp);
|
2024-07-10 20:49:28 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/****************************************************************************
|
2025-04-10 09:51:25 +08:00
|
|
|
* Name: libelf_remove
|
2024-07-10 20:49:28 +08:00
|
|
|
*
|
|
|
|
|
* Description:
|
|
|
|
|
* Remove a previously installed module from memory.
|
|
|
|
|
*
|
|
|
|
|
* Input Parameters:
|
2025-04-10 09:51:25 +08:00
|
|
|
* handle - The module handler previously returned by libelf_insert().
|
2024-07-10 20:49:28 +08:00
|
|
|
*
|
|
|
|
|
* Returned Value:
|
|
|
|
|
* Zero (OK) on success. On any failure, -1 (ERROR) is returned the
|
|
|
|
|
* errno value is set appropriately.
|
|
|
|
|
*
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
2025-04-10 09:51:25 +08:00
|
|
|
int libelf_remove(FAR void *handle)
|
2024-07-10 20:49:28 +08:00
|
|
|
{
|
|
|
|
|
FAR struct module_s *modp = (FAR struct module_s *)handle;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
DEBUGASSERT(modp != NULL);
|
|
|
|
|
|
|
|
|
|
/* Get exclusive access to the module registry */
|
|
|
|
|
|
2025-04-10 09:51:25 +08:00
|
|
|
libelf_registry_lock();
|
2024-07-10 20:49:28 +08:00
|
|
|
|
|
|
|
|
/* Verify that the module is in the registry */
|
|
|
|
|
|
2025-04-10 09:51:25 +08:00
|
|
|
ret = libelf_registry_verify(modp);
|
2024-07-10 20:49:28 +08:00
|
|
|
if (ret < 0)
|
|
|
|
|
{
|
|
|
|
|
berr("ERROR: Failed to verify module: %d\n", ret);
|
|
|
|
|
goto errout_with_lock;
|
|
|
|
|
}
|
|
|
|
|
|
libc/dlfcn: Count opens so a library can be shared.
dlopen() of a library that is already loaded fails. libelf_insert()
rejects a name that is already in the module registry with EEXIST, and
dlinsert() passes that straight out, so the second caller gets NULL.
POSIX says dlopen() shall return a handle to the object, and there is no
way today for two modules to hold the same library at once -- which is
what a shared library is for.
So dlopen() now takes another reference on a library that is already
there, and dlclose() only tears it down when the last handle goes. The
count lives in the dlfcn layer rather than in libelf_insert() so that
insmod keeps its own behaviour: a second insmod of the same name still
fails with EEXIST, which is right for a kernel module.
The module name is what makes any of this possible, and a PROTECTED build
did not have one. Names were defined for CONFIG_BUILD_FLAT or the kernel
side of a split build, on the reasoning that only the kernel needed them,
which predates dlopen() being usable from user space. Without a name the
user-space copy of libelf cannot recognise a second open of a library,
cannot count opens, and cannot make dlclose() mean anything -- two
dlopen()s there produce two independent copies of the library and lose
track of the first. Names are therefore defined wherever CONFIG_LIBC_DLFCN
is, which costs NAME_MAX per loaded module in that configuration.
The path no longer has to be copied either. The module name is the
basename of the file and libelf_insert() takes it as a const string, so
dlinsert() finds it with strrchr() instead of handing a writable
duplicate of the whole path to basename().
BUILD_KERNEL is deliberately untouched. dlopen() returns NULL there
unconditionally: dlinsert() is a stub, because sharing a library between
processes with separate address spaces needs the text in a shared region
and the data per process at a matching virtual address, which is a
different problem from this one.
Built for mps3-an547:picostest with and without CONFIG_LIBC_DLFCN, and
for stm32f4discovery:kostest, a PROTECTED configuration, with it enabled.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-03 12:05:40 +02:00
|
|
|
/* Give back a reference. The module goes only when the last one does,
|
libs/libc/elf: Load the libraries a module names in DT_NEEDED.
A module that names a shared library in DT_NEEDED now gets it loaded and
its imports bound against it, rather than being refused.
libelf_insert() does the loading, which is what dlopen() calls anyway: the
library lands in the module registry like anything else, its exports come
back through libelf_getsymbol() -- the same call dlsym() uses -- and a
library named by two modules is loaded once. A bare name is looked for
along LD_LIBRARY_PATH, where dlopen() looks for it. Undefined symbols
resolve against the globally registered symbols first, then the modules
this one depends on, then the table exec() supplied. Nothing here calls
into dlfcn, because this loader is also the kernel's module loader, which
has none.
Each library becomes one of the module's dependencies[], and the dependency
holds it in place of the reference libelf_insert() took. So a library
loaded only for DT_NEEDED is kept by the modules that depend on it, and
libelf_undepend() unloads it with the last of them; one that dlopen() or
insmod also opened stays until that reference goes too.
CONFIG_LIBC_ELF_MAXDEPEND bounds how many libraries a module may name,
which is what it already meant.
Six things had to be fixed to make it work, none of which a build shows.
reldata was a file-scope global. Loading a library from inside
libelf_relocatedyn() makes that function reentrant, so the nested load
overwrote the outer one's relocation offsets and the module resumed binding
with the library's DT_REL. It is now per call.
A cross-object call needs the callee's data base, not the caller's. A
symbol resolved from an FDPIC library comes back as a descriptor, and
R_ARM_FUNCDESC_VALUE was treating it as a code address and pairing it with
the importing module's GOT. It now copies both words, so the library runs
with its own.
An object with no imports has no PLT and so no DT_PLTGOT, but it still has
a GOT and still has to be entered with it. Without the fallback its
descriptors carried a data base of zero and the library read its globals
through a null pointer.
R_ARM_FUNCDESC, a pointer to a descriptor, wrapped a library's descriptor
in a second one. It now stores the library's descriptor as it is.
The flag that says a resolved value is a descriptor was set only for an
import and never cleared, so the next relocation against a symbol of the
module itself took that symbol for a descriptor too. It is cleared there.
libelf_symname() was static, and reading a DT_NEEDED name needs it.
A module with DT_NEEDED is refused where CONFIG_LIBC_ELF_MAXDEPEND is zero,
since that is where the dependency logic is compiled out.
A DT_NEEDED library is one shared instance, its data included, because the
loader returns the object already in the registry. A module started with
exec() is different: that path loads the module afresh each time, so two
running instances have separate data while sharing one copy of the text.
Built for mps3-an547:picostest with CONFIG_FDPIC both ways. Run on
mps2-an500:xipfs under QEMU: fdpicxip solib loads libcounter.so by name out
of DT_NEEDED, two instances share one pinned copy of its text, and the
library is unloaded, and its pin given back, when the second one exits. A
library also opened with dlopen() stays loaded after its DT_NEEDED user
exits, and dlclose() unloads it.
With CONFIG_ARCH_ADDRENV the program runs in its own address space, which
a library libelf_insert() loads cannot reach, so DT_NEEDED is refused
there as before.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-25 11:18:55 +02:00
|
|
|
* so an rmmod() cannot pull a module out from under a dlopen(), or from
|
|
|
|
|
* under a module that names it in DT_NEEDED.
|
libc/dlfcn: Count opens so a library can be shared.
dlopen() of a library that is already loaded fails. libelf_insert()
rejects a name that is already in the module registry with EEXIST, and
dlinsert() passes that straight out, so the second caller gets NULL.
POSIX says dlopen() shall return a handle to the object, and there is no
way today for two modules to hold the same library at once -- which is
what a shared library is for.
So dlopen() now takes another reference on a library that is already
there, and dlclose() only tears it down when the last handle goes. The
count lives in the dlfcn layer rather than in libelf_insert() so that
insmod keeps its own behaviour: a second insmod of the same name still
fails with EEXIST, which is right for a kernel module.
The module name is what makes any of this possible, and a PROTECTED build
did not have one. Names were defined for CONFIG_BUILD_FLAT or the kernel
side of a split build, on the reasoning that only the kernel needed them,
which predates dlopen() being usable from user space. Without a name the
user-space copy of libelf cannot recognise a second open of a library,
cannot count opens, and cannot make dlclose() mean anything -- two
dlopen()s there produce two independent copies of the library and lose
track of the first. Names are therefore defined wherever CONFIG_LIBC_DLFCN
is, which costs NAME_MAX per loaded module in that configuration.
The path no longer has to be copied either. The module name is the
basename of the file and libelf_insert() takes it as a const string, so
dlinsert() finds it with strrchr() instead of handing a writable
duplicate of the whole path to basename().
BUILD_KERNEL is deliberately untouched. dlopen() returns NULL there
unconditionally: dlinsert() is a stub, because sharing a library between
processes with separate address spaces needs the text in a shared region
and the data per process at a matching virtual address, which is a
different problem from this one.
Built for mps3-an547:picostest with and without CONFIG_LIBC_DLFCN, and
for stm32f4discovery:kostest, a PROTECTED configuration, with it enabled.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-03 12:05:40 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (modp->nopen > 1)
|
|
|
|
|
{
|
|
|
|
|
modp->nopen--;
|
|
|
|
|
libelf_registry_unlock();
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
modp->nopen = 0;
|
|
|
|
|
|
2025-04-10 09:51:25 +08:00
|
|
|
ret = libelf_uninit(modp);
|
2024-07-10 20:49:28 +08:00
|
|
|
if (ret < 0)
|
|
|
|
|
{
|
|
|
|
|
berr("ERROR: Failed to uninitialize module %d\n", ret);
|
|
|
|
|
goto errout_with_lock;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-29 19:59:34 +08:00
|
|
|
/* Remove the module from the registry */
|
|
|
|
|
|
2025-04-10 09:51:25 +08:00
|
|
|
ret = libelf_registry_del(modp);
|
2024-04-29 19:59:34 +08:00
|
|
|
if (ret < 0)
|
|
|
|
|
{
|
|
|
|
|
berr("ERROR: Failed to remove the module from the registry: %d\n",
|
|
|
|
|
ret);
|
|
|
|
|
goto errout_with_lock;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-10 09:51:25 +08:00
|
|
|
libelf_registry_unlock();
|
2024-04-29 19:59:34 +08:00
|
|
|
|
|
|
|
|
/* And free the registry entry */
|
|
|
|
|
|
2026-09-16 15:51:04 +08:00
|
|
|
lib_free(modp);
|
|
|
|
|
|
2024-07-10 20:49:28 +08:00
|
|
|
return ret;
|
2024-04-29 19:59:34 +08:00
|
|
|
|
|
|
|
|
errout_with_lock:
|
2025-04-10 09:51:25 +08:00
|
|
|
libelf_registry_unlock();
|
2024-04-29 19:59:34 +08:00
|
|
|
set_errno(-ret);
|
|
|
|
|
return ERROR;
|
|
|
|
|
}
|