diff options
author | Florian Weimer <fweimer@redhat.com> | 2020-12-04 09:13:43 +0100 |
---|---|---|
committer | Florian Weimer <fweimer@redhat.com> | 2020-12-04 09:35:43 +0100 |
commit | 600d9e0c87940da9b0fdeff492bf888df852d40c (patch) | |
tree | 747a7d39be81da06ecab9311e3f9e8a4a2726e3c /elf/dl-cache.c | |
parent | b44ac4f4c7a8bbe5eaa2701aa9452eaf2c96e1dd (diff) | |
download | glibc-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.c | 194 |
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 |