]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - elf/dl-lookup.c
Update copyright dates with scripts/update-copyrights.
[thirdparty/glibc.git] / elf / dl-lookup.c
index e655d14dd414a36d99e946834886368ab346dae7..378f28fa7d2dae82d8a62ee79073a8331f30b962 100644 (file)
@@ -1,5 +1,5 @@
 /* Look up a symbol in the loaded objects.
-   Copyright (C) 1995-2007, 2009, 2010, 2011 Free Software Foundation, Inc.
+   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
@@ -13,9 +13,8 @@
    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, write to the Free
-   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
-   02111-1307 USA.  */
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
 
 #include <alloca.h>
 #include <libintl.h>
 #include <dl-hash.h>
 #include <dl-machine.h>
 #include <sysdep-cancel.h>
-#include <bits/libc-lock.h>
+#include <libc-lock.h>
 #include <tls.h>
+#include <atomic.h>
 
 #include <assert.h>
 
-#define VERSTAG(tag)   (DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGIDX (tag))
+/* Return nonzero if check_match should consider SYM to fail to match a
+   symbol reference for some machine-specific reason.  */
+#ifndef ELF_MACHINE_SYM_NO_MATCH
+# define ELF_MACHINE_SYM_NO_MATCH(sym) 0
+#endif
 
-/* We need this string more than once.  */
-static const char undefined_msg[] = "undefined symbol: ";
+#define VERSTAG(tag)   (DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGIDX (tag))
 
 
 struct sym_val
@@ -44,23 +47,6 @@ struct sym_val
   };
 
 
-#define make_string(string, rest...) \
-  ({                                                                         \
-    const char *all[] = { string, ## rest };                                 \
-    size_t len, cnt;                                                         \
-    char *result, *cp;                                                       \
-                                                                             \
-    len = 1;                                                                 \
-    for (cnt = 0; cnt < sizeof (all) / sizeof (all[0]); ++cnt)               \
-      len += strlen (all[cnt]);                                                      \
-                                                                             \
-    cp = result = alloca (len);                                                      \
-    for (cnt = 0; cnt < sizeof (all) / sizeof (all[0]); ++cnt)               \
-      cp = __stpcpy (cp, all[cnt]);                                          \
-                                                                             \
-    result;                                                                  \
-  })
-
 /* Statistics function.  */
 #ifdef SHARED
 # define bump_num_relocations() ++GL(dl_num_relocations)
@@ -68,6 +54,306 @@ struct sym_val
 # define bump_num_relocations() ((void) 0)
 #endif
 
+/* Utility function for do_lookup_x. The caller is called with undef_name,
+   ref, version, flags and type_class, and those are passed as the first
+   five arguments. The caller then computes sym, symidx, strtab, and map
+   and passes them as the next four arguments. Lastly the caller passes in
+   versioned_sym and num_versions which are modified by check_match during
+   the checking process.  */
+static const ElfW(Sym) *
+check_match (const char *const undef_name,
+            const ElfW(Sym) *const ref,
+            const struct r_found_version *const version,
+            const int flags,
+            const int type_class,
+            const ElfW(Sym) *const sym,
+            const Elf_Symndx symidx,
+            const char *const strtab,
+            const struct link_map *const map,
+            const ElfW(Sym) **const versioned_sym,
+            int *const num_versions)
+{
+  unsigned int stt = ELFW(ST_TYPE) (sym->st_info);
+  assert (ELF_RTYPE_CLASS_PLT == 1);
+  if (__glibc_unlikely ((sym->st_value == 0 /* No value.  */
+                        && sym->st_shndx != SHN_ABS
+                        && stt != STT_TLS)
+                       || ELF_MACHINE_SYM_NO_MATCH (sym)
+                       || (type_class & (sym->st_shndx == SHN_UNDEF))))
+    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;
+
+  if (sym != ref && strcmp (strtab + sym->st_name, undef_name))
+    /* Not the symbol we are looking for.  */
+    return NULL;
+
+  const ElfW(Half) *verstab = map->l_versyms;
+  if (version != NULL)
+    {
+      if (__glibc_unlikely (verstab == NULL))
+       {
+         /* We need a versioned symbol but haven't found any.  If
+            this is the object which is referenced in the verneed
+            entry it is a bug in the library since a symbol must
+            not simply disappear.
+
+            It would also be a bug in the object since it means that
+            the list of required versions is incomplete and so the
+            tests in dl-version.c haven't found a problem.*/
+         assert (version->filename == NULL
+                 || ! _dl_name_match_p (version->filename, map));
+
+         /* Otherwise we accept the symbol.  */
+       }
+      else
+       {
+         /* We can match the version information or use the
+            default one if it is not hidden.  */
+         ElfW(Half) ndx = verstab[symidx] & 0x7fff;
+         if ((map->l_versions[ndx].hash != version->hash
+              || strcmp (map->l_versions[ndx].name, version->name))
+             && (version->hidden || map->l_versions[ndx].hash
+                 || (verstab[symidx] & 0x8000)))
+           /* It's not the version we want.  */
+           return NULL;
+       }
+    }
+  else
+    {
+      /* No specific version is selected.  There are two ways we
+        can got here:
+
+        - a binary which does not include versioning information
+        is loaded
+
+        - dlsym() instead of dlvsym() is used to get a symbol which
+        might exist in more than one form
+
+        If the library does not provide symbol version information
+        there is no problem at all: we simply use the symbol if it
+        is defined.
+
+        These two lookups need to be handled differently if the
+        library defines versions.  In the case of the old
+        unversioned application the oldest (default) version
+        should be used.  In case of a dlsym() call the latest and
+        public interface should be returned.  */
+      if (verstab != NULL)
+       {
+         if ((verstab[symidx] & 0x7fff)
+             >= ((flags & DL_LOOKUP_RETURN_NEWEST) ? 2 : 3))
+           {
+             /* Don't accept hidden symbols.  */
+             if ((verstab[symidx] & 0x8000) == 0
+                 && (*num_versions)++ == 0)
+               /* No version so far.  */
+               *versioned_sym = sym;
+
+             return NULL;
+           }
+       }
+    }
+
+  /* There cannot be another entry for this symbol so stop here.  */
+  return sym;
+}
+
+/* Utility function for do_lookup_unique.  Add a symbol to TABLE.  */
+static void
+enter_unique_sym (struct unique_sym *table, size_t size,
+                  unsigned int hash, const char *name,
+                  const ElfW(Sym) *sym, const struct link_map *map)
+{
+  size_t idx = hash % size;
+  size_t hash2 = 1 + hash % (size - 2);
+  while (table[idx].name != NULL)
+    {
+      idx += hash2;
+      if (idx >= size)
+        idx -= size;
+    }
+
+  table[idx].hashval = hash;
+  table[idx].name = name;
+  table[idx].sym = sym;
+  table[idx].map = map;
+}
+
+/* Mark MAP as NODELETE according to the lookup mode in FLAGS.  During
+   initial relocation, NODELETE state is pending only.  */
+static void
+mark_nodelete (struct link_map *map, int flags)
+{
+  if (flags & DL_LOOKUP_FOR_RELOCATE)
+    map->l_nodelete_pending = true;
+  else
+    map->l_nodelete_active = true;
+}
+
+/* Return true if MAP is marked as NODELETE according to the lookup
+   mode in FLAGS> */
+static bool
+is_nodelete (struct link_map *map, int flags)
+{
+  /* Non-pending NODELETE always counts.  Pending NODELETE only counts
+     during initial relocation processing.  */
+  return map->l_nodelete_active
+    || ((flags & DL_LOOKUP_FOR_RELOCATE) && map->l_nodelete_pending);
+}
+
+/* Utility function for do_lookup_x. Lookup an STB_GNU_UNIQUE symbol
+   in the unique symbol table, creating a new entry if necessary.
+   Return the matching symbol in RESULT.  */
+static void
+do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
+                 struct link_map *map, struct sym_val *result,
+                 int type_class, const ElfW(Sym) *sym, const char *strtab,
+                 const ElfW(Sym) *ref, const struct link_map *undef_map,
+                 int flags)
+{
+  /* We have to determine whether we already found a symbol with this
+     name before.  If not then we have to add it to the search table.
+     If we already found a definition we have to use it.  */
+
+  struct unique_sym_table *tab
+    = &GL(dl_ns)[map->l_ns]._ns_unique_sym_table;
+
+  __rtld_lock_lock_recursive (tab->lock);
+
+  struct unique_sym *entries = tab->entries;
+  size_t size = tab->size;
+  if (entries != NULL)
+    {
+      size_t idx = new_hash % size;
+      size_t hash2 = 1 + new_hash % (size - 2);
+      while (1)
+       {
+         if (entries[idx].hashval == new_hash
+             && strcmp (entries[idx].name, undef_name) == 0)
+           {
+             if ((type_class & ELF_RTYPE_CLASS_COPY) != 0)
+               {
+                 /* We possibly have to initialize the central
+                    copy from the copy addressed through the
+                    relocation.  */
+                 result->s = sym;
+                 result->m = map;
+               }
+             else
+               {
+                 result->s = entries[idx].sym;
+                 result->m = (struct link_map *) entries[idx].map;
+               }
+             __rtld_lock_unlock_recursive (tab->lock);
+             return;
+           }
+
+         if (entries[idx].name == NULL)
+           break;
+
+         idx += hash2;
+         if (idx >= size)
+           idx -= size;
+       }
+
+      if (size * 3 <= tab->n_elements * 4)
+       {
+         /* Expand the table.  */
+#ifdef RTLD_CHECK_FOREIGN_CALL
+         /* This must not happen during runtime relocations.  */
+         assert (!RTLD_CHECK_FOREIGN_CALL);
+#endif
+         size_t newsize = _dl_higher_prime_number (size + 1);
+         struct unique_sym *newentries
+           = calloc (sizeof (struct unique_sym), newsize);
+         if (newentries == NULL)
+           {
+           nomem:
+             __rtld_lock_unlock_recursive (tab->lock);
+             _dl_fatal_printf ("out of memory\n");
+           }
+
+         for (idx = 0; idx < size; ++idx)
+           if (entries[idx].name != NULL)
+             enter_unique_sym (newentries, newsize, entries[idx].hashval,
+                                entries[idx].name, entries[idx].sym,
+                                entries[idx].map);
+
+         tab->free (entries);
+         tab->size = newsize;
+         size = newsize;
+         entries = tab->entries = newentries;
+         tab->free = free;
+       }
+    }
+  else
+    {
+#ifdef RTLD_CHECK_FOREIGN_CALL
+      /* This must not happen during runtime relocations.  */
+      assert (!RTLD_CHECK_FOREIGN_CALL);
+#endif
+
+#ifdef SHARED
+      /* If tab->entries is NULL, but tab->size is not, it means
+        this is the second, conflict finding, lookup for
+        LD_TRACE_PRELINKING in _dl_debug_bindings.  Don't
+        allocate anything and don't enter anything into the
+        hash table.  */
+      if (__glibc_unlikely (tab->size))
+       {
+         assert (GLRO(dl_debug_mask) & DL_DEBUG_PRELINK);
+         goto success;
+       }
+#endif
+
+#define INITIAL_NUNIQUE_SYM_TABLE 31
+      size = INITIAL_NUNIQUE_SYM_TABLE;
+      entries = calloc (sizeof (struct unique_sym), size);
+      if (entries == NULL)
+       goto nomem;
+
+      tab->entries = entries;
+      tab->size = size;
+      tab->free = free;
+    }
+
+  if ((type_class & ELF_RTYPE_CLASS_COPY) != 0)
+    enter_unique_sym (entries, size, new_hash, strtab + sym->st_name, ref,
+          undef_map);
+  else
+    {
+      enter_unique_sym (entries, size,
+                        new_hash, strtab + sym->st_name, sym, map);
+
+      if (map->l_type == lt_loaded && !is_nodelete (map, flags))
+       {
+         /* Make sure we don't unload this object by
+            setting the appropriate flag.  */
+         if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS))
+           _dl_debug_printf ("\
+marking %s [%lu] as NODELETE due to unique symbol\n",
+                             map->l_name, map->l_ns);
+         mark_nodelete (map, flags);
+       }
+    }
+  ++tab->n_elements;
+
+#ifdef SHARED
+ success:
+#endif
+  __rtld_lock_unlock_recursive (tab->lock);
+
+  result->s = sym;
+  result->m = (struct link_map *) map;
+}
 
 /* Inner part of the lookup functions.  We return a value > 0 if we
    found the symbol, the value 0 if nothing is found and < 0 if
@@ -90,11 +376,6 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
 
   do
     {
-      /* These variables are used in the nested function.  */
-      Elf_Symndx symidx;
-      int num_versions = 0;
-      const ElfW(Sym) *versioned_sym = NULL;
-
       const struct link_map *map = list[i]->l_real;
 
       /* Here come the extra test needed for `_dl_lookup_symbol_skip'.  */
@@ -110,121 +391,26 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
        continue;
 
       /* Print some debugging info if wanted.  */
-      if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_SYMBOLS, 0))
+      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SYMBOLS))
        _dl_debug_printf ("symbol=%s;  lookup in file=%s [%lu]\n",
-                         undef_name,
-                         map->l_name[0] ? map->l_name : rtld_progname,
+                         undef_name, DSO_FILENAME (map->l_name),
                          map->l_ns);
 
       /* If the hash table is empty there is nothing to do here.  */
       if (map->l_nbuckets == 0)
        continue;
 
+      Elf_Symndx symidx;
+      int num_versions = 0;
+      const ElfW(Sym) *versioned_sym = NULL;
+
       /* The tables for this map.  */
       const ElfW(Sym) *symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
       const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
 
-
-      /* Nested routine to check whether the symbol matches.  */
-      const ElfW(Sym) *
-      __attribute_noinline__
-      check_match (const ElfW(Sym) *sym)
-      {
-       unsigned int stt = ELFW(ST_TYPE) (sym->st_info);
-       assert (ELF_RTYPE_CLASS_PLT == 1);
-       if (__builtin_expect ((sym->st_value == 0 /* No value.  */
-                              && stt != STT_TLS)
-                             || (type_class & (sym->st_shndx == SHN_UNDEF)),
-                             0))
-         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 (__builtin_expect (((1 << stt) & ALLOWED_STT) == 0, 0))
-         return NULL;
-
-       if (sym != ref && strcmp (strtab + sym->st_name, undef_name))
-         /* Not the symbol we are looking for.  */
-         return NULL;
-
-       const ElfW(Half) *verstab = map->l_versyms;
-       if (version != NULL)
-         {
-           if (__builtin_expect (verstab == NULL, 0))
-             {
-               /* We need a versioned symbol but haven't found any.  If
-                  this is the object which is referenced in the verneed
-                  entry it is a bug in the library since a symbol must
-                  not simply disappear.
-
-                  It would also be a bug in the object since it means that
-                  the list of required versions is incomplete and so the
-                  tests in dl-version.c haven't found a problem.*/
-               assert (version->filename == NULL
-                       || ! _dl_name_match_p (version->filename, map));
-
-               /* Otherwise we accept the symbol.  */
-             }
-           else
-             {
-               /* We can match the version information or use the
-                  default one if it is not hidden.  */
-               ElfW(Half) ndx = verstab[symidx] & 0x7fff;
-               if ((map->l_versions[ndx].hash != version->hash
-                    || strcmp (map->l_versions[ndx].name, version->name))
-                   && (version->hidden || map->l_versions[ndx].hash
-                       || (verstab[symidx] & 0x8000)))
-                 /* It's not the version we want.  */
-                 return NULL;
-             }
-         }
-       else
-         {
-           /* No specific version is selected.  There are two ways we
-              can got here:
-
-              - a binary which does not include versioning information
-              is loaded
-
-              - dlsym() instead of dlvsym() is used to get a symbol which
-              might exist in more than one form
-
-              If the library does not provide symbol version information
-              there is no problem at all: we simply use the symbol if it
-              is defined.
-
-              These two lookups need to be handled differently if the
-              library defines versions.  In the case of the old
-              unversioned application the oldest (default) version
-              should be used.  In case of a dlsym() call the latest and
-              public interface should be returned.  */
-           if (verstab != NULL)
-             {
-               if ((verstab[symidx] & 0x7fff)
-                   >= ((flags & DL_LOOKUP_RETURN_NEWEST) ? 2 : 3))
-                 {
-                   /* Don't accept hidden symbols.  */
-                   if ((verstab[symidx] & 0x8000) == 0
-                       && num_versions++ == 0)
-                     /* No version so far.  */
-                     versioned_sym = sym;
-
-                   return NULL;
-                 }
-             }
-         }
-
-       /* There cannot be another entry for this symbol so stop here.  */
-       return sym;
-      }
-
       const ElfW(Sym) *sym;
       const ElfW(Addr) *bitmask = map->l_gnu_bitmask;
-      if (__builtin_expect (bitmask != NULL, 1))
+      if (__glibc_likely (bitmask != NULL))
        {
          ElfW(Addr) bitmask_word
            = bitmask[(new_hash / __ELF_NATIVE_CLASS)
@@ -234,8 +420,8 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
          unsigned int hashbit2 = ((new_hash >> map->l_gnu_shift)
                                   & (__ELF_NATIVE_CLASS - 1));
 
-         if (__builtin_expect ((bitmask_word >> hashbit1)
-                               & (bitmask_word >> hashbit2) & 1, 0))
+         if (__glibc_unlikely ((bitmask_word >> hashbit1)
+                               & (bitmask_word >> hashbit2) & 1))
            {
              Elf32_Word bucket = map->l_gnu_buckets[new_hash
                                                     % map->l_nbuckets];
@@ -246,8 +432,11 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
                  do
                    if (((*hasharr ^ new_hash) >> 1) == 0)
                      {
-                       symidx = hasharr - map->l_gnu_chain_zero;
-                       sym = check_match (&symtab[symidx]);
+                       symidx = ELF_MACHINE_HASH_SYMIDX (map, hasharr);
+                       sym = check_match (undef_name, ref, version, flags,
+                                          type_class, &symtab[symidx], symidx,
+                                          strtab, map, &versioned_sym,
+                                          &num_versions);
                        if (sym != NULL)
                          goto found_it;
                      }
@@ -269,7 +458,10 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
               symidx != STN_UNDEF;
               symidx = map->l_chain[symidx])
            {
-             sym = check_match (&symtab[symidx]);
+             sym = check_match (undef_name, ref, version, flags,
+                                type_class, &symtab[symidx], symidx,
+                                strtab, map, &versioned_sym,
+                                &num_versions);
              if (sym != NULL)
                goto found_it;
            }
@@ -284,11 +476,68 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
       if (sym != NULL)
        {
        found_it:
-         switch (__builtin_expect (ELFW(ST_BIND) (sym->st_info), STB_GLOBAL))
+         /* When UNDEF_MAP is NULL, which indicates we are called from
+            do_lookup_x on relocation against protected data, we skip
+            the data definion in the executable from copy reloc.  */
+         if (ELF_RTYPE_CLASS_EXTERN_PROTECTED_DATA
+             && undef_map == NULL
+             && map->l_type == lt_executable
+             && type_class == ELF_RTYPE_CLASS_EXTERN_PROTECTED_DATA)
+           {
+             const ElfW(Sym) *s;
+             unsigned int i;
+
+#if ! ELF_MACHINE_NO_RELA
+             if (map->l_info[DT_RELA] != NULL
+                 && map->l_info[DT_RELASZ] != NULL
+                 && map->l_info[DT_RELASZ]->d_un.d_val != 0)
+               {
+                 const ElfW(Rela) *rela
+                   = (const ElfW(Rela) *) D_PTR (map, l_info[DT_RELA]);
+                 unsigned int rela_count
+                   = map->l_info[DT_RELASZ]->d_un.d_val / sizeof (*rela);
+
+                 for (i = 0; i < rela_count; i++, rela++)
+                   if (elf_machine_type_class (ELFW(R_TYPE) (rela->r_info))
+                       == ELF_RTYPE_CLASS_COPY)
+                     {
+                       s = &symtab[ELFW(R_SYM) (rela->r_info)];
+                       if (!strcmp (strtab + s->st_name, undef_name))
+                         goto skip;
+                     }
+               }
+#endif
+#if ! ELF_MACHINE_NO_REL
+             if (map->l_info[DT_REL] != NULL
+                 && map->l_info[DT_RELSZ] != NULL
+                 && map->l_info[DT_RELSZ]->d_un.d_val != 0)
+               {
+                 const ElfW(Rel) *rel
+                   = (const ElfW(Rel) *) D_PTR (map, l_info[DT_REL]);
+                 unsigned int rel_count
+                   = map->l_info[DT_RELSZ]->d_un.d_val / sizeof (*rel);
+
+                 for (i = 0; i < rel_count; i++, rel++)
+                   if (elf_machine_type_class (ELFW(R_TYPE) (rel->r_info))
+                       == ELF_RTYPE_CLASS_COPY)
+                     {
+                       s = &symtab[ELFW(R_SYM) (rel->r_info)];
+                       if (!strcmp (strtab + s->st_name, undef_name))
+                         goto skip;
+                     }
+               }
+#endif
+           }
+
+         /* Hidden and internal symbols are local, ignore them.  */
+         if (__glibc_unlikely (dl_symbol_visibility_binds_local_p (sym)))
+           goto skip;
+
+         switch (ELFW(ST_BIND) (sym->st_info))
            {
            case STB_WEAK:
              /* Weak definition.  Use this value if we don't find another.  */
-             if (__builtin_expect (GLRO(dl_dynamic_weak), 0))
+             if (__glibc_unlikely (GLRO(dl_dynamic_weak)))
                {
                  if (! result->s)
                    {
@@ -299,157 +548,16 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
                }
              /* FALLTHROUGH */
            case STB_GLOBAL:
-           success:
              /* Global definition.  Just what we need.  */
              result->s = sym;
              result->m = (struct link_map *) map;
              return 1;
 
            case STB_GNU_UNIQUE:;
-             /* We have to determine whether we already found a
-                symbol with this name before.  If not then we have to
-                add it to the search table.  If we already found a
-                definition we have to use it.  */
-             void enter (struct unique_sym *table, size_t size,
-                         unsigned int hash, const char *name,
-                         const ElfW(Sym) *sym, const struct link_map *map)
-             {
-               size_t idx = hash % size;
-               size_t hash2 = 1 + hash % (size - 2);
-               while (table[idx].name != NULL)
-                 {
-                   idx += hash2;
-                   if (idx >= size)
-                     idx -= size;
-                 }
-
-               table[idx].hashval = hash;
-               table[idx].name = name;
-               table[idx].sym = sym;
-               table[idx].map = map;
-             }
-
-             struct unique_sym_table *tab
-               = &GL(dl_ns)[map->l_ns]._ns_unique_sym_table;
-
-             __rtld_lock_lock_recursive (tab->lock);
-
-             struct unique_sym *entries = tab->entries;
-             size_t size = tab->size;
-             if (entries != NULL)
-               {
-                 size_t idx = new_hash % size;
-                 size_t hash2 = 1 + new_hash % (size - 2);
-                 while (1)
-                   {
-                     if (entries[idx].hashval == new_hash
-                         && strcmp (entries[idx].name, undef_name) == 0)
-                       {
-                         if ((type_class & ELF_RTYPE_CLASS_COPY) != 0)
-                           {
-                             /* We possibly have to initialize the central
-                                copy from the copy addressed through the
-                                relocation.  */
-                             result->s = sym;
-                             result->m = (struct link_map *) map;
-                           }
-                         else
-                           {
-                             result->s = entries[idx].sym;
-                             result->m = (struct link_map *) entries[idx].map;
-                           }
-                         __rtld_lock_unlock_recursive (tab->lock);
-                         return 1;
-                       }
-
-                     if (entries[idx].name == NULL)
-                       break;
-
-                     idx += hash2;
-                     if (idx >= size)
-                       idx -= size;
-                   }
-
-                 if (size * 3 <= tab->n_elements * 4)
-                   {
-                     /* Expand the table.  */
-#ifdef RTLD_CHECK_FOREIGN_CALL
-                     /* This must not happen during runtime relocations.  */
-                     assert (!RTLD_CHECK_FOREIGN_CALL);
-#endif
-                     size_t newsize = _dl_higher_prime_number (size + 1);
-                     struct unique_sym *newentries
-                       = calloc (sizeof (struct unique_sym), newsize);
-                     if (newentries == NULL)
-                       {
-                       nomem:
-                         __rtld_lock_unlock_recursive (tab->lock);
-                         _dl_fatal_printf ("out of memory\n");
-                       }
-
-                     for (idx = 0; idx < size; ++idx)
-                       if (entries[idx].name != NULL)
-                         enter (newentries, newsize, entries[idx].hashval,
-                                entries[idx].name, entries[idx].sym,
-                                entries[idx].map);
-
-                     tab->free (entries);
-                     tab->size = newsize;
-                     size = newsize;
-                     entries = tab->entries = newentries;
-                     tab->free = free;
-                   }
-               }
-             else
-               {
-#ifdef RTLD_CHECK_FOREIGN_CALL
-                 /* This must not happen during runtime relocations.  */
-                 assert (!RTLD_CHECK_FOREIGN_CALL);
-#endif
-
-#ifdef SHARED
-                 /* If tab->entries is NULL, but tab->size is not, it means
-                    this is the second, conflict finding, lookup for
-                    LD_TRACE_PRELINKING in _dl_debug_bindings.  Don't
-                    allocate anything and don't enter anything into the
-                    hash table.  */
-                 if (__builtin_expect (tab->size, 0))
-                   {
-                     assert (GLRO(dl_debug_mask) & DL_DEBUG_PRELINK);
-                     __rtld_lock_unlock_recursive (tab->lock);
-                     goto success;
-                   }
-#endif
-
-#define INITIAL_NUNIQUE_SYM_TABLE 31
-                 size = INITIAL_NUNIQUE_SYM_TABLE;
-                 entries = calloc (sizeof (struct unique_sym), size);
-                 if (entries == NULL)
-                   goto nomem;
-
-                 tab->entries = entries;
-                 tab->size = size;
-                 tab->free = free;
-               }
-
-             if ((type_class & ELF_RTYPE_CLASS_COPY) != 0)
-               enter (entries, size, new_hash, strtab + sym->st_name, ref,
-                      undef_map);
-             else
-               {
-                 enter (entries, size, new_hash, strtab + sym->st_name, sym,
-                        map);
-
-                 if (map->l_type == lt_loaded)
-                   /* Make sure we don't unload this object by
-                      setting the appropriate flag.  */
-                   ((struct link_map *) map)->l_flags_1 |= DF_1_NODELETE;
-               }
-             ++tab->n_elements;
-
-             __rtld_lock_unlock_recursive (tab->lock);
-
-             goto success;
+             do_lookup_unique (undef_name, new_hash, (struct link_map *) map,
+                               result, type_class, sym, strtab, ref,
+                               undef_map, flags);
+             return 1;
 
            default:
              /* Local symbols are ignored.  */
@@ -457,11 +565,8 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
            }
        }
 
-      /* If this current map is the one mentioned in the verneed entry
-        and we have not found a weak entry, it is a bug.  */
-      if (symidx == STN_UNDEF && version != NULL && version->filename != NULL
-         && __builtin_expect (_dl_name_match_p (version->filename, map), 0))
-       return -1;
+skip:
+      ;
     }
   while (++i < n);
 
@@ -482,7 +587,6 @@ dl_new_hash (const char *s)
 
 /* Add extra dependency on MAP to UNDEF_MAP.  */
 static int
-internal_function
 add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
 {
   struct link_map *runp;
@@ -494,9 +598,13 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
   if (undef_map == map)
     return 0;
 
-  /* Avoid references to objects which cannot be unloaded anyway.  */
+  /* Avoid references to objects which cannot be unloaded anyway.  We
+     do not need to record dependencies if this object goes away
+     during dlopen failure, either.  IFUNC resolvers with relocation
+     dependencies may pick an dependency which can be dlclose'd, but
+     such IFUNC resolvers are undefined anyway.  */
   assert (map->l_type == lt_loaded);
-  if ((map->l_flags_1 & DF_1_NODELETE) != 0)
+  if (is_nodelete (map, flags))
     return 0;
 
   struct link_map_reldeps *l_reldeps
@@ -531,7 +639,7 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
   unsigned long long serial = map->l_serial;
 
   /* Make sure nobody can unload the object while we are at it.  */
-  if (__builtin_expect (flags & DL_LOOKUP_GSCOPE_LOCK, 0))
+  if (__glibc_unlikely (flags & DL_LOOKUP_GSCOPE_LOCK))
     {
       /* We can't just call __rtld_lock_lock_recursive (GL(dl_load_lock))
         here, that can result in ABBA deadlock.  */
@@ -604,21 +712,33 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
 
       /* Redo the NODELETE check, as when dl_load_lock wasn't held
         yet this could have changed.  */
-      if ((map->l_flags_1 & DF_1_NODELETE) != 0)
+      if (is_nodelete (map, flags))
        goto out;
 
       /* If the object with the undefined reference cannot be removed ever
         just make sure the same is true for the object which contains the
         definition.  */
-      if (undef_map->l_type != lt_loaded
-         || (undef_map->l_flags_1 & DF_1_NODELETE) != 0)
+      if (undef_map->l_type != lt_loaded || is_nodelete (map, flags))
        {
-         map->l_flags_1 |= DF_1_NODELETE;
+         if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
+             && !is_nodelete (map, flags))
+           {
+             if (undef_map->l_name[0] == '\0')
+               _dl_debug_printf ("\
+marking %s [%lu] as NODELETE due to reference to main program\n",
+                                 map->l_name, map->l_ns);
+             else
+               _dl_debug_printf ("\
+marking %s [%lu] as NODELETE due to reference to %s [%lu]\n",
+                                 map->l_name, map->l_ns,
+                                 undef_map->l_name, undef_map->l_ns);
+           }
+         mark_nodelete (map, flags);
          goto out;
        }
 
       /* Add the reference now.  */
-      if (__builtin_expect (l_reldepsact >= undef_map->l_reldepsmax, 0))
+      if (__glibc_unlikely (l_reldepsact >= undef_map->l_reldepsmax))
        {
          /* Allocate more memory for the dependency list.  Since this
             can never happen during the startup phase we can use
@@ -638,7 +758,15 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
                 no fatal problem.  We simply make sure the referenced object
                 cannot be unloaded.  This is semantically the correct
                 behavior.  */
-             map->l_flags_1 |= DF_1_NODELETE;
+             if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
+                 && !is_nodelete (map, flags))
+               _dl_debug_printf ("\
+marking %s [%lu] as NODELETE due to memory allocation failure\n",
+                                 map->l_name, map->l_ns);
+             /* In case of non-lazy binding, we could actually report
+                the memory allocation error, but for now, we use the
+                conservative approximation as well.  */
+             mark_nodelete (map, flags);
              goto out;
            }
          else
@@ -664,13 +792,12 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
        }
 
       /* Display information if we are debugging.  */
-      if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_FILES, 0))
+      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
        _dl_debug_printf ("\
 \nfile=%s [%lu];  needed by %s [%lu] (relocation dependency)\n\n",
-                         map->l_name[0] ? map->l_name : rtld_progname,
+                         DSO_FILENAME (map->l_name),
                          map->l_ns,
-                         undef_map->l_name[0]
-                         ? undef_map->l_name : rtld_progname,
+                         DSO_FILENAME (undef_map->l_name),
                          undef_map->l_ns);
     }
   else
@@ -681,7 +808,7 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
   /* Release the lock.  */
   __rtld_lock_unlock_recursive (GL(dl_load_lock));
 
-  if (__builtin_expect (flags & DL_LOOKUP_GSCOPE_LOCK, 0))
+  if (__glibc_unlikely (flags & DL_LOOKUP_GSCOPE_LOCK))
     THREAD_GSCOPE_SET_FLAG ();
 
   return result;
@@ -693,7 +820,6 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
 }
 
 static void
-internal_function
 _dl_debug_bindings (const char *undef_name, struct link_map *undef_map,
                    const ElfW(Sym) **ref, struct sym_val *value,
                    const struct r_found_version *version, int type_class,
@@ -707,7 +833,6 @@ _dl_debug_bindings (const char *undef_name, struct link_map *undef_map,
    or in any function which gets called.  If this would happen the audit
    code might create a thread which can throw off all the scope locking.  */
 lookup_t
-internal_function
 _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
                     const ElfW(Sym) **ref,
                     struct r_scope_elem *symbol_scope[],
@@ -721,70 +846,41 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
 
   bump_num_relocations ();
 
-  /* No other flag than DL_LOOKUP_ADD_DEPENDENCY or DL_LOOKUP_GSCOPE_LOCK
-     is allowed if we look up a versioned symbol.  */
-  assert (version == NULL
-         || (flags & ~(DL_LOOKUP_ADD_DEPENDENCY | DL_LOOKUP_GSCOPE_LOCK))
-            == 0);
+  /* DL_LOOKUP_RETURN_NEWEST does not make sense for versioned
+     lookups.  */
+  assert (version == NULL || !(flags & DL_LOOKUP_RETURN_NEWEST));
 
   size_t i = 0;
-  if (__builtin_expect (skip_map != NULL, 0))
+  if (__glibc_unlikely (skip_map != NULL))
     /* Search the relevant loaded objects for a definition.  */
     while ((*scope)->r_list[i] != skip_map)
       ++i;
 
   /* Search the relevant loaded objects for a definition.  */
   for (size_t start = i; *scope != NULL; start = 0, ++scope)
-    {
-      int res = do_lookup_x (undef_name, new_hash, &old_hash, *ref,
-                            &current_value, *scope, start, version, flags,
-                            skip_map, type_class, undef_map);
-      if (res > 0)
-       break;
-
-      if (__builtin_expect (res, 0) < 0 && skip_map == NULL)
-       {
-         /* Oh, oh.  The file named in the relocation entry does not
-            contain the needed symbol.  This code is never reached
-            for unversioned lookups.  */
-         assert (version != NULL);
-         const char *reference_name = undef_map ? undef_map->l_name : NULL;
-
-         /* XXX We cannot translate the message.  */
-         _dl_signal_cerror (0, (reference_name[0]
-                                ? reference_name
-                                : (rtld_progname ?: "<main program>")),
-                            N_("relocation error"),
-                            make_string ("symbol ", undef_name, ", version ",
-                                         version->name,
-                                         " not defined in file ",
-                                         version->filename,
-                                         " with link time reference",
-                                         res == -2
-                                         ? " (no version symbols)" : ""));
-         *ref = NULL;
-         return 0;
-       }
-    }
+    if (do_lookup_x (undef_name, new_hash, &old_hash, *ref,
+                    &current_value, *scope, start, version, flags,
+                    skip_map, type_class, undef_map) != 0)
+      break;
 
-  if (__builtin_expect (current_value.s == NULL, 0))
+  if (__glibc_unlikely (current_value.s == NULL))
     {
       if ((*ref == NULL || ELFW(ST_BIND) ((*ref)->st_info) != STB_WEAK)
-         && skip_map == NULL)
+         && !(GLRO(dl_debug_mask) & DL_DEBUG_UNUSED))
        {
          /* We could find no value for a strong reference.  */
          const char *reference_name = undef_map ? undef_map->l_name : "";
          const char *versionstr = version ? ", version " : "";
          const char *versionname = (version && version->name
                                     ? version->name : "");
-
+         struct dl_exception exception;
          /* XXX We cannot translate the message.  */
-         _dl_signal_cerror (0, (reference_name[0]
-                                ? reference_name
-                                : (rtld_progname ?: "<main program>")),
-                            N_("symbol lookup error"),
-                            make_string (undefined_msg, undef_name,
-                                         versionstr, versionname));
+         _dl_exception_create_format
+           (&exception, DSO_FILENAME (reference_name),
+            "undefined symbol: %s%s%s",
+            undef_name, versionstr, versionname);
+         _dl_signal_cexception (0, &exception, N_("symbol lookup error"));
+         _dl_exception_free (&exception);
        }
       *ref = NULL;
       return 0;
@@ -792,7 +888,7 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
 
   int protected = (*ref
                   && ELFW(ST_VISIBILITY) ((*ref)->st_other) == STV_PROTECTED);
-  if (__builtin_expect (protected != 0, 0))
+  if (__glibc_unlikely (protected != 0))
     {
       /* It is very tricky.  We need to figure out what value to
         return for the protected symbol.  */
@@ -811,7 +907,12 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
          for (scope = symbol_scope; *scope != NULL; i = 0, ++scope)
            if (do_lookup_x (undef_name, new_hash, &old_hash, *ref,
                             &protected_value, *scope, i, version, flags,
-                            skip_map, ELF_RTYPE_CLASS_PLT, NULL) != 0)
+                            skip_map,
+                            (ELF_RTYPE_CLASS_EXTERN_PROTECTED_DATA
+                             && ELFW(ST_TYPE) ((*ref)->st_info) == STT_OBJECT
+                             && type_class == ELF_RTYPE_CLASS_EXTERN_PROTECTED_DATA)
+                            ? ELF_RTYPE_CLASS_EXTERN_PROTECTED_DATA
+                            : ELF_RTYPE_CLASS_PLT, NULL) != 0)
              break;
 
          if (protected_value.s != NULL && protected_value.m != undef_map)
@@ -826,7 +927,7 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
      in the global scope which was dynamically loaded.  In this case
      we have to prevent the latter from being unloaded unless the
      UNDEF_MAP object is also unloaded.  */
-  if (__builtin_expect (current_value.m->l_type == lt_loaded, 0)
+  if (__glibc_unlikely (current_value.m->l_type == lt_loaded)
       /* Don't do this for explicit lookups as opposed to implicit
         runtime lookups.  */
       && (flags & DL_LOOKUP_ADD_DEPENDENCY) != 0
@@ -840,11 +941,11 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
                                  version, type_class, flags, skip_map);
 
   /* The object is used.  */
-  if (__builtin_expect (current_value.m->l_used == 0, 0))
+  if (__glibc_unlikely (current_value.m->l_used == 0))
     current_value.m->l_used = 1;
 
-  if (__builtin_expect (GLRO(dl_debug_mask)
-                       & (DL_DEBUG_BINDINGS|DL_DEBUG_PRELINK), 0))
+  if (__glibc_unlikely (GLRO(dl_debug_mask)
+                       & (DL_DEBUG_BINDINGS|DL_DEBUG_PRELINK)))
     _dl_debug_bindings (undef_name, undef_map, ref,
                        &current_value, version, type_class, protected);
 
@@ -856,19 +957,14 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
 /* Cache the location of MAP's hash table.  */
 
 void
-internal_function
 _dl_setup_hash (struct link_map *map)
 {
   Elf_Symndx *hash;
 
-  if (__builtin_expect (map->l_info[DT_ADDRTAGIDX (DT_GNU_HASH) + DT_NUM
-                                   + DT_THISPROCNUM + DT_VERSIONTAGNUM
-                                   + DT_EXTRANUM + DT_VALNUM] != NULL, 1))
+  if (__glibc_likely (map->l_info[ELF_MACHINE_GNU_HASH_ADDRIDX] != NULL))
     {
       Elf32_Word *hash32
-       = (void *) D_PTR (map, l_info[DT_ADDRTAGIDX (DT_GNU_HASH) + DT_NUM
-                                     + DT_THISPROCNUM + DT_VERSIONTAGNUM
-                                     + DT_EXTRANUM + DT_VALNUM]);
+       = (void *) D_PTR (map, l_info[ELF_MACHINE_GNU_HASH_ADDRIDX]);
       map->l_nbuckets = *hash32++;
       Elf32_Word symbias = *hash32++;
       Elf32_Word bitmask_nwords = *hash32++;
@@ -883,6 +979,10 @@ _dl_setup_hash (struct link_map *map)
       map->l_gnu_buckets = hash32;
       hash32 += map->l_nbuckets;
       map->l_gnu_chain_zero = hash32 - symbias;
+
+      /* Initialize MIPS xhash translation table.  */
+      ELF_MACHINE_XHASH_SETUP (hash32, symbias, map);
+
       return;
     }
 
@@ -900,7 +1000,6 @@ _dl_setup_hash (struct link_map *map)
 
 
 static void
-internal_function
 _dl_debug_bindings (const char *undef_name, struct link_map *undef_map,
                    const ElfW(Sym) **ref, struct sym_val *value,
                    const struct r_found_version *version, int type_class,
@@ -911,11 +1010,9 @@ _dl_debug_bindings (const char *undef_name, struct link_map *undef_map,
   if (GLRO(dl_debug_mask) & DL_DEBUG_BINDINGS)
     {
       _dl_debug_printf ("binding file %s [%lu] to %s [%lu]: %s symbol `%s'",
-                       (reference_name[0]
-                        ? reference_name
-                        : (rtld_progname ?: "<main program>")),
+                       DSO_FILENAME (reference_name),
                        undef_map->l_ns,
-                       value->m->l_name[0] ? value->m->l_name : rtld_progname,
+                       DSO_FILENAME (value->m->l_name),
                        value->m->l_ns,
                        protected ? "protected" : "normal", undef_name);
       if (version)
@@ -926,6 +1023,18 @@ _dl_debug_bindings (const char *undef_name, struct link_map *undef_map,
 #ifdef SHARED
   if (GLRO(dl_debug_mask) & DL_DEBUG_PRELINK)
     {
+/* ELF_RTYPE_CLASS_XXX must match RTYPE_CLASS_XXX used by prelink with
+   LD_TRACE_PRELINKING.  */
+#define RTYPE_CLASS_VALID      8
+#define RTYPE_CLASS_PLT                (8|1)
+#define RTYPE_CLASS_COPY       (8|2)
+#define RTYPE_CLASS_TLS                (8|4)
+#if ELF_RTYPE_CLASS_PLT != 0 && ELF_RTYPE_CLASS_PLT != 1
+# error ELF_RTYPE_CLASS_PLT must be 0 or 1!
+#endif
+#if ELF_RTYPE_CLASS_COPY != 0 && ELF_RTYPE_CLASS_COPY != 2
+# error ELF_RTYPE_CLASS_COPY must be 0 or 2!
+#endif
       int conflict = 0;
       struct sym_val val = { NULL, NULL };
 
@@ -944,10 +1053,10 @@ _dl_debug_bindings (const char *undef_name, struct link_map *undef_map,
                       type_class, undef_map);
          if (val.s != value->s || val.m != value->m)
            conflict = 1;
-         else if (__builtin_expect (undef_map->l_symbolic_in_local_scope, 0)
+         else if (__glibc_unlikely (undef_map->l_symbolic_in_local_scope)
                   && val.s
-                  && __builtin_expect (ELFW(ST_BIND) (val.s->st_info),
-                                       STB_GLOBAL) == STB_GNU_UNIQUE)
+                  && __glibc_unlikely (ELFW(ST_BIND) (val.s->st_info)
+                                       == STB_GNU_UNIQUE))
            {
              /* If it is STB_GNU_UNIQUE and undef_map's l_local_scope
                 contains any DT_SYMBOLIC libraries, unfortunately there
@@ -981,12 +1090,17 @@ _dl_debug_bindings (const char *undef_name, struct link_map *undef_map,
 
       if (value->s)
        {
-         if (__builtin_expect (ELFW(ST_TYPE) (value->s->st_info)
-                               == STT_TLS, 0))
-           type_class = 4;
-         else if (__builtin_expect (ELFW(ST_TYPE) (value->s->st_info)
-                                    == STT_GNU_IFUNC, 0))
-           type_class |= 8;
+         /* Keep only ELF_RTYPE_CLASS_PLT and ELF_RTYPE_CLASS_COPY
+            bits since since prelink only uses them.  */
+         type_class &= ELF_RTYPE_CLASS_PLT | ELF_RTYPE_CLASS_COPY;
+         if (__glibc_unlikely (ELFW(ST_TYPE) (value->s->st_info)
+                               == STT_TLS))
+           /* Clear the RTYPE_CLASS_VALID bit in RTYPE_CLASS_TLS.  */
+           type_class = RTYPE_CLASS_TLS & ~RTYPE_CLASS_VALID;
+         else if (__glibc_unlikely (ELFW(ST_TYPE) (value->s->st_info)
+                                    == STT_GNU_IFUNC))
+           /* Set the RTYPE_CLASS_VALID bit.  */
+           type_class |= RTYPE_CLASS_VALID;
        }
 
       if (conflict