/* 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
#include <stdint.h>
#include <_itoa.h>
#include <dl-hwcaps.h>
+#include <dl-isa-level.h>
#ifndef _DL_PLATFORMS_COUNT
# define _DL_PLATFORMS_COUNT 0
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)
int left = 0;
int right = nlibs - 1;
const char *best = NULL;
+#ifdef SHARED
+ uint32_t best_priority = 0;
+#endif
while (left <= right)
{
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);
&& ((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;
}
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
{
__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