mirror of
				https://sourceware.org/git/glibc.git
				synced 2025-11-03 20:53:13 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			390 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			390 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* Handle symbol and library versioning.
 | 
						|
   Copyright (C) 1997-2017 Free Software Foundation, Inc.
 | 
						|
   This file is part of the GNU C Library.
 | 
						|
   Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997.
 | 
						|
 | 
						|
   The GNU C Library is free software; you can redistribute it and/or
 | 
						|
   modify it under the terms of the GNU Lesser General Public
 | 
						|
   License as published by the Free Software Foundation; either
 | 
						|
   version 2.1 of the License, or (at your option) any later version.
 | 
						|
 | 
						|
   The GNU C Library is distributed in the hope that it will be useful,
 | 
						|
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
   Lesser General Public License for more details.
 | 
						|
 | 
						|
   You should have received a copy of the GNU Lesser General Public
 | 
						|
   License along with the GNU C Library; if not, see
 | 
						|
   <http://www.gnu.org/licenses/>.  */
 | 
						|
 | 
						|
#include <elf.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <libintl.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <ldsodefs.h>
 | 
						|
#include <_itoa.h>
 | 
						|
 | 
						|
#include <assert.h>
 | 
						|
 | 
						|
 | 
						|
#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;								      \
 | 
						|
  })
 | 
						|
 | 
						|
 | 
						|
static inline struct link_map *
 | 
						|
__attribute ((always_inline))
 | 
						|
find_needed (const char *name, struct link_map *map)
 | 
						|
{
 | 
						|
  struct link_map *tmap;
 | 
						|
  unsigned int n;
 | 
						|
 | 
						|
  for (tmap = GL(dl_ns)[map->l_ns]._ns_loaded; tmap != NULL;
 | 
						|
       tmap = tmap->l_next)
 | 
						|
    if (_dl_name_match_p (name, tmap))
 | 
						|
      return tmap;
 | 
						|
 | 
						|
  /* The required object is not in the global scope, look to see if it is
 | 
						|
     a dependency of the current object.  */
 | 
						|
  for (n = 0; n < map->l_searchlist.r_nlist; n++)
 | 
						|
    if (_dl_name_match_p (name, map->l_searchlist.r_list[n]))
 | 
						|
      return map->l_searchlist.r_list[n];
 | 
						|
 | 
						|
  /* Should never happen.  */
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
internal_function
 | 
						|
match_symbol (const char *name, Lmid_t ns, ElfW(Word) hash, const char *string,
 | 
						|
	      struct link_map *map, int verbose, int weak)
 | 
						|
{
 | 
						|
  const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
 | 
						|
  ElfW(Addr) def_offset;
 | 
						|
  ElfW(Verdef) *def;
 | 
						|
  /* Initialize to make the compiler happy.  */
 | 
						|
  const char *errstring = NULL;
 | 
						|
  int result = 0;
 | 
						|
 | 
						|
  /* Display information about what we are doing while debugging.  */
 | 
						|
  if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_VERSIONS))
 | 
						|
    _dl_debug_printf ("\
 | 
						|
checking for version `%s' in file %s [%lu] required by file %s [%lu]\n",
 | 
						|
		      string, DSO_FILENAME (map->l_name),
 | 
						|
		      map->l_ns, name, ns);
 | 
						|
 | 
						|
  if (__glibc_unlikely (map->l_info[VERSYMIDX (DT_VERDEF)] == NULL))
 | 
						|
    {
 | 
						|
      /* The file has no symbol versioning.  I.e., the dependent
 | 
						|
	 object was linked against another version of this file.  We
 | 
						|
	 only print a message if verbose output is requested.  */
 | 
						|
      if (verbose)
 | 
						|
	{
 | 
						|
	  /* XXX We cannot translate the messages.  */
 | 
						|
	  errstring = make_string ("\
 | 
						|
no version information available (required by ", name, ")");
 | 
						|
	  goto call_cerror;
 | 
						|
	}
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
 | 
						|
  def_offset = map->l_info[VERSYMIDX (DT_VERDEF)]->d_un.d_ptr;
 | 
						|
  assert (def_offset != 0);
 | 
						|
 | 
						|
  def = (ElfW(Verdef) *) ((char *) map->l_addr + def_offset);
 | 
						|
  while (1)
 | 
						|
    {
 | 
						|
      /* Currently the version number of the definition entry is 1.
 | 
						|
	 Make sure all we see is this version.  */
 | 
						|
      if (__builtin_expect (def->vd_version, 1) != 1)
 | 
						|
	{
 | 
						|
	  char buf[20];
 | 
						|
	  buf[sizeof (buf) - 1] = '\0';
 | 
						|
	  /* XXX We cannot translate the message.  */
 | 
						|
	  errstring = make_string ("unsupported version ",
 | 
						|
				   _itoa (def->vd_version,
 | 
						|
					  &buf[sizeof (buf) - 1], 10, 0),
 | 
						|
				   " of Verdef record");
 | 
						|
	  result = 1;
 | 
						|
	  goto call_cerror;
 | 
						|
	}
 | 
						|
 | 
						|
      /* Compare the hash values.  */
 | 
						|
      if (hash == def->vd_hash)
 | 
						|
	{
 | 
						|
	  ElfW(Verdaux) *aux = (ElfW(Verdaux) *) ((char *) def + def->vd_aux);
 | 
						|
 | 
						|
	  /* To be safe, compare the string as well.  */
 | 
						|
	  if (__builtin_expect (strcmp (string, strtab + aux->vda_name), 0)
 | 
						|
	      == 0)
 | 
						|
	    /* Bingo!  */
 | 
						|
	    return 0;
 | 
						|
	}
 | 
						|
 | 
						|
      /* If no more definitions we failed to find what we want.  */
 | 
						|
      if (def->vd_next == 0)
 | 
						|
	break;
 | 
						|
 | 
						|
      /* Next definition.  */
 | 
						|
      def = (ElfW(Verdef) *) ((char *) def + def->vd_next);
 | 
						|
    }
 | 
						|
 | 
						|
  /* Symbol not found.  If it was a weak reference it is not fatal.  */
 | 
						|
  if (__glibc_likely (weak))
 | 
						|
    {
 | 
						|
      if (verbose)
 | 
						|
	{
 | 
						|
	  /* XXX We cannot translate the message.  */
 | 
						|
	  errstring = make_string ("weak version `", string,
 | 
						|
				   "' not found (required by ", name, ")");
 | 
						|
	  goto call_cerror;
 | 
						|
	}
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
 | 
						|
  /* XXX We cannot translate the message.  */
 | 
						|
  errstring = make_string ("version `", string, "' not found (required by ",
 | 
						|
			   name, ")");
 | 
						|
  result = 1;
 | 
						|
 call_cerror:
 | 
						|
  _dl_signal_cerror (0, DSO_FILENAME (map->l_name),
 | 
						|
		     N_("version lookup error"), errstring);
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
int
 | 
						|
internal_function
 | 
						|
_dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
 | 
						|
{
 | 
						|
  int result = 0;
 | 
						|
  const char *strtab;
 | 
						|
  /* Pointer to section with needed versions.  */
 | 
						|
  ElfW(Dyn) *dyn;
 | 
						|
  /* Pointer to dynamic section with definitions.  */
 | 
						|
  ElfW(Dyn) *def;
 | 
						|
  /* We need to find out which is the highest version index used
 | 
						|
    in a dependecy.  */
 | 
						|
  unsigned int ndx_high = 0;
 | 
						|
  /* Initialize to make the compiler happy.  */
 | 
						|
  const char *errstring = NULL;
 | 
						|
  int errval = 0;
 | 
						|
 | 
						|
  /* If we don't have a string table, we must be ok.  */
 | 
						|
  if (map->l_info[DT_STRTAB] == NULL)
 | 
						|
    return 0;
 | 
						|
  strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
 | 
						|
 | 
						|
  dyn = map->l_info[VERSYMIDX (DT_VERNEED)];
 | 
						|
  def = map->l_info[VERSYMIDX (DT_VERDEF)];
 | 
						|
 | 
						|
  if (dyn != NULL)
 | 
						|
    {
 | 
						|
      /* This file requires special versions from its dependencies.  */
 | 
						|
      ElfW(Verneed) *ent = (ElfW(Verneed) *) (map->l_addr + dyn->d_un.d_ptr);
 | 
						|
 | 
						|
      /* Currently the version number of the needed entry is 1.
 | 
						|
	 Make sure all we see is this version.  */
 | 
						|
      if (__builtin_expect (ent->vn_version, 1) != 1)
 | 
						|
	{
 | 
						|
	  char buf[20];
 | 
						|
	  buf[sizeof (buf) - 1] = '\0';
 | 
						|
	  /* XXX We cannot translate the message.  */
 | 
						|
	  errstring = make_string ("unsupported version ",
 | 
						|
				   _itoa (ent->vn_version,
 | 
						|
					  &buf[sizeof (buf) - 1], 10, 0),
 | 
						|
				   " of Verneed record\n");
 | 
						|
	call_error:
 | 
						|
	  _dl_signal_error (errval, DSO_FILENAME (map->l_name),
 | 
						|
			    NULL, errstring);
 | 
						|
	}
 | 
						|
 | 
						|
      while (1)
 | 
						|
	{
 | 
						|
	  ElfW(Vernaux) *aux;
 | 
						|
	  struct link_map *needed = find_needed (strtab + ent->vn_file, map);
 | 
						|
 | 
						|
	  /* If NEEDED is NULL this means a dependency was not found
 | 
						|
	     and no stub entry was created.  This should never happen.  */
 | 
						|
	  assert (needed != NULL);
 | 
						|
 | 
						|
	  /* Make sure this is no stub we created because of a missing
 | 
						|
	     dependency.  */
 | 
						|
	  if (__builtin_expect (! trace_mode, 1)
 | 
						|
	      || ! __builtin_expect (needed->l_faked, 0))
 | 
						|
	    {
 | 
						|
	      /* NEEDED is the map for the file we need.  Now look for the
 | 
						|
		 dependency symbols.  */
 | 
						|
	      aux = (ElfW(Vernaux) *) ((char *) ent + ent->vn_aux);
 | 
						|
	      while (1)
 | 
						|
		{
 | 
						|
		  /* Match the symbol.  */
 | 
						|
		  result |= match_symbol (DSO_FILENAME (map->l_name),
 | 
						|
					  map->l_ns, aux->vna_hash,
 | 
						|
					  strtab + aux->vna_name,
 | 
						|
					  needed->l_real, verbose,
 | 
						|
					  aux->vna_flags & VER_FLG_WEAK);
 | 
						|
 | 
						|
		  /* Compare the version index.  */
 | 
						|
		  if ((unsigned int) (aux->vna_other & 0x7fff) > ndx_high)
 | 
						|
		    ndx_high = aux->vna_other & 0x7fff;
 | 
						|
 | 
						|
		  if (aux->vna_next == 0)
 | 
						|
		    /* No more symbols.  */
 | 
						|
		    break;
 | 
						|
 | 
						|
		  /* Next symbol.  */
 | 
						|
		  aux = (ElfW(Vernaux) *) ((char *) aux + aux->vna_next);
 | 
						|
		}
 | 
						|
	    }
 | 
						|
 | 
						|
	  if (ent->vn_next == 0)
 | 
						|
	    /* No more dependencies.  */
 | 
						|
	    break;
 | 
						|
 | 
						|
	  /* Next dependency.  */
 | 
						|
	  ent = (ElfW(Verneed) *) ((char *) ent + ent->vn_next);
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
  /* We also must store the names of the defined versions.  Determine
 | 
						|
     the maximum index here as well.
 | 
						|
 | 
						|
     XXX We could avoid the loop by just taking the number of definitions
 | 
						|
     as an upper bound of new indeces.  */
 | 
						|
  if (def != NULL)
 | 
						|
    {
 | 
						|
      ElfW(Verdef) *ent;
 | 
						|
      ent = (ElfW(Verdef) *) (map->l_addr + def->d_un.d_ptr);
 | 
						|
      while (1)
 | 
						|
	{
 | 
						|
	  if ((unsigned int) (ent->vd_ndx & 0x7fff) > ndx_high)
 | 
						|
	    ndx_high = ent->vd_ndx & 0x7fff;
 | 
						|
 | 
						|
	  if (ent->vd_next == 0)
 | 
						|
	    /* No more definitions.  */
 | 
						|
	    break;
 | 
						|
 | 
						|
	  ent = (ElfW(Verdef) *) ((char *) ent + ent->vd_next);
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
  if (ndx_high > 0)
 | 
						|
    {
 | 
						|
      /* 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 = (struct r_found_version *)
 | 
						|
	calloc (ndx_high + 1, sizeof (*map->l_versions));
 | 
						|
      if (__glibc_unlikely (map->l_versions == NULL))
 | 
						|
	{
 | 
						|
	  errstring = N_("cannot allocate version reference table");
 | 
						|
	  errval = ENOMEM;
 | 
						|
	  goto call_error;
 | 
						|
	}
 | 
						|
 | 
						|
      /* Store the number of available symbols.  */
 | 
						|
      map->l_nversions = ndx_high + 1;
 | 
						|
 | 
						|
      /* Compute the pointer to the version symbols.  */
 | 
						|
      map->l_versyms = (void *) D_PTR (map, l_info[VERSYMIDX (DT_VERSYM)]);
 | 
						|
 | 
						|
      if (dyn != NULL)
 | 
						|
	{
 | 
						|
	  ElfW(Verneed) *ent;
 | 
						|
	  ent = (ElfW(Verneed) *) (map->l_addr + dyn->d_un.d_ptr);
 | 
						|
	  while (1)
 | 
						|
	    {
 | 
						|
	      ElfW(Vernaux) *aux;
 | 
						|
	      aux = (ElfW(Vernaux) *) ((char *) ent + ent->vn_aux);
 | 
						|
	      while (1)
 | 
						|
		{
 | 
						|
		  ElfW(Half) ndx = aux->vna_other & 0x7fff;
 | 
						|
		  /* In trace mode, dependencies may be missing.  */
 | 
						|
		  if (__glibc_likely (ndx < map->l_nversions))
 | 
						|
		    {
 | 
						|
		      map->l_versions[ndx].hash = aux->vna_hash;
 | 
						|
		      map->l_versions[ndx].hidden = aux->vna_other & 0x8000;
 | 
						|
		      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.  */
 | 
						|
		    break;
 | 
						|
 | 
						|
		  /* Advance to next symbol.  */
 | 
						|
		  aux = (ElfW(Vernaux) *) ((char *) aux + aux->vna_next);
 | 
						|
		}
 | 
						|
 | 
						|
	      if (ent->vn_next == 0)
 | 
						|
		/* No more dependencies.  */
 | 
						|
		break;
 | 
						|
 | 
						|
	      /* Advance to next dependency.  */
 | 
						|
	      ent = (ElfW(Verneed) *) ((char *) ent + ent->vn_next);
 | 
						|
	    }
 | 
						|
	}
 | 
						|
 | 
						|
      /* And insert the defined versions.  */
 | 
						|
      if (def != NULL)
 | 
						|
	{
 | 
						|
	  ElfW(Verdef) *ent;
 | 
						|
	  ent = (ElfW(Verdef)  *) (map->l_addr + def->d_un.d_ptr);
 | 
						|
	  while (1)
 | 
						|
	    {
 | 
						|
	      ElfW(Verdaux) *aux;
 | 
						|
	      aux = (ElfW(Verdaux) *) ((char *) ent + ent->vd_aux);
 | 
						|
 | 
						|
	      if ((ent->vd_flags & VER_FLG_BASE) == 0)
 | 
						|
		{
 | 
						|
		  /* The name of the base version should not be
 | 
						|
		     available for matching a versioned symbol.  */
 | 
						|
		  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)
 | 
						|
		/* No more definitions.  */
 | 
						|
		break;
 | 
						|
 | 
						|
	      ent = (ElfW(Verdef) *) ((char *) ent + ent->vd_next);
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
int
 | 
						|
internal_function
 | 
						|
_dl_check_all_versions (struct link_map *map, int verbose, int trace_mode)
 | 
						|
{
 | 
						|
  struct link_map *l;
 | 
						|
  int result = 0;
 | 
						|
 | 
						|
  for (l = map; l != NULL; l = l->l_next)
 | 
						|
    result |= (! l->l_faked
 | 
						|
	       && _dl_check_map_versions (l, verbose, trace_mode));
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 |