| commit dad90d528259b669342757c37dedefa8577e2636 |
| Author: Florian Weimer <fweimer@redhat.com> |
| Date: Fri Dec 4 09:13:43 2020 +0100 |
| |
| elf: Add glibc-hwcaps support for LD_LIBRARY_PATH |
| |
| This hacks non-power-set processing into _dl_important_hwcaps. |
| Once the legacy hwcaps handling goes away, the subdirectory |
| handling needs to be reworked, but it is premature to do this |
| while both approaches are still supported. |
| |
| ld.so supports two new arguments, --glibc-hwcaps-prepend and |
| --glibc-hwcaps-mask. Each accepts a colon-separated list of |
| glibc-hwcaps subdirectory names. The prepend option adds additional |
| subdirectories that are searched first, in the specified order. The |
| mask option restricts the automatically selected subdirectories to |
| those listed in the option argument. For example, on systems where |
| /usr/lib64 is on the library search path, |
| --glibc-hwcaps-prepend=valgrind:debug causes the dynamic loader to |
| search the directories /usr/lib64/glibc-hwcaps/valgrind and |
| /usr/lib64/glibc-hwcaps/debug just before /usr/lib64 is searched. |
| |
| Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org> |
| |
| Conflicts: |
| elf/Makefile |
| (Test backport differences.) |
| |
| diff --git a/elf/Makefile b/elf/Makefile |
| index bc96b8fd65e376cc..f795617780b393ec 100644 |
| |
| |
| @@ -59,7 +59,8 @@ elide-routines.os = $(all-dl-routines) dl-support enbl-secure dl-origin \ |
| # ld.so uses those routines, plus some special stuff for being the program |
| # interpreter and operating independent of libc. |
| rtld-routines = rtld $(all-dl-routines) dl-sysdep dl-environ dl-minimal \ |
| - dl-error-minimal dl-conflict dl-hwcaps dl-usage |
| + dl-error-minimal dl-conflict dl-hwcaps dl-hwcaps_split dl-hwcaps-subdirs \ |
| + dl-usage |
| all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines) |
| |
| CFLAGS-dl-runtime.c += -fexceptions -fasynchronous-unwind-tables |
| @@ -199,13 +200,14 @@ tests += restest1 preloadtest loadfail multiload origtest resolvfail \ |
| tst-filterobj tst-filterobj-dlopen tst-auxobj tst-auxobj-dlopen \ |
| tst-audit14 tst-audit15 tst-audit16 \ |
| tst-tls-ie tst-tls-ie-dlmopen \ |
| - argv0test |
| + argv0test \ |
| + tst-glibc-hwcaps tst-glibc-hwcaps-prepend tst-glibc-hwcaps-mask |
| # reldep9 |
| tests-internal += loadtest unload unload2 circleload1 \ |
| neededtest neededtest2 neededtest3 neededtest4 \ |
| tst-tls3 tst-tls6 tst-tls7 tst-tls8 tst-dlmopen2 \ |
| tst-ptrguard1 tst-stackguard1 tst-libc_dlvsym \ |
| - tst-create_format1 tst-tls-surplus |
| + tst-create_format1 tst-tls-surplus tst-dl-hwcaps_split |
| tests-container += tst-pldd |
| ifeq ($(build-hardcoded-path-in-tests),yes) |
| tests += tst-dlopen-aout |
| @@ -318,7 +320,10 @@ modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \ |
| tst-auditlogmod-1 tst-auditlogmod-2 tst-auditlogmod-3 \ |
| tst-tls-ie-mod0 tst-tls-ie-mod1 tst-tls-ie-mod2 \ |
| tst-tls-ie-mod3 tst-tls-ie-mod4 tst-tls-ie-mod5 \ |
| - tst-tls-ie-mod6 |
| + tst-tls-ie-mod6 libmarkermod1-1 libmarkermod1-2 libmarkermod1-3 \ |
| + libmarkermod2-1 libmarkermod2-2 \ |
| + libmarkermod3-1 libmarkermod3-2 libmarkermod3-3 \ |
| + libmarkermod4-1 libmarkermod4-2 libmarkermod4-3 libmarkermod4-4 \ |
| |
| # Most modules build with _ISOMAC defined, but those filtered out |
| # depend on internal headers. |
| @@ -1732,3 +1737,60 @@ $(objpfx)argv0test.out: tst-rtld-argv0.sh $(objpfx)ld.so \ |
| '$(test-wrapper-env)' '$(run_program_env)' \ |
| '$(rpath-link)' 'test-argv0' > $@; \ |
| $(evaluate-test) |
| + |
| +# A list containing the name of the most likely searched subdirectory |
| +# of the glibc-hwcaps directory, for each supported architecture (in |
| +# other words, the oldest hardware level recognized by the |
| +# glibc-hwcaps mechanism for this architecture). Used to obtain test |
| +# coverage for some glibc-hwcaps tests for the widest possible range |
| +# of systems. |
| +glibc-hwcaps-first-subdirs-for-tests = |
| + |
| +# The test modules are parameterized by preprocessor macros. |
| +LDFLAGS-libmarkermod1-1.so += -Wl,-soname,libmarkermod1.so |
| +LDFLAGS-libmarkermod2-1.so += -Wl,-soname,libmarkermod2.so |
| +LDFLAGS-libmarkermod3-1.so += -Wl,-soname,libmarkermod3.so |
| +LDFLAGS-libmarkermod4-1.so += -Wl,-soname,libmarkermod4.so |
| +$(objpfx)libmarkermod%.os : markermodMARKER-VALUE.c |
| + $(compile-command.c) \ |
| + -DMARKER=marker$(firstword $(subst -, ,$*)) \ |
| + -DVALUE=$(lastword $(subst -, ,$*)) |
| +$(objpfx)libmarkermod1.so: $(objpfx)libmarkermod1-1.so |
| + cp $< $@ |
| +$(objpfx)libmarkermod2.so: $(objpfx)libmarkermod2-1.so |
| + cp $< $@ |
| +$(objpfx)libmarkermod3.so: $(objpfx)libmarkermod3-1.so |
| + cp $< $@ |
| +$(objpfx)libmarkermod4.so: $(objpfx)libmarkermod4-1.so |
| + cp $< $@ |
| + |
| +# tst-glibc-hwcaps-prepend checks that --glibc-hwcaps-prepend is |
| +# preferred over auto-detected subdirectories. |
| +$(objpfx)tst-glibc-hwcaps-prepend: $(objpfx)libmarkermod1-1.so |
| +$(objpfx)glibc-hwcaps/prepend-markermod1/libmarkermod1.so: \ |
| + $(objpfx)libmarkermod1-2.so |
| + $(make-target-directory) |
| + cp $< $@ |
| +$(objpfx)glibc-hwcaps/%/libmarkermod1.so: $(objpfx)libmarkermod1-3.so |
| + $(make-target-directory) |
| + cp $< $@ |
| +$(objpfx)tst-glibc-hwcaps-prepend.out: \ |
| + $(objpfx)tst-glibc-hwcaps-prepend $(objpfx)libmarkermod1.so \ |
| + $(patsubst %,$(objpfx)glibc-hwcaps/%/libmarkermod1.so,prepend-markermod1 \ |
| + $(glibc-hwcaps-first-subdirs-for-tests)) |
| + $(test-wrapper) $(rtld-prefix) \ |
| + --glibc-hwcaps-prepend prepend-markermod1 \ |
| + $< > $@; \ |
| + $(evaluate-test) |
| + |
| +# tst-glibc-hwcaps-mask checks that --glibc-hwcaps-mask can be used to |
| +# suppress all auto-detected subdirectories. |
| +$(objpfx)tst-glibc-hwcaps-mask: $(objpfx)libmarkermod1-1.so |
| +$(objpfx)tst-glibc-hwcaps-mask.out: \ |
| + $(objpfx)tst-glibc-hwcaps-mask $(objpfx)libmarkermod1.so \ |
| + $(patsubst %,$(objpfx)glibc-hwcaps/%/libmarkermod1.so,\ |
| + $(glibc-hwcaps-first-subdirs-for-tests)) |
| + $(test-wrapper) $(rtld-prefix) \ |
| + --glibc-hwcaps-mask does-not-exist \ |
| + $< > $@; \ |
| + $(evaluate-test) |
| diff --git a/elf/dl-hwcaps-subdirs.c b/elf/dl-hwcaps-subdirs.c |
| new file mode 100644 |
| index 0000000000000000..60c6d59731ee3188 |
| |
| |
| @@ -0,0 +1,29 @@ |
| +/* Architecture-specific glibc-hwcaps subdirectories. Generic version. |
| + Copyright (C) 2020 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 |
| + modify it under the terms of the GNU Lesser General Public |
| + License as published by the Free Software Foundation; either |
| + version 2.1 of the License, or (at your option) any later version. |
| + |
| + The GNU C Library is distributed in the hope that it will be useful, |
| + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| + Lesser General Public License for more details. |
| + |
| + You should have received a copy of the GNU Lesser General Public |
| + License along with the GNU C Library; if not, see |
| + <https://www.gnu.org/licenses/>. */ |
| + |
| +#include <dl-hwcaps.h> |
| + |
| +/* In the generic version, there are no subdirectories defined. */ |
| + |
| +const char _dl_hwcaps_subdirs[] = ""; |
| + |
| +uint32_t |
| +_dl_hwcaps_subdirs_active (void) |
| +{ |
| + return 0; |
| +} |
| diff --git a/elf/dl-hwcaps.c b/elf/dl-hwcaps.c |
| index 82ee89c36a1eb4ab..e57d0d2d41741021 100644 |
| |
| |
| @@ -26,20 +26,97 @@ |
| #include <dl-procinfo.h> |
| #include <dl-hwcaps.h> |
| |
| +/* This is the result of counting the substrings in a colon-separated |
| + hwcaps string. */ |
| +struct hwcaps_counts |
| +{ |
| + /* Number of substrings. */ |
| + size_t count; |
| + |
| + /* Sum of the individual substring lengths (without separators or |
| + null terminators). */ |
| + size_t total_length; |
| + |
| + /* Maximum length of an individual substring. */ |
| + size_t maximum_length; |
| +}; |
| + |
| +/* Update *COUNTS according to the contents of HWCAPS. Skip over |
| + entries whose bit is not set in MASK. */ |
| +static void |
| +update_hwcaps_counts (struct hwcaps_counts *counts, const char *hwcaps, |
| + uint32_t bitmask, const char *mask) |
| +{ |
| + struct dl_hwcaps_split_masked sp; |
| + _dl_hwcaps_split_masked_init (&sp, hwcaps, bitmask, mask); |
| + while (_dl_hwcaps_split_masked (&sp)) |
| + { |
| + ++counts->count; |
| + counts->total_length += sp.split.length; |
| + if (sp.split.length > counts->maximum_length) |
| + counts->maximum_length = sp.split.length; |
| + } |
| +} |
| + |
| +/* State for copy_hwcaps. Must be initialized to point to |
| + the storage areas for the array and the strings themselves. */ |
| +struct copy_hwcaps |
| +{ |
| + struct r_strlenpair *next_pair; |
| + char *next_string; |
| +}; |
| + |
| +/* Copy HWCAPS into the string pairs and strings, advancing *TARGET. |
| + Skip over entries whose bit is not set in MASK. */ |
| +static void |
| +copy_hwcaps (struct copy_hwcaps *target, const char *hwcaps, |
| + uint32_t bitmask, const char *mask) |
| +{ |
| + struct dl_hwcaps_split_masked sp; |
| + _dl_hwcaps_split_masked_init (&sp, hwcaps, bitmask, mask); |
| + while (_dl_hwcaps_split_masked (&sp)) |
| + { |
| + target->next_pair->str = target->next_string; |
| + char *slash = __mempcpy (__mempcpy (target->next_string, |
| + GLIBC_HWCAPS_PREFIX, |
| + strlen (GLIBC_HWCAPS_PREFIX)), |
| + sp.split.segment, sp.split.length); |
| + *slash = '/'; |
| + target->next_pair->len |
| + = strlen (GLIBC_HWCAPS_PREFIX) + sp.split.length + 1; |
| + ++target->next_pair; |
| + target->next_string = slash + 1; |
| + } |
| +} |
| + |
| /* Return an array of useful/necessary hardware capability names. */ |
| const struct r_strlenpair * |
| -_dl_important_hwcaps (size_t *sz, size_t *max_capstrlen) |
| +_dl_important_hwcaps (const char *glibc_hwcaps_prepend, |
| + const char *glibc_hwcaps_mask, |
| + size_t *sz, size_t *max_capstrlen) |
| { |
| uint64_t hwcap_mask = GET_HWCAP_MASK(); |
| /* Determine how many important bits are set. */ |
| uint64_t masked = GLRO(dl_hwcap) & hwcap_mask; |
| size_t cnt = GLRO (dl_platform) != NULL; |
| size_t n, m; |
| - size_t total; |
| struct r_strlenpair *result; |
| struct r_strlenpair *rp; |
| char *cp; |
| |
| + /* glibc-hwcaps subdirectories. These are exempted from the power |
| + set construction below. */ |
| + uint32_t hwcaps_subdirs_active = _dl_hwcaps_subdirs_active (); |
| + struct hwcaps_counts hwcaps_counts = { 0, }; |
| + update_hwcaps_counts (&hwcaps_counts, glibc_hwcaps_prepend, -1, NULL); |
| + update_hwcaps_counts (&hwcaps_counts, _dl_hwcaps_subdirs, |
| + hwcaps_subdirs_active, glibc_hwcaps_mask); |
| + |
| + /* Each hwcaps subdirectory has a GLIBC_HWCAPS_PREFIX string prefix |
| + and a "/" suffix once stored in the result. */ |
| + size_t total = (hwcaps_counts.count * (strlen (GLIBC_HWCAPS_PREFIX) + 1) |
| + + hwcaps_counts.total_length); |
| + |
| /* Count the number of bits set in the masked value. */ |
| for (n = 0; (~((1ULL << n) - 1) & masked) != 0; ++n) |
| if ((masked & (1ULL << n)) != 0) |
| @@ -74,10 +151,10 @@ _dl_important_hwcaps (size_t *sz, size_t *max_capstrlen) |
| |
| /* Determine the total size of all strings together. */ |
| if (cnt == 1) |
| - total = temp[0].len + 1; |
| + total += temp[0].len + 1; |
| else |
| { |
| - total = temp[0].len + temp[cnt - 1].len + 2; |
| + total += temp[0].len + temp[cnt - 1].len + 2; |
| if (cnt > 2) |
| { |
| total <<= 1; |
| @@ -94,26 +171,48 @@ _dl_important_hwcaps (size_t *sz, size_t *max_capstrlen) |
| } |
| } |
| |
| - /* The result structure: we use a very compressed way to store the |
| - various combinations of capability names. */ |
| - *sz = 1 << cnt; |
| - result = (struct r_strlenpair *) malloc (*sz * sizeof (*result) + total); |
| - if (result == NULL) |
| + *sz = hwcaps_counts.count + (1 << cnt); |
| + |
| + /* This is the overall result, including both glibc-hwcaps |
| + subdirectories and the legacy hwcaps subdirectories using the |
| + power set construction. */ |
| + struct r_strlenpair *overall_result |
| + = malloc (*sz * sizeof (*result) + total); |
| + if (overall_result == NULL) |
| _dl_signal_error (ENOMEM, NULL, NULL, |
| N_("cannot create capability list")); |
| |
| + /* Fill in the glibc-hwcaps subdirectories. */ |
| + { |
| + struct copy_hwcaps target; |
| + target.next_pair = overall_result; |
| + target.next_string = (char *) (overall_result + *sz); |
| + copy_hwcaps (&target, glibc_hwcaps_prepend, -1, NULL); |
| + copy_hwcaps (&target, _dl_hwcaps_subdirs, |
| + hwcaps_subdirs_active, glibc_hwcaps_mask); |
| + /* Set up the write target for the power set construction. */ |
| + result = target.next_pair; |
| + cp = target.next_string; |
| + } |
| + |
| + |
| + /* Power set construction begins here. We use a very compressed way |
| + to store the various combinations of capability names. */ |
| + |
| if (cnt == 1) |
| { |
| - result[0].str = (char *) (result + *sz); |
| + result[0].str = cp; |
| result[0].len = temp[0].len + 1; |
| - result[1].str = (char *) (result + *sz); |
| + result[1].str = cp; |
| result[1].len = 0; |
| - cp = __mempcpy ((char *) (result + *sz), temp[0].str, temp[0].len); |
| + cp = __mempcpy (cp, temp[0].str, temp[0].len); |
| *cp = '/'; |
| - *sz = 2; |
| - *max_capstrlen = result[0].len; |
| + if (result[0].len > hwcaps_counts.maximum_length) |
| + *max_capstrlen = result[0].len; |
| + else |
| + *max_capstrlen = hwcaps_counts.maximum_length; |
| |
| - return result; |
| + return overall_result; |
| } |
| |
| /* Fill in the information. This follows the following scheme |
| @@ -124,7 +223,7 @@ _dl_important_hwcaps (size_t *sz, size_t *max_capstrlen) |
| #3: 0, 3 1001 |
| This allows the representation of all possible combinations of |
| capability names in the string. First generate the strings. */ |
| - result[1].str = result[0].str = cp = (char *) (result + *sz); |
| + result[1].str = result[0].str = cp; |
| #define add(idx) \ |
| cp = __mempcpy (__mempcpy (cp, temp[idx].str, temp[idx].len), "/", 1); |
| if (cnt == 2) |
| @@ -191,7 +290,10 @@ _dl_important_hwcaps (size_t *sz, size_t *max_capstrlen) |
| while (--n != 0); |
| |
| /* The maximum string length. */ |
| - *max_capstrlen = result[0].len; |
| + if (result[0].len > hwcaps_counts.maximum_length) |
| + *max_capstrlen = result[0].len; |
| + else |
| + *max_capstrlen = hwcaps_counts.maximum_length; |
| |
| - return result; |
| + return overall_result; |
| } |
| diff --git a/elf/dl-hwcaps.h b/elf/dl-hwcaps.h |
| index d69ee11dc27bb5e5..3fcfbceb1a8fc1c8 100644 |
| |
| |
| @@ -16,6 +16,11 @@ |
| License along with the GNU C Library; if not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| +#ifndef _DL_HWCAPS_H |
| +#define _DL_HWCAPS_H |
| + |
| +#include <stdint.h> |
| + |
| #include <elf/dl-tunables.h> |
| |
| #if HAVE_TUNABLES |
| @@ -28,3 +33,103 @@ |
| # define GET_HWCAP_MASK() (0) |
| # endif |
| #endif |
| + |
| +#define GLIBC_HWCAPS_SUBDIRECTORY "glibc-hwcaps" |
| +#define GLIBC_HWCAPS_PREFIX GLIBC_HWCAPS_SUBDIRECTORY "/" |
| + |
| +/* Used by _dl_hwcaps_split below, to split strings at ':' |
| + separators. */ |
| +struct dl_hwcaps_split |
| +{ |
| + const char *segment; /* Start of the current segment. */ |
| + size_t length; /* Number of bytes until ':' or NUL. */ |
| +}; |
| + |
| +/* Prepare *S to parse SUBJECT, for future _dl_hwcaps_split calls. If |
| + SUBJECT is NULL, it is treated as the empty string. */ |
| +static inline void |
| +_dl_hwcaps_split_init (struct dl_hwcaps_split *s, const char *subject) |
| +{ |
| + s->segment = subject; |
| + /* The initial call to _dl_hwcaps_split will not skip anything. */ |
| + s->length = 0; |
| +} |
| + |
| +/* Extract the next non-empty string segment, up to ':' or the null |
| + terminator. Return true if one more segment was found, or false if |
| + the end of the string was reached. On success, S->segment is the |
| + start of the segment found, and S->length is its length. |
| + (Typically, S->segment[S->length] is not null.) */ |
| +_Bool _dl_hwcaps_split (struct dl_hwcaps_split *s) attribute_hidden; |
| + |
| +/* Similar to dl_hwcaps_split, but with bit-based and name-based |
| + masking. */ |
| +struct dl_hwcaps_split_masked |
| +{ |
| + struct dl_hwcaps_split split; |
| + |
| + /* For used by the iterator implementation. */ |
| + const char *mask; |
| + uint32_t bitmask; |
| +}; |
| + |
| +/* Prepare *S for iteration with _dl_hwcaps_split_masked. Only HWCAP |
| + names in SUBJECT whose bit is set in BITMASK and whose name is in |
| + MASK will be returned. SUBJECT must not contain empty HWCAP names. |
| + If MASK is NULL, no name-based masking is applied. Likewise for |
| + BITMASK if BITMASK is -1 (infinite number of bits). */ |
| +static inline void |
| +_dl_hwcaps_split_masked_init (struct dl_hwcaps_split_masked *s, |
| + const char *subject, |
| + uint32_t bitmask, const char *mask) |
| +{ |
| + _dl_hwcaps_split_init (&s->split, subject); |
| + s->bitmask = bitmask; |
| + s->mask = mask; |
| +} |
| + |
| +/* Like _dl_hwcaps_split, but apply masking. */ |
| +_Bool _dl_hwcaps_split_masked (struct dl_hwcaps_split_masked *s) |
| + attribute_hidden; |
| + |
| +/* Returns true if the colon-separated HWCAP list HWCAPS contains the |
| + capability NAME (with length NAME_LENGTH). If HWCAPS is NULL, the |
| + function returns true. */ |
| +_Bool _dl_hwcaps_contains (const char *hwcaps, const char *name, |
| + size_t name_length) attribute_hidden; |
| + |
| +/* Colon-separated string of glibc-hwcaps subdirectories, without the |
| + "glibc-hwcaps/" prefix. The most preferred subdirectory needs to |
| + be listed first. Up to 32 subdirectories are supported, limited by |
| + the width of the uint32_t mask. */ |
| +extern const char _dl_hwcaps_subdirs[] attribute_hidden; |
| + |
| +/* Returns a bitmap of active subdirectories in _dl_hwcaps_subdirs. |
| + Bit 0 (the LSB) corresponds to the first substring in |
| + _dl_hwcaps_subdirs, bit 1 to the second substring, and so on. |
| + There is no direct correspondence between HWCAP bitmasks and this |
| + bitmask. */ |
| +uint32_t _dl_hwcaps_subdirs_active (void) attribute_hidden; |
| + |
| +/* Returns a bitmask that marks the last ACTIVE subdirectories in a |
| + _dl_hwcaps_subdirs_active string (containing SUBDIRS directories in |
| + total) as active. Intended for use in _dl_hwcaps_subdirs_active |
| + implementations (if a contiguous tail of the list in |
| + _dl_hwcaps_subdirs is selected). */ |
| +static inline uint32_t |
| +_dl_hwcaps_subdirs_build_bitmask (int subdirs, int active) |
| +{ |
| + /* Leading subdirectories that are not active. */ |
| + int inactive = subdirs - active; |
| + if (inactive == 32) |
| + return 0; |
| + |
| + uint32_t mask; |
| + if (subdirs != 32) |
| + mask = (1U << subdirs) - 1; |
| + else |
| + mask = -1; |
| + return mask ^ ((1U << inactive) - 1); |
| +} |
| + |
| +#endif /* _DL_HWCAPS_H */ |
| diff --git a/elf/dl-hwcaps_split.c b/elf/dl-hwcaps_split.c |
| new file mode 100644 |
| index 0000000000000000..95225e9f409ca229 |
| |
| |
| @@ -0,0 +1,77 @@ |
| +/* Hardware capability support for run-time dynamic loader. String splitting. |
| + Copyright (C) 2020 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 |
| + modify it under the terms of the GNU Lesser General Public |
| + License as published by the Free Software Foundation; either |
| + version 2.1 of the License, or (at your option) any later version. |
| + |
| + The GNU C Library is distributed in the hope that it will be useful, |
| + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| + Lesser General Public License for more details. |
| + |
| + You should have received a copy of the GNU Lesser General Public |
| + License along with the GNU C Library; if not, see |
| + <https://www.gnu.org/licenses/>. */ |
| + |
| +#include <dl-hwcaps.h> |
| +#include <stdbool.h> |
| +#include <string.h> |
| + |
| +_Bool |
| +_dl_hwcaps_split (struct dl_hwcaps_split *s) |
| +{ |
| + if (s->segment == NULL) |
| + return false; |
| + |
| + /* Skip over the previous segment. */ |
| + s->segment += s->length; |
| + |
| + /* Consume delimiters. This also avoids returning an empty |
| + segment. */ |
| + while (*s->segment == ':') |
| + ++s->segment; |
| + if (*s->segment == '\0') |
| + return false; |
| + |
| + /* This could use strchrnul, but we would have to link the function |
| + into ld.so for that. */ |
| + const char *colon = strchr (s->segment, ':'); |
| + if (colon == NULL) |
| + s->length = strlen (s->segment); |
| + else |
| + s->length = colon - s->segment; |
| + return true; |
| +} |
| + |
| +_Bool |
| +_dl_hwcaps_split_masked (struct dl_hwcaps_split_masked *s) |
| +{ |
| + while (true) |
| + { |
| + if (!_dl_hwcaps_split (&s->split)) |
| + return false; |
| + bool active = s->bitmask & 1; |
| + s->bitmask >>= 1; |
| + if (active && _dl_hwcaps_contains (s->mask, |
| + s->split.segment, s->split.length)) |
| + return true; |
| + } |
| +} |
| + |
| +_Bool |
| +_dl_hwcaps_contains (const char *hwcaps, const char *name, size_t name_length) |
| +{ |
| + if (hwcaps == NULL) |
| + return true; |
| + |
| + struct dl_hwcaps_split split; |
| + _dl_hwcaps_split_init (&split, hwcaps); |
| + while (_dl_hwcaps_split (&split)) |
| + if (split.length == name_length |
| + && memcmp (split.segment, name, name_length) == 0) |
| + return true; |
| + return false; |
| +} |
| diff --git a/elf/dl-load.c b/elf/dl-load.c |
| index d2be21ea7d1545fe..fee08d7816714178 100644 |
| |
| |
| @@ -682,7 +682,9 @@ cache_rpath (struct link_map *l, |
| |
| |
| void |
| -_dl_init_paths (const char *llp, const char *source) |
| +_dl_init_paths (const char *llp, const char *source, |
| + const char *glibc_hwcaps_prepend, |
| + const char *glibc_hwcaps_mask) |
| { |
| size_t idx; |
| const char *strp; |
| @@ -697,7 +699,8 @@ _dl_init_paths (const char *llp, const char *source) |
| |
| #ifdef SHARED |
| /* Get the capabilities. */ |
| - capstr = _dl_important_hwcaps (&ncapstr, &max_capstrlen); |
| + capstr = _dl_important_hwcaps (glibc_hwcaps_prepend, glibc_hwcaps_mask, |
| + &ncapstr, &max_capstrlen); |
| #endif |
| |
| /* First set up the rest of the default search directory entries. */ |
| diff --git a/elf/dl-main.h b/elf/dl-main.h |
| index b51256d3b48230b0..566713a0d10cfdb7 100644 |
| |
| |
| @@ -84,6 +84,14 @@ struct dl_main_state |
| /* The preload list passed as a command argument. */ |
| const char *preloadarg; |
| |
| + /* Additional glibc-hwcaps subdirectories to search first. |
| + Colon-separated list. */ |
| + const char *glibc_hwcaps_prepend; |
| + |
| + /* Mask for the internal glibc-hwcaps subdirectories. |
| + Colon-separated list. */ |
| + const char *glibc_hwcaps_mask; |
| + |
| enum rtld_mode mode; |
| |
| /* True if any of the debugging options is enabled. */ |
| @@ -98,7 +106,8 @@ struct dl_main_state |
| static inline void |
| call_init_paths (const struct dl_main_state *state) |
| { |
| - _dl_init_paths (state->library_path, state->library_path_source); |
| + _dl_init_paths (state->library_path, state->library_path_source, |
| + state->glibc_hwcaps_prepend, state->glibc_hwcaps_mask); |
| } |
| |
| /* Print ld.so usage information and exit. */ |
| diff --git a/elf/dl-support.c b/elf/dl-support.c |
| index fb9672367f8d6abd..34be8e5babfb6af3 100644 |
| |
| |
| @@ -315,7 +315,10 @@ _dl_non_dynamic_init (void) |
| |
| /* Initialize the data structures for the search paths for shared |
| objects. */ |
| - _dl_init_paths (getenv ("LD_LIBRARY_PATH"), "LD_LIBRARY_PATH"); |
| + _dl_init_paths (getenv ("LD_LIBRARY_PATH"), "LD_LIBRARY_PATH", |
| + /* No glibc-hwcaps selection support in statically |
| + linked binaries. */ |
| + NULL, NULL); |
| |
| /* Remember the last search directory added at startup. */ |
| _dl_init_all_dirs = GL(dl_all_dirs); |
| diff --git a/elf/dl-usage.c b/elf/dl-usage.c |
| index 796ad38b43c2211b..e22a9c39427187d1 100644 |
| |
| |
| @@ -83,7 +83,7 @@ print_search_path_for_help (struct dl_main_state *state) |
| { |
| if (__rtld_search_dirs.dirs == NULL) |
| /* The run-time search paths have not yet been initialized. */ |
| - _dl_init_paths (state->library_path, state->library_path_source); |
| + call_init_paths (state); |
| |
| _dl_printf ("\nShared library search path:\n"); |
| |
| @@ -132,6 +132,67 @@ print_hwcap_1_finish (bool *first) |
| _dl_printf (")\n"); |
| } |
| |
| +/* Print the header for print_hwcaps_subdirectories. */ |
| +static void |
| +print_hwcaps_subdirectories_header (bool *nothing_printed) |
| +{ |
| + if (*nothing_printed) |
| + { |
| + _dl_printf ("\n\ |
| +Subdirectories of glibc-hwcaps directories, in priority order:\n"); |
| + *nothing_printed = false; |
| + } |
| +} |
| + |
| +/* Print the HWCAP name itself, indented. */ |
| +static void |
| +print_hwcaps_subdirectories_name (const struct dl_hwcaps_split *split) |
| +{ |
| + _dl_write (STDOUT_FILENO, " ", 2); |
| + _dl_write (STDOUT_FILENO, split->segment, split->length); |
| +} |
| + |
| +/* Print the list of recognized glibc-hwcaps subdirectories. */ |
| +static void |
| +print_hwcaps_subdirectories (const struct dl_main_state *state) |
| +{ |
| + bool nothing_printed = true; |
| + struct dl_hwcaps_split split; |
| + |
| + /* The prepended glibc-hwcaps subdirectories. */ |
| + _dl_hwcaps_split_init (&split, state->glibc_hwcaps_prepend); |
| + while (_dl_hwcaps_split (&split)) |
| + { |
| + print_hwcaps_subdirectories_header (¬hing_printed); |
| + print_hwcaps_subdirectories_name (&split); |
| + bool first = true; |
| + print_hwcap_1 (&first, true, "searched"); |
| + print_hwcap_1_finish (&first); |
| + } |
| + |
| + /* The built-in glibc-hwcaps subdirectories. Do the filtering |
| + manually, so that more precise diagnostics are possible. */ |
| + uint32_t mask = _dl_hwcaps_subdirs_active (); |
| + _dl_hwcaps_split_init (&split, _dl_hwcaps_subdirs); |
| + while (_dl_hwcaps_split (&split)) |
| + { |
| + print_hwcaps_subdirectories_header (¬hing_printed); |
| + print_hwcaps_subdirectories_name (&split); |
| + bool first = true; |
| + print_hwcap_1 (&first, mask & 1, "supported"); |
| + bool listed = _dl_hwcaps_contains (state->glibc_hwcaps_mask, |
| + split.segment, split.length); |
| + print_hwcap_1 (&first, !listed, "masked"); |
| + print_hwcap_1 (&first, (mask & 1) && listed, "searched"); |
| + print_hwcap_1_finish (&first); |
| + mask >>= 1; |
| + } |
| + |
| + if (nothing_printed) |
| + _dl_printf ("\n\ |
| +No subdirectories of glibc-hwcaps directories are searched.\n"); |
| +} |
| + |
| /* Write a list of hwcap subdirectories to standard output. See |
| _dl_important_hwcaps in dl-hwcaps.c. */ |
| static void |
| @@ -186,6 +247,10 @@ setting environment variables (which would be inherited by subprocesses).\n\ |
| --inhibit-cache Do not use " LD_SO_CACHE "\n\ |
| --library-path PATH use given PATH instead of content of the environment\n\ |
| variable LD_LIBRARY_PATH\n\ |
| + --glibc-hwcaps-prepend LIST\n\ |
| + search glibc-hwcaps subdirectories in LIST\n\ |
| + --glibc-hwcaps-mask LIST\n\ |
| + only search built-in subdirectories if in LIST\n\ |
| --inhibit-rpath LIST ignore RUNPATH and RPATH information in object names\n\ |
| in LIST\n\ |
| --audit LIST use objects named in LIST as auditors\n\ |
| @@ -198,6 +263,7 @@ This program interpreter self-identifies as: " RTLD "\n\ |
| ", |
| argv0); |
| print_search_path_for_help (state); |
| + print_hwcaps_subdirectories (state); |
| print_legacy_hwcap_directories (); |
| _exit (EXIT_SUCCESS); |
| } |
| diff --git a/elf/markermodMARKER-VALUE.c b/elf/markermodMARKER-VALUE.c |
| new file mode 100644 |
| index 0000000000000000..99bdcf71a4e219c6 |
| |
| |
| @@ -0,0 +1,29 @@ |
| +/* Source file template for building shared objects with marker functions. |
| + Copyright (C) 2020 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 |
| + modify it under the terms of the GNU Lesser General Public |
| + License as published by the Free Software Foundation; either |
| + version 2.1 of the License, or (at your option) any later version. |
| + |
| + The GNU C Library is distributed in the hope that it will be useful, |
| + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| + Lesser General Public License for more details. |
| + |
| + You should have received a copy of the GNU Lesser General Public |
| + License along with the GNU C Library; if not, see |
| + <https://www.gnu.org/licenses/>. */ |
| + |
| +/* MARKER and VALUE must be set on the compiler command line. */ |
| + |
| +#ifndef MARKER |
| +# error MARKER not defined |
| +#endif |
| + |
| +int |
| +MARKER (void) |
| +{ |
| + return VALUE; |
| +} |
| diff --git a/elf/rtld.c b/elf/rtld.c |
| index da1eef108508b95f..fde5a6a4a485207e 100644 |
| |
| |
| @@ -287,6 +287,8 @@ dl_main_state_init (struct dl_main_state *state) |
| state->library_path_source = NULL; |
| state->preloadlist = NULL; |
| state->preloadarg = NULL; |
| + state->glibc_hwcaps_prepend = NULL; |
| + state->glibc_hwcaps_mask = NULL; |
| state->mode = rtld_mode_normal; |
| state->any_debug = false; |
| state->version_info = false; |
| @@ -1238,6 +1240,22 @@ dl_main (const ElfW(Phdr) *phdr, |
| { |
| argv0 = _dl_argv[2]; |
| |
| + _dl_skip_args += 2; |
| + _dl_argc -= 2; |
| + _dl_argv += 2; |
| + } |
| + else if (strcmp (_dl_argv[1], "--glibc-hwcaps-prepend") == 0 |
| + && _dl_argc > 2) |
| + { |
| + state.glibc_hwcaps_prepend = _dl_argv[2]; |
| + _dl_skip_args += 2; |
| + _dl_argc -= 2; |
| + _dl_argv += 2; |
| + } |
| + else if (strcmp (_dl_argv[1], "--glibc-hwcaps-mask") == 0 |
| + && _dl_argc > 2) |
| + { |
| + state.glibc_hwcaps_mask = _dl_argv[2]; |
| _dl_skip_args += 2; |
| _dl_argc -= 2; |
| _dl_argv += 2; |
| diff --git a/elf/tst-dl-hwcaps_split.c b/elf/tst-dl-hwcaps_split.c |
| new file mode 100644 |
| index 0000000000000000..364159427074bd1c |
| |
| |
| @@ -0,0 +1,148 @@ |
| +/* Unit tests for dl-hwcaps.c. |
| + Copyright (C) 2020 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 |
| + modify it under the terms of the GNU Lesser General Public |
| + License as published by the Free Software Foundation; either |
| + version 2.1 of the License, or (at your option) any later version. |
| + |
| + The GNU C Library is distributed in the hope that it will be useful, |
| + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| + Lesser General Public License for more details. |
| + |
| + You should have received a copy of the GNU Lesser General Public |
| + License along with the GNU C Library; if not, see |
| + <https://www.gnu.org/licenses/>. */ |
| + |
| +#include <array_length.h> |
| +#include <dl-hwcaps.h> |
| +#include <string.h> |
| +#include <support/check.h> |
| + |
| +static void |
| +check_split_masked (const char *input, int32_t bitmask, const char *mask, |
| + const char *expected[], size_t expected_length) |
| +{ |
| + struct dl_hwcaps_split_masked split; |
| + _dl_hwcaps_split_masked_init (&split, input, bitmask, mask); |
| + size_t index = 0; |
| + while (_dl_hwcaps_split_masked (&split)) |
| + { |
| + TEST_VERIFY_EXIT (index < expected_length); |
| + TEST_COMPARE_BLOB (expected[index], strlen (expected[index]), |
| + split.split.segment, split.split.length); |
| + ++index; |
| + } |
| + TEST_COMPARE (index, expected_length); |
| +} |
| + |
| +static void |
| +check_split (const char *input, |
| + const char *expected[], size_t expected_length) |
| +{ |
| + struct dl_hwcaps_split split; |
| + _dl_hwcaps_split_init (&split, input); |
| + size_t index = 0; |
| + while (_dl_hwcaps_split (&split)) |
| + { |
| + TEST_VERIFY_EXIT (index < expected_length); |
| + TEST_COMPARE_BLOB (expected[index], strlen (expected[index]), |
| + split.segment, split.length); |
| + ++index; |
| + } |
| + TEST_COMPARE (index, expected_length); |
| + |
| + /* Reuse the test cases with masking that does not actually remove |
| + anything. */ |
| + check_split_masked (input, -1, NULL, expected, expected_length); |
| + check_split_masked (input, -1, input, expected, expected_length); |
| +} |
| + |
| +static int |
| +do_test (void) |
| +{ |
| + /* Splitting tests, without masking. */ |
| + check_split (NULL, NULL, 0); |
| + check_split ("", NULL, 0); |
| + check_split (":", NULL, 0); |
| + check_split ("::", NULL, 0); |
| + |
| + { |
| + const char *expected[] = { "first" }; |
| + check_split ("first", expected, array_length (expected)); |
| + check_split (":first", expected, array_length (expected)); |
| + check_split ("first:", expected, array_length (expected)); |
| + check_split (":first:", expected, array_length (expected)); |
| + } |
| + |
| + { |
| + const char *expected[] = { "first", "second" }; |
| + check_split ("first:second", expected, array_length (expected)); |
| + check_split ("first::second", expected, array_length (expected)); |
| + check_split (":first:second", expected, array_length (expected)); |
| + check_split ("first:second:", expected, array_length (expected)); |
| + check_split (":first:second:", expected, array_length (expected)); |
| + } |
| + |
| + /* Splitting tests with masking. */ |
| + { |
| + const char *expected[] = { "first" }; |
| + check_split_masked ("first", 3, "first:second", |
| + expected, array_length (expected)); |
| + check_split_masked ("first:second", 3, "first:", |
| + expected, array_length (expected)); |
| + check_split_masked ("first:second", 1, NULL, |
| + expected, array_length (expected)); |
| + } |
| + { |
| + const char *expected[] = { "second" }; |
| + check_split_masked ("first:second", 3, "second", |
| + expected, array_length (expected)); |
| + check_split_masked ("first:second:third", -1, "second:", |
| + expected, array_length (expected)); |
| + check_split_masked ("first:second", 2, NULL, |
| + expected, array_length (expected)); |
| + check_split_masked ("first:second:third", 2, "first:second", |
| + expected, array_length (expected)); |
| + } |
| + |
| + /* Tests for _dl_hwcaps_contains. */ |
| + TEST_VERIFY (_dl_hwcaps_contains (NULL, "first", strlen ("first"))); |
| + TEST_VERIFY (_dl_hwcaps_contains (NULL, "", 0)); |
| + TEST_VERIFY (! _dl_hwcaps_contains ("", "first", strlen ("first"))); |
| + TEST_VERIFY (! _dl_hwcaps_contains ("firs", "first", strlen ("first"))); |
| + TEST_VERIFY (_dl_hwcaps_contains ("firs", "first", strlen ("first") - 1)); |
| + for (int i = 0; i < strlen ("first"); ++i) |
| + TEST_VERIFY (! _dl_hwcaps_contains ("first", "first", i)); |
| + TEST_VERIFY (_dl_hwcaps_contains ("first", "first", strlen ("first"))); |
| + TEST_VERIFY (_dl_hwcaps_contains ("first:", "first", strlen ("first"))); |
| + TEST_VERIFY (_dl_hwcaps_contains ("first:second", |
| + "first", strlen ("first"))); |
| + TEST_VERIFY (_dl_hwcaps_contains (":first:second", "first", |
| + strlen ("first"))); |
| + TEST_VERIFY (_dl_hwcaps_contains ("first:second", "second", |
| + strlen ("second"))); |
| + TEST_VERIFY (_dl_hwcaps_contains ("first:second:", "second", |
| + strlen ("second"))); |
| + TEST_VERIFY (_dl_hwcaps_contains ("first::second:", "second", |
| + strlen ("second"))); |
| + TEST_VERIFY (_dl_hwcaps_contains ("first:second::", "second", |
| + strlen ("second"))); |
| + for (int i = 0; i < strlen ("second"); ++i) |
| + { |
| + TEST_VERIFY (!_dl_hwcaps_contains ("first:second", "second", i)); |
| + TEST_VERIFY (!_dl_hwcaps_contains ("first:second:", "second", i)); |
| + TEST_VERIFY (!_dl_hwcaps_contains ("first:second::", "second", i)); |
| + TEST_VERIFY (!_dl_hwcaps_contains ("first::second", "second", i)); |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +#include <support/test-driver.c> |
| + |
| +/* Rebuild the sources here because the object file is built for |
| + inclusion into the dynamic loader. */ |
| +#include "dl-hwcaps_split.c" |
| diff --git a/elf/tst-glibc-hwcaps-mask.c b/elf/tst-glibc-hwcaps-mask.c |
| new file mode 100644 |
| index 0000000000000000..27b09b358caf7853 |
| |
| |
| @@ -0,0 +1,31 @@ |
| +/* Test that --glibc-hwcaps-mask works. |
| + Copyright (C) 2020 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 |
| + modify it under the terms of the GNU Lesser General Public |
| + License as published by the Free Software Foundation; either |
| + version 2.1 of the License, or (at your option) any later version. |
| + |
| + The GNU C Library is distributed in the hope that it will be useful, |
| + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| + Lesser General Public License for more details. |
| + |
| + You should have received a copy of the GNU Lesser General Public |
| + License along with the GNU C Library; if not, see |
| + <https://www.gnu.org/licenses/>. */ |
| + |
| +#include <support/check.h> |
| + |
| +extern int marker1 (void); |
| + |
| +static int |
| +do_test (void) |
| +{ |
| + /* The marker1 function in elf/markermod1.so returns 1. */ |
| + TEST_COMPARE (marker1 (), 1); |
| + return 0; |
| +} |
| + |
| +#include <support/test-driver.c> |
| diff --git a/elf/tst-glibc-hwcaps-prepend.c b/elf/tst-glibc-hwcaps-prepend.c |
| new file mode 100644 |
| index 0000000000000000..57d7319f1484ca4b |
| |
| |
| @@ -0,0 +1,32 @@ |
| +/* Test that --glibc-hwcaps-prepend works. |
| + Copyright (C) 2020 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 |
| + modify it under the terms of the GNU Lesser General Public |
| + License as published by the Free Software Foundation; either |
| + version 2.1 of the License, or (at your option) any later version. |
| + |
| + The GNU C Library is distributed in the hope that it will be useful, |
| + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| + Lesser General Public License for more details. |
| + |
| + You should have received a copy of the GNU Lesser General Public |
| + License along with the GNU C Library; if not, see |
| + <https://www.gnu.org/licenses/>. */ |
| + |
| +#include <support/check.h> |
| + |
| +extern int marker1 (void); |
| + |
| +static int |
| +do_test (void) |
| +{ |
| + /* The marker1 function in |
| + glibc-hwcaps/prepend-markermod1/markermod1.so returns 2. */ |
| + TEST_COMPARE (marker1 (), 2); |
| + return 0; |
| +} |
| + |
| +#include <support/test-driver.c> |
| diff --git a/elf/tst-glibc-hwcaps.c b/elf/tst-glibc-hwcaps.c |
| new file mode 100644 |
| index 0000000000000000..28f47cf8914a1f2a |
| |
| |
| @@ -0,0 +1,28 @@ |
| +/* Stub test for glibc-hwcaps. |
| + Copyright (C) 2020 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 |
| + modify it under the terms of the GNU Lesser General Public |
| + License as published by the Free Software Foundation; either |
| + version 2.1 of the License, or (at your option) any later version. |
| + |
| + The GNU C Library is distributed in the hope that it will be useful, |
| + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| + Lesser General Public License for more details. |
| + |
| + You should have received a copy of the GNU Lesser General Public |
| + License along with the GNU C Library; if not, see |
| + <https://www.gnu.org/licenses/>. */ |
| + |
| +#include <stdio.h> |
| + |
| +static int |
| +do_test (void) |
| +{ |
| + puts ("info: generic tst-glibc-hwcaps (tests nothing)"); |
| + return 0; |
| +} |
| + |
| +#include <support/test-driver.c> |
| diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h |
| index 2c9fdeb286bdaadf..77923499d3de4366 100644 |
| |
| |
| @@ -1045,8 +1045,13 @@ extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns) |
| attribute_hidden; |
| |
| /* Initialize the basic data structure for the search paths. SOURCE |
| - is either "LD_LIBRARY_PATH" or "--library-path". */ |
| -extern void _dl_init_paths (const char *library_path, const char *source) |
| + is either "LD_LIBRARY_PATH" or "--library-path". |
| + GLIBC_HWCAPS_PREPEND adds additional glibc-hwcaps subdirectories to |
| + search. GLIBC_HWCAPS_MASK is used to filter the built-in |
| + subdirectories if not NULL. */ |
| +extern void _dl_init_paths (const char *library_path, const char *source, |
| + const char *glibc_hwcaps_prepend, |
| + const char *glibc_hwcaps_mask) |
| attribute_hidden; |
| |
| /* Gather the information needed to install the profiling tables and start |
| @@ -1070,9 +1075,14 @@ extern void _dl_show_auxv (void) attribute_hidden; |
| extern char *_dl_next_ld_env_entry (char ***position) attribute_hidden; |
| |
| /* Return an array with the names of the important hardware |
| - capabilities. The length of the array is written to *SZ, and the |
| - maximum of all strings length is written to *MAX_CAPSTRLEN. */ |
| -const struct r_strlenpair *_dl_important_hwcaps (size_t *sz, |
| + capabilities. PREPEND is a colon-separated list of glibc-hwcaps |
| + directories to search first. MASK is a colon-separated list used |
| + to filter the built-in glibc-hwcaps subdirectories. The length of |
| + the array is written to *SZ, and the maximum of all strings length |
| + is written to *MAX_CAPSTRLEN. */ |
| +const struct r_strlenpair *_dl_important_hwcaps (const char *prepend, |
| + const char *mask, |
| + size_t *sz, |
| size_t *max_capstrlen) |
| attribute_hidden; |
| |