glibc/elf/dl-lookup-direct.c

117 lines
4.2 KiB
C
Raw Normal View History

elf: Implement __libc_early_init This function is defined in libc.so, and the dynamic loader calls right after relocation has been finished, before any ELF constructors or the preinit function is invoked. It is also used in the static build for initializing parts of the static libc. To locate __libc_early_init, a direct symbol lookup function is used, _dl_lookup_direct. It does not search the entire symbol scope and consults merely a single link map. This function could also be used to implement lookups in the vDSO (as an optimization). A per-namespace variable (libc_map) is added for locating libc.so, to avoid repeated traversals of the search scope. It is similar to GL(dl_initfirst). An alternative would have been to thread a context argument from _dl_open down to _dl_map_object_from_fd (where libc.so is identified). This could have avoided the global variable, but the change would be larger as a result. It would not have been possible to use this to replace GL(dl_initfirst) because that global variable is used to pass the function pointer past the stack switch from dl_main to the main program. Replacing that requires adding a new argument to _dl_init, which in turn needs changes to the architecture-specific libc.so startup code written in assembler. __libc_early_init should not be used to replace _dl_var_init (as it exists today on some architectures). Instead, _dl_lookup_direct should be used to look up a new variable symbol in libc.so, and that should then be initialized from the dynamic loader, immediately after the object has been loaded in _dl_map_object_from_fd (before relocation is run). This way, more IFUNC resolvers which depend on these variables will work. Reviewed-by: Carlos O'Donell <carlos@redhat.com>
2020-04-25 04:31:15 +08:00
/* Look up a symbol in a single specified object.
Copyright (C) 1995-2021 Free Software Foundation, Inc.
elf: Implement __libc_early_init This function is defined in libc.so, and the dynamic loader calls right after relocation has been finished, before any ELF constructors or the preinit function is invoked. It is also used in the static build for initializing parts of the static libc. To locate __libc_early_init, a direct symbol lookup function is used, _dl_lookup_direct. It does not search the entire symbol scope and consults merely a single link map. This function could also be used to implement lookups in the vDSO (as an optimization). A per-namespace variable (libc_map) is added for locating libc.so, to avoid repeated traversals of the search scope. It is similar to GL(dl_initfirst). An alternative would have been to thread a context argument from _dl_open down to _dl_map_object_from_fd (where libc.so is identified). This could have avoided the global variable, but the change would be larger as a result. It would not have been possible to use this to replace GL(dl_initfirst) because that global variable is used to pass the function pointer past the stack switch from dl_main to the main program. Replacing that requires adding a new argument to _dl_init, which in turn needs changes to the architecture-specific libc.so startup code written in assembler. __libc_early_init should not be used to replace _dl_var_init (as it exists today on some architectures). Instead, _dl_lookup_direct should be used to look up a new variable symbol in libc.so, and that should then be initialized from the dynamic loader, immediately after the object has been loaded in _dl_map_object_from_fd (before relocation is run). This way, more IFUNC resolvers which depend on these variables will work. Reviewed-by: Carlos O'Donell <carlos@redhat.com>
2020-04-25 04:31:15 +08:00
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <ldsodefs.h>
#include <string.h>
#include <elf_machine_sym_no_match.h>
#include <dl-hash.h>
/* This function corresponds to do_lookup_x in elf/dl-lookup.c. The
variant here is simplified because it requires symbol
versioning. */
static const ElfW(Sym) *
check_match (const struct link_map *const map, const char *const undef_name,
const char *version, uint32_t version_hash,
const Elf_Symndx symidx)
{
const ElfW(Sym) *symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
const ElfW(Sym) *sym = &symtab[symidx];
unsigned int stt = ELFW(ST_TYPE) (sym->st_info);
if (__glibc_unlikely ((sym->st_value == 0 /* No value. */
&& sym->st_shndx != SHN_ABS
&& stt != STT_TLS)
|| elf_machine_sym_no_match (sym)))
return NULL;
/* Ignore all but STT_NOTYPE, STT_OBJECT, STT_FUNC,
STT_COMMON, STT_TLS, and STT_GNU_IFUNC since these are no
code/data definitions. */
#define ALLOWED_STT \
((1 << STT_NOTYPE) | (1 << STT_OBJECT) | (1 << STT_FUNC) \
| (1 << STT_COMMON) | (1 << STT_TLS) | (1 << STT_GNU_IFUNC))
if (__glibc_unlikely (((1 << stt) & ALLOWED_STT) == 0))
return NULL;
const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
if (strcmp (strtab + sym->st_name, undef_name) != 0)
/* Not the symbol we are looking for. */
return NULL;
ElfW(Half) ndx = map->l_versyms[symidx] & 0x7fff;
if (map->l_versions[ndx].hash != version_hash
|| strcmp (map->l_versions[ndx].name, version) != 0)
/* It's not the version we want. */
return NULL;
return sym;
}
/* This function corresponds to do_lookup_x in elf/dl-lookup.c. The
variant here is simplified because it does not search object
dependencies. It is optimized for a successful lookup. */
const ElfW(Sym) *
_dl_lookup_direct (struct link_map *map,
const char *undef_name, uint32_t new_hash,
const char *version, uint32_t version_hash)
{
const ElfW(Addr) *bitmask = map->l_gnu_bitmask;
if (__glibc_likely (bitmask != NULL))
{
Elf32_Word bucket = map->l_gnu_buckets[new_hash % map->l_nbuckets];
if (bucket != 0)
{
const Elf32_Word *hasharr = &map->l_gnu_chain_zero[bucket];
do
if (((*hasharr ^ new_hash) >> 1) == 0)
{
Elf_Symndx symidx = ELF_MACHINE_HASH_SYMIDX (map, hasharr);
const ElfW(Sym) *sym = check_match (map, undef_name,
version, version_hash,
symidx);
if (sym != NULL)
return sym;
}
while ((*hasharr++ & 1u) == 0);
}
}
else
{
/* Fallback code for lack of GNU_HASH support. */
uint32_t old_hash = _dl_elf_hash (undef_name);
/* Use the old SysV-style hash table. Search the appropriate
hash bucket in this object's symbol table for a definition
for the same symbol name. */
for (Elf_Symndx symidx = map->l_buckets[old_hash % map->l_nbuckets];
symidx != STN_UNDEF;
symidx = map->l_chain[symidx])
{
const ElfW(Sym) *sym = check_match (map, undef_name,
version, version_hash, symidx);
if (sym != NULL)
return sym;
}
}
return NULL;
}