]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - elf/dl-cache.c
Remove kernel version check
[thirdparty/glibc.git] / elf / dl-cache.c
index 45894ecd2fb4e0396aef0eaa17f85c146332d328..8bbf110d02c832df53924faf6b86a09df656c308 100644 (file)
@@ -1,5 +1,5 @@
 /* Support for reading /etc/ld.so.cache files written by Linux ldconfig.
-   Copyright (C) 1996-2020 Free Software Foundation, Inc.
+   Copyright (C) 1996-2022 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
@@ -25,6 +25,7 @@
 #include <stdint.h>
 #include <_itoa.h>
 #include <dl-hwcaps.h>
+#include <dl-isa-level.h>
 
 #ifndef _DL_PLATFORMS_COUNT
 # define _DL_PLATFORMS_COUNT 0
@@ -35,6 +36,144 @@ static struct cache_file *cache;
 static struct cache_file_new *cache_new;
 static size_t cachesize;
 
+#ifdef SHARED
+/* This is used to cache the priorities of glibc-hwcaps
+   subdirectories.  The elements of _dl_cache_priorities correspond to
+   the strings in the cache_extension_tag_glibc_hwcaps section.  */
+static uint32_t *glibc_hwcaps_priorities;
+static uint32_t glibc_hwcaps_priorities_length;
+static uint32_t glibc_hwcaps_priorities_allocated;
+
+/* True if the full malloc was used to allocated the array.  */
+static bool glibc_hwcaps_priorities_malloced;
+
+/* Deallocate the glibc_hwcaps_priorities array.  */
+static void
+glibc_hwcaps_priorities_free (void)
+{
+  /* When the minimal malloc is in use, free does not do anything,
+     so it does not make sense to call it.  */
+  if (glibc_hwcaps_priorities_malloced)
+    free (glibc_hwcaps_priorities);
+  glibc_hwcaps_priorities = NULL;
+  glibc_hwcaps_priorities_allocated = 0;
+}
+
+/* Ordered comparison of a hwcaps string from the cache on the left
+   (identified by its string table index) and a _dl_hwcaps_priorities
+   element on the right.  */
+static int
+glibc_hwcaps_compare (uint32_t left_index, struct dl_hwcaps_priority *right)
+{
+  const char *left_name = (const char *) cache + left_index;
+  uint32_t left_name_length = strlen (left_name);
+  uint32_t to_compare;
+  if (left_name_length < right->name_length)
+    to_compare = left_name_length;
+  else
+    to_compare = right->name_length;
+  int cmp = memcmp (left_name, right->name, to_compare);
+  if (cmp != 0)
+    return cmp;
+  if (left_name_length < right->name_length)
+    return -1;
+  else if (left_name_length > right->name_length)
+    return 1;
+  else
+    return 0;
+}
+
+/* Initialize the glibc_hwcaps_priorities array and its length,
+   glibc_hwcaps_priorities_length.  */
+static void
+glibc_hwcaps_priorities_init (void)
+{
+  struct cache_extension_all_loaded ext;
+  if (!cache_extension_load (cache_new, cache, cachesize, &ext))
+    return;
+
+  uint32_t length = (ext.sections[cache_extension_tag_glibc_hwcaps].size
+                    / sizeof (uint32_t));
+  if (length > glibc_hwcaps_priorities_allocated)
+    {
+      glibc_hwcaps_priorities_free ();
+
+      uint32_t *new_allocation = malloc (length * sizeof (uint32_t));
+      if (new_allocation == NULL)
+       /* This effectively disables hwcaps on memory allocation
+          errors.  */
+       return;
+
+      glibc_hwcaps_priorities = new_allocation;
+      glibc_hwcaps_priorities_allocated = length;
+      glibc_hwcaps_priorities_malloced = __rtld_malloc_is_complete ();
+    }
+
+  /* Compute the priorities for the subdirectories by merging the
+     array in the cache with the dl_hwcaps_priorities array.  */
+  const uint32_t *left = ext.sections[cache_extension_tag_glibc_hwcaps].base;
+  const uint32_t *left_end = left + length;
+  struct dl_hwcaps_priority *right = _dl_hwcaps_priorities;
+  struct dl_hwcaps_priority *right_end = right + _dl_hwcaps_priorities_length;
+  uint32_t *result = glibc_hwcaps_priorities;
+
+  while (left < left_end && right < right_end)
+    {
+      if (*left < cachesize)
+       {
+         int cmp = glibc_hwcaps_compare (*left, right);
+         if (cmp == 0)
+           {
+             *result = right->priority;
+             ++result;
+             ++left;
+             ++right;
+           }
+         else if (cmp < 0)
+           {
+             *result = 0;
+             ++result;
+             ++left;
+           }
+         else
+           ++right;
+       }
+      else
+       {
+         *result = 0;
+         ++result;
+       }
+    }
+  while (left < left_end)
+    {
+      *result = 0;
+      ++result;
+      ++left;
+    }
+
+  glibc_hwcaps_priorities_length = length;
+}
+
+/* Return the priority of the cache_extension_tag_glibc_hwcaps section
+   entry at INDEX.  Zero means do not use.  Otherwise, lower values
+   indicate greater preference.  */
+static uint32_t
+glibc_hwcaps_priority (uint32_t index)
+{
+  /* This does not need to repeated initialization attempts because
+     this function is only called if there is glibc-hwcaps data in the
+     cache, so the first call initializes the glibc_hwcaps_priorities
+     array.  */
+  if (glibc_hwcaps_priorities_length == 0)
+    glibc_hwcaps_priorities_init ();
+
+  if (index < glibc_hwcaps_priorities_length)
+    return glibc_hwcaps_priorities[index];
+  else
+    return 0;
+}
+#endif /* SHARED */
+
 /* True if PTR is a valid string table index.  */
 static inline bool
 _dl_cache_verify_ptr (uint32_t ptr, size_t string_table_size)
@@ -74,6 +213,9 @@ search_cache (const char *string_table, uint32_t string_table_size,
   int left = 0;
   int right = nlibs - 1;
   const char *best = NULL;
+#ifdef SHARED
+  uint32_t best_priority = 0;
+#endif
 
   while (left <= right)
     {
@@ -127,35 +269,74 @@ search_cache (const char *string_table, uint32_t string_table_size,
              if (_dl_cache_check_flags (flags)
                  && _dl_cache_verify_ptr (lib->value, string_table_size))
                {
-                 if (best == NULL || flags == GLRO (dl_correct_cache_id))
+                 /* Named/extension hwcaps get slightly different
+                    treatment: We keep searching for a better
+                    match.  */
+                 bool named_hwcap = false;
+
+                 if (entry_size >= sizeof (struct file_entry_new))
                    {
-                     if (entry_size >= sizeof (struct file_entry_new))
-                       {
-                         /* The entry is large enough to include
-                            HWCAP data.  Check it.  */
-                         struct file_entry_new *libnew
-                           = (struct file_entry_new *) lib;
+                     /* The entry is large enough to include
+                        HWCAP data.  Check it.  */
+                     struct file_entry_new *libnew
+                       = (struct file_entry_new *) lib;
+
+#ifdef SHARED
+                     named_hwcap = dl_cache_hwcap_extension (libnew);
+                     if (named_hwcap
+                         && !dl_cache_hwcap_isa_level_compatible (libnew))
+                       continue;
+#endif
 
-                         if (libnew->hwcap & hwcap_exclude)
-                           continue;
-                         if (GLRO (dl_osversion)
-                             && libnew->osversion > GLRO (dl_osversion))
+                     /* The entries with named/extension hwcaps have
+                        been exhausted (they are listed before all
+                        other entries).  Return the best match
+                        encountered so far if there is one.  */
+                     if (!named_hwcap && best != NULL)
+                       break;
+
+                     if ((libnew->hwcap & hwcap_exclude) && !named_hwcap)
+                       continue;
+                     if (_DL_PLATFORMS_COUNT
+                         && (libnew->hwcap & _DL_HWCAP_PLATFORM) != 0
+                         && ((libnew->hwcap & _DL_HWCAP_PLATFORM)
+                             != platform))
+                       continue;
+
+#ifdef SHARED
+                     /* For named hwcaps, determine the priority and
+                        see if beats what has been found so far.  */
+                     if (named_hwcap)
+                       {
+                         uint32_t entry_priority
+                           = glibc_hwcaps_priority (libnew->hwcap);
+                         if (entry_priority == 0)
+                           /* Not usable at all.  Skip.  */
                            continue;
-                         if (_DL_PLATFORMS_COUNT
-                             && (libnew->hwcap & _DL_HWCAP_PLATFORM) != 0
-                             && ((libnew->hwcap & _DL_HWCAP_PLATFORM)
-                                 != platform))
+                         else if (best == NULL
+                                  || entry_priority < best_priority)
+                           /* This entry is of higher priority
+                              than the previous one, or it is the
+                              first entry.  */
+                           best_priority = entry_priority;
+                         else
+                           /* An entry has already been found,
+                              but it is a better match.  */
                            continue;
                        }
+#endif /* SHARED */
+                   }
 
-                     best = string_table + lib->value;
+                 best = string_table + lib->value;
 
-                     if (flags == GLRO (dl_correct_cache_id))
-                       /* We've found an exact match for the shared
-                          object and no general `ELF' release.  Stop
-                          searching.  */
-                       break;
-                   }
+                 if (!named_hwcap && flags == _DL_CACHE_DEFAULT_ID)
+                   /* With named hwcaps, we need to keep searching to
+                      see if we find a better match.  A better match
+                      is also possible if the flags of the current
+                      entry do not match the expected cache flags.
+                      But if the flags match, no better entry will be
+                      found.  */
+                   break;
                }
            }
          while (++middle <= right);
@@ -242,6 +423,11 @@ _dl_load_cache_lookup (const char *name)
          && ((cachesize - sizeof *cache_new) / sizeof (struct file_entry_new)
              >= ((struct cache_file_new *) file)->nlibs))
        {
+         if (! cache_file_new_matches_endian (file))
+           {
+             __munmap (file, cachesize);
+             file = (void *) -1;
+           }
          cache_new = file;
          cache = file;
        }
@@ -263,7 +449,20 @@ _dl_load_cache_lookup (const char *name)
          if (cachesize < (offset + sizeof (struct cache_file_new))
              || memcmp (cache_new->magic, CACHEMAGIC_VERSION_NEW,
                         sizeof CACHEMAGIC_VERSION_NEW - 1) != 0)
-           cache_new = (void *) -1;
+             cache_new = (void *) -1;
+         else
+           {
+             if (! cache_file_new_matches_endian (cache_new))
+               {
+                 /* The old-format part of the cache is bogus as well
+                    if the endianness does not match.  (But it is
+                    unclear how the new header can be located if the
+                    endianess does not match.)  */
+                 cache = (void *) -1;
+                 cache_new = (void *) -1;
+                 __munmap (file, cachesize);
+               }
+           }
        }
       else
        {
@@ -328,5 +527,9 @@ _dl_unload_cache (void)
       __munmap (cache, cachesize);
       cache = NULL;
     }
+#ifdef SHARED
+  /* This marks the glibc_hwcaps_priorities array as out-of-date.  */
+  glibc_hwcaps_priorities_length = 0;
+#endif
 }
 #endif