aboutsummaryrefslogtreecommitdiff
path: root/elf/dl-cache.c
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2020-12-04 09:13:43 +0100
committerFlorian Weimer <fweimer@redhat.com>2020-12-04 09:35:43 +0100
commit600d9e0c87940da9b0fdeff492bf888df852d40c (patch)
tree747a7d39be81da06ecab9311e3f9e8a4a2726e3c /elf/dl-cache.c
parentb44ac4f4c7a8bbe5eaa2701aa9452eaf2c96e1dd (diff)
downloadglibc-600d9e0c87940da9b0fdeff492bf888df852d40c.tar
glibc-600d9e0c87940da9b0fdeff492bf888df852d40c.tar.gz
glibc-600d9e0c87940da9b0fdeff492bf888df852d40c.tar.bz2
glibc-600d9e0c87940da9b0fdeff492bf888df852d40c.zip
elf: Add glibc-hwcaps subdirectory support to ld.so cache processing
This recognizes the DL_CACHE_HWCAP_EXTENSION flag in cache entries, and picks the supported cache entry with the highest priority. The elf/tst-glibc-hwcaps-prepend-cache test documents a non-desired aspect of the current cache implementation: If the cache selects a DSO that does not exist on disk, _dl_map_object falls back to open_path, which may or may not find an alternative implementation. This is an existing limitation that also applies to the legacy hwcaps processing for ld.so.cache. Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Diffstat (limited to 'elf/dl-cache.c')
-rw-r--r--elf/dl-cache.c194
1 files changed, 191 insertions, 3 deletions
diff --git a/elf/dl-cache.c b/elf/dl-cache.c
index 02c46ffb0c..97093bb700 100644
--- a/elf/dl-cache.c
+++ b/elf/dl-cache.c
@@ -35,6 +35,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 +212,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)
{
@@ -129,6 +270,11 @@ search_cache (const char *string_table, uint32_t 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))
{
/* The entry is large enough to include
@@ -136,7 +282,18 @@ search_cache (const char *string_table, uint32_t string_table_size,
struct file_entry_new *libnew
= (struct file_entry_new *) lib;
- if (libnew->hwcap & hwcap_exclude)
+#ifdef SHARED
+ named_hwcap = dl_cache_hwcap_extension (libnew);
+#endif
+
+ /* The entries with named/extension hwcaps
+ have been exhausted. 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 (GLRO (dl_osversion)
&& libnew->osversion > GLRO (dl_osversion))
@@ -146,14 +303,41 @@ search_cache (const char *string_table, uint32_t string_table_size,
&& ((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;
+ 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;
- if (flags == GLRO (dl_correct_cache_id))
+ if (flags == GLRO (dl_correct_cache_id)
+ && !named_hwcap)
/* We've found an exact match for the shared
object and no general `ELF' release. Stop
- searching. */
+ searching, but not if a named (extension)
+ hwcap is used. In this case, an entry with
+ a higher priority may come up later. */
break;
}
}
@@ -346,5 +530,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