1
0
mirror of https://sourceware.org/git/glibc.git synced 2025-07-29 11:41:21 +03:00

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>
This commit is contained in:
Florian Weimer
2020-12-04 09:13:43 +01:00
parent b44ac4f4c7
commit 600d9e0c87
10 changed files with 508 additions and 3 deletions

View File

@ -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