about summary refs log tree commit diff
path: root/elf/dl-lookup.c
diff options
context:
space:
mode:
Diffstat (limited to 'elf/dl-lookup.c')
-rw-r--r--elf/dl-lookup.c159
1 files changed, 144 insertions, 15 deletions
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
index 1000a52e0d..06d1d09b90 100644
--- a/elf/dl-lookup.c
+++ b/elf/dl-lookup.c
@@ -1,5 +1,5 @@
 /* Look up a symbol in the loaded objects.
-   Copyright (C) 1995, 1996 Free Software Foundation, Inc.
+   Copyright (C) 1995, 1996, 1997 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
@@ -22,6 +22,9 @@
 #include <assert.h>
 #include <string.h>
 
+#include "../stdio-common/_itoa.h"
+
+#define VERSTAG(tag)	(DT_NUM + DT_PROCNUM + DT_VERSIONTAGIDX (tag))
 
 struct sym_val
   {
@@ -30,22 +33,46 @@ struct sym_val
   };
 
 
-/* This is the hashing function specified by the ELF ABI.  */
+/* This is the hashing function specified by the ELF ABI.  In the
+   first five operations now overflow is possible so we optimized it a
+   bit.  */
 static inline unsigned
 _dl_elf_hash (const char *name)
 {
   unsigned long int hash = 0;
-  while (*name != '\0')
+  if (*name != '\0')
     {
-      unsigned long int hi;
       hash = (hash << 4) + *name++;
-      hi = hash & 0xf0000000;
-      if (hi != 0)
+      if (*name != '\0')
 	{
-	  hash ^= hi >> 24;
-	  /* The ELF ABI says `hash &= ~hi', but this is equivalent
-	     in this case and on some machines one insn instead of two.  */
-	  hash ^= hi;
+	  hash = (hash << 4) + *name++;
+	  if (*name != '\0')
+	    {
+	      hash = (hash << 4) + *name++;
+	      if (*name != '\0')
+		{
+		  hash = (hash << 4) + *name++;
+		  if (*name != '\0')
+		    {
+		      hash = (hash << 4) + *name++;
+		      while (*name != '\0')
+			{
+			  unsigned long int hi;
+			  hash = (hash << 4) + *name++;
+			  hi = hash & 0xf0000000;
+			  if (hi != 0)
+			    {
+			      hash ^= hi >> 24;
+			      /* The ELF ABI says `hash &= ~hi', but
+				 this is equivalent in this case and
+				 on some machines one insn instead of
+				 two.  */
+			      hash ^= hi;
+			    }
+			}
+		    }
+		}
+	    }
 	}
     }
   return hash;
@@ -57,7 +84,8 @@ static inline ElfW(Addr)
 do_lookup (const char *undef_name, unsigned long int hash,
 	   const ElfW(Sym) **ref, struct sym_val *result,
 	   struct link_map *list[], size_t i, size_t n,
-	   const char *reference_name, struct link_map *skip, int flags)
+	   const char *reference_name, const hash_name_pair *version,
+	   struct link_map *skip, int flags)
 {
   struct link_map *map;
 
@@ -65,6 +93,7 @@ do_lookup (const char *undef_name, unsigned long int hash,
     {
       const ElfW(Sym) *symtab;
       const char *strtab;
+      const ElfW(Half) *verstab;
       ElfW(Symndx) symidx;
 
       map = list[i];
@@ -79,6 +108,12 @@ do_lookup (const char *undef_name, unsigned long int hash,
 
       symtab = ((void *) map->l_addr + map->l_info[DT_SYMTAB]->d_un.d_ptr);
       strtab = ((void *) map->l_addr + map->l_info[DT_STRTAB]->d_un.d_ptr);
+      if (version != NULL
+	  && map->l_nversions > 0 && map->l_info[VERSTAG (DT_VERSYM)] != NULL)
+	verstab = ((void *) map->l_addr
+		   + map->l_info[VERSTAG (DT_VERSYM)]->d_un.d_ptr);
+      else
+	verstab = NULL;
 
       /* Search the appropriate hash bucket in this object's symbol table
 	 for a definition for the same symbol name.  */
@@ -108,6 +143,15 @@ do_lookup (const char *undef_name, unsigned long int hash,
 	    /* Not the symbol we are looking for.  */
 	    continue;
 
+	  if (verstab != NULL)
+	    {
+	      /* We can match the version information.  */
+	      ElfW(Half) ndx = verstab[symidx] & 0x7fff;
+	      if (map->l_versions[ndx].hash != version->hash
+		  || strcmp (map->l_versions[ndx].name, version->name))
+	      continue;
+	    }
+
 	  switch (ELFW(ST_BIND) (sym->st_info))
 	    {
 	    case STB_GLOBAL:
@@ -155,14 +199,14 @@ _dl_lookup_symbol (const char *undef_name, const ElfW(Sym) **ref,
   for (scope = symbol_scope; *scope; ++scope)
     if (do_lookup (undef_name, hash, ref, &current_value,
 		   (*scope)->l_searchlist, 0, (*scope)->l_nsearchlist,
-		   reference_name, NULL, flags))
+		   reference_name, NULL, NULL, flags))
       break;
 
   if (current_value.s == NULL &&
       (*ref == NULL || ELFW(ST_BIND) ((*ref)->st_info) != STB_WEAK))
     {
       /* We could find no value for a strong reference.  */
-      const char msg[] = "undefined symbol: ";
+      static const char msg[] = "undefined symbol: ";
       const size_t len = strlen (undef_name);
       char buf[sizeof msg + len];
       memcpy (buf, msg, sizeof msg - 1);
@@ -199,11 +243,96 @@ _dl_lookup_symbol_skip (const char *undef_name, const ElfW(Sym) **ref,
 
   if (! do_lookup (undef_name, hash, ref, &current_value,
 		   (*scope)->l_dupsearchlist, i, (*scope)->l_ndupsearchlist,
-		   reference_name, skip_map, flags))
+		   reference_name, NULL, skip_map, flags))
+    while (*++scope)
+      if (do_lookup (undef_name, hash, ref, &current_value,
+		     (*scope)->l_dupsearchlist, 0, (*scope)->l_ndupsearchlist,
+		     reference_name, NULL, skip_map, flags))
+	break;
+
+  *ref = current_value.s;
+  return current_value.a;
+}
+
+
+/* This function works like _dl_lookup_symbol but it takes an
+   additional arguement with the version number of the requested
+   symbol.
+
+   XXX We'll see whether we need this separate function.  */
+ElfW(Addr)
+_dl_lookup_versioned_symbol (const char *undef_name, const ElfW(Sym) **ref,
+			     struct link_map *symbol_scope[],
+			     const char *reference_name,
+			     const hash_name_pair *version, int flags)
+{
+  const unsigned long int hash = _dl_elf_hash (undef_name);
+  struct sym_val current_value = { 0, NULL };
+  struct link_map **scope;
+
+  /* Search the relevant loaded objects for a definition.  */
+  for (scope = symbol_scope; *scope; ++scope)
+    if (do_lookup (undef_name, hash, ref, &current_value,
+		   (*scope)->l_searchlist, 0, (*scope)->l_nsearchlist,
+		   reference_name, version, NULL, flags))
+      break;
+
+  if (current_value.s == NULL &&
+      (*ref == NULL || ELFW(ST_BIND) ((*ref)->st_info) != STB_WEAK))
+    {
+      /* We could find no value for a strong reference.  */
+      static const char msg1[] = "undefined symbol: ";
+      const size_t len = strlen (undef_name);
+      static const char msg2[] = ", version ";
+      const size_t verslen = strlen (version->name ?: "") + 1;
+      char buf[sizeof msg1 - 1 + len + sizeof msg2 - 1 + verslen];
+
+      memcpy (buf, msg1, sizeof msg1 - 1);
+      memcpy (&buf[sizeof msg1 - 1], undef_name, len + 1);
+      memcpy (&buf[sizeof msg1 - 1 + len], msg2, sizeof msg2 - 1);
+      memcpy (&buf[sizeof msg1 - 1 + len + sizeof msg2 - 1], version->name,
+	      verslen);
+      _dl_signal_error (0, reference_name, buf);
+    }
+
+  *ref = current_value.s;
+  return current_value.a;
+}
+
+
+/* Similar to _dl_lookup_symbol_skip but takes an additional argument
+   with the version we are looking for.  */
+ElfW(Addr)
+_dl_lookup_versioned_symbol_skip (const char *undef_name,
+				  const ElfW(Sym) **ref,
+				  struct link_map *symbol_scope[],
+				  const char *reference_name,
+				  const char *version_name,
+				  struct link_map *skip_map,
+				  int flags)
+{
+  const unsigned long int hash = _dl_elf_hash (undef_name);
+  struct sym_val current_value = { 0, NULL };
+  struct link_map **scope;
+  hash_name_pair version;
+  size_t i;
+
+  /* First convert the VERSION_NAME into a `hash_name_pair' value.  */
+  version.hash = _dl_elf_hash (version_name);
+  version.name = version_name;
+
+  /* Search the relevant loaded objects for a definition.  */
+  scope = symbol_scope;
+  for (i = 0; (*scope)->l_dupsearchlist[i] != skip_map; ++i)
+    assert (i < (*scope)->l_ndupsearchlist);
+
+  if (! do_lookup (undef_name, hash, ref, &current_value,
+		   (*scope)->l_dupsearchlist, i, (*scope)->l_ndupsearchlist,
+		   reference_name, &version, skip_map, flags))
     while (*++scope)
       if (do_lookup (undef_name, hash, ref, &current_value,
 		     (*scope)->l_dupsearchlist, 0, (*scope)->l_ndupsearchlist,
-		     reference_name, skip_map, flags))
+		     reference_name, &version, skip_map, flags))
 	break;
 
   *ref = current_value.s;