summary refs log tree commit diff
path: root/elf
diff options
context:
space:
mode:
Diffstat (limited to 'elf')
-rw-r--r--elf/dl-hash.h69
-rw-r--r--elf/dl-load.c2
-rw-r--r--elf/dl-lookup.c187
-rw-r--r--elf/dl-version.c13
-rw-r--r--elf/dlvsym.c18
-rw-r--r--elf/link.h23
6 files changed, 198 insertions, 114 deletions
diff --git a/elf/dl-hash.h b/elf/dl-hash.h
new file mode 100644
index 0000000000..007f192a23
--- /dev/null
+++ b/elf/dl-hash.h
@@ -0,0 +1,69 @@
+/* Compute hash alue for given string according to ELF standard.
+   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
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifndef _DL_HASH_H
+#define _DL_HASH_H	1
+
+
+/* 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;
+  if (*name != '\0')
+    {
+      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++;
+		  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;
+}
+
+#endif /* dl-hash.h */
diff --git a/elf/dl-load.c b/elf/dl-load.c
index e3de0764ec..a103d38548 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -540,7 +540,7 @@ _dl_map_object (struct link_map *loader, const char *name, int type,
 
   /* Look for this name among those already loaded.  */
   for (l = _dl_loaded; l; l = l->l_next)
-    if (_dl_does_name_match_p (name, l) ||
+    if (_dl_name_match_p (name, l) ||
 	/* If the requested name matches the soname of a loaded object,
 	   use that object.  */
 	(l->l_info[DT_SONAME] &&
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
index 06d1d09b90..8702a85bbb 100644
--- a/elf/dl-lookup.c
+++ b/elf/dl-lookup.c
@@ -22,6 +22,7 @@
 #include <assert.h>
 #include <string.h>
 
+#include <dl-hash.h>
 #include "../stdio-common/_itoa.h"
 
 #define VERSTAG(tag)	(DT_NUM + DT_PROCNUM + DT_VERSIONTAGIDX (tag))
@@ -33,58 +34,32 @@ struct sym_val
   };
 
 
-/* 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;
-  if (*name != '\0')
-    {
-      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++;
-		  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;
-}
-
-
-/* Inner part of the lookup functions.  */
-static inline ElfW(Addr)
+#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;								      \
+  })
+
+
+/* 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 inline int
 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, const hash_name_pair *version,
+	   const char *reference_name, const struct r_found_version *version,
 	   struct link_map *skip, int flags)
 {
   struct link_map *map;
@@ -108,8 +83,7 @@ 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)
+      if (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
@@ -143,13 +117,41 @@ do_lookup (const char *undef_name, unsigned long int hash,
 	    /* Not the symbol we are looking for.  */
 	    continue;
 
-	  if (verstab != NULL)
+	  if (version == 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;
+	      /* No specific version is selected.  When the object
+		 file also does not define a version we have a match.
+		 Otherwise we only accept the default version, i.e.,
+		 the version which name is "".  */
+	      if (verstab != NULL)
+		{
+		  ElfW(Half) ndx = verstab[symidx] & 0x7fff;
+		  if (map->l_versions[ndx].hash != 0)
+		    continue;
+		}
+	    }
+	  else
+	    {
+	      if (verstab == NULL)
+		{
+		  /* We need a versioned system 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.  */
+		  if (version->filename != NULL
+		      && _dl_name_match_p (version->filename, map))
+		    return -1;
+		  /* Otherwise we accept the symbol.  */
+		}
+	      else
+		{
+		  /* 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))
+		    /* It's not the version we want.  */
+		    continue;
+		}
 	    }
 
 	  switch (ELFW(ST_BIND) (sym->st_info))
@@ -173,6 +175,12 @@ do_lookup (const char *undef_name, unsigned long int hash,
 	      break;
 	    }
 	}
+
+      /* If this current is the one mentioned in the verneed entry it
+	 is a bug.  */
+      if (version != NULL && version->filename != NULL
+	  && _dl_name_match_p (version->filename, map))
+	return -1;
     }
 
   /* We have not found anything until now.  */
@@ -204,15 +212,9 @@ _dl_lookup_symbol (const char *undef_name, const ElfW(Sym) **ref,
 
   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 msg[] = "undefined symbol: ";
-      const size_t len = strlen (undef_name);
-      char buf[sizeof msg + len];
-      memcpy (buf, msg, sizeof msg - 1);
-      memcpy (&buf[sizeof msg - 1], undef_name, len + 1);
-      _dl_signal_error (0, reference_name, buf);
-    }
+    /* We could find no value for a strong reference.  */
+    _dl_signal_error (0, reference_name,
+		      make_string ("undefined symbol: ", undef_name));
 
   *ref = current_value.s;
   return current_value.a;
@@ -264,7 +266,7 @@ 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 struct r_found_version *version, int flags)
 {
   const unsigned long int hash = _dl_elf_hash (undef_name);
   struct sym_val current_value = { 0, NULL };
@@ -272,28 +274,30 @@ _dl_lookup_versioned_symbol (const char *undef_name, const ElfW(Sym) **ref,
 
   /* 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;
+    {
+      int res = do_lookup (undef_name, hash, ref, &current_value,
+			   (*scope)->l_searchlist, 0, (*scope)->l_nsearchlist,
+			   reference_name, version, NULL, flags);
+      if (res > 0)
+	break;
+
+      if (res < 0)
+	/* Oh, oh.  The file named in the relocation entry does not
+	   contain the needed symbol.  */
+	_dl_signal_error (0, *reference_name ? reference_name : NULL,
+			  make_string ("symbol ", undef_name, ", version ",
+				       version->name,
+				       " not defined in file ",
+				       version->filename,
+				       " with link time reference"));
+    }
 
   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);
-    }
+    /* We could find no value for a strong reference.  */
+    _dl_signal_error (0, reference_name,
+		      make_string ("undefined symbol: ", undef_name,
+				   ", version ", version->name ?: NULL));
 
   *ref = current_value.s;
   return current_value.a;
@@ -307,20 +311,15 @@ _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,
+				  const struct r_found_version *version,
 				  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)
@@ -328,11 +327,11 @@ _dl_lookup_versioned_symbol_skip (const char *undef_name,
 
   if (! do_lookup (undef_name, hash, ref, &current_value,
 		   (*scope)->l_dupsearchlist, i, (*scope)->l_ndupsearchlist,
-		   reference_name, &version, skip_map, flags))
+		   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, &version, skip_map, flags))
+		     reference_name, version, skip_map, flags))
 	break;
 
   *ref = current_value.s;
diff --git a/elf/dl-version.c b/elf/dl-version.c
index 4382a4c0ae..ae1c2f34f7 100644
--- a/elf/dl-version.c
+++ b/elf/dl-version.c
@@ -58,7 +58,7 @@ find_needed (struct link_map *map, const char *name)
   unsigned int n;
 
   for (n = 0; n < map->l_nsearchlist; ++n)
-    if (_dl_does_name_match_p (name, map->l_searchlist[n]))
+    if (_dl_name_match_p (name, map->l_searchlist[n]))
       return map->l_searchlist[n];
 
   /* Should never happen.  */
@@ -246,13 +246,14 @@ _dl_check_map_versions (struct link_map *map, int verbose)
       /* Now we are ready to build the array with the version names
 	 which can be indexed by the version index in the VERSYM
 	 section.  */
-      map->l_versions = (hash_name_pair*) malloc ((ndx_high + 1)
-						  * sizeof (hash_name_pair));
-      memset (map->l_versions, '\0', (ndx_high + 1) * sizeof (hash_name_pair));
+      map->l_versions = (struct r_found_version *)
+	malloc ((ndx_high + 1) * sizeof (*map->l_versions));
+      memset (map->l_versions, '\0',
+	      (ndx_high + 1) * sizeof (*map->l_versions));
       if (map->l_versions == NULL)
 	{
 	  _dl_signal_error (ENOMEM, (*map->l_name ? map->l_name : _dl_argv[0]),
-			    "cannot allocate version name table");
+			    "cannot allocate version reference table");
 	  result = 1;
 	}
       else
@@ -273,6 +274,7 @@ _dl_check_map_versions (struct link_map *map, int verbose)
 		      ElfW(Half) ndx = aux->vna_other & 0x7fff;
 		      map->l_versions[ndx].hash = aux->vna_hash;
 		      map->l_versions[ndx].name = &strtab[aux->vna_name];
+		      map->l_versions[ndx].filename = &strtab[ent->vn_file];
 
 		      if (aux->vna_next == 0)
 			/* No more symbols.  */
@@ -308,6 +310,7 @@ _dl_check_map_versions (struct link_map *map, int verbose)
 		      ElfW(Half) ndx = ent->vd_ndx & 0x7fff;
 		      map->l_versions[ndx].hash = ent->vd_hash;
 		      map->l_versions[ndx].name = &strtab[aux->vda_name];
+		      map->l_versions[ndx].filename = NULL;
 		    }
 
 		  if (ent->vd_next == 0)
diff --git a/elf/dlvsym.c b/elf/dlvsym.c
index 5063b3ba0d..922a6eed1f 100644
--- a/elf/dlvsym.c
+++ b/elf/dlvsym.c
@@ -1,4 +1,4 @@
-/* Look up a symbol in a shared object loaded by `dlopen'.
+/* Look up a versioned symbol in a shared object loaded by `dlopen'.
    Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
@@ -22,12 +22,14 @@
 #include <dlfcn.h>
 #include <setjmp.h>
 
+#include <dl-hash.h>
 
 void *
-__dlvsym (void *handle, const char *name, const char *version)
+__dlvsym (void *handle, const char *name, const char *version_str)
 {
   ElfW(Addr) caller = (ElfW(Addr)) __builtin_return_address (0);
   ElfW(Addr) loadbase;
+  struct r_found_version version;
   const ElfW(Sym) *ref = NULL;
   void doit (void)
     {
@@ -35,7 +37,7 @@ __dlvsym (void *handle, const char *name, const char *version)
 	/* Search the global scope.  */
 	loadbase = _dl_lookup_versioned_symbol
 	  (name, &ref, &(_dl_global_scope ?: _dl_default_scope)[2], NULL,
-	   version, 0);
+	   &version, 0);
       else if (handle == RTLD_NEXT)
 	{
 	  struct link_map *l, *match;
@@ -55,7 +57,7 @@ RTLD_NEXT used in code not dynamically loaded"));
 	    l = l->l_loader;
 
 	  loadbase = _dl_lookup_versioned_symbol_skip
-	    (name, &ref, &_dl_loaded, NULL, version, l, 0);
+	    (name, &ref, &_dl_loaded, NULL, &version, l, 0);
 	}
       else
 	{
@@ -63,10 +65,16 @@ RTLD_NEXT used in code not dynamically loaded"));
 	  struct link_map *map = handle;
 	  struct link_map *mapscope[2] = { map, NULL };
 	  loadbase = _dl_lookup_versioned_symbol
-	    (name, &ref, mapscope, map->l_name, version, 0);
+	    (name, &ref, mapscope, map->l_name, &version, 0);
 	}
     }
 
+  /* Compute hash value to the version string.  */
+  version.name = version_str;
+  version.hash = _dl_elf_hash (version_str);
+  /* We don't have a specific file where the symbol can be found.  */
+  version.filename = NULL;
+
   return _dlerror_run (doit) ? NULL : (void *) (loadbase + ref->st_value);
 }
 weak_alias (__dlvsym, dlvsym)
diff --git a/elf/link.h b/elf/link.h
index 58041332e9..b0b920074b 100644
--- a/elf/link.h
+++ b/elf/link.h
@@ -25,6 +25,8 @@
 
 #include <elf.h>
 
+__BEGIN_DECLS
+
 /* We use this macro to refer to ELF types independent of the native wordsize.
    `ElfW(TYPE)' is used in place of `Elf32_TYPE' or `Elf64_TYPE'.  */
 #define ElfW(type)	_ElfW (Elf, __ELF_NATIVE_CLASS, type)
@@ -76,11 +78,13 @@ extern ElfW(Dyn) _DYNAMIC[];
 
 /* For the version handling we need an array with only names and their
    hash values.  */
-typedef struct
-{
-  const char *name;
-  ElfW(Word) hash;
-} hash_name_pair;
+struct r_found_version
+  {
+    const char *name;
+    ElfW(Word) hash;
+
+    const char *filename;
+  };
 
 /* Structure describing a loaded shared object.  The `l_next' and `l_prev'
    members form a chain of all the shared objects loaded at startup.
@@ -153,14 +157,14 @@ struct link_map
 
     /* Array with version names.  */
     unsigned int l_nversions;
-    hash_name_pair *l_versions;
+    struct r_found_version *l_versions;
   };
 
 
 /* Test whether given NAME matches any of the names of the given object.  */
 static inline int
 __attribute__ ((unused))
-_dl_does_name_match_p (const char *__name, struct link_map *__map)
+_dl_name_match_p (const char *__name, struct link_map *__map)
 {
   int __found = strcmp (__name, __map->l_name) == 0;
   struct libname_list *__runp = __map->l_libname;
@@ -301,7 +305,7 @@ extern ElfW(Addr) _dl_lookup_versioned_symbol (const char *undef,
 					       const ElfW(Sym) **sym,
 					       struct link_map *symbol_scope[],
 					       const char *reference_name,
-					       const hash_name_pair *version,
+					       const struct r_found_version *version,
 					       int flags);
 
 /* For handling RTLD_NEXT we must be able to skip shared objects.  */
@@ -318,7 +322,7 @@ extern ElfW(Addr) _dl_lookup_versioned_symbol_skip (const char *undef,
 						    const ElfW(Sym) **sym,
 						    struct link_map *symbol_scope[],
 						    const char *reference_name,
-						    const char *version_name,
+						    const struct r_found_version *version,
 						    struct link_map *skip_this,
 						    int flags);
 
@@ -398,5 +402,6 @@ extern void _dl_debug_state (void);
    in the `r_ldbase' member.  Returns the address of the structure.  */
 extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase);
 
+__END_DECLS
 
 #endif /* link.h */