mirror of
https://github.com/apache/nuttx.git
synced 2026-10-08 23:05:20 +00:00
Some checks are pending
Build Documentation / build-html (push) Waiting to run
MemBrowse Memory Report / changes-filter (push) Waiting to run
MemBrowse Memory Report / load-targets (push) Waiting to run
MemBrowse Memory Report / identical (push) Blocked by required conditions
MemBrowse Memory Report / analyze (push) Blocked by required conditions
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>
192 lines
5.9 KiB
C
192 lines
5.9 KiB
C
/****************************************************************************
|
|
* libs/libc/elf/elf_depend.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* 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 <stdlib.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <nuttx/debug.h>
|
|
|
|
#include <nuttx/lib/elf.h>
|
|
|
|
#if CONFIG_LIBC_ELF_MAXDEPEND > 0
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: libelf_depend
|
|
*
|
|
* Description:
|
|
* Set up module dependencies between the exporter and the importer of a
|
|
* symbol. The happens when the module is installed via insmod and a
|
|
* symbol is imported from another module.
|
|
*
|
|
* Returned Value:
|
|
* 0 (OK) is returned on success and a negated errno is returned on
|
|
* failure.
|
|
*
|
|
* Assumptions:
|
|
* Caller holds the registry lock.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int libelf_depend(FAR struct module_s *importer,
|
|
FAR struct module_s *exporter)
|
|
{
|
|
int freendx = -1;
|
|
int i;
|
|
|
|
DEBUGASSERT(importer != NULL && exporter != NULL);
|
|
|
|
/* First checker if the exported is already in our list if dependencies.
|
|
* This would happen if we are importing multiple symbols from the same
|
|
* exporting module. In that case, the module would already be in the
|
|
* list of dependencies.
|
|
*
|
|
* The list dependency list is a a dumb, unpacked array of pointers. This
|
|
* should not be too inefficient if the number of CONFIG_LIBC_ELF_MAXDEPEND
|
|
* is small. Otherwise, a more dynamic data structure would be in order.
|
|
*/
|
|
|
|
for (i = 0; i < CONFIG_LIBC_ELF_MAXDEPEND; i++)
|
|
{
|
|
FAR const struct module_s *modp;
|
|
|
|
/* Check if this dependency slot is available. */
|
|
|
|
modp = importer->dependencies[i];
|
|
if (modp == NULL)
|
|
{
|
|
/* Remember this slot for use the module is NOT already in the
|
|
* list of dependencies.
|
|
*/
|
|
|
|
freendx = i;
|
|
}
|
|
else if (modp == exporter)
|
|
{
|
|
/* Yes, we are already importing symbols from this module. Nothing
|
|
* more needs to be done.
|
|
*/
|
|
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
/* If we get here, then (1) this is a new exporting module that does not
|
|
* already appear in the list of dependencies, and (2) freendx is the
|
|
* index to the last free slot in the dependency list. If freendx is
|
|
* negative, then the dependency list is full.
|
|
*/
|
|
|
|
if (freendx >= 0)
|
|
{
|
|
/* Increment the count of dependencies on the exporter module */
|
|
|
|
DEBUGASSERT(exporter->dependents < UINT8_MAX);
|
|
if (exporter->dependents >= UINT8_MAX)
|
|
{
|
|
return -ENOSPC;
|
|
}
|
|
|
|
exporter->dependents++;
|
|
|
|
/* And remember the exporter so that we can decrement the count of
|
|
* dependents if the importer is removed.
|
|
*/
|
|
|
|
DEBUGASSERT(importer->dependencies[freendx] == NULL);
|
|
importer->dependencies[freendx] = exporter;
|
|
return OK;
|
|
}
|
|
|
|
/* If we get there then the list of dependencies is full. */
|
|
|
|
DEBUGPANIC();
|
|
return -ENFILE;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: libelf_undepend
|
|
*
|
|
* Description:
|
|
* Tear down module dependencies between the exporters and the importer of
|
|
* symbols. This happens when the module is removed via rmmod (and on
|
|
* error handling cases in insmod).
|
|
*
|
|
* Returned Value:
|
|
* 0 (OK) is returned on success and a negated errno is returned on
|
|
* failure.
|
|
*
|
|
* Assumptions:
|
|
* Caller holds the registry lock.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int libelf_undepend(FAR struct module_s *importer)
|
|
{
|
|
FAR struct module_s *exporter;
|
|
int i;
|
|
|
|
DEBUGASSERT(importer != NULL && importer->dependents == 0);
|
|
|
|
/* Decrement the dependency count on each of exporters of symbols used by
|
|
* this importer module. This is an unpacked array of pointers. This
|
|
* should not be too inefficient if the number of CONFIG_LIBC_ELF_MAXDEPEND
|
|
* is small. Otherwise, a more dynamic data structure would be in order.
|
|
*/
|
|
|
|
for (i = 0; i < CONFIG_LIBC_ELF_MAXDEPEND; i++)
|
|
{
|
|
exporter = importer->dependencies[i];
|
|
if (exporter != NULL)
|
|
{
|
|
DEBUGASSERT(exporter->dependents > 0);
|
|
if (exporter->dependents > 0)
|
|
{
|
|
exporter->dependents--;
|
|
}
|
|
|
|
importer->dependencies[i] = NULL;
|
|
|
|
/* A library loaded for a DT_NEEDED entry is held only by the
|
|
* modules that depend on it, so the last of them unloads it.
|
|
*/
|
|
|
|
if (exporter->dependents == 0 && exporter->nopen == 0)
|
|
{
|
|
libelf_remove(exporter);
|
|
}
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
#endif
|