]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - elf/dl-lookup.c
Update copyright notices with scripts/update-copyrights
[thirdparty/glibc.git] / elf / dl-lookup.c
index 72381698dbb091831f7d12c8cb7ddb730ec14f19..a58e5bc72a08bd600139935140e8fe1c762d59c9 100644 (file)
@@ -1,5 +1,5 @@
 /* Look up a symbol in the loaded objects.
-   Copyright (C) 1995-2005, 2006 Free Software Foundation, Inc.
+   Copyright (C) 1995-2014 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
+   <http://www.gnu.org/licenses/>.  */
 
 #include <alloca.h>
 #include <libintl.h>
 #include <ldsodefs.h>
 #include <dl-hash.h>
 #include <dl-machine.h>
+#include <sysdep-cancel.h>
 #include <bits/libc-lock.h>
 #include <tls.h>
+#include <atomic.h>
 
 #include <assert.h>
 
@@ -68,8 +69,404 @@ struct sym_val
 #endif
 
 
-/* The actual lookup code.  */
-#include "do-lookup.h"
+/* 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
+   something bad happened.  */
+static int
+__attribute_noinline__
+do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
+            unsigned long int *old_hash, const ElfW(Sym) *ref,
+            struct sym_val *result, struct r_scope_elem *scope, size_t i,
+            const struct r_found_version *const version, int flags,
+            struct link_map *skip, int type_class, struct link_map *undef_map)
+{
+  size_t n = scope->r_nlist;
+  /* Make sure we read the value before proceeding.  Otherwise we
+     might use r_list pointing to the initial scope and r_nlist being
+     the value after a resize.  That is the only path in dl-open.c not
+     protected by GSCOPE.  A read barrier here might be to expensive.  */
+  __asm volatile ("" : "+r" (n), "+m" (scope->r_list));
+  struct link_map **list = scope->r_list;
+
+  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'.  */
+      if (map == skip)
+       continue;
+
+      /* Don't search the executable when resolving a copy reloc.  */
+      if ((type_class & ELF_RTYPE_CLASS_COPY) && map->l_type == lt_executable)
+       continue;
+
+      /* Do not look into objects which are going to be removed.  */
+      if (map->l_removed)
+       continue;
+
+      /* Print some debugging info if wanted.  */
+      if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_SYMBOLS, 0))
+       _dl_debug_printf ("symbol=%s;  lookup in file=%s [%lu]\n",
+                         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;
+
+      /* 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))
+       {
+         ElfW(Addr) bitmask_word
+           = bitmask[(new_hash / __ELF_NATIVE_CLASS)
+                     & map->l_gnu_bitmask_idxbits];
+
+         unsigned int hashbit1 = new_hash & (__ELF_NATIVE_CLASS - 1);
+         unsigned int hashbit2 = ((new_hash >> map->l_gnu_shift)
+                                  & (__ELF_NATIVE_CLASS - 1));
+
+         if (__builtin_expect ((bitmask_word >> hashbit1)
+                               & (bitmask_word >> hashbit2) & 1, 0))
+           {
+             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)
+                     {
+                       symidx = hasharr - map->l_gnu_chain_zero;
+                       sym = check_match (&symtab[symidx]);
+                       if (sym != NULL)
+                         goto found_it;
+                     }
+                 while ((*hasharr++ & 1u) == 0);
+               }
+           }
+         /* No symbol found.  */
+         symidx = SHN_UNDEF;
+       }
+      else
+       {
+         if (*old_hash == 0xffffffff)
+           *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 (symidx = map->l_buckets[*old_hash % map->l_nbuckets];
+              symidx != STN_UNDEF;
+              symidx = map->l_chain[symidx])
+           {
+             sym = check_match (&symtab[symidx]);
+             if (sym != NULL)
+               goto found_it;
+           }
+       }
+
+      /* If we have seen exactly one versioned symbol while we are
+        looking for an unversioned symbol and the version is not the
+        default version we still accept this symbol since there are
+        no possible ambiguities.  */
+      sym = num_versions == 1 ? versioned_sym : NULL;
+
+      if (sym != NULL)
+       {
+       found_it:
+         switch (__builtin_expect (ELFW(ST_BIND) (sym->st_info), STB_GLOBAL))
+           {
+           case STB_WEAK:
+             /* Weak definition.  Use this value if we don't find another.  */
+             if (__builtin_expect (GLRO(dl_dynamic_weak), 0))
+               {
+                 if (! result->s)
+                   {
+                     result->s = sym;
+                     result->m = (struct link_map *) map;
+                   }
+                 break;
+               }
+             /* 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;
+
+           default:
+             /* Local symbols are ignored.  */
+             break;
+           }
+       }
+
+      /* 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;
+    }
+  while (++i < n);
+
+  /* We have not found anything until now.  */
+  return 0;
+}
 
 
 static uint_fast32_t
@@ -85,11 +482,9 @@ 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)
+add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
 {
-  struct link_map **list;
   struct link_map *runp;
-  unsigned int act;
   unsigned int i;
   int result = 0;
 
@@ -98,43 +493,95 @@ add_dependency (struct link_map *undef_map, struct link_map *map)
   if (undef_map == map)
     return 0;
 
-  /* Make sure nobody can unload the object while we are at it.  */
-  __rtld_lock_lock_recursive (GL(dl_load_lock));
-
   /* Avoid references to objects which cannot be unloaded anyway.  */
-  if (map->l_type != lt_loaded
-      || (map->l_flags_1 & DF_1_NODELETE) != 0)
-    goto out;
+  assert (map->l_type == lt_loaded);
+  if ((map->l_flags_1 & DF_1_NODELETE) != 0)
+    return 0;
 
-  /* 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)
-    {
-      map->l_flags_1 |= DF_1_NODELETE;
-      goto out;
-    }
+  struct link_map_reldeps *l_reldeps
+    = atomic_forced_read (undef_map->l_reldeps);
+
+  /* Make sure l_reldeps is read before l_initfini.  */
+  atomic_read_barrier ();
 
   /* Determine whether UNDEF_MAP already has a reference to MAP.  First
      look in the normal dependencies.  */
-  if (undef_map->l_initfini != NULL)
+  struct link_map **l_initfini = atomic_forced_read (undef_map->l_initfini);
+  if (l_initfini != NULL)
     {
-      list = undef_map->l_initfini;
-
-      for (i = 0; list[i] != NULL; ++i)
-       if (list[i] == map)
-         goto out;
+      for (i = 0; l_initfini[i] != NULL; ++i)
+       if (l_initfini[i] == map)
+         return 0;
     }
 
   /* No normal dependency.  See whether we already had to add it
      to the special list of dynamic dependencies.  */
-  list = undef_map->l_reldeps;
-  act = undef_map->l_reldepsact;
+  unsigned int l_reldepsact = 0;
+  if (l_reldeps != NULL)
+    {
+      struct link_map **list = &l_reldeps->list[0];
+      l_reldepsact = l_reldeps->act;
+      for (i = 0; i < l_reldepsact; ++i)
+       if (list[i] == map)
+         return 0;
+    }
 
-  for (i = 0; i < act; ++i)
-    if (list[i] == map)
-      goto out;
+  /* Save serial number of the target MAP.  */
+  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))
+    {
+      /* We can't just call __rtld_lock_lock_recursive (GL(dl_load_lock))
+        here, that can result in ABBA deadlock.  */
+      THREAD_GSCOPE_RESET_FLAG ();
+      __rtld_lock_lock_recursive (GL(dl_load_lock));
+      /* While MAP value won't change, after THREAD_GSCOPE_RESET_FLAG ()
+        it can e.g. point to unallocated memory.  So avoid the optimizer
+        treating the above read from MAP->l_serial as ensurance it
+        can safely dereference it.  */
+      map = atomic_forced_read (map);
+
+      /* From this point on it is unsafe to dereference MAP, until it
+        has been found in one of the lists.  */
+
+      /* Redo the l_initfini check in case undef_map's l_initfini
+        changed in the mean time.  */
+      if (undef_map->l_initfini != l_initfini
+         && undef_map->l_initfini != NULL)
+       {
+         l_initfini = undef_map->l_initfini;
+         for (i = 0; l_initfini[i] != NULL; ++i)
+           if (l_initfini[i] == map)
+             goto out_check;
+       }
+
+      /* Redo the l_reldeps check if undef_map's l_reldeps changed in
+        the mean time.  */
+      if (undef_map->l_reldeps != NULL)
+       {
+         if (undef_map->l_reldeps != l_reldeps)
+           {
+             struct link_map **list = &undef_map->l_reldeps->list[0];
+             l_reldepsact = undef_map->l_reldeps->act;
+             for (i = 0; i < l_reldepsact; ++i)
+               if (list[i] == map)
+                 goto out_check;
+           }
+         else if (undef_map->l_reldeps->act > l_reldepsact)
+           {
+             struct link_map **list
+               = &undef_map->l_reldeps->list[0];
+             i = l_reldepsact;
+             l_reldepsact = undef_map->l_reldeps->act;
+             for (; i < l_reldepsact; ++i)
+               if (list[i] == map)
+                 goto out_check;
+           }
+       }
+    }
+  else
+    __rtld_lock_lock_recursive (GL(dl_load_lock));
 
   /* The object is not yet in the dependency list.  Before we add
      it make sure just one more time the object we are about to
@@ -147,43 +594,81 @@ add_dependency (struct link_map *undef_map, struct link_map *map)
 
   if (runp != NULL)
     {
-      /* The object is still available.  Add the reference now.  */
-      if (__builtin_expect (act >= undef_map->l_reldepsmax, 0))
+      /* The object is still available.  */
+
+      /* MAP could have been dlclosed, freed and then some other dlopened
+        library could have the same link_map pointer.  */
+      if (map->l_serial != serial)
+       goto out_check;
+
+      /* 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)
+       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)
+       {
+         map->l_flags_1 |= DF_1_NODELETE;
+         goto out;
+       }
+
+      /* Add the reference now.  */
+      if (__builtin_expect (l_reldepsact >= undef_map->l_reldepsmax, 0))
        {
          /* Allocate more memory for the dependency list.  Since this
             can never happen during the startup phase we can use
             `realloc'.  */
-         void *newp;
+         struct link_map_reldeps *newp;
+         unsigned int max
+           = undef_map->l_reldepsmax ? undef_map->l_reldepsmax * 2 : 10;
 
-         undef_map->l_reldepsmax += 5;
-         newp = realloc (undef_map->l_reldeps,
-                         undef_map->l_reldepsmax
-                         * sizeof (struct link_map *));
+#ifdef RTLD_PREPARE_FOREIGN_CALL
+         RTLD_PREPARE_FOREIGN_CALL;
+#endif
 
-         if (__builtin_expect (newp != NULL, 1))
-           undef_map->l_reldeps = (struct link_map **) newp;
+         newp = malloc (sizeof (*newp) + max * sizeof (struct link_map *));
+         if (newp == NULL)
+           {
+             /* If we didn't manage to allocate memory for the list this is
+                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;
+             goto out;
+           }
          else
-           /* Correct the addition.  */
-           undef_map->l_reldepsmax -= 5;
+           {
+             if (l_reldepsact)
+               memcpy (&newp->list[0], &undef_map->l_reldeps->list[0],
+                       l_reldepsact * sizeof (struct link_map *));
+             newp->list[l_reldepsact] = map;
+             newp->act = l_reldepsact + 1;
+             atomic_write_barrier ();
+             void *old = undef_map->l_reldeps;
+             undef_map->l_reldeps = newp;
+             undef_map->l_reldepsmax = max;
+             if (old)
+               _dl_scope_free (old);
+           }
+       }
+      else
+       {
+         undef_map->l_reldeps->list[l_reldepsact] = map;
+         atomic_write_barrier ();
+         undef_map->l_reldeps->act = l_reldepsact + 1;
        }
-
-      /* If we didn't manage to allocate memory for the list this is
-        no fatal mistake.  We simply increment the use counter of the
-        referenced object and don't record the dependencies.  This
-        means this increment can never be reverted and the object
-        will never be unloaded.  This is semantically the correct
-        behavior.  */
-      if (__builtin_expect (act < undef_map->l_reldepsmax, 1))
-       undef_map->l_reldeps[undef_map->l_reldepsact++] = map;
 
       /* Display information if we are debugging.  */
       if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_FILES, 0))
        _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
@@ -194,14 +679,21 @@ add_dependency (struct link_map *undef_map, struct link_map *map)
   /* Release the lock.  */
   __rtld_lock_unlock_recursive (GL(dl_load_lock));
 
+  if (__builtin_expect (flags & DL_LOOKUP_GSCOPE_LOCK, 0))
+    THREAD_GSCOPE_SET_FLAG ();
+
   return result;
+
+ out_check:
+  if (map->l_serial != serial)
+    result = -1;
+  goto out;
 }
 
 static void
 internal_function
 _dl_debug_bindings (const char *undef_name, struct link_map *undef_map,
-                   const ElfW(Sym) **ref, struct r_scope_elem *symbol_scope[],
-                   struct sym_val *value,
+                   const ElfW(Sym) **ref, struct sym_val *value,
                    const struct r_found_version *version, int type_class,
                    int protected);
 
@@ -227,26 +719,24 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
 
   bump_num_relocations ();
 
-  /* No other flag than DL_LOOKUP_ADD_DEPENDENCY is allowed if we look
-     up a versioned symbol.  */
-  assert (version == NULL || flags == 0 || flags == DL_LOOKUP_ADD_DEPENDENCY);
+  /* 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);
 
   size_t i = 0;
   if (__builtin_expect (skip_map != NULL, 0))
-    {
-      /* Search the relevant loaded objects for a definition.  */
-      while ((*scope)->r_list[i] != skip_map)
-       ++i;
-
-      assert (i < (*scope)->r_nlist);
-    }
+    /* 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);
+                            skip_map, type_class, undef_map);
       if (res > 0)
        break;
 
@@ -256,12 +746,10 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
             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;
+         const char *reference_name = undef_map ? undef_map->l_name : "";
 
          /* XXX We cannot translate the message.  */
-         _dl_signal_cerror (0, (reference_name[0]
-                                ? reference_name
-                                : (rtld_progname ?: "<main program>")),
+         _dl_signal_cerror (0, DSO_FILENAME (reference_name),
                             N_("relocation error"),
                             make_string ("symbol ", undef_name, ", version ",
                                          version->name,
@@ -278,7 +766,8 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
   if (__builtin_expect (current_value.s == NULL, 0))
     {
       if ((*ref == NULL || ELFW(ST_BIND) ((*ref)->st_info) != STB_WEAK)
-         && skip_map == NULL)
+         && 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 : "";
@@ -287,9 +776,7 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
                                     ? version->name : "");
 
          /* XXX We cannot translate the message.  */
-         _dl_signal_cerror (0, (reference_name[0]
-                                ? reference_name
-                                : (rtld_progname ?: "<main program>")),
+         _dl_signal_cerror (0, DSO_FILENAME (reference_name),
                             N_("symbol lookup error"),
                             make_string (undefined_msg, undef_name,
                                          versionstr, versionname));
@@ -303,7 +790,7 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
   if (__builtin_expect (protected != 0, 0))
     {
       /* It is very tricky.  We need to figure out what value to
-         return for the protected symbol.  */
+        return for the protected symbol.  */
       if (type_class == ELF_RTYPE_CLASS_PLT)
        {
          if (current_value.s != NULL && current_value.m != undef_map)
@@ -319,7 +806,7 @@ _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) != 0)
+                            skip_map, ELF_RTYPE_CLASS_PLT, NULL) != 0)
              break;
 
          if (protected_value.s != NULL && protected_value.m != undef_map)
@@ -339,19 +826,21 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
         runtime lookups.  */
       && (flags & DL_LOOKUP_ADD_DEPENDENCY) != 0
       /* Add UNDEF_MAP to the dependencies.  */
-      && add_dependency (undef_map, current_value.m) < 0)
+      && add_dependency (undef_map, current_value.m, flags) < 0)
       /* Something went wrong.  Perhaps the object we tried to reference
         was just removed.  Try finding another definition.  */
       return _dl_lookup_symbol_x (undef_name, undef_map, ref,
-                                 symbol_scope, version, type_class,
-                                 flags, skip_map);
+                                 (flags & DL_LOOKUP_GSCOPE_LOCK)
+                                 ? undef_map->l_scope : symbol_scope,
+                                 version, type_class, flags, skip_map);
 
   /* The object is used.  */
-  current_value.m->l_used = 1;
+  if (__builtin_expect (current_value.m->l_used == 0, 0))
+    current_value.m->l_used = 1;
 
   if (__builtin_expect (GLRO(dl_debug_mask)
                        & (DL_DEBUG_BINDINGS|DL_DEBUG_PRELINK), 0))
-    _dl_debug_bindings (undef_name, undef_map, ref, symbol_scope,
+    _dl_debug_bindings (undef_name, undef_map, ref,
                        &current_value, version, type_class, protected);
 
   *ref = current_value.s;
@@ -366,10 +855,9 @@ internal_function
 _dl_setup_hash (struct link_map *map)
 {
   Elf_Symndx *hash;
-  Elf_Symndx nchain;
 
   if (__builtin_expect (map->l_info[DT_ADDRTAGIDX (DT_GNU_HASH) + DT_NUM
-                                   + DT_THISPROCNUM + DT_VERSIONTAGNUM
+                                   + DT_THISPROCNUM + DT_VERSIONTAGNUM
                                    + DT_EXTRANUM + DT_VALNUM] != NULL, 1))
     {
       Elf32_Word *hash32
@@ -398,7 +886,8 @@ _dl_setup_hash (struct link_map *map)
   hash = (void *) D_PTR (map, l_info[DT_HASH]);
 
   map->l_nbuckets = *hash++;
-  nchain = *hash++;
+  /* Skip nchain.  */
+  hash++;
   map->l_buckets = hash;
   hash += map->l_nbuckets;
   map->l_chain = hash;
@@ -408,8 +897,7 @@ _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 r_scope_elem *symbol_scope[],
-                   struct sym_val *value,
+                   const ElfW(Sym) **ref, struct sym_val *value,
                    const struct r_found_version *version, int type_class,
                    int protected)
 {
@@ -418,11 +906,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)
@@ -442,26 +928,64 @@ _dl_debug_bindings (const char *undef_name, struct link_map *undef_map,
        {
          const uint_fast32_t new_hash = dl_new_hash (undef_name);
          unsigned long int old_hash = 0xffffffff;
+         struct unique_sym *saved_entries
+           = GL(dl_ns)[LM_ID_BASE]._ns_unique_sym_table.entries;
 
+         GL(dl_ns)[LM_ID_BASE]._ns_unique_sym_table.entries = NULL;
          do_lookup_x (undef_name, new_hash, &old_hash, *ref, &val,
                       undef_map->l_local_scope[0], 0, version, 0, NULL,
-                      type_class);
-
+                      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)
+                  && val.s
+                  && __builtin_expect (ELFW(ST_BIND) (val.s->st_info),
+                                       STB_GLOBAL) == STB_GNU_UNIQUE)
+           {
+             /* If it is STB_GNU_UNIQUE and undef_map's l_local_scope
+                contains any DT_SYMBOLIC libraries, unfortunately there
+                can be conflicts even if the above is equal.  As symbol
+                resolution goes from the last library to the first and
+                if a STB_GNU_UNIQUE symbol is found in some late DT_SYMBOLIC
+                library, it would be the one that is looked up.  */
+             struct sym_val val2 = { NULL, NULL };
+             size_t n;
+             struct r_scope_elem *scope = undef_map->l_local_scope[0];
+
+             for (n = 0; n < scope->r_nlist; n++)
+               if (scope->r_list[n] == val.m)
+                 break;
+
+             for (n++; n < scope->r_nlist; n++)
+               if (scope->r_list[n]->l_info[DT_SYMBOLIC] != NULL
+                   && do_lookup_x (undef_name, new_hash, &old_hash, *ref,
+                                   &val2,
+                                   &scope->r_list[n]->l_symbolic_searchlist,
+                                   0, version, 0, NULL, type_class,
+                                   undef_map) > 0)
+                 {
+                   conflict = 1;
+                   val = val2;
+                   break;
+                 }
+           }
+         GL(dl_ns)[LM_ID_BASE]._ns_unique_sym_table.entries = saved_entries;
        }
 
-# ifdef USE_TLS
-      if (value->s
-         && (__builtin_expect (ELFW(ST_TYPE) (value->s->st_info)
-                               == STT_TLS, 0)))
-       type_class = 4;
-# endif
+      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;
+       }
 
       if (conflict
          || GLRO(dl_trace_prelink_map) == undef_map
          || GLRO(dl_trace_prelink_map) == NULL
-         || type_class == 4)
+         || type_class >= 4)
        {
          _dl_printf ("%s 0x%0*Zx 0x%0*Zx -> 0x%0*Zx 0x%0*Zx ",
                      conflict ? "conflict" : "lookup",