| commit ec935dea6332cb22f9881cd1162bad156173f4b0 |
| Author: Florian Weimer <fweimer@redhat.com> |
| Date: Fri Apr 24 22:31:15 2020 +0200 |
| |
| 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> |
| |
| diff --git a/csu/init-first.c b/csu/init-first.c |
| index 289373f9d8bd98f4..544229151ef79c67 100644 |
| |
| |
| @@ -16,7 +16,6 @@ |
| License along with the GNU C Library; if not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| -#include <ctype.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <fcntl.h> |
| @@ -80,9 +79,6 @@ _init (int argc, char **argv, char **envp) |
| |
| __init_misc (argc, argv, envp); |
| |
| - /* Initialize ctype data. */ |
| - __ctype_init (); |
| - |
| #if defined SHARED && !defined NO_CTORS_DTORS_SECTIONS |
| __libc_global_ctors (); |
| #endif |
| diff --git a/csu/libc-start.c b/csu/libc-start.c |
| index dfbf195328239a17..d9c3248219d8f84f 100644 |
| |
| |
| @@ -22,6 +22,7 @@ |
| #include <ldsodefs.h> |
| #include <exit-thread.h> |
| #include <libc-internal.h> |
| +#include <elf/libc-early-init.h> |
| |
| #include <elf/dl-tunables.h> |
| |
| @@ -238,6 +239,10 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL), |
| __cxa_atexit ((void (*) (void *)) rtld_fini, NULL, NULL); |
| |
| #ifndef SHARED |
| + /* Perform early initialization. In the shared case, this function |
| + is called from the dynamic loader as early as possible. */ |
| + __libc_early_init (); |
| + |
| /* Call the initializer of the libc. This is only needed here if we |
| are compiling for the static library in which case we haven't |
| run the constructors in `_dl_start_user'. */ |
| diff --git a/elf/Makefile b/elf/Makefile |
| index a6601ba84c8f4017..cbced7605ebe2443 100644 |
| |
| |
| @@ -25,7 +25,7 @@ headers = elf.h bits/elfclass.h link.h bits/link.h |
| routines = $(all-dl-routines) dl-support dl-iteratephdr \ |
| dl-addr dl-addr-obj enbl-secure dl-profstub \ |
| dl-origin dl-libc dl-sym dl-sysdep dl-error \ |
| - dl-reloc-static-pie |
| + dl-reloc-static-pie libc_early_init |
| |
| # The core dynamic linking functions are in libc for the static and |
| # profiled libraries. |
| @@ -33,7 +33,8 @@ dl-routines = $(addprefix dl-,load lookup object reloc deps hwcaps \ |
| runtime init fini debug misc \ |
| version profile tls origin scope \ |
| execstack open close trampoline \ |
| - exception sort-maps) |
| + exception sort-maps lookup-direct \ |
| + call-libc-early-init) |
| ifeq (yes,$(use-ldconfig)) |
| dl-routines += dl-cache |
| endif |
| diff --git a/elf/Versions b/elf/Versions |
| index 705489fc51f4ac5f..3be879c4adfa74c7 100644 |
| |
| |
| @@ -26,6 +26,7 @@ libc { |
| _dl_open_hook; _dl_open_hook2; |
| _dl_sym; _dl_vsym; |
| __libc_dlclose; __libc_dlopen_mode; __libc_dlsym; __libc_dlvsym; |
| + __libc_early_init; |
| |
| # Internal error handling support. Interposes the functions in ld.so. |
| _dl_signal_exception; _dl_catch_exception; |
| diff --git a/elf/dl-call-libc-early-init.c b/elf/dl-call-libc-early-init.c |
| new file mode 100644 |
| index 0000000000000000..41e9ad9aad8b5b46 |
| |
| |
| @@ -0,0 +1,41 @@ |
| +/* Invoke the early initialization function in libc.so. |
| + Copyright (C) 2020 Free Software Foundation, Inc. |
| + 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 <assert.h> |
| +#include <ldsodefs.h> |
| +#include <libc-early-init.h> |
| +#include <link.h> |
| +#include <stddef.h> |
| + |
| +void |
| +_dl_call_libc_early_init (struct link_map *libc_map) |
| +{ |
| + /* There is nothing to do if we did not actually load libc.so. */ |
| + if (libc_map == NULL) |
| + return; |
| + |
| + const ElfW(Sym) *sym |
| + = _dl_lookup_direct (libc_map, "__libc_early_init", |
| + 0x069682ac, /* dl_new_hash output. */ |
| + "GLIBC_PRIVATE", |
| + 0x0963cf85); /* _dl_elf_hash output. */ |
| + assert (sym != NULL); |
| + __typeof (__libc_early_init) *early_init |
| + = DL_SYMBOL_ADDRESS (libc_map, sym); |
| + early_init (); |
| +} |
| diff --git a/elf/dl-load.c b/elf/dl-load.c |
| index 8f8869ff524ab9f2..64da5323d0e368c1 100644 |
| |
| |
| @@ -30,6 +30,7 @@ |
| #include <sys/param.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| +#include <gnu/lib-names.h> |
| |
| /* Type for the buffer we put the ELF header and hopefully the program |
| header. This buffer does not really have to be too large. In most |
| @@ -1390,6 +1391,14 @@ cannot enable executable stack as shared object requires"); |
| add_name_to_object (l, ((const char *) D_PTR (l, l_info[DT_STRTAB]) |
| + l->l_info[DT_SONAME]->d_un.d_val)); |
| |
| + /* If we have newly loaded libc.so, update the namespace |
| + description. */ |
| + if (GL(dl_ns)[nsid].libc_map == NULL |
| + && l->l_info[DT_SONAME] != NULL |
| + && strcmp (((const char *) D_PTR (l, l_info[DT_STRTAB]) |
| + + l->l_info[DT_SONAME]->d_un.d_val), LIBC_SO) == 0) |
| + GL(dl_ns)[nsid].libc_map = l; |
| + |
| /* _dl_close can only eventually undo the module ID assignment (via |
| remove_slotinfo) if this function returns a pointer to a link |
| map. Therefore, delay this step until all possibilities for |
| diff --git a/elf/dl-lookup-direct.c b/elf/dl-lookup-direct.c |
| new file mode 100644 |
| index 0000000000000000..5637ae89de8a9d61 |
| |
| |
| @@ -0,0 +1,116 @@ |
| +/* Look up a symbol in a single specified object. |
| + Copyright (C) 1995-2020 Free Software Foundation, Inc. |
| + 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; |
| +} |
| diff --git a/elf/dl-open.c b/elf/dl-open.c |
| index 7113c4a04f0fddbc..1a77ec833cad6c55 100644 |
| |
| |
| @@ -34,6 +34,7 @@ |
| #include <atomic.h> |
| #include <libc-internal.h> |
| #include <array_length.h> |
| +#include <libc-early-init.h> |
| |
| #include <dl-dst.h> |
| #include <dl-prop.h> |
| @@ -57,6 +58,13 @@ struct dl_open_args |
| (non-negative). */ |
| unsigned int original_global_scope_pending_adds; |
| |
| + /* Set to true by dl_open_worker if libc.so was already loaded into |
| + the namespace at the time dl_open_worker was called. This is |
| + used to determine whether libc.so early initialization has |
| + already been done before, and whether to roll back the cached |
| + libc_map value in the namespace in case of a dlopen failure. */ |
| + bool libc_already_loaded; |
| + |
| /* Original parameters to the program and the current environment. */ |
| int argc; |
| char **argv; |
| @@ -500,6 +508,11 @@ dl_open_worker (void *a) |
| args->nsid = call_map->l_ns; |
| } |
| |
| + /* The namespace ID is now known. Keep track of whether libc.so was |
| + already loaded, to determine whether it is necessary to call the |
| + early initialization routine (or clear libc_map on error). */ |
| + args->libc_already_loaded = GL(dl_ns)[args->nsid].libc_map != NULL; |
| + |
| /* Retain the old value, so that it can be restored. */ |
| args->original_global_scope_pending_adds |
| = GL (dl_ns)[args->nsid]._ns_global_scope_pending_adds; |
| @@ -734,6 +747,11 @@ dl_open_worker (void *a) |
| if (relocation_in_progress) |
| LIBC_PROBE (reloc_complete, 3, args->nsid, r, new); |
| |
| + /* If libc.so was not there before, attempt to call its early |
| + initialization routine. */ |
| + if (!args->libc_already_loaded) |
| + _dl_call_libc_early_init (GL(dl_ns)[args->nsid].libc_map); |
| + |
| #ifndef SHARED |
| DL_STATIC_INIT (new); |
| #endif |
| @@ -828,6 +846,8 @@ no more namespaces available for dlmopen()")); |
| args.caller_dlopen = caller_dlopen; |
| args.map = NULL; |
| args.nsid = nsid; |
| + /* args.libc_already_loaded is always assigned by dl_open_worker |
| + (before any explicit/non-local returns). */ |
| args.argc = argc; |
| args.argv = argv; |
| args.env = env; |
| @@ -856,6 +876,11 @@ no more namespaces available for dlmopen()")); |
| /* See if an error occurred during loading. */ |
| if (__glibc_unlikely (exception.errstring != NULL)) |
| { |
| + /* Avoid keeping around a dangling reference to the libc.so link |
| + map in case it has been cached in libc_map. */ |
| + if (!args.libc_already_loaded) |
| + GL(dl_ns)[nsid].libc_map = NULL; |
| + |
| /* Remove the object from memory. It may be in an inconsistent |
| state if relocation failed, for example. */ |
| if (args.map) |
| diff --git a/elf/libc-early-init.h b/elf/libc-early-init.h |
| new file mode 100644 |
| index 0000000000000000..5185fa8895c0e11a |
| |
| |
| @@ -0,0 +1,35 @@ |
| +/* Early initialization of libc.so. |
| + Copyright (C) 2020 Free Software Foundation, Inc. |
| + 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/>. */ |
| + |
| +#ifndef _LIBC_EARLY_INIT_H |
| +#define _LIBC_EARLY_INIT_H |
| + |
| +struct link_map; |
| + |
| +/* If LIBC_MAP is not NULL, look up the __libc_early_init symbol in it |
| + and call this function. */ |
| +void _dl_call_libc_early_init (struct link_map *libc_map) attribute_hidden; |
| + |
| +/* In the shared case, this function is defined in libc.so and invoked |
| + from ld.so (or on the fist static dlopen) after complete relocation |
| + of a new loaded libc.so, but before user-defined ELF constructors |
| + run. In the static case, this function is called directly from the |
| + startup code. */ |
| +void __libc_early_init (void); |
| + |
| +#endif /* _LIBC_EARLY_INIT_H */ |
| diff --git a/elf/libc_early_init.c b/elf/libc_early_init.c |
| new file mode 100644 |
| index 0000000000000000..7f4ca332b805a22c |
| |
| |
| @@ -0,0 +1,27 @@ |
| +/* Early initialization of libc.so, libc.so side. |
| + Copyright (C) 2020 Free Software Foundation, Inc. |
| + 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 <ctype.h> |
| +#include <libc-early-init.h> |
| + |
| +void |
| +__libc_early_init (void) |
| +{ |
| + /* Initialize ctype data. */ |
| + __ctype_init (); |
| +} |
| diff --git a/elf/rtld.c b/elf/rtld.c |
| index d44facf5343b3301..a40d5f17db0dac8b 100644 |
| |
| |
| @@ -44,6 +44,7 @@ |
| #include <stackinfo.h> |
| #include <not-cancel.h> |
| #include <array_length.h> |
| +#include <libc-early-init.h> |
| |
| #include <assert.h> |
| |
| @@ -2365,6 +2366,9 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]); |
| rtld_timer_accum (&relocate_time, start); |
| } |
| |
| + /* Relocation is complete. Perform early libc initialization. */ |
| + _dl_call_libc_early_init (GL(dl_ns)[LM_ID_BASE].libc_map); |
| + |
| /* Do any necessary cleanups for the startup OS interface code. |
| We do these now so that no calls are made after rtld re-relocation |
| which might be resolved to different functions than we expect. |
| diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h |
| index a8fb0d211426e4b1..ccec08929e4ad4e7 100644 |
| |
| |
| @@ -336,6 +336,10 @@ struct rtld_global |
| recursive dlopen calls from ELF constructors. */ |
| unsigned int _ns_global_scope_pending_adds; |
| |
| + /* Once libc.so has been loaded into the namespace, this points to |
| + its link map. */ |
| + struct link_map *libc_map; |
| + |
| /* Search table for unique objects. */ |
| struct unique_sym_table |
| { |
| @@ -943,6 +947,19 @@ extern lookup_t _dl_lookup_symbol_x (const char *undef, |
| attribute_hidden; |
| |
| |
| +/* Restricted version of _dl_lookup_symbol_x. Searches MAP (and only |
| + MAP) for the symbol UNDEF_NAME, with GNU hash NEW_HASH (computed |
| + with dl_new_hash), symbol version VERSION, and symbol version hash |
| + VERSION_HASH (computed with _dl_elf_hash). Returns a pointer to |
| + the symbol table entry in MAP on success, or NULL on failure. MAP |
| + must have symbol versioning information, or otherwise the result is |
| + undefined. */ |
| +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) attribute_hidden; |
| + |
| /* Add the new link_map NEW to the end of the namespace list. */ |
| extern void _dl_add_to_namespace_list (struct link_map *new, Lmid_t nsid) |
| attribute_hidden; |
| diff --git a/sysdeps/mach/hurd/i386/init-first.c b/sysdeps/mach/hurd/i386/init-first.c |
| index f8ad2ceb8e324f92..1636a40ee5d78858 100644 |
| |
| |
| @@ -17,7 +17,6 @@ |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include <assert.h> |
| -#include <ctype.h> |
| #include <hurd.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| @@ -84,9 +83,6 @@ posixland_init (int argc, char **argv, char **envp) |
| #endif |
| __init_misc (argc, argv, envp); |
| |
| - /* Initialize ctype data. */ |
| - __ctype_init (); |
| - |
| #if defined SHARED && !defined NO_CTORS_DTORS_SECTIONS |
| __libc_global_ctors (); |
| #endif |