nuttx/libs/libc/elf/elf_bind.c
Marco Casaroli dcd93b0f67
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
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-10-06 17:48:49 -03:00

1507 lines
44 KiB
C

/****************************************************************************
* libs/libc/elf/elf_bind.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 <stdint.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <inttypes.h>
#include <nuttx/debug.h>
#include <nuttx/envpath.h>
#include <nuttx/arch.h>
#include <nuttx/cache.h>
#include <nuttx/elf.h>
#include <nuttx/symtab.h>
#include <nuttx/lib/elf.h>
#include "libc.h"
#include "elf/elf.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* With an address environment the program lives in its own address space,
* which a library loaded by libelf_insert() cannot reach.
*/
#if CONFIG_LIBC_ELF_MAXDEPEND > 0 && !defined(CONFIG_ARCH_ADDRENV)
# define LIBELF_NEEDED
#endif
#define I_REL 0 /* Index into relxxx[] arrays for relocations */
#define I_PLT 1 /* ... for PLTs */
#define N_RELS 2 /* Number of relxxx[] indexes */
#ifdef ARCH_ELFDATA
# define ARCH_ELFDATA_DEF arch_elfdata_t arch_data; \
memset(&arch_data, 0, sizeof(arch_elfdata_t))
# define ARCH_ELFDATA_PARM &arch_data
#else
# define ARCH_ELFDATA_DEF
# define ARCH_ELFDATA_PARM NULL
#endif
/* Move loader state in and out of the arch_data block, and say which
* relocation table is being walked. Nothing for an architecture whose
* relocations do not need any of it.
*/
#if defined(ARCH_ELFDATA) && defined(ARCH_ELFDATA_SET_PLTREL)
# define ARCH_ELFDATA_PLTREL(v) ARCH_ELFDATA_SET_PLTREL(&arch_data, v)
#else
# define ARCH_ELFDATA_PLTREL(v) ((void)(v))
#endif
#if defined(ARCH_ELFDATA) && defined(ARCH_ELFDATA_SET_SYMISDESC)
# define ARCH_ELFDATA_SYMISDESC(v) ARCH_ELFDATA_SET_SYMISDESC(&arch_data, v)
#else
# define ARCH_ELFDATA_SYMISDESC(v) ((void)(v))
#endif
#if defined(ARCH_ELFDATA) && defined(ARCH_ELFDATA_INIT)
# define ARCH_ELFDATA_SETUP(l) ARCH_ELFDATA_INIT(&arch_data, l)
# define ARCH_ELFDATA_TEARDOWN(l) ARCH_ELFDATA_FINI(&arch_data, l)
#else
# define ARCH_ELFDATA_SETUP(l)
# define ARCH_ELFDATA_TEARDOWN(l)
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/* REVISIT: This naming breaks the NuttX coding standard, but is consistent
* with legacy naming of other ELF types.
*/
typedef struct
{
dq_entry_t entry;
Elf_Sym sym;
int idx;
} Elf_SymCache;
/* Where a dynamic object's relocation tables live. Per load, not per file:
* loading a DT_NEEDED library re-enters this function, and a shared
* instance would be overwritten by the nested load.
*/
struct reldata_s
{
int stroff; /* offset to string table */
int symoff; /* offset to symbol table */
int lsymtab; /* size of symbol table */
int relentsz[2]; /* size of relocation entry */
int reloff[2]; /* offset to the relocation section */
int relsz[2]; /* size of relocation table */
int relrela[2]; /* type of relocation type - 0: DT_REL / 1: DT_RELA */
};
/****************************************************************************
* Private Functions
****************************************************************************/
#ifdef LIBELF_NEEDED
/****************************************************************************
* Name: libelf_insertneeded
*
* Description:
* Load a library a module names in DT_NEEDED. A bare name is looked for
* along LD_LIBRARY_PATH, as dlopen() looks for it, and the module is
* registered under its base name, so a library two modules name is loaded
* once and reference counted.
*
* Returned Value:
* The handle of the loaded library, or NULL.
*
****************************************************************************/
static FAR void *libelf_insertneeded(FAR const char *name)
{
FAR const char *modname;
modname = strrchr(name, '/');
modname = modname != NULL ? modname + 1 : name;
#ifdef CONFIG_LIBC_ENVPATH
if (name[0] != '/')
{
FAR void *handle = NULL;
FAR char *fullpath;
ENVPATH_HANDLE env;
env = envpath_init("LD_LIBRARY_PATH");
if (env != NULL)
{
while ((fullpath = envpath_next(env, name)) != NULL)
{
handle = libelf_insert(fullpath, modname);
lib_free(fullpath);
if (handle != NULL)
{
break;
}
}
envpath_release(env);
}
return handle;
}
#endif
return libelf_insert(name, modname);
}
#endif
/****************************************************************************
* Name: libelf_readrels
*
* Description:
* Read the (ELF_Rel structure * buffer count) into memory.
*
****************************************************************************/
static inline int libelf_readrels(FAR struct mod_loadinfo_s *loadinfo,
FAR const Elf_Shdr *relsec,
int index, FAR Elf_Rel *rels,
int count)
{
off_t offset;
int size;
/* Verify that the symbol table index lies within symbol table */
if (index < 0 || index > (relsec->sh_size / sizeof(Elf_Rel)))
{
berr("ERROR: Bad relocation symbol index: %d\n", index);
return -EINVAL;
}
/* Get the file offset to the symbol table entry */
offset = sizeof(Elf_Rel) * index;
size = sizeof(Elf_Rel) * count;
if (offset + size > relsec->sh_size)
{
size = relsec->sh_size - offset;
}
/* And, finally, read the symbol table entry into memory */
return libelf_read(loadinfo, (FAR uint8_t *)rels, size,
relsec->sh_offset + offset);
}
/****************************************************************************
* Name: libelf_readrelas
*
* Description:
* Read the (ELF_Rela structure * buffer count) into memory.
*
****************************************************************************/
static inline int libelf_readrelas(FAR struct mod_loadinfo_s *loadinfo,
FAR const Elf_Shdr *relsec,
int index, FAR Elf_Rela *relas,
int count)
{
off_t offset;
int size;
/* Verify that the symbol table index lies within symbol table */
if (index < 0 || index > (relsec->sh_size / sizeof(Elf_Rela)))
{
berr("ERROR: Bad relocation symbol index: %d\n", index);
return -EINVAL;
}
/* Get the file offset to the symbol table entry */
offset = sizeof(Elf_Rela) * index;
size = sizeof(Elf_Rela) * count;
if (offset + size > relsec->sh_size)
{
size = relsec->sh_size - offset;
}
/* And, finally, read the symbol table entry into memory */
return libelf_read(loadinfo, (FAR uint8_t *)relas, size,
relsec->sh_offset + offset);
}
/****************************************************************************
* Name: libelf_relocate and libelf_relocateadd
*
* Description:
* Perform all relocations associated with a section.
*
* Returned Value:
* 0 (OK) is returned on success and a negated errno is returned on
* failure.
*
****************************************************************************/
static int libelf_relocate(FAR struct module_s *modp,
FAR struct mod_loadinfo_s *loadinfo, int relidx,
FAR const struct symtab_s *exports, int nexports)
{
FAR Elf_Shdr *relsec = &loadinfo->shdr[relidx];
FAR Elf_Shdr *dstsec = &loadinfo->shdr[relsec->sh_info];
FAR Elf_Rel *rels;
FAR Elf_Rel *rel;
FAR Elf_SymCache *cache;
FAR Elf_Sym *sym;
FAR dq_entry_t *e;
dq_queue_t q;
uintptr_t addr;
int symidx;
int ret = OK;
int i;
int j;
/* Define potential architecture specific elf data container */
ARCH_ELFDATA_DEF;
rels = lib_malloc(CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT *
sizeof(Elf_Rel));
if (!rels)
{
berr("Failed to allocate memory for elf relocation rels\n");
return -ENOMEM;
}
dq_init(&q);
/* Examine each relocation in the section. 'relsec' is the section
* containing the relations. 'dstsec' is the section containing the data
* to be relocated.
*/
for (i = j = 0; i < relsec->sh_size / sizeof(Elf_Rel); i++)
{
/* Read the relocation entry into memory */
rel = &rels[i % CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT];
if (!(i % CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT))
{
ret = libelf_readrels(loadinfo, relsec, i, rels,
CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT);
if (ret < 0)
{
berr("ERROR: Section %d reloc %d: "
"Failed to read relocation entry: %d\n",
relidx, i, ret);
break;
}
}
/* Get the symbol table index for the relocation. This is contained
* in a bit-field within the r_info element.
*/
symidx = ELF_R_SYM(rel->r_info);
/* First try the cache */
sym = NULL;
for (e = dq_peek(&q); e; e = dq_next(e))
{
cache = (FAR Elf_SymCache *)e;
if (cache->idx == symidx)
{
dq_rem(&cache->entry, &q);
dq_addfirst(&cache->entry, &q);
sym = &cache->sym;
break;
}
}
/* If the symbol was not found in the cache, we will need to read the
* symbol from the file.
*/
if (sym == NULL)
{
if (j < CONFIG_LIBC_ELF_SYMBOL_CACHECOUNT)
{
cache = lib_malloc(sizeof(Elf_SymCache));
if (!cache)
{
berr("Failed to allocate memory for elf symbols\n");
ret = -ENOMEM;
break;
}
j++;
}
else
{
cache = (FAR Elf_SymCache *)dq_remlast(&q);
}
sym = &cache->sym;
/* Read the symbol table entry into memory */
ret = libelf_readsym(loadinfo, symidx, sym,
&loadinfo->shdr[loadinfo->symtabidx]);
if (ret < 0)
{
berr("ERROR: Section %d reloc %d: "
"Failed to read symbol[%d]: %d\n",
relidx, i, symidx, ret);
lib_free(cache);
break;
}
/* Get the value of the symbol (in sym.st_value) */
ret = libelf_symvalue(modp, loadinfo, sym,
loadinfo->shdr[loadinfo->strtabidx].sh_offset,
exports, nexports);
if (ret < 0)
{
/* The special error -ESRCH is returned only in one condition:
* The symbol has no name.
*
* There are a few relocations for a few architectures that do
* no depend upon a named symbol. We don't know if that is the
* case here, but we will use a NULL symbol pointer to indicate
* that case to up_relocate(). That function can then do what
* is best.
*/
if (ret == -ESRCH)
{
berr("ERROR: Section %d reloc %d: "
"Undefined symbol[%d] has no name: %d\n",
relidx, i, symidx, ret);
}
else
{
berr("ERROR: Section %d reloc %d: "
"Failed to get value of symbol[%d]: %d\n",
relidx, i, symidx, ret);
lib_free(cache);
break;
}
}
cache->idx = symidx;
dq_addfirst(&cache->entry, &q);
}
if (sym->st_shndx == SHN_UNDEF && sym->st_name == 0)
{
sym = NULL;
}
/* Calculate the relocation address. */
if (loadinfo->gotsize != 0)
{
if (sym->st_shndx == SHN_UNDEF)
{
/* Symbol type is undefined, we need to set the address
* to the value of the symbol.
*/
FAR uintptr_t *gotaddr = (FAR uintptr_t *)(loadinfo->gotbase +
*((FAR uintptr_t *)(dstsec->sh_addr + rel->r_offset)));
*gotaddr = sym->st_value;
continue;
}
if ((dstsec->sh_flags & SHF_WRITE) == 0)
{
/* Skip relocations for read-only sections */
continue;
}
/* Use the GOT to store the address */
if (rel->r_offset - dstsec->sh_offset >
dstsec->sh_size)
{
berr("ERROR: Section %d reloc %d: "
"Relocation address out of range, "
"offset %" PRIuPTR " size %ju\n",
relidx, i, (uintptr_t)rel->r_offset,
(uintmax_t)dstsec->sh_size);
ret = -EINVAL;
break;
}
addr = dstsec->sh_addr + rel->r_offset - dstsec->sh_offset;
if (ELF_ST_TYPE(sym->st_info) == STT_SECTION)
{
/* Symbol type is section, we need clear the address
* and keep the original value.
*/
*(FAR uintptr_t *)addr -=
loadinfo->shdr[sym->st_shndx].sh_offset;
}
else
{
/* Normal symbol, just keep it zero */
*(FAR uintptr_t *)addr = 0;
}
}
else
{
if (rel->r_offset > dstsec->sh_size)
{
berr("ERROR: Section %d reloc %d: "
"Relocation address out of range, "
"offset %" PRIuPTR " size %ju\n",
relidx, i, (uintptr_t)rel->r_offset,
(uintmax_t)dstsec->sh_size);
ret = -EINVAL;
break;
}
addr = dstsec->sh_addr + rel->r_offset;
}
/* Now perform the architecture-specific relocation */
ret = up_relocate(rel, sym, addr, ARCH_ELFDATA_PARM);
if (ret < 0)
{
berr("ERROR: Section %d reloc %d: Relocation failed: %d\n",
relidx, i, ret);
break;
}
}
lib_free(rels);
while ((e = dq_peek(&q)) != NULL)
{
dq_rem(e, &q);
lib_free(e);
}
return ret;
}
static int libelf_relocateadd(FAR struct module_s *modp,
FAR struct mod_loadinfo_s *loadinfo,
int relidx,
FAR const struct symtab_s *exports,
int nexports)
{
FAR Elf_Shdr *relsec = &loadinfo->shdr[relidx];
FAR Elf_Shdr *dstsec = &loadinfo->shdr[relsec->sh_info];
FAR Elf_Rela *relas;
FAR Elf_Rela *rela;
FAR Elf_SymCache *cache;
FAR Elf_Sym *sym;
FAR dq_entry_t *e;
dq_queue_t q;
uintptr_t addr;
int symidx;
int ret = OK;
int i;
int j;
/* Define potential architecture specific elf data container */
ARCH_ELFDATA_DEF;
relas = lib_malloc(CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT *
sizeof(Elf_Rela));
if (!relas)
{
berr("Failed to allocate memory for elf relocation relas\n");
return -ENOMEM;
}
dq_init(&q);
/* Examine each relocation in the section. 'relsec' is the section
* containing the relations. 'dstsec' is the section containing the data
* to be relocated.
*/
for (i = j = 0; i < relsec->sh_size / sizeof(Elf_Rela); i++)
{
/* Read the relocation entry into memory */
rela = &relas[i % CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT];
if (!(i % CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT))
{
ret = libelf_readrelas(loadinfo, relsec, i, relas,
CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT);
if (ret < 0)
{
berr("ERROR: Section %d reloc %d: "
"Failed to read relocation entry: %d\n",
relidx, i, ret);
break;
}
}
/* Get the symbol table index for the relocation. This is contained
* in a bit-field within the r_info element.
*/
symidx = ELF_R_SYM(rela->r_info);
/* First try the cache */
sym = NULL;
for (e = dq_peek(&q); e; e = dq_next(e))
{
cache = (FAR Elf_SymCache *)e;
if (cache->idx == symidx)
{
dq_rem(&cache->entry, &q);
dq_addfirst(&cache->entry, &q);
sym = &cache->sym;
break;
}
}
/* If the symbol was not found in the cache, we will need to read the
* symbol from the file.
*/
if (sym == NULL)
{
if (j < CONFIG_LIBC_ELF_SYMBOL_CACHECOUNT)
{
cache = lib_malloc(sizeof(Elf_SymCache));
if (!cache)
{
berr("Failed to allocate memory for elf symbols\n");
ret = -ENOMEM;
break;
}
j++;
}
else
{
cache = (FAR Elf_SymCache *)dq_remlast(&q);
}
sym = &cache->sym;
/* Read the symbol table entry into memory */
ret = libelf_readsym(loadinfo, symidx, sym,
&loadinfo->shdr[loadinfo->symtabidx]);
if (ret < 0)
{
berr("ERROR: Section %d reloc %d: "
"Failed to read symbol[%d]: %d\n",
relidx, i, symidx, ret);
lib_free(cache);
break;
}
/* Get the value of the symbol (in sym.st_value) */
ret = libelf_symvalue(modp, loadinfo, sym,
loadinfo->shdr[loadinfo->strtabidx].sh_offset,
exports, nexports);
if (ret < 0)
{
/* The special error -ESRCH is returned only in one condition:
* The symbol has no name.
*
* There are a few relocations for a few architectures that do
* no depend upon a named symbol. We don't know if that is the
* case here, but we will use a NULL symbol pointer to indicate
* that case to up_relocate(). That function can then do what
* is best.
*/
if (ret == -ESRCH)
{
berr("ERROR: Section %d reloc %d: "
"Undefined symbol[%d] has no name: %d\n",
relidx, i, symidx, ret);
}
else
{
berr("ERROR: Section %d reloc %d: "
"Failed to get value of symbol[%d]: %d\n",
relidx, i, symidx, ret);
lib_free(cache);
break;
}
}
cache->idx = symidx;
dq_addfirst(&cache->entry, &q);
}
if (sym->st_shndx == SHN_UNDEF && sym->st_name == 0)
{
sym = NULL;
}
/* Calculate the relocation address. */
if (rela->r_offset < 0 ||
rela->r_offset > dstsec->sh_size)
{
berr("ERROR: Section %d reloc %d: "
"Relocation address out of range, "
"offset %" PRIuPTR " size %ju\n",
relidx, i, (uintptr_t)rela->r_offset,
(uintmax_t)dstsec->sh_size);
ret = -EINVAL;
break;
}
addr = dstsec->sh_addr + rela->r_offset;
/* Now perform the architecture-specific relocation */
ret = up_relocateadd(rela, sym, addr, ARCH_ELFDATA_PARM);
if (ret < 0)
{
berr("ERROR: Section %d reloc %d: Relocation failed: %d\n",
relidx, i, ret);
break;
}
}
lib_free(relas);
while ((e = dq_peek(&q)) != NULL)
{
dq_rem(e, &q);
lib_free(e);
}
return ret;
}
/****************************************************************************
* Name: libelf_fileoff
*
* Description:
* Translate a link-time address named by a dynamic tag into its offset in
* the file. They are the same only when its segment starts at offset 0.
*
****************************************************************************/
static off_t libelf_fileoff(FAR struct mod_loadinfo_s *loadinfo,
uintptr_t vaddr)
{
int i;
for (i = 0; loadinfo->phdr != NULL && i < loadinfo->ehdr.e_phnum; i++)
{
FAR Elf_Phdr *phdr = &loadinfo->phdr[i];
if (phdr->p_type == PT_LOAD && vaddr >= phdr->p_vaddr &&
vaddr - phdr->p_vaddr < phdr->p_filesz)
{
return phdr->p_offset + (vaddr - phdr->p_vaddr);
}
}
return vaddr;
}
/****************************************************************************
* Name: libelf_relocatedyn
*
* Description:
* Perform all relocations associated with a dynamic section.
*
* Returned Value:
* 0 (OK) is returned on success and a negated errno is returned on
* failure.
*
****************************************************************************/
static int libelf_relocatedyn(FAR struct module_s *modp,
FAR struct mod_loadinfo_s *loadinfo,
int relidx,
FAR const struct symtab_s *exports,
int nexports)
{
FAR Elf_Shdr *shdr = &loadinfo->shdr[relidx];
FAR Elf_Shdr *symhdr;
FAR Elf_Dyn *dyn = NULL;
FAR Elf_Rel *rels = NULL;
FAR Elf_Rel *rel;
FAR Elf_Rela *relas = NULL;
FAR Elf_Rela *rela;
FAR Elf_Sym *sym = NULL;
uintptr_t addr;
int ret;
int i;
int idx_rel;
int idx_sym;
#ifdef LIBELF_NEEDED
int j;
uintptr_t libs[CONFIG_LIBC_ELF_MAXDEPEND];
int nlibs = 0;
#endif
struct reldata_s reldata;
bool symfromlib;
/* Define potential architecture specific elf data container */
ARCH_ELFDATA_DEF;
dyn = lib_malloc(shdr->sh_size);
if (dyn == NULL)
{
berr("Failed to allocate memory for elf dynamic section\n");
return -ENOMEM;
}
ret = libelf_read(loadinfo, (FAR uint8_t *)dyn, shdr->sh_size,
shdr->sh_offset);
if (ret < 0)
{
berr("Failed to read dynamic section header");
lib_free(dyn);
return ret;
}
/* Assume DT_RELA to get maximum size required */
rels = lib_zalloc(CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT *
sizeof(Elf_Rela));
if (!rels)
{
berr("Failed to allocate memory for elf relocation rels\n");
lib_free(dyn);
return -ENOMEM;
}
memset((FAR void *)&reldata, 0, sizeof(reldata));
relas = (FAR Elf_Rela *)rels;
for (i = 0; dyn[i].d_tag != DT_NULL; i++)
{
switch (dyn[i].d_tag)
{
case DT_REL:
reldata.reloff[I_REL] = libelf_fileoff(loadinfo,
dyn[i].d_un.d_ptr);
break;
case DT_RELSZ:
reldata.relsz[I_REL] = dyn[i].d_un.d_val;
break;
case DT_RELENT:
reldata.relentsz[I_REL] = dyn[i].d_un.d_val;
break;
case DT_SYMTAB:
reldata.symoff = dyn[i].d_un.d_val;
break;
case DT_STRTAB:
reldata.stroff = dyn[i].d_un.d_val;
break;
case DT_JMPREL:
reldata.reloff[I_PLT] = libelf_fileoff(loadinfo,
dyn[i].d_un.d_ptr);
break;
case DT_PLTRELSZ:
reldata.relsz[I_PLT] = dyn[i].d_un.d_val;
break;
case DT_NEEDED:
#ifdef LIBELF_NEEDED
/* Remember it; the name lives in the string table, which is
* not located until the loop has seen DT_STRTAB.
*/
if (nlibs >= CONFIG_LIBC_ELF_MAXDEPEND)
{
berr("ERROR: More than %d DT_NEEDED entries\n",
CONFIG_LIBC_ELF_MAXDEPEND);
lib_free(sym);
lib_free(rels);
lib_free(dyn);
return -ENOEXEC;
}
libs[nlibs++] = dyn[i].d_un.d_val;
break;
#else
berr("ERROR: Cannot load a DT_NEEDED library\n");
lib_free(sym);
lib_free(rels);
lib_free(dyn);
return -ENOSYS;
#endif
case DT_PLTGOT:
/* The object's data base. Every function descriptor built
* for it names this base.
*/
loadinfo->gotbase = libelf_addr(loadinfo,
dyn[i].d_un.d_ptr);
break;
/* The constructor and destructor tables. Section headers are
* optional, so the dynamic tags are the authoritative copy.
*/
case DT_INIT_ARRAY:
loadinfo->initarr = libelf_addr(loadinfo, dyn[i].d_un.d_ptr);
break;
case DT_INIT_ARRAYSZ:
loadinfo->ninit = dyn[i].d_un.d_val / sizeof(uintptr_t);
break;
case DT_FINI_ARRAY:
loadinfo->finiarr = libelf_addr(loadinfo, dyn[i].d_un.d_ptr);
break;
case DT_FINI_ARRAYSZ:
loadinfo->nfini = dyn[i].d_un.d_val / sizeof(uintptr_t);
break;
case DT_PREINIT_ARRAY:
loadinfo->preiarr = libelf_addr(loadinfo, dyn[i].d_un.d_ptr);
break;
case DT_PREINIT_ARRAYSZ:
loadinfo->nprei = dyn[i].d_un.d_val / sizeof(uintptr_t);
break;
case DT_PLTREL:
if (dyn[i].d_un.d_val == DT_REL)
{
reldata.relentsz[I_PLT] = sizeof(Elf_Rel);
reldata.relrela[I_PLT] = 0;
}
else if (loadinfo->fdpic)
{
/* The ARM FDPIC ABI is REL throughout. RELA entries are
* longer, so walking them as REL reads the wrong place.
*/
berr("ERROR: FDPIC object claims RELA PLT relocations\n");
lib_free(sym);
lib_free(rels);
lib_free(dyn);
return -ENOEXEC;
}
else
{
reldata.relentsz[I_PLT] = sizeof(Elf_Rela);
reldata.relrela[I_PLT] = 1;
}
break;
}
}
/* 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: the linker puts it
* immediately after the dynamic section.
*/
if (loadinfo->fdpic && loadinfo->gotbase == 0)
{
loadinfo->gotbase = libelf_addr(loadinfo,
shdr->sh_addr + shdr->sh_size);
binfo("No DT_PLTGOT; taking the GOT at %08" PRIxPTR "\n",
loadinfo->gotbase);
}
/* Load whatever the object names in DT_NEEDED. This is what dlopen()
* does, through the same libelf_insert(), but the loader is also the
* kernel's module loader, which has no dlfcn.
*/
#ifdef LIBELF_NEEDED
symhdr = &loadinfo->shdr[loadinfo->dsymtabidx];
for (i = 0; i < nlibs; i++)
{
Elf_Sym namesym;
FAR void *handle;
/* The name is a string table offset, which is what st_name is, so
* the existing reader can fetch it.
*/
memset(&namesym, 0, sizeof(namesym));
namesym.st_name = libs[i];
ret = libelf_symname(loadinfo, &namesym,
loadinfo->shdr[symhdr->sh_link].sh_offset);
if (ret < 0)
{
berr("ERROR: DT_NEEDED %d has no name\n", i);
lib_free(sym);
lib_free(rels);
lib_free(dyn);
return ret;
}
handle = libelf_insertneeded((FAR const char *)loadinfo->iobuffer);
if (handle == NULL)
{
berr("ERROR: Cannot open needed library %s\n",
(FAR char *)loadinfo->iobuffer);
lib_free(sym);
lib_free(rels);
lib_free(dyn);
return -ELIBACC;
}
binfo("Opened needed library %s\n", (FAR char *)loadinfo->iobuffer);
/* The dependency holds the library from now on, in place of the
* reference libelf_insert() took, so libelf_undepend() lets it go.
*/
libelf_registry_lock();
ret = libelf_depend(modp, handle);
if (ret >= 0)
{
((FAR struct module_s *)handle)->nopen--;
}
libelf_registry_unlock();
if (ret < 0)
{
berr("ERROR: Cannot depend on %s: %d\n",
(FAR char *)loadinfo->iobuffer, ret);
libelf_remove(handle);
lib_free(sym);
lib_free(rels);
lib_free(dyn);
return ret;
}
}
#endif
/* Must follow the tag loop, which is where DT_PLTGOT is read. Both
* relocation tables are walked under this one arch_data, so a cursor in
* it spans the object.
*/
ARCH_ELFDATA_SETUP(loadinfo);
symhdr = &loadinfo->shdr[loadinfo->dsymtabidx];
sym = lib_malloc(symhdr->sh_size);
if (!sym)
{
berr("Error obtaining storage for dynamic symbol table");
lib_free(rels);
lib_free(dyn);
return -ENOMEM;
}
ret = libelf_read(loadinfo, (FAR uint8_t *)sym, symhdr->sh_size,
symhdr->sh_offset);
if (ret < 0)
{
berr("Error reading dynamic symbol table - %d", ret);
lib_free(sym);
lib_free(rels);
lib_free(dyn);
return ret;
}
reldata.lsymtab = reldata.stroff - reldata.symoff;
for (idx_rel = 0; idx_rel < N_RELS; idx_rel++)
{
int lrelent;
if ((reldata.relsz[idx_rel] == 0) || (reldata.reloff[idx_rel] == 0))
{
continue;
}
/* Examine each relocation in the .rel.* section. */
ret = OK;
lrelent = reldata.relsz[idx_rel] / reldata.relentsz[idx_rel];
/* Say which table this is, for an architecture that cares. */
ARCH_ELFDATA_PLTREL(idx_rel == I_PLT);
for (i = 0; i < lrelent; i++)
{
/* Process each relocation entry
* - we cheat by using the fact the 1st two fields of Elf_Rel
* and Elf_Rela are identical so can do things based on the
* former until it's important
*/
if (reldata.relrela[idx_rel] == 0)
{
rel = &rels[i % CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT];
rela = (Elf_Rela *)rel; /* Just to keep the compiler happy */
}
else
{
rela = &relas[i % CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT];
rel = (Elf_Rel *)rela;
}
if (!(i % CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT))
{
size_t relsize = (sizeof(Elf_Rela) *
CONFIG_LIBC_ELF_RELOCATION_BUFFERCOUNT);
if (reldata.relsz[idx_rel] < relsize)
{
relsize = reldata.relsz[idx_rel];
}
ret = libelf_read(loadinfo, (FAR uint8_t *)rels,
relsize,
reldata.reloff[idx_rel] +
i * sizeof(Elf_Rel));
if (ret < 0)
{
berr("ERROR: Section %d reloc %d:"
"Failed to read relocation entry: %d\n",
relidx, i, ret);
break;
}
}
/* Now perform the architecture-specific relocation */
if ((idx_sym = ELF_R_SYM(rel->r_info)) != 0)
{
/* We have an external reference */
if (sym[idx_sym].st_shndx == SHN_UNDEF)
{
FAR void *ep;
symfromlib = false;
ep = libelf_findglobal(modp, loadinfo, symhdr,
&sym[idx_sym]);
/* libelf_findglobal() searches only the globally
* registered symbols, and has left the name in the
* I/O buffer. Try the modules this one depends on,
* its DT_NEEDED libraries among them, then the table
* exec() supplied.
*/
#ifdef LIBELF_NEEDED
for (j = 0; ep == NULL && j < CONFIG_LIBC_ELF_MAXDEPEND;
j++)
{
FAR struct module_s *dep = modp->dependencies[j];
if (dep != NULL)
{
ep = (FAR void *)
libelf_getsymbol(dep,
(FAR char *)loadinfo->iobuffer);
/* An FDPIC object exports descriptors */
symfromlib = ep != NULL && dep->gotbase != 0;
}
}
#endif
if (ep == NULL && exports != NULL)
{
FAR const struct symtab_s *sm;
sm = symtab_findbyname(exports,
(FAR char *)
loadinfo->iobuffer,
nexports);
if (sm != NULL)
{
ep = (FAR void *)sm->sym_value;
}
}
if ((ep == NULL) && (ELF_ST_BIND(sym[idx_sym].st_info)
!= STB_WEAK))
{
berr("ERROR: Unable to resolve addr of ext ref %s\n",
loadinfo->iobuffer);
ret = -EINVAL;
lib_free(sym);
lib_free(rels);
lib_free(dyn);
return ret;
}
addr = libelf_addr(loadinfo, rel->r_offset);
if (reldata.relrela[idx_rel] == 1)
{
addr += rela->r_addend;
}
/* An import may be a descriptor under FDPIC, which is
* built rather than assigned, so the relocation type
* decides what to write. Everything else stores the
* resolved address, which R_ARM_JUMP_SLOT and
* R_ARM_GLOB_DAT do, so one path serves both.
*/
Elf_Sym extsym =
{
0
};
extsym.st_value = (uintptr_t)ep;
/* Whether the resolved value is itself a descriptor,
* which it is when the symbol came from a library.
*/
ARCH_ELFDATA_SYMISDESC(symfromlib);
ret = up_relocate(rel, &extsym, addr, ARCH_ELFDATA_PARM);
if (ret < 0)
{
berr("ERROR: Section %d reloc %d: "
"Relocation failed: %d\n", relidx, i, ret);
lib_free(sym);
lib_free(rels);
lib_free(dyn);
return ret;
}
}
else if (loadinfo->fdpic)
{
/* A symbol defined inside this object. Its value is
* the symbol's own, translated; the addend stays where
* the relocation type expects it.
*/
Elf_Sym defsym = sym[idx_sym];
defsym.st_value = libelf_addr(loadinfo,
sym[idx_sym].st_value);
ARCH_ELFDATA_SYMISDESC(false);
addr = libelf_addr(loadinfo, rel->r_offset);
if (reldata.relrela[idx_rel] == 1)
{
addr += rela->r_addend;
}
ret = up_relocate(rel, &defsym, addr, ARCH_ELFDATA_PARM);
if (ret < 0)
{
berr("ERROR: Section %d reloc %d: "
"Relocation failed: %d\n", relidx, i, ret);
lib_free(sym);
lib_free(rels);
lib_free(dyn);
return ret;
}
}
}
else
{
Elf_Sym dynsym =
{
0
};
addr = libelf_addr(loadinfo, rel->r_offset);
if (reldata.relrela[idx_rel] == 1)
{
addr += rela->r_addend;
}
dynsym.st_value = libelf_addr(loadinfo,
*(FAR uint32_t *)addr);
ARCH_ELFDATA_SYMISDESC(false);
ret = up_relocate(rel, &dynsym, addr, ARCH_ELFDATA_PARM);
}
if (ret < 0)
{
berr("ERROR: Section %d reloc %d: Relocation failed: %d\n",
relidx, i, ret);
lib_free(sym);
lib_free(rels);
lib_free(dyn);
return ret;
}
}
}
/* Hand back what the relocations consumed. */
ARCH_ELFDATA_TEARDOWN(loadinfo);
lib_free(sym);
lib_free(rels);
lib_free(dyn);
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: libelf_bind
*
* Description:
* Bind the imported symbol names in the loaded module described by
* 'loadinfo' using the exported symbol values provided by
* libelf_setsymtab().
*
* Input Parameters:
* modp - Module state information
* loadinfo - Load state information
* exports - The table of exported symbols
* nexports - The number of symbols in the exports table
*
* Returned Value:
* 0 (OK) is returned on success and a negated errno is returned on
* failure.
*
****************************************************************************/
int libelf_bind(FAR struct module_s *modp,
FAR struct mod_loadinfo_s *loadinfo,
FAR const struct symtab_s *exports, int nexports)
{
int ret;
int i;
#ifdef CONFIG_ARCH_ADDRENV
/* If CONFIG_ARCH_ADDRENV=y, then the loaded ELF lies in a virtual address
* space that may not be in place now. libelf_addrenv_select() will
* temporarily instantiate that address space.
*/
if (loadinfo->addrenv != NULL)
{
ret = libelf_addrenv_select(loadinfo);
if (ret < 0)
{
berr("ERROR: libelf_addrenv_select() failed: %d\n", ret);
return ret;
}
}
#endif
/* Find the symbol and string tables */
ret = libelf_findsymtab(loadinfo);
if (ret < 0)
{
goto errout_with_addrenv;
}
/* Process relocations in every allocated section */
for (i = 1; i < loadinfo->ehdr.e_shnum; i++)
{
/* Get the index to the relocation section */
int infosec = loadinfo->shdr[i].sh_info;
if (infosec >= loadinfo->ehdr.e_shnum)
{
continue;
}
if (loadinfo->ehdr.e_type == ET_DYN)
{
modp->dynamic = 1;
switch (loadinfo->shdr[i].sh_type)
{
case SHT_DYNAMIC:
ret = libelf_relocatedyn(modp, loadinfo, i,
exports, nexports);
break;
case SHT_DYNSYM:
loadinfo->dsymtabidx = i;
break;
case SHT_INIT_ARRAY:
loadinfo->initarr = libelf_addr(loadinfo,
loadinfo->shdr[i].sh_addr);
loadinfo->ninit = loadinfo->shdr[i].sh_size /
sizeof(uintptr_t);
break;
case SHT_FINI_ARRAY:
loadinfo->finiarr = libelf_addr(loadinfo,
loadinfo->shdr[i].sh_addr);
loadinfo->nfini = loadinfo->shdr[i].sh_size /
sizeof(uintptr_t);
break;
case SHT_PREINIT_ARRAY:
loadinfo->preiarr = libelf_addr(loadinfo,
loadinfo->shdr[i].sh_addr);
loadinfo->nprei = loadinfo->shdr[i].sh_size /
sizeof(uintptr_t);
break;
}
if (ret < 0)
{
goto errout_with_addrenv;
}
}
else
{
modp->dynamic = 0;
/* Make sure that the section is allocated. We can't
* relocate sections that were not loaded into memory.
*/
if ((loadinfo->shdr[i].sh_flags & SHF_ALLOC) == 0 &&
(loadinfo->shdr[i].sh_flags & SHF_INFO_LINK) == 0)
{
continue;
}
/* Process the relocations by type */
switch (loadinfo->shdr[i].sh_type)
{
case SHT_REL:
if ((loadinfo->shdr[infosec].sh_flags & SHF_ALLOC) == 0)
{
continue;
}
ret = libelf_relocate(modp, loadinfo, i, exports, nexports);
break;
case SHT_RELA:
if ((loadinfo->shdr[infosec].sh_flags & SHF_ALLOC) == 0)
{
continue;
}
ret = libelf_relocateadd(modp, loadinfo, i, exports,
nexports);
break;
case SHT_INIT_ARRAY:
loadinfo->initarr = loadinfo->shdr[i].sh_addr;
loadinfo->ninit = loadinfo->shdr[i].sh_size /
sizeof(uintptr_t);
break;
case SHT_FINI_ARRAY:
loadinfo->finiarr = loadinfo->shdr[i].sh_addr;
loadinfo->nfini = loadinfo->shdr[i].sh_size /
sizeof(uintptr_t);
break;
}
}
if (ret < 0)
{
goto errout_with_addrenv;
}
}
modp->xipbase = loadinfo->xipbase;
/* Ensure that the I and D caches are coherent before starting the newly
* loaded module by cleaning the D cache (i.e., flushing the D cache
* contents to memory and invalidating the I cache).
*/
if (loadinfo->textsize > 0)
{
up_coherent_dcache(loadinfo->textalloc, loadinfo->textsize);
}
if (loadinfo->datasize > 0)
{
up_coherent_dcache(loadinfo->datastart, loadinfo->datasize);
}
#ifdef CONFIG_ARCH_USE_SEPARATED_SECTION
for (i = 0; loadinfo->ehdr.e_type == ET_REL && i < loadinfo->ehdr.e_shnum;
i++)
{
if (loadinfo->sectalloc[i] == 0)
{
continue;
}
up_coherent_dcache(loadinfo->sectalloc[i], loadinfo->shdr[i].sh_size);
}
#endif
errout_with_addrenv:
#ifdef CONFIG_ARCH_ADDRENV
if (loadinfo->addrenv != NULL)
{
int status = libelf_addrenv_restore(loadinfo);
if (status < 0)
{
berr("ERROR: libelf_addrenv_restore() failed: %d\n", status);
if (ret == OK)
{
ret = status;
}
}
}
#endif
return ret;
}