diff options
author | H.J. Lu <hjl.tools@gmail.com> | 2021-08-17 19:35:48 -0700 |
---|---|---|
committer | H.J. Lu <hjl.tools@gmail.com> | 2021-09-19 13:51:35 -0700 |
commit | a93d9e03a31ec14405cb3a09aa95413b67067380 (patch) | |
tree | 532f865c7241b3cf363433272e5923c468ea2969 | |
parent | 885762aa31d75de8b9fea4c0e2e372b582d4c548 (diff) | |
download | glibc-a93d9e03a31ec14405cb3a09aa95413b67067380.tar.gz glibc-a93d9e03a31ec14405cb3a09aa95413b67067380.tar.xz glibc-a93d9e03a31ec14405cb3a09aa95413b67067380.zip |
Extend struct r_debug to support multiple namespaces [BZ #15971]
Glibc does not provide an interface for debugger to access libraries loaded in multiple namespaces via dlmopen. The current rtld-debugger interface is described in the file: elf/rtld-debugger-interface.txt under the "Standard debugger interface" heading. This interface only provides access to the first link-map (LM_ID_BASE). 1. Bump r_version to 2 when multiple namespaces are used. This triggers the GDB bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28236 2. Add struct r_debug_extended to extend struct r_debug into a linked-list, where each element correlates to an unique namespace. 3. Initialize the r_debug_extended structure. Bump r_version to 2 for the new namespace and add the new namespace to the namespace linked list. 4. Add _dl_debug_update to return the address of struct r_debug' of a namespace. 5. Add a hidden symbol, _r_debug_extended, for struct r_debug_extended. 6. Provide the symbol, _r_debug, with size of struct r_debug, as an alias of _r_debug_extended, for programs which reference _r_debug. This fixes BZ #15971. Reviewed-by: Florian Weimer <fweimer@redhat.com>
-rw-r--r-- | NEWS | 11 | ||||
-rw-r--r-- | csu/Makefile | 3 | ||||
-rw-r--r-- | csu/rtld-sizes.sym | 6 | ||||
-rw-r--r-- | elf/Makefile | 7 | ||||
-rw-r--r-- | elf/dl-close.c | 2 | ||||
-rw-r--r-- | elf/dl-debug-symbols.S | 36 | ||||
-rw-r--r-- | elf/dl-debug.c | 81 | ||||
-rw-r--r-- | elf/dl-load.c | 2 | ||||
-rw-r--r-- | elf/dl-open.c | 10 | ||||
-rw-r--r-- | elf/dl-reloc-static-pie.c | 2 | ||||
-rw-r--r-- | elf/link.h | 35 | ||||
-rw-r--r-- | elf/rtld-debugger-interface.txt | 15 | ||||
-rw-r--r-- | elf/rtld.c | 4 | ||||
-rw-r--r-- | elf/tst-dlmopen4.c | 72 | ||||
-rw-r--r-- | include/link.h | 4 | ||||
-rw-r--r-- | sysdeps/generic/ldsodefs.h | 12 |
16 files changed, 257 insertions, 45 deletions
diff --git a/NEWS b/NEWS index 838381f562..3e3c074238 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,9 @@ Version 2.35 Major new features: +* Bump r_version in the debugger interface to 2 and add a new field, + r_next, support multiple namespaces. + * Support for the C.UTF-8 locale has been added to glibc. The locale supports full code-point sorting for all valid Unicode code points. A limitation in the framework for fnmatch, regexec, and regcomp requires @@ -28,7 +31,13 @@ Major new features: Deprecated and removed features, and other changes affecting compatibility: - [Add deprecations, removals and changes affecting compatibility here] +* The r_version update in the debugger interface makes the glibc binary + incompatible with GDB binaries built without the following commits: + + c0154a4a21a gdb: Don't assume r_ldsomap when r_version > 1 on Linux + 4eb629d50d4 gdbserver: Check r_version < 1 for Linux debugger interface + + when audit modules or dlmopen are used. Changes to build and runtime requirements: diff --git a/csu/Makefile b/csu/Makefile index 3054329cea..e2390e4a7d 100644 --- a/csu/Makefile +++ b/csu/Makefile @@ -88,6 +88,9 @@ endif before-compile += $(objpfx)abi-tag.h generated += abi-tag.h +# Put it here to generate it earlier. +gen-as-const-headers += rtld-sizes.sym + # These are the special initializer/finalizer files. They are always the # first and last file in the link. crti.o ... crtn.o define the global # "functions" _init and _fini to run the .init and .fini sections. diff --git a/csu/rtld-sizes.sym b/csu/rtld-sizes.sym new file mode 100644 index 0000000000..13924d5efd --- /dev/null +++ b/csu/rtld-sizes.sym @@ -0,0 +1,6 @@ +#include <link.h> + +-- +R_DEBUG_SIZE sizeof (struct r_debug) +R_DEBUG_EXTENDED_SIZE sizeof (struct r_debug_extended) +R_DEBUG_EXTENDED_ALIGN __alignof (struct r_debug_extended) diff --git a/elf/Makefile b/elf/Makefile index 9f3fadc37e..835b85bd7c 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -35,7 +35,8 @@ dl-routines = $(addprefix dl-,load lookup object reloc deps \ execstack open close trampoline \ exception sort-maps lookup-direct \ call-libc-early-init write \ - thread_gscope_wait tls_init_tp) + thread_gscope_wait tls_init_tp \ + debug-symbols) ifeq (yes,$(use-ldconfig)) dl-routines += dl-cache endif @@ -203,7 +204,7 @@ tests += restest1 preloadtest loadfail multiload origtest resolvfail \ tst-tls16 tst-tls17 tst-tls18 tst-tls19 tst-tls-dlinfo \ tst-align tst-align2 \ tst-dlmodcount tst-dlopenrpath tst-deep1 \ - tst-dlmopen1 tst-dlmopen3 \ + tst-dlmopen1 tst-dlmopen3 tst-dlmopen4 \ unload3 unload4 unload5 unload6 unload7 unload8 tst-global1 order2 \ tst-audit1 tst-audit2 tst-audit8 tst-audit9 \ tst-addr1 tst-thrlock \ @@ -1244,6 +1245,8 @@ $(objpfx)tst-dlmopen2.out: $(objpfx)tst-dlmopen1mod.so $(objpfx)tst-dlmopen3.out: $(objpfx)tst-dlmopen1mod.so +$(objpfx)tst-dlmopen4.out: $(objpfx)tst-dlmopen1mod.so + $(objpfx)tst-audit1.out: $(objpfx)tst-auditmod1.so tst-audit1-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so diff --git a/elf/dl-close.c b/elf/dl-close.c index f39001cab9..93ff5c96e9 100644 --- a/elf/dl-close.c +++ b/elf/dl-close.c @@ -500,7 +500,7 @@ _dl_close_worker (struct link_map *map, bool force) #endif /* Notify the debugger we are about to remove some loaded objects. */ - struct r_debug *r = _dl_debug_initialize (0, nsid); + struct r_debug *r = _dl_debug_update (nsid); r->r_state = RT_DELETE; _dl_debug_state (); LIBC_PROBE (unmap_start, 2, nsid, r); diff --git a/elf/dl-debug-symbols.S b/elf/dl-debug-symbols.S new file mode 100644 index 0000000000..b7e9f5d947 --- /dev/null +++ b/elf/dl-debug-symbols.S @@ -0,0 +1,36 @@ +/* Define symbols used to communicate dynamic linker state to the + debugger at runtime. + Copyright (C) 2021 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 <rtld-sizes.h> + +/* Define 2 symbols, _r_debug_extended and _r_debug, which is an alias + of _r_debug_extended, but with the size of struct r_debug. */ + + .globl _r_debug + .type _r_debug, %object + .size _r_debug, R_DEBUG_SIZE + .hidden _r_debug_extended + .globl _r_debug_extended + .type _r_debug_extended, %object + .size _r_debug_extended, R_DEBUG_EXTENDED_SIZE + .section .bss + .balign R_DEBUG_EXTENDED_ALIGN +_r_debug: +_r_debug_extended: + .zero R_DEBUG_EXTENDED_SIZE diff --git a/elf/dl-debug.c b/elf/dl-debug.c index 2cd5f09753..f637d4bb8d 100644 --- a/elf/dl-debug.c +++ b/elf/dl-debug.c @@ -30,37 +30,80 @@ extern const int verify_link_map_members[(VERIFY_MEMBER (l_addr) && VERIFY_MEMBER (l_prev)) ? 1 : -1]; -/* This structure communicates dl state to the debugger. The debugger - normally finds it via the DT_DEBUG entry in the dynamic section, but in - a statically-linked program there is no dynamic section for the debugger - to examine and it looks for this particular symbol name. */ -struct r_debug _r_debug; +/* Update the `r_map' member and return the address of `struct r_debug' + of the namespace NS. */ +struct r_debug * +_dl_debug_update (Lmid_t ns) +{ + struct r_debug_extended *r; + if (ns == LM_ID_BASE) + r = &_r_debug_extended; + else + r = &GL(dl_ns)[ns]._ns_debug; + if (r->base.r_map == NULL) + atomic_store_release (&r->base.r_map, + (void *) GL(dl_ns)[ns]._ns_loaded); + return &r->base; +} -/* Initialize _r_debug if it has not already been done. The argument is - the run-time load address of the dynamic linker, to be put in - _r_debug.r_ldbase. Returns the address of _r_debug. */ +/* Initialize _r_debug_extended for the namespace NS. LDBASE is the + run-time load address of the dynamic linker, to be put in + _r_debug_extended.r_ldbase. Return the address of _r_debug. */ struct r_debug * _dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns) { - struct r_debug *r; + struct r_debug_extended *r, **pp = NULL; if (ns == LM_ID_BASE) - r = &_r_debug; - else - r = &GL(dl_ns)[ns]._ns_debug; + { + r = &_r_debug_extended; + /* Initialize r_version to 1. */ + if (_r_debug_extended.base.r_version == 0) + _r_debug_extended.base.r_version = 1; + } + else if (DL_NNS > 1) + { + r = &GL(dl_ns)[ns]._ns_debug; + if (r->base.r_brk == 0) + { + /* Add the new namespace to the linked list. After a namespace + is initialized, r_brk becomes non-zero. A namespace becomes + empty (r_map == NULL) when it is unused. But it is never + removed from the linked list. */ + struct r_debug_extended *p; + for (pp = &_r_debug_extended.r_next; + (p = *pp) != NULL; + pp = &p->r_next) + ; + + r->base.r_version = 2; + } + } + + if (r->base.r_brk == 0) + { + /* Tell the debugger where to find the map of loaded objects. + This function is called from dlopen. Initialize the namespace + only once. */ + r->base.r_ldbase = ldbase ?: _r_debug_extended.base.r_ldbase; + r->base.r_brk = (ElfW(Addr)) &_dl_debug_state; + r->r_next = NULL; + } + + if (r->base.r_map == NULL) + atomic_store_release (&r->base.r_map, + (void *) GL(dl_ns)[ns]._ns_loaded); - if (r->r_map == NULL || ldbase != 0) + if (pp != NULL) { - /* Tell the debugger where to find the map of loaded objects. */ - r->r_version = 1 /* R_DEBUG_VERSION XXX */; - r->r_ldbase = ldbase ?: _r_debug.r_ldbase; - r->r_map = (void *) GL(dl_ns)[ns]._ns_loaded; - r->r_brk = (ElfW(Addr)) &_dl_debug_state; + atomic_store_release (pp, r); + /* Bump r_version to 2 for the new namespace. */ + atomic_store_release (&_r_debug_extended.base.r_version, 2); } - return r; + return &r->base; } diff --git a/elf/dl-load.c b/elf/dl-load.c index 650e4edc35..39e0d074b4 100644 --- a/elf/dl-load.c +++ b/elf/dl-load.c @@ -949,7 +949,7 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd, /* Initialize to keep the compiler happy. */ const char *errstring = NULL; int errval = 0; - struct r_debug *r = _dl_debug_initialize (0, nsid); + struct r_debug *r = _dl_debug_update (nsid); bool make_consistent = false; /* Get file information. To match the kernel behavior, do not fill diff --git a/elf/dl-open.c b/elf/dl-open.c index ec386626f9..a25443f6d1 100644 --- a/elf/dl-open.c +++ b/elf/dl-open.c @@ -574,7 +574,7 @@ dl_open_worker (void *a) if ((mode & RTLD_GLOBAL) && new->l_global == 0) add_to_global_update (new); - assert (_dl_debug_initialize (0, args->nsid)->r_state == RT_CONSISTENT); + assert (_dl_debug_update (args->nsid)->r_state == RT_CONSISTENT); return; } @@ -630,7 +630,7 @@ dl_open_worker (void *a) #endif /* Notify the debugger all new objects are now ready to go. */ - struct r_debug *r = _dl_debug_initialize (0, args->nsid); + struct r_debug *r = _dl_debug_update (args->nsid); r->r_state = RT_CONSISTENT; _dl_debug_state (); LIBC_PROBE (map_complete, 3, args->nsid, r, new); @@ -830,7 +830,7 @@ no more namespaces available for dlmopen()")); ++GL(dl_nns); } - _dl_debug_initialize (0, nsid)->r_state = RT_CONSISTENT; + _dl_debug_update (nsid)->r_state = RT_CONSISTENT; } /* Never allow loading a DSO in a namespace which is empty. Such direct placements is only causing problems. Also don't allow @@ -899,7 +899,7 @@ no more namespaces available for dlmopen()")); the flag here. */ } - assert (_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT); + assert (_dl_debug_update (args.nsid)->r_state == RT_CONSISTENT); /* Release the lock. */ __rtld_lock_unlock_recursive (GL(dl_load_lock)); @@ -908,7 +908,7 @@ no more namespaces available for dlmopen()")); _dl_signal_exception (errcode, &exception, NULL); } - assert (_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT); + assert (_dl_debug_update (args.nsid)->r_state == RT_CONSISTENT); /* Release the lock. */ __rtld_lock_unlock_recursive (GL(dl_load_lock)); diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c index d5bd2f31e9..289651b341 100644 --- a/elf/dl-reloc-static-pie.c +++ b/elf/dl-reloc-static-pie.c @@ -51,7 +51,7 @@ _dl_relocate_static_pie (void) ELF_DYNAMIC_RELOCATE (main_map, 0, 0, 0); main_map->l_relocated = 1; - /* Initialize _r_debug. */ + /* Initialize _r_debug_extended. */ struct r_debug *r = _dl_debug_initialize (0, LM_ID_BASE); r->r_state = RT_CONSISTENT; diff --git a/elf/link.h b/elf/link.h index ff3a85c847..200d40c9c6 100644 --- a/elf/link.h +++ b/elf/link.h @@ -34,14 +34,13 @@ #include <bits/elfclass.h> /* Defines __ELF_NATIVE_CLASS. */ #include <bits/link.h> -/* Rendezvous structure used by the run-time dynamic linker to communicate - details of shared object loading to the debugger. If the executable's - dynamic section has a DT_DEBUG element, the run-time linker sets that - element's value to the address where this structure can be found. */ +/* The legacy rendezvous structure used by the run-time dynamic linker to + communicate details of shared object loading to the debugger. */ struct r_debug { - int r_version; /* Version number for this protocol. */ + /* Version number for this protocol. It should be greater than 0. */ + int r_version; struct link_map *r_map; /* Head of the chain of loaded objects. */ @@ -63,16 +62,34 @@ struct r_debug ElfW(Addr) r_ldbase; /* Base address the linker is loaded at. */ }; -/* This is the instance of that structure used by the dynamic linker. */ +/* This is the symbol of that structure provided by the dynamic linker. */ extern struct r_debug _r_debug; +/* The extended rendezvous structure used by the run-time dynamic linker + to communicate details of shared object loading to the debugger. If + the executable's dynamic section has a DT_DEBUG element, the run-time + linker sets that element's value to the address where this structure + can be found. */ + +struct r_debug_extended + { + struct r_debug base; + + /* The following field is added by r_version == 2. */ + + /* Link to the next r_debug_extended structure. Each r_debug_extended + structure represents a different namespace. The first + r_debug_extended structure is for the default namespace. */ + struct r_debug_extended *r_next; + }; + /* This symbol refers to the "dynamic structure" in the `.dynamic' section of whatever module refers to `_DYNAMIC'. So, to find its own - `struct r_debug', a program could do: + `struct r_debug_extended', a program could do: for (dyn = _DYNAMIC; dyn->d_tag != DT_NULL; ++dyn) if (dyn->d_tag == DT_DEBUG) - r_debug = (struct r_debug *) dyn->d_un.d_ptr; - */ + r_debug_extended = (struct r_debug_extended *) dyn->d_un.d_ptr; + */ extern ElfW(Dyn) _DYNAMIC[]; /* Structure describing a loaded shared object. The `l_next' and `l_prev' diff --git a/elf/rtld-debugger-interface.txt b/elf/rtld-debugger-interface.txt index 61bc99e4b0..f3476d8308 100644 --- a/elf/rtld-debugger-interface.txt +++ b/elf/rtld-debugger-interface.txt @@ -9,6 +9,9 @@ structure can be found. The r_debug structure contains (amongst others) the following fields: + int r_version: + Version number for this protocol. It should be greater than 0. + struct link_map *r_map: A linked list of loaded objects. @@ -32,6 +35,18 @@ but there is no way for the debugger to discover whether any of the objects in the link-map have been relocated or not. +Extension to the r_debug structure +================================== + +The r_debug_extended structure is an extension of the r_debug interface. +If r_version is 2, one additional field is available: + + struct r_debug_extended *r_next; + Link to the next r_debug_extended structure. Each r_debug_extended + structure represents a different namespace. A namespace is active + if its r_map field isn't NULL. The first r_debug_extended structure + is for the default namespace. + Probe-based debugger interface ============================== diff --git a/elf/rtld.c b/elf/rtld.c index 878e6480f4..742c413c48 100644 --- a/elf/rtld.c +++ b/elf/rtld.c @@ -1660,7 +1660,7 @@ dl_main (const ElfW(Phdr) *phdr, objects. */ call_init_paths (&state); - /* Initialize _r_debug. */ + /* Initialize _r_debug_extended. */ struct r_debug *r = _dl_debug_initialize (GL(dl_rtld_map).l_addr, LM_ID_BASE); r->r_state = RT_CONSISTENT; @@ -2491,7 +2491,7 @@ dl_main (const ElfW(Phdr) *phdr, /* Notify the debugger all new objects are now ready to go. We must re-get the address since by now the variable might be in another object. */ - r = _dl_debug_initialize (0, LM_ID_BASE); + r = _dl_debug_update (LM_ID_BASE); r->r_state = RT_CONSISTENT; _dl_debug_state (); LIBC_PROBE (init_complete, 2, LM_ID_BASE, r); diff --git a/elf/tst-dlmopen4.c b/elf/tst-dlmopen4.c new file mode 100644 index 0000000000..3fe150e50b --- /dev/null +++ b/elf/tst-dlmopen4.c @@ -0,0 +1,72 @@ +/* Test struct r_debug_extended via DT_DEBUG. + Copyright (C) 2021 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> +#include <link.h> +#include <stdlib.h> +#include <string.h> +#include <gnu/lib-names.h> +#include <support/xdlfcn.h> +#include <support/check.h> +#include <support/test-driver.h> + +#ifndef ELF_MACHINE_GET_R_DEBUG +# define ELF_MACHINE_GET_R_DEBUG(d) \ + (__extension__ ({ \ + struct r_debug_extended *debug; \ + if ((d)->d_tag == DT_DEBUG) \ + debug = (struct r_debug_extended *) (d)->d_un.d_ptr; \ + else \ + debug = NULL; \ + debug; })) +#endif + +static int +do_test (void) +{ + ElfW(Dyn) *d; + struct r_debug_extended *debug = NULL; + + for (d = _DYNAMIC; d->d_tag != DT_NULL; ++d) + { + debug = ELF_MACHINE_GET_R_DEBUG (d); + if (debug != NULL) + break; + } + + TEST_VERIFY_EXIT (debug != NULL); + TEST_COMPARE (debug->base.r_version, 1); + TEST_VERIFY_EXIT (debug->r_next == NULL); + + void *h = xdlmopen (LM_ID_NEWLM, "$ORIGIN/tst-dlmopen1mod.so", + RTLD_LAZY); + + TEST_COMPARE (debug->base.r_version, 2); + TEST_VERIFY_EXIT (debug->r_next != NULL); + TEST_VERIFY_EXIT (debug->r_next->r_next == NULL); + TEST_VERIFY_EXIT (debug->r_next->base.r_map != NULL); + TEST_VERIFY_EXIT (debug->r_next->base.r_map->l_name != NULL); + const char *name = basename (debug->r_next->base.r_map->l_name); + TEST_COMPARE_STRING (name, "tst-dlmopen1mod.so"); + + xdlclose (h); + + return 0; +} + +#include <support/test-driver.c> diff --git a/include/link.h b/include/link.h index 4af16cb596..7b8250db36 100644 --- a/include/link.h +++ b/include/link.h @@ -353,6 +353,10 @@ struct auditstate }; +/* This is the hidden instance of struct r_debug_extended used by the + dynamic linker. */ +extern struct r_debug_extended _r_debug_extended attribute_hidden; + #if __ELF_NATIVE_CLASS == 32 # define symbind symbind32 #elif __ELF_NATIVE_CLASS == 64 diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h index fd67871f4b..6e50fcd7cd 100644 --- a/sysdeps/generic/ldsodefs.h +++ b/sysdeps/generic/ldsodefs.h @@ -355,7 +355,7 @@ struct rtld_global void (*free) (void *); } _ns_unique_sym_table; /* Keep track of changes to each namespace' list. */ - struct r_debug _ns_debug; + struct r_debug_extended _ns_debug; } _dl_ns[DL_NNS]; /* One higher than index of last used namespace. */ EXTERN size_t _dl_nns; @@ -1099,12 +1099,16 @@ extern void _dl_sort_maps (struct link_map **maps, unsigned int nmaps, extern void _dl_debug_state (void); rtld_hidden_proto (_dl_debug_state) -/* Initialize `struct r_debug' if it has not already been done. The - argument is the run-time load address of the dynamic linker, to be put - in the `r_ldbase' member. Returns the address of the structure. */ +/* Initialize `struct r_debug_extended' for the namespace NS. LDBASE + is the run-time load address of the dynamic linker, to be put in the + `r_ldbase' member. Return the address of the structure. */ extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns) attribute_hidden; +/* Update the `r_map' member and return the address of `struct r_debug' + of the namespace NS. */ +extern struct r_debug *_dl_debug_update (Lmid_t ns) attribute_hidden; + /* Initialize the basic data structure for the search paths. SOURCE is either "LD_LIBRARY_PATH" or "--library-path". GLIBC_HWCAPS_PREPEND adds additional glibc-hwcaps subdirectories to |