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