import UBI glibc-2.34-231.el9_7.2

This commit is contained in:
eabdullin 2025-11-11 16:12:23 +00:00
parent 1e4fd33b14
commit d92826cafc
229 changed files with 40109 additions and 603 deletions

View File

@ -0,0 +1,148 @@
commit 8329939a37f483a16013dd8af8303cbcb86d92cb
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Jul 4 21:46:16 2025 +0200
elf: Introduce _dl_debug_change_state
It combines updating r_state with the debugger notification.
The second change to _dl_open introduces an additional debugger
notification for dlmopen, but debuggers are expected to ignore it.
Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
diff --git a/elf/dl-close.c b/elf/dl-close.c
index 236d89f67f3bf410..fa3974afba798073 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -472,8 +472,7 @@ _dl_close_worker (struct link_map *map, bool force)
/* Notify the debugger we are about to remove some loaded objects.
LA_ACT_DELETE has already been signalled above for !unload_any. */
struct r_debug *r = _dl_debug_update (nsid);
- r->r_state = RT_DELETE;
- _dl_debug_state ();
+ _dl_debug_change_state (r, RT_DELETE);
LIBC_PROBE (unmap_start, 2, nsid, r);
if (unload_global)
@@ -762,8 +761,7 @@ _dl_close_worker (struct link_map *map, bool force)
__rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
/* Notify the debugger those objects are finalized and gone. */
- r->r_state = RT_CONSISTENT;
- _dl_debug_state ();
+ _dl_debug_change_state (r, RT_CONSISTENT);
LIBC_PROBE (unmap_complete, 2, nsid, r);
#ifdef SHARED
diff --git a/elf/dl-debug.c b/elf/dl-debug.c
index 649386d5a6b885ed..f840a1b92292968d 100644
--- a/elf/dl-debug.c
+++ b/elf/dl-debug.c
@@ -67,6 +67,13 @@ _dl_debug_update (Lmid_t ns)
return &r->base;
}
+void
+_dl_debug_change_state (struct r_debug *r, int state)
+{
+ atomic_store_release (&r->r_state, state);
+ _dl_debug_state ();
+}
+
/* 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. */
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 6714807946b60188..c118db811d8899f6 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -946,8 +946,7 @@ _dl_notify_new_object (int mode, Lmid_t nsid, struct link_map *l)
/* Notify the debugger we have added some objects. We need to
call _dl_debug_initialize in a static program in case dynamic
linking has not been used before. */
- r->r_state = RT_ADD;
- _dl_debug_state ();
+ _dl_debug_change_state (r, RT_ADD);
LIBC_PROBE (map_start, 2, nsid, r);
}
else
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 1e61e402455da666..df6aa55a8842ee62 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -787,8 +787,7 @@ dl_open_worker (void *a)
#ifdef SHARED
bool was_not_consistent = r->r_state != RT_CONSISTENT;
#endif
- r->r_state = RT_CONSISTENT;
- _dl_debug_state ();
+ _dl_debug_change_state (r, RT_CONSISTENT);
LIBC_PROBE (map_complete, 3, nsid, r, args->map);
#ifdef SHARED
@@ -866,7 +865,7 @@ no more namespaces available for dlmopen()"));
}
GL(dl_ns)[nsid].libc_map = NULL;
- _dl_debug_update (nsid)->r_state = RT_CONSISTENT;
+ _dl_debug_change_state (_dl_debug_update (nsid), RT_CONSISTENT);
}
/* Never allow loading a DSO in a namespace which is empty. Such
direct placements is only causing problems. Also don't allow
diff --git a/elf/rtld.c b/elf/rtld.c
index cd233174c9d944b2..0fe9986e4c7ed830 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1842,8 +1842,7 @@ dl_main (const ElfW(Phdr) *phdr,
#endif
/* We start adding objects. */
- r->r_state = RT_ADD;
- _dl_debug_state ();
+ _dl_debug_change_state (r, RT_ADD);
LIBC_PROBE (init_start, 2, LM_ID_BASE, r);
/* Auditing checkpoint: we are ready to signal that the initial map
@@ -2527,8 +2526,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_update (LM_ID_BASE);
- r->r_state = RT_CONSISTENT;
- _dl_debug_state ();
+ _dl_debug_change_state (r, RT_CONSISTENT);
LIBC_PROBE (init_complete, 2, LM_ID_BASE, r);
#ifdef SHARED
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 21dbe2d21ed8e605..371c32dd79c3ea2b 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -1129,8 +1129,14 @@ extern void _dl_debug_state (void);
rtld_hidden_proto (_dl_debug_state)
/* 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. */
+ is the run-time load address of the dynamic linker, to be put in
+ the `r_ldbase' member.
+
+ Return the address of the r_debug structure for the namespace.
+ This is not merely a convenience or optimization, but it is
+ necessary for the LIBC_PROBE Systemtap/debugger probes to work
+ reliably: direct variable access can create probes that tools
+ cannot consume. */
extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
attribute_hidden;
@@ -1138,6 +1144,10 @@ extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
of the namespace NS. */
extern struct r_debug *_dl_debug_update (Lmid_t ns) attribute_hidden;
+/* Update R->r_state to STATE and notify the debugger by calling
+ _dl_debug_state. */
+void _dl_debug_change_state (struct r_debug *r, int state) 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

View File

@ -0,0 +1,264 @@
commit ea85e7d55087075376a29261e722e4fae14ecbe7
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Jul 4 21:46:30 2025 +0200
elf: Restore support for _r_debug interpositions and copy relocations
The changes in commit a93d9e03a31ec14405cb3a09aa95413b67067380
("Extend struct r_debug to support multiple namespaces [BZ #15971]")
break the dyninst dynamic instrumentation tool. It brings its
own definition of _r_debug (rather than a declaration).
Furthermore, it turns out it is rather hard to use the proposed
handshake for accessing _r_debug via DT_DEBUG. If applications want
to access _r_debug, they can do so directly if the relevant code has
been built as PIC. To protect against harm from accidental copy
relocations due to linker relaxations, this commit restores copy
relocation support by adjusting both copies if interposition or
copy relocations are in play. Therefore, it is possible to
use a hidden reference in ld.so to access _r_debug.
Only perform the copy relocation initialization if libc has been
loaded. Otherwise, the ld.so search scope can be empty, and the
lookup of the _r_debug symbol mail fail.
Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
Conflicts:
elf/rtld.c: Adjust for prelink.
diff --git a/elf/Makefile b/elf/Makefile
index 721f254d121118c0..3eac746d21042ec9 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -404,6 +404,8 @@ tests += \
tst-dlmopen1 \
tst-dlmopen3 \
tst-dlmopen4 \
+ tst-dlmopen4-nonpic \
+ tst-dlmopen4-pic \
tst-dlopen-auditdup \
tst-dlopen-constructor-null \
tst-dlopen-self \
@@ -1986,6 +1988,13 @@ $(objpfx)tst-dlmopen3.out: $(objpfx)tst-dlmopen1mod.so
$(objpfx)tst-dlmopen4.out: $(objpfx)tst-dlmopen1mod.so
+CFLAGS-tst-dlmopen4-pic.c += -fPIC
+$(objpfx)tst-dlmopen4-pic.out: $(objpfx)tst-dlmopen1mod.so
+
+CFLAGS-tst-dlmopen4-nonpic.c += -fno-pie
+tst-dlmopen4-nonpic-no-pie = yes
+$(objpfx)tst-dlmopen4-nonpic.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-debug-symbols.S b/elf/dl-debug-symbols.S
index 28456ab1f237ea87..629b0c0c6b2cd9e1 100644
--- a/elf/dl-debug-symbols.S
+++ b/elf/dl-debug-symbols.S
@@ -38,3 +38,4 @@
_r_debug:
_r_debug_extended:
.zero R_DEBUG_EXTENDED_SIZE
+rtld_hidden_def (_r_debug)
diff --git a/elf/dl-debug.c b/elf/dl-debug.c
index f840a1b92292968d..4388a04cdf828898 100644
--- a/elf/dl-debug.c
+++ b/elf/dl-debug.c
@@ -16,6 +16,7 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
+#include <assert.h>
#include <ldsodefs.h>
@@ -37,6 +38,37 @@ extern const int verify_link_map_members[(VERIFY_MEMBER (l_addr)
to LM_ID_BASE + 1. See elf/dl-debug-symbols.S. */
struct r_debug_extended _r_debug_array[DL_NNS - 1];
+/* If not null, pointer to the _r_debug in the main executable. */
+static struct r_debug *_r_debug_main;
+
+void
+_dl_debug_post_relocate (struct link_map *main_map)
+{
+ /* Perform a full symbol search in all objects, to maintain
+ compatibility if interposed _r_debug definitions. The lookup
+ cannot fail because there is a definition in ld.so, and this
+ function is only called if the ld.so search scope is not empty. */
+ const ElfW(Sym) *sym = NULL;
+ lookup_t result =_dl_lookup_symbol_x ("_r_debug", main_map, &sym,
+ main_map->l_scope, NULL, 0, 0, NULL);
+ if (sym->st_size >= sizeof (struct r_debug))
+ {
+ struct r_debug *main_r_debug = DL_SYMBOL_ADDRESS (result, sym);
+ if (main_r_debug != &_r_debug_extended.base)
+ {
+ /* The extended version of the struct is not available in
+ the main executable because a copy relocation has been
+ used. r_map etc. have already been copied as part of the
+ copy relocation processing. */
+ main_r_debug->r_version = 1;
+
+ /* Record that dual updates of the initial link map are
+ required. */
+ _r_debug_main = main_r_debug;
+ }
+ }
+}
+
/* Return the r_debug object for the namespace NS. */
static inline struct r_debug_extended *
get_rdebug (Lmid_t ns)
@@ -71,6 +103,11 @@ void
_dl_debug_change_state (struct r_debug *r, int state)
{
atomic_store_release (&r->r_state, state);
+#ifdef SHARED
+ if (r == &_r_debug_extended.base && _r_debug_main != NULL)
+ /* Update the copy-relocation of _r_debug. */
+ atomic_store_release (&_r_debug_main->r_state, state);
+#endif
_dl_debug_state ();
}
@@ -103,7 +140,9 @@ _dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
if (ns - 1 == LM_ID_BASE)
{
atomic_store_release (&_r_debug_extended.r_next, r);
- /* Now there are multiple namespaces. */
+ /* Now there are multiple namespaces. Note that this
+ deliberately does not update the copy in the main
+ executable (if it exists). */
atomic_store_release (&_r_debug_extended.base.r_version, 2);
}
else
@@ -116,8 +155,15 @@ _dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
}
if (r->base.r_map == NULL)
- atomic_store_release (&r->base.r_map,
- (void *) GL(dl_ns)[ns]._ns_loaded);
+ {
+ struct link_map_public *l = (void *) GL(dl_ns)[ns]._ns_loaded;
+ atomic_store_release (&r->base.r_map, l);
+#ifdef SHARED
+ if (ns == LM_ID_BASE && _r_debug_main != NULL)
+ /* Update the copy-relocation of _r_debug. */
+ atomic_store_release (&_r_debug_main->r_map, l);
+#endif
+ }
return &r->base;
}
diff --git a/elf/rtld.c b/elf/rtld.c
index 0fe9986e4c7ed830..dac827e249b2fe14 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2395,6 +2395,9 @@ dl_main (const ElfW(Phdr) *phdr,
/* Likewise for the locking implementation. */
__rtld_mutex_init ();
+ /* Update copy-relocated _r_debug if necessary. */
+ _dl_debug_post_relocate (main_map);
+
/* Mark all the objects so we know they have been already relocated. */
for (struct link_map *l = main_map; l != NULL; l = l->l_next)
{
@@ -2505,6 +2508,9 @@ dl_main (const ElfW(Phdr) *phdr,
__rtld_mutex_init ();
__rtld_malloc_init_real (main_map);
+
+ /* Update copy-relocated _r_debug if necessary. */
+ _dl_debug_post_relocate (main_map);
}
/* All ld.so initialization is complete. Apply RELRO. */
diff --git a/elf/tst-dlmopen4-nonpic.c b/elf/tst-dlmopen4-nonpic.c
new file mode 100644
index 0000000000000000..ad4e40995337f4f9
--- /dev/null
+++ b/elf/tst-dlmopen4-nonpic.c
@@ -0,0 +1,2 @@
+#define BUILD_FOR_NONPIC
+#include "tst-dlmopen4.c"
diff --git a/elf/tst-dlmopen4-pic.c b/elf/tst-dlmopen4-pic.c
new file mode 100644
index 0000000000000000..919fa85c2579fb5d
--- /dev/null
+++ b/elf/tst-dlmopen4-pic.c
@@ -0,0 +1,2 @@
+#define BUILD_FOR_PIC
+#include "tst-dlmopen4.c"
diff --git a/elf/tst-dlmopen4.c b/elf/tst-dlmopen4.c
index 3fe150e50bc259f0..633addf41978cee8 100644
--- a/elf/tst-dlmopen4.c
+++ b/elf/tst-dlmopen4.c
@@ -53,6 +53,15 @@ do_test (void)
TEST_COMPARE (debug->base.r_version, 1);
TEST_VERIFY_EXIT (debug->r_next == NULL);
+#ifdef BUILD_FOR_PIC
+ /* In a PIC build, using _r_debug directly should give us the same
+ object. */
+ TEST_VERIFY (&_r_debug == &debug->base);
+#endif
+#ifdef BUILD_FOR_NONPIC
+ TEST_COMPARE (_r_debug.r_version, 1);
+#endif
+
void *h = xdlmopen (LM_ID_NEWLM, "$ORIGIN/tst-dlmopen1mod.so",
RTLD_LAZY);
@@ -64,6 +73,19 @@ do_test (void)
const char *name = basename (debug->r_next->base.r_map->l_name);
TEST_COMPARE_STRING (name, "tst-dlmopen1mod.so");
+#ifdef BUILD_FOR_NONPIC
+ /* If a copy relocation is used, it must be at version 1. */
+ if (&_r_debug != &debug->base)
+ {
+ TEST_COMPARE (_r_debug.r_version, 1);
+ TEST_COMPARE ((uintptr_t) _r_debug.r_map,
+ (uintptr_t) debug->base.r_map);
+ TEST_COMPARE (_r_debug.r_brk, debug->base.r_brk);
+ TEST_COMPARE (_r_debug.r_state, debug->base.r_state);
+ TEST_COMPARE (_r_debug.r_ldbase, debug->base.r_ldbase);
+ }
+#endif
+
xdlclose (h);
return 0;
diff --git a/include/link.h b/include/link.h
index 0cf130ddb8af2e89..bafac6c9628b183c 100644
--- a/include/link.h
+++ b/include/link.h
@@ -366,6 +366,8 @@ struct auditstate
dynamic linker. */
extern struct r_debug_extended _r_debug_extended attribute_hidden;
+rtld_hidden_proto (_r_debug)
+
#if __ELF_NATIVE_CLASS == 32
# define symbind symbind32
# define LA_SYMBIND "la_symbind32"
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 371c32dd79c3ea2b..484893c2928db8e7 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -1140,6 +1140,10 @@ rtld_hidden_proto (_dl_debug_state)
extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
attribute_hidden;
+/* This is called after relocation processing to handle a potential
+ copy relocation for _r_debug. */
+void _dl_debug_post_relocate (struct link_map *main_map) 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;

View File

@ -0,0 +1,106 @@
commit 2cac9559e06044ba520e785c151fbbd25011865f
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Aug 1 10:20:23 2025 +0200
elf: Extract rtld_setup_phdr function from dl_main
Remove historic binutils reference from comment and update
how this data is used by applications.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
elf/rtld.
(missing __ehdr_start cleanup downstream)
diff --git a/elf/rtld.c b/elf/rtld.c
index 667880e18ae816d8..a9073d4e14b07410 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1306,6 +1306,45 @@ rtld_setup_main_map (struct link_map *main_map)
return has_interp;
}
+/* Set up the program header information for the dynamic linker
+ itself. It can be accessed via _r_debug and dl_iterate_phdr
+ callbacks. */
+static void
+rtld_setup_phdr (void)
+{
+ const ElfW(Ehdr) *rtld_ehdr;
+
+ /* Starting from binutils-2.23, the linker will define the magic symbol
+ __ehdr_start to point to our own ELF header if it is visible in a
+ segment that also includes the phdrs. If that's not available, we use
+ the old method that assumes the beginning of the file is part of the
+ lowest-addressed PT_LOAD segment. */
+#ifdef HAVE_EHDR_START
+ extern const ElfW(Ehdr) __ehdr_start __attribute__ ((visibility ("hidden")));
+ rtld_ehdr = &__ehdr_start;
+#else
+ rtld_ehdr = (void *) GL(dl_rtld_map).l_map_start;
+#endif
+ assert (rtld_ehdr->e_ehsize == sizeof *rtld_ehdr);
+ assert (rtld_ehdr->e_phentsize == sizeof (ElfW(Phdr)));
+
+ const ElfW(Phdr) *rtld_phdr = (const void *) rtld_ehdr + rtld_ehdr->e_phoff;
+
+ GL(dl_rtld_map).l_phdr = rtld_phdr;
+ GL(dl_rtld_map).l_phnum = rtld_ehdr->e_phnum;
+
+
+ /* PT_GNU_RELRO is usually the last phdr. */
+ size_t cnt = rtld_ehdr->e_phnum;
+ while (cnt-- > 0)
+ if (rtld_phdr[cnt].p_type == PT_GNU_RELRO)
+ {
+ GL(dl_rtld_map).l_relro_addr = rtld_phdr[cnt].p_vaddr;
+ GL(dl_rtld_map).l_relro_size = rtld_phdr[cnt].p_memsz;
+ break;
+ }
+}
+
/* Adjusts the contents of the stack and related globals for the user
entry point. The ld.so processed skip_args arguments and bumped
_dl_argv and _dl_argc accordingly. Those arguments are removed from
@@ -1790,39 +1829,7 @@ dl_main (const ElfW(Phdr) *phdr,
if (GLRO(dl_use_load_bias) == (ElfW(Addr)) -2)
GLRO(dl_use_load_bias) = main_map->l_addr == 0 ? -1 : 0;
- /* Set up the program header information for the dynamic linker
- itself. It is needed in the dl_iterate_phdr callbacks. */
- const ElfW(Ehdr) *rtld_ehdr;
-
- /* Starting from binutils-2.23, the linker will define the magic symbol
- __ehdr_start to point to our own ELF header if it is visible in a
- segment that also includes the phdrs. If that's not available, we use
- the old method that assumes the beginning of the file is part of the
- lowest-addressed PT_LOAD segment. */
-#ifdef HAVE_EHDR_START
- extern const ElfW(Ehdr) __ehdr_start __attribute__ ((visibility ("hidden")));
- rtld_ehdr = &__ehdr_start;
-#else
- rtld_ehdr = (void *) GL(dl_rtld_map).l_map_start;
-#endif
- assert (rtld_ehdr->e_ehsize == sizeof *rtld_ehdr);
- assert (rtld_ehdr->e_phentsize == sizeof (ElfW(Phdr)));
-
- const ElfW(Phdr) *rtld_phdr = (const void *) rtld_ehdr + rtld_ehdr->e_phoff;
-
- GL(dl_rtld_map).l_phdr = rtld_phdr;
- GL(dl_rtld_map).l_phnum = rtld_ehdr->e_phnum;
-
-
- /* PT_GNU_RELRO is usually the last phdr. */
- size_t cnt = rtld_ehdr->e_phnum;
- while (cnt-- > 0)
- if (rtld_phdr[cnt].p_type == PT_GNU_RELRO)
- {
- GL(dl_rtld_map).l_relro_addr = rtld_phdr[cnt].p_vaddr;
- GL(dl_rtld_map).l_relro_size = rtld_phdr[cnt].p_memsz;
- break;
- }
+ rtld_setup_phdr ();
/* Add the dynamic linker to the TLS list if it also uses TLS. */
if (GL(dl_rtld_map).l_tls_blocksize != 0)

View File

@ -0,0 +1,457 @@
commit 20681be149b9eb1b6c1f4246bf4bd801221c86cd
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Aug 1 10:20:23 2025 +0200
elf: Handle ld.so with LOAD segment gaps in _dl_find_object (bug 31943)
Detect if ld.so not contiguous and handle that case in _dl_find_object.
Set l_find_object_processed even for initially loaded link maps,
otherwise dlopen of an initially loaded object adds it to
_dlfo_loaded_mappings (where maps are expected to be contiguous),
in addition to _dlfo_nodelete_mappings.
Test elf/tst-link-map-contiguous-ldso iterates over the loader
image, reading every word to make sure memory is actually mapped.
It only does that if the l_contiguous flag is set for the link map.
Otherwise, it finds gaps with mmap and checks that _dl_find_object
does not return the ld.so mapping for them.
The test elf/tst-link-map-contiguous-main does the same thing for
the libc.so shared object. This only works if the kernel loaded
the main program because the glibc dynamic loader may fill
the gaps with PROT_NONE mappings in some cases, making it contiguous,
but accesses to individual words may still fault.
Test elf/tst-link-map-contiguous-libc is again slightly different
because the dynamic loader always fills the gaps with PROT_NONE
mappings, so a different form of probing has to be used.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
elf/dl-find_object.h
(missing SFrame support downstream. Update
l_find_object_processed early due to the possible
return from the for loop).
elf/dl-find_object.c
(missing is_rtld_link_map downstream)
elf/rtld.c
(missing GL(dl_rtld_map) refactoring downstream)
diff --git a/elf/Makefile b/elf/Makefile
index b37636c0f865f4e6..190ee83120c498a3 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -519,6 +519,8 @@ tests-internal += \
tst-dl-hwcaps_split \
tst-dlmopen2 \
tst-hash-collision3 \
+ tst-link-map-contiguous-ldso \
+ tst-link-map-contiguous-libc \
tst-ptrguard1 \
tst-stackguard1 \
tst-tls-surplus \
@@ -531,6 +533,10 @@ tests-internal += \
tst-dl_find_object tst-dl_find_object-threads \
# tests-internal
+ifeq ($(build-hardcoded-path-in-tests),yes)
+tests-internal += tst-link-map-contiguous-main
+endif
+
tests-container += \
tst-dlopen-self-container \
tst-dlopen-tlsmodid-container \
diff --git a/elf/dl-find_object.c b/elf/dl-find_object.c
index 62a8a61f6b6032cf..128329aa20ef9ed6 100644
--- a/elf/dl-find_object.c
+++ b/elf/dl-find_object.c
@@ -468,6 +468,37 @@ rtld_hidden_def (__dl_find_object_internal)
strong_alias (__dl_find_object_internal, _dl_find_object)
#endif
+/* Subroutine of _dlfo_process_initial to split out noncontigous link
+ maps. NODELETE is the number of used _dlfo_nodelete_mappings
+ elements. It is incremented as needed, and the new NODELETE value
+ is returned. */
+static size_t
+_dlfo_process_initial_noncontiguous_map (struct link_map *map,
+ size_t nodelete)
+{
+ struct dl_find_object_internal dlfo;
+ _dl_find_object_from_map (map, &dlfo);
+
+ /* PT_LOAD segments for a non-contiguous link map are added to the
+ non-closeable mappings. */
+ const ElfW(Phdr) *ph = map->l_phdr;
+ const ElfW(Phdr) *ph_end = map->l_phdr + map->l_phnum;
+ for (; ph < ph_end; ++ph)
+ if (ph->p_type == PT_LOAD)
+ {
+ if (_dlfo_nodelete_mappings != NULL)
+ {
+ /* Second pass only. */
+ _dlfo_nodelete_mappings[nodelete] = dlfo;
+ ElfW(Addr) start = ph->p_vaddr + map->l_addr;
+ _dlfo_nodelete_mappings[nodelete].map_start = start;
+ _dlfo_nodelete_mappings[nodelete].map_end = start + ph->p_memsz;
+ }
+ ++nodelete;
+ }
+ return nodelete;
+}
+
/* _dlfo_process_initial is called twice. First to compute the array
sizes from the initial loaded mappings. Second to fill in the
bases and infos arrays with the (still unsorted) data. Returns the
@@ -479,29 +510,8 @@ _dlfo_process_initial (void)
size_t nodelete = 0;
if (!main_map->l_contiguous)
- {
- struct dl_find_object_internal dlfo;
- _dl_find_object_from_map (main_map, &dlfo);
-
- /* PT_LOAD segments for a non-contiguous are added to the
- non-closeable mappings. */
- for (const ElfW(Phdr) *ph = main_map->l_phdr,
- *ph_end = main_map->l_phdr + main_map->l_phnum;
- ph < ph_end; ++ph)
- if (ph->p_type == PT_LOAD)
- {
- if (_dlfo_nodelete_mappings != NULL)
- {
- /* Second pass only. */
- _dlfo_nodelete_mappings[nodelete] = dlfo;
- _dlfo_nodelete_mappings[nodelete].map_start
- = ph->p_vaddr + main_map->l_addr;
- _dlfo_nodelete_mappings[nodelete].map_end
- = _dlfo_nodelete_mappings[nodelete].map_start + ph->p_memsz;
- }
- ++nodelete;
- }
- }
+ /* Contiguous case already handled in _dl_find_object_init. */
+ nodelete = _dlfo_process_initial_noncontiguous_map (main_map, nodelete);
size_t loaded = 0;
for (Lmid_t ns = 0; ns < GL(dl_nns); ++ns)
@@ -513,11 +523,22 @@ _dlfo_process_initial (void)
/* lt_library link maps are implicitly NODELETE. */
if (l->l_type == lt_library || l->l_nodelete_active)
{
- if (_dlfo_nodelete_mappings != NULL)
- /* Second pass only. */
- _dl_find_object_from_map
- (l, _dlfo_nodelete_mappings + nodelete);
- ++nodelete;
+ /* The kernel may have loaded ld.so with gaps. */
+ if (!l->l_contiguous
+#ifdef SHARED
+ && l == &GL(dl_rtld_map)
+#endif
+ )
+ nodelete
+ = _dlfo_process_initial_noncontiguous_map (l, nodelete);
+ else
+ {
+ if (_dlfo_nodelete_mappings != NULL)
+ /* Second pass only. */
+ _dl_find_object_from_map
+ (l, _dlfo_nodelete_mappings + nodelete);
+ ++nodelete;
+ }
}
else if (l->l_type == lt_loaded)
{
@@ -767,7 +788,6 @@ _dl_find_object_update_1 (struct link_map **loaded, size_t count)
/* Prefer newly loaded link map. */
assert (loaded_index1 > 0);
_dl_find_object_from_map (loaded[loaded_index1 - 1], dlfo);
- loaded[loaded_index1 - 1]->l_find_object_processed = 1;
--loaded_index1;
}
diff --git a/elf/dl-find_object.h b/elf/dl-find_object.h
index 11569efc9b7daf9c..fae25747edc6dca0 100644
--- a/elf/dl-find_object.h
+++ b/elf/dl-find_object.h
@@ -87,7 +87,7 @@ _dl_find_object_to_external (struct dl_find_object_internal *internal,
}
/* Extract the object location data from a link map and writes it to
- *RESULT using relaxed MO stores. */
+ *RESULT using relaxed MO stores. Set L->l_find_object_processed. */
static void __attribute__ ((unused))
_dl_find_object_from_map (struct link_map *l,
struct dl_find_object_internal *result)
@@ -100,6 +100,8 @@ _dl_find_object_from_map (struct link_map *l,
atomic_store_relaxed (&result->eh_dbase, (void *) l->l_info[DT_PLTGOT]);
#endif
+ l->l_find_object_processed = 1;
+
for (const ElfW(Phdr) *ph = l->l_phdr, *ph_end = l->l_phdr + l->l_phnum;
ph < ph_end; ++ph)
if (ph->p_type == DLFO_EH_SEGMENT_TYPE)
diff --git a/elf/rtld.c b/elf/rtld.c
index a9073d4e14b07410..425003e6c8e452ab 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1308,7 +1308,7 @@ rtld_setup_main_map (struct link_map *main_map)
/* Set up the program header information for the dynamic linker
itself. It can be accessed via _r_debug and dl_iterate_phdr
- callbacks. */
+ callbacks, and it is used by _dl_find_object. */
static void
rtld_setup_phdr (void)
{
@@ -1334,6 +1334,29 @@ rtld_setup_phdr (void)
GL(dl_rtld_map).l_phnum = rtld_ehdr->e_phnum;
+ GL(dl_rtld_map).l_contiguous = 1;
+ /* The linker may not have produced a contiguous object. The kernel
+ will load the object with actual gaps (unlike the glibc loader
+ for shared objects, which always produces a contiguous mapping).
+ See similar logic in rtld_setup_main_map above. */
+ {
+ ElfW(Addr) expected_load_address = 0;
+ for (const ElfW(Phdr) *ph = rtld_phdr; ph < &rtld_phdr[rtld_ehdr->e_phnum];
+ ++ph)
+ if (ph->p_type == PT_LOAD)
+ {
+ ElfW(Addr) mapstart = ph->p_vaddr & ~(GLRO(dl_pagesize) - 1);
+ if (GL(dl_rtld_map).l_contiguous && expected_load_address != 0
+ && expected_load_address != mapstart)
+ GL(dl_rtld_map).l_contiguous = 0;
+ ElfW(Addr) allocend = ph->p_vaddr + ph->p_memsz;
+ /* The next expected address is the page following this load
+ segment. */
+ expected_load_address = ((allocend + GLRO(dl_pagesize) - 1)
+ & ~(GLRO(dl_pagesize) - 1));
+ }
+ }
+
/* PT_GNU_RELRO is usually the last phdr. */
size_t cnt = rtld_ehdr->e_phnum;
while (cnt-- > 0)
diff --git a/elf/tst-link-map-contiguous-ldso.c b/elf/tst-link-map-contiguous-ldso.c
new file mode 100644
index 0000000000000000..04de808bb234fe38
--- /dev/null
+++ b/elf/tst-link-map-contiguous-ldso.c
@@ -0,0 +1,98 @@
+/* Check that _dl_find_object behavior matches up with gaps.
+ Copyright (C) 2025 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 <dlfcn.h>
+#include <gnu/lib-names.h>
+#include <link.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+#include <support/xunistd.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+static int
+do_test (void)
+{
+ struct link_map *l = xdlopen (LD_SO, RTLD_NOW);
+ if (!l->l_contiguous)
+ {
+ puts ("info: ld.so link map is not contiguous");
+
+ /* Try to find holes by probing with mmap. */
+ int pagesize = getpagesize ();
+ bool gap_found = false;
+ ElfW(Addr) addr = l->l_map_start;
+ TEST_COMPARE (addr % pagesize, 0);
+ while (addr < l->l_map_end)
+ {
+ void *expected = (void *) addr;
+ void *ptr = xmmap (expected, 1, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
+ struct dl_find_object dlfo;
+ int dlfo_ret = _dl_find_object (expected, &dlfo);
+ if (ptr == expected)
+ {
+ if (dlfo_ret < 0)
+ {
+ TEST_COMPARE (dlfo_ret, -1);
+ printf ("info: hole without mapping data found at %p\n", ptr);
+ }
+ else
+ FAIL ("object \"%s\" found in gap at %p",
+ dlfo.dlfo_link_map->l_name, ptr);
+ gap_found = true;
+ }
+ else if (dlfo_ret == 0)
+ {
+ if ((void *) dlfo.dlfo_link_map != (void *) l)
+ {
+ printf ("info: object \"%s\" found at %p\n",
+ dlfo.dlfo_link_map->l_name, ptr);
+ gap_found = true;
+ }
+ }
+ else
+ TEST_COMPARE (dlfo_ret, -1);
+ xmunmap (ptr, 1);
+ addr += pagesize;
+ }
+ if (!gap_found)
+ FAIL ("no ld.so gap found");
+ }
+ else
+ {
+ puts ("info: ld.so link map is contiguous");
+
+ /* Assert that ld.so is truly contiguous in memory. */
+ volatile long int *p = (volatile long int *) l->l_map_start;
+ volatile long int *end = (volatile long int *) l->l_map_end;
+ while (p < end)
+ {
+ *p;
+ ++p;
+ }
+ }
+
+ xdlclose (l);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-link-map-contiguous-libc.c b/elf/tst-link-map-contiguous-libc.c
new file mode 100644
index 0000000000000000..eb5728c765ac3cfb
--- /dev/null
+++ b/elf/tst-link-map-contiguous-libc.c
@@ -0,0 +1,57 @@
+/* Check that the entire libc.so program image is readable if contiguous.
+ Copyright (C) 2025 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 <gnu/lib-names.h>
+#include <link.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+#include <support/xunistd.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+static int
+do_test (void)
+{
+ struct link_map *l = xdlopen (LIBC_SO, RTLD_NOW);
+
+ /* The dynamic loader fills holes with PROT_NONE mappings. */
+ if (!l->l_contiguous)
+ FAIL_EXIT1 ("libc.so link map is not contiguous");
+
+ /* Direct probing does not work because not everything is readable
+ due to PROT_NONE mappings. */
+ int pagesize = getpagesize ();
+ ElfW(Addr) addr = l->l_map_start;
+ TEST_COMPARE (addr % pagesize, 0);
+ while (addr < l->l_map_end)
+ {
+ void *expected = (void *) addr;
+ void *ptr = xmmap (expected, 1, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
+ if (ptr == expected)
+ FAIL ("hole in libc.so memory image after %lu bytes",
+ (unsigned long int) (addr - l->l_map_start));
+ xmunmap (ptr, 1);
+ addr += pagesize;
+ }
+
+ xdlclose (l);
+
+ return 0;
+}
+#include <support/test-driver.c>
diff --git a/elf/tst-link-map-contiguous-main.c b/elf/tst-link-map-contiguous-main.c
new file mode 100644
index 0000000000000000..2d1a054f0fbb0855
--- /dev/null
+++ b/elf/tst-link-map-contiguous-main.c
@@ -0,0 +1,45 @@
+/* Check that the entire main program image is readable if contiguous.
+ Copyright (C) 2025 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 <link.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+
+static int
+do_test (void)
+{
+ struct link_map *l = xdlopen ("", RTLD_NOW);
+ if (!l->l_contiguous)
+ FAIL_UNSUPPORTED ("main link map is not contiguous");
+
+ /* This check only works if the kernel loaded the main program. The
+ dynamic loader replaces gaps with PROT_NONE mappings, resulting
+ in faults. */
+ volatile long int *p = (volatile long int *) l->l_map_start;
+ volatile long int *end = (volatile long int *) l->l_map_end;
+ while (p < end)
+ {
+ *p;
+ ++p;
+ }
+
+ xdlclose (l);
+
+ return 0;
+}
+#include <support/test-driver.c>

View File

@ -12,21 +12,22 @@ Date: Mon Jul 21 21:43:49 2025 +0200
Reviewed-by: Andreas K. Huettel <dilfridge@gentoo.org>
Conflicts:
posix/Makefile
(tests list not reformatted downstream)
posix/Makefile (New test added)
diff --git a/posix/Makefile b/posix/Makefile
index 4c32a088a73723c7..ef7a9ca31d9ee136 100644
index 7b70b4a736bc1215..562e8cb85fdb6f43 100644
--- a/posix/Makefile
+++ b/posix/Makefile
@@ -111,6 +111,7 @@ tests := test-errno tstgetopt testfnm runtests runptests \
@@ -112,7 +112,8 @@ tests := test-errno tstgetopt testfnm runtests runptests \
tst-sched_getaffinity \
tst-cpuset-dynamic \
tst-cpuset-static \
+ tst-regcomp-bracket-free \
- tst-spawn6
+ tst-spawn6 \
+ tst-regcomp-bracket-free
# Test for the glob symbol version that was replaced in glibc 2.27.
ifeq ($(have-GLIBC_2.26)$(build-shared),yesyes)
diff --git a/posix/regcomp.c b/posix/regcomp.c
index 887e5b50684e22f5..005e6459bbe8bd55 100644
--- a/posix/regcomp.c

View File

@ -0,0 +1,33 @@
Downstream patch only.
Revert changes made to elf/dl-readonly-area.c compared to
ed6a68bac7cd056abda9008019c71b167f0362dc since `_dl_find_object` has
been backported.
Note: `_dl_find_object` isn't directly made available internally
downstream, we can use `__dl_find_object_internal` instead.
diff --git a/elf/dl-readonly-area.c b/elf/dl-readonly-area.c
index 570b99b11527db13..3b39eed06a379ce3 100644
--- a/elf/dl-readonly-area.c
+++ b/elf/dl-readonly-area.c
@@ -40,16 +40,11 @@ check_relro (const struct link_map *l, uintptr_t start, uintptr_t end)
enum dl_readonly_area_error_type
_dl_readonly_area (const void *ptr, size_t size)
{
- /* Protect against concurrent loads and unloads. */
- __rtld_lock_lock_recursive (GL(dl_load_lock));
-
- const struct link_map *l = _dl_find_dso_for_object ((ElfW(Addr)) ptr);
-
- __rtld_lock_unlock_recursive (GL(dl_load_lock));
-
- if (l == NULL)
+ struct dl_find_object dlfo;
+ if (__dl_find_object_internal ((void *)ptr, &dlfo) != 0)
return dl_readonly_area_not_found;
+ const struct link_map *l = dlfo.dlfo_link_map;
uintptr_t ptr_start = (uintptr_t) ptr;
uintptr_t ptr_end = ptr_start + size;

View File

@ -0,0 +1,80 @@
commit 620f0730f311635cd0e175a3ae4d0fc700c76366
Author: Florian Weimer <fweimer@redhat.com>
Date: Mon Jul 28 14:16:52 2025 +0200
elf: Compile _dl_debug_state separately (bug 33224)
This ensures that the compiler will not inline it, so that
debuggers which do not use the Systemtap probes can reliably
set a breakpoint on it.
Reviewed-by: Andreas K. Huettel <dilfridge@gentoo.org>
Tested-by: Andreas K. Huettel <dilfridge@gentoo.org>
diff --git a/elf/Makefile b/elf/Makefile
index b181150b36773d24..b37636c0f865f4e6 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -56,6 +56,7 @@ dl-routines = \
dl-close \
dl-debug \
dl-debug-symbols \
+ dl-debug_state \
dl-deps \
dl-exception \
dl-execstack \
diff --git a/elf/dl-debug.c b/elf/dl-debug.c
index 4388a04cdf828898..8c1472a84ebfefe7 100644
--- a/elf/dl-debug.c
+++ b/elf/dl-debug.c
@@ -167,14 +167,3 @@ _dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
return &r->base;
}
-
-
-/* This function exists solely to have a breakpoint set on it by the
- debugger. The debugger is supposed to find this function's address by
- examining the r_brk member of struct r_debug, but GDB 4.15 in fact looks
- for this particular symbol name in the PT_INTERP file. */
-void
-_dl_debug_state (void)
-{
-}
-rtld_hidden_def (_dl_debug_state)
diff --git a/elf/dl-debug_state.c b/elf/dl-debug_state.c
new file mode 100644
index 0000000000000000..40c134a49e2455f3
--- /dev/null
+++ b/elf/dl-debug_state.c
@@ -0,0 +1,30 @@
+/* Debugger hook called after dynamic linker updates.
+ Copyright (C) 1996-2025 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 <ldsodefs.h>
+
+/* This function exists solely to have a breakpoint set on it by the
+ debugger. The debugger is supposed to find this function's address by
+ examining the r_brk member of struct r_debug, but GDB 4.15 in fact looks
+ for this particular symbol name in the PT_INTERP file. Therefore,
+ this function must not be inlined. */
+void
+_dl_debug_state (void)
+{
+}
+rtld_hidden_def (_dl_debug_state)

View File

@ -0,0 +1,39 @@
commit e5363e6f460c2d58809bf10fc96d70fd1ef8b5b2
Author: Jens Remus <jremus@linux.ibm.com>
Date: Fri Jul 25 15:40:03 2025 +0200
Use TLS initial-exec model for __libc_tsd_CTYPE_* thread variables [BZ #33234]
Commit 10a66a8e421b ("Remove <libc-tsd.h>") removed the TLS initial-exec
(IE) model attribute from the __libc_tsd_CTYPE_* thread variable declarations
and definitions. Commit a894f04d8776 ("Optimize __libc_tsd_* thread
variable access") restored it on declarations.
Restore the TLS initial-exec model attribute on __libc_tsd_CTYPE_* thread
variable definitions.
This resolves test tst-locale1 failure on s390 32-bit, when using a
GNU linker without the fix from GNU binutils commit aefebe82dc89
("IBM zSystems: Fix offset relative to static TLS").
Reviewed-by: Florian Weimer <fweimer@redhat.com>
diff --git a/ctype/ctype-info.c b/ctype/ctype-info.c
index e0752b4a1af6df15..315cedcfaa357ead 100644
--- a/ctype/ctype-info.c
+++ b/ctype/ctype-info.c
@@ -24,11 +24,11 @@
__ctype_init before user code runs, but this does not happen for
threads in secondary namespaces. With the initializers, secondary
namespaces at least get locale data from the C locale. */
-__thread const uint16_t * __libc_tsd_CTYPE_B
+__thread const uint16_t * __libc_tsd_CTYPE_B attribute_tls_model_ie
= (const uint16_t *) _nl_C_LC_CTYPE_class + 128;
-__thread const int32_t * __libc_tsd_CTYPE_TOLOWER
+__thread const int32_t * __libc_tsd_CTYPE_TOLOWER attribute_tls_model_ie
= (const int32_t *) _nl_C_LC_CTYPE_tolower + 128;
-__thread const int32_t * __libc_tsd_CTYPE_TOUPPER
+__thread const int32_t * __libc_tsd_CTYPE_TOUPPER attribute_tls_model_ie
= (const int32_t *) _nl_C_LC_CTYPE_toupper + 128;

View File

@ -0,0 +1,35 @@
Downstream-only patch correcting the GLIBC_PRIVATE ABI preservation
change in glibc-RHEL-93320-19.patch.
This patch adds a symbol alias that supplies the expect external
name _dl_find_object within libc.a, where the forwarder in
elf/libc-dl_find_object.c no longer exists after the changes
in glibc-RHEL-93320-19.patch. It also brings back the
tst-dl_find_object-static static linking test.
diff --git a/elf/Makefile b/elf/Makefile
index ba11f3a8b81e7218..7b75afda32bcd579 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -281,6 +281,7 @@ tests-static-normal := \
# tests-static-normal
tests-static-internal := \
+ tst-dl_find_object-static \
tst-ptrguard1-static \
tst-stackguard1-static \
tst-tls1-static \
diff --git a/elf/dl-find_object.c b/elf/dl-find_object.c
index 99797580066cdce8..62a8a61f6b6032cf 100644
--- a/elf/dl-find_object.c
+++ b/elf/dl-find_object.c
@@ -464,6 +464,9 @@ __dl_find_object_internal (void *pc1, struct dl_find_object *result)
} /* Transaction retry loop. */
}
rtld_hidden_def (__dl_find_object_internal)
+#ifndef SHARED
+strong_alias (__dl_find_object_internal, _dl_find_object)
+#endif
/* _dlfo_process_initial is called twice. First to compute the array
sizes from the initial loaded mappings. Second to fill in the

View File

@ -0,0 +1,18 @@
Downstream-only patch to remove duplicate Makefile target entry for
tst-dlmopen1mod.
Conflict resolution in glibc-RHEL-93320-4.patch led to the duplicate
tst-dlmopen1mod entry. Therefore remove it.
diff --git a/elf/Makefile b/elf/Makefile
index 190ee83120c498a3..e8587b10c1a8bedd 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -811,7 +811,6 @@ modules-names = \
tst-dl_find_object-mod7 \
tst-dl_find_object-mod8 \
tst-dl_find_object-mod9 \
- tst-dlmopen1mod \
tst-dlclose-lazy-mod1 \
tst-dlclose-lazy-mod2 \
tst-dlmopen-dlerror-mod \

View File

@ -0,0 +1,36 @@
In glibc-RHEL-93320-9.patch, the elf/tst-dl_find_object test was
added. It was later disabled in glibc-RHEL-93320-19.patch,
as noted in the patch description. This was missed when the
patch was re-added in glibc-RHEL-107564.patch. The test remains
valuable because we do not test _dl_find_object in libc.a elsewhere
in the glibc build, so this patch disables just the failing subtest,
and puts an explanation directly into the test.
diff --git a/elf/tst-dl_find_object.c b/elf/tst-dl_find_object.c
index d8c217545d116453..6bfda7bd23a34c0d 100644
--- a/elf/tst-dl_find_object.c
+++ b/elf/tst-dl_find_object.c
@@ -231,6 +231,7 @@ do_test (void)
check (map_start, &expected, __LINE__);
check (map_end, &expected, __LINE__);
+#ifndef FOR_STATIC
/* Check that _dl_find_object works from a shared object (mostly for
static dlopen). */
__typeof (_dl_find_object) *find_object
@@ -238,6 +239,15 @@ do_test (void)
struct dl_find_object actual;
TEST_COMPARE (find_object (&main_program_data, &actual), 0);
check (&main_program_data, &actual, __LINE__); /* Reversed check. */
+#else
+ /* Downstream, _dl_find_object does not work after static dlopen
+ because the ld.so copy loaded as part of static dlopen is not
+ initialized. Upstream, we redirect _dl_find_object to the
+ statically version from the main program by patching a function
+ pointer in _rtld_global_ro. Downstream, we have not changed the
+ layout of _rtld_global_ro, so this patching is missing. */
+ printf ("info: skipping dlopen-based test for static build\n");
+#endif
return 0;
}

View File

@ -13,10 +13,10 @@ Date: Fri Sep 12 21:33:34 2025 +0200
Reviewed-by: Collin Funk <collin.funk1@gmail.com>
diff --git a/nss/getXXbyYY_r.c b/nss/getXXbyYY_r.c
index c344528f72a2dcab..30f913876eb81594 100644
index eae6c3480e..2b0735fb6a 100644
--- a/nss/getXXbyYY_r.c
+++ b/nss/getXXbyYY_r.c
@@ -158,19 +158,15 @@ __merge_einval (LOOKUP_TYPE *a,
@@ -157,19 +157,15 @@ __merge_einval (LOOKUP_TYPE *a,
#define CHECK_MERGE(err, status) \
({ \

View File

@ -0,0 +1,226 @@
Downstream-only patch to add arc4random to support/ for use in qsort
testing.
The arc4random implementation is up-to-date with upstream commit
2642002380aafb71a1d3b569b6d7ebeab3284816, with minor changes to keep
everything self-contained within support infrastructure. Unlike the
upstream version, this implementation is a cancellation point.
diff --git a/support/Makefile b/support/Makefile
index d6d03c2ed3af3e6d..bffcb06d7185d674 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -41,6 +41,8 @@ libsupport-routines = \
resolv_response_context_free \
resolv_test \
set_fortify_handler \
+ support-arc4random \
+ support-arc4random_uniform \
support-open-dev-null-range \
support_become_root \
support_can_chroot \
diff --git a/support/support-arc4random.c b/support/support-arc4random.c
new file mode 100644
index 0000000000000000..c4462b098c68cef5
--- /dev/null
+++ b/support/support-arc4random.c
@@ -0,0 +1,99 @@
+/* Pseudo Random Number Generator
+ Copyright (C) 2022-2025 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 <errno.h>
+#include <not-cancel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/random.h>
+#include <poll.h>
+
+static void
+arc4random_getrandom_failure (void)
+{
+ __libc_fatal ("Fatal glibc error: cannot get entropy for arc4random\n");
+}
+
+void
+arc4random_buf (void *p, size_t n)
+{
+ static int seen_initialized;
+ ssize_t l;
+ int fd;
+
+ if (n == 0)
+ return;
+
+ for (;;)
+ {
+ l = TEMP_FAILURE_RETRY (getrandom (p, n, 0));
+ if (l > 0)
+ {
+ if ((size_t) l == n)
+ return; /* Done reading, success. */
+ p = (uint8_t *) p + l;
+ n -= l;
+ continue; /* Interrupted by a signal; keep going. */
+ }
+ else if (l < 0 && errno == ENOSYS)
+ break; /* No syscall, so fallback to /dev/urandom. */
+ arc4random_getrandom_failure ();
+ }
+
+ if (atomic_load_relaxed (&seen_initialized) == 0)
+ {
+ /* Poll /dev/random as an approximation of RNG initialization. */
+ struct pollfd pfd = { .events = POLLIN };
+ pfd.fd = TEMP_FAILURE_RETRY (
+ __open64_nocancel ("/dev/random", O_RDONLY | O_CLOEXEC | O_NOCTTY));
+ if (pfd.fd < 0)
+ arc4random_getrandom_failure ();
+ if (TEMP_FAILURE_RETRY (poll (&pfd, 1, -1)) < 0)
+ arc4random_getrandom_failure ();
+ if (__close_nocancel (pfd.fd) < 0)
+ arc4random_getrandom_failure ();
+ atomic_store_relaxed (&seen_initialized, 1);
+ }
+
+ fd = TEMP_FAILURE_RETRY (
+ __open64_nocancel ("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY));
+ if (fd < 0)
+ arc4random_getrandom_failure ();
+ for (;;)
+ {
+ l = TEMP_FAILURE_RETRY (__read_nocancel (fd, p, n));
+ if (l <= 0)
+ arc4random_getrandom_failure ();
+ if ((size_t) l == n)
+ break; /* Done reading, success. */
+ p = (uint8_t *) p + l;
+ n -= l;
+ }
+ if (__close_nocancel (fd) < 0)
+ arc4random_getrandom_failure ();
+}
+
+uint32_t
+arc4random (void)
+{
+ uint32_t r;
+ arc4random_buf (&r, sizeof (r));
+ return r;
+}
diff --git a/support/support-arc4random_uniform.c b/support/support-arc4random_uniform.c
new file mode 100644
index 0000000000000000..20108e7409cca81b
--- /dev/null
+++ b/support/support-arc4random_uniform.c
@@ -0,0 +1,70 @@
+/* Random pseudo generator number which returns a single 32 bit value
+ uniformly distributed but with an upper_bound.
+ Copyright (C) 2022-2025 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 <stdlib.h>
+#include <sys/param.h>
+#include <support/support.h>
+
+/* Return a uniformly distributed random number less than N. The algorithm
+ calculates a mask being the lowest power of two bounding the upper bound
+ N, successively queries new random values, and rejects values outside of
+ the request range.
+
+ For reject values, it also tries if the remaining entropy could fit on
+ the asked range after range adjustment.
+
+ The algorithm avoids modulo and divide operations, which might be costly
+ depending on the architecture. */
+uint32_t
+arc4random_uniform (uint32_t n)
+{
+ if (n <= 1)
+ /* There is no valid return value for a zero limit, and 0 is the
+ only possible result for limit 1. */
+ return 0;
+
+ /* Powers of two are easy. */
+ if (powerof2 (n))
+ return arc4random () & (n - 1);
+
+ /* mask is the smallest power of 2 minus 1 number larger than n. */
+ int z = __builtin_clz (n);
+ uint32_t mask = ~UINT32_C(0) >> z;
+ int bits = CHAR_BIT * sizeof (uint32_t) - z;
+
+ while (1)
+ {
+ uint32_t value = arc4random ();
+
+ /* Return if the lower power of 2 minus 1 satisfy the condition. */
+ uint32_t r = value & mask;
+ if (r < n)
+ return r;
+
+ /* Otherwise check if remaining bits of entropy provides fits in the
+ bound. */
+ for (int bits_left = z; bits_left >= bits; bits_left -= bits)
+ {
+ value >>= bits;
+ r = value & mask;
+ if (r < n)
+ return r;
+ }
+ }
+}
diff --git a/support/support.h b/support/support.h
index b69f588e2edce6be..ed7862daf9e4120a 100644
--- a/support/support.h
+++ b/support/support.h
@@ -220,6 +220,19 @@ void support_stack_free (struct support_stack *stack);
The returned value is the lowest file descriptor number. */
int support_open_dev_null_range (int num, int flags, mode_t mode);
+/* Return a random integer between zero and 2**32-1 (inclusive). */
+extern uint32_t arc4random (void)
+ __THROW __wur;
+
+/* Fill the buffer with random data. */
+extern void arc4random_buf (void *__buf, size_t __size)
+ __THROW __nonnull ((1));
+
+/* Return a random number between zero (inclusive) and the specified
+ limit (exclusive). */
+extern uint32_t arc4random_uniform (__uint32_t __upper_bound)
+ __THROW __wur;
+
__END_DECLS
#endif /* SUPPORT_H */

View File

@ -0,0 +1,43 @@
commit f8cfb6836e8d91bb789b2e7fd65338d6f5bd459c
Author: Florian Weimer <fweimer@redhat.com>
Date: Wed Nov 8 15:18:02 2023 +0100
stdlib: Avoid element self-comparisons in qsort
This improves compatibility with applications which assume that qsort
does not invoke the comparison function with equal pointer arguments.
The newly introduced branches should be predictable, as leading to a
call to the comparison function. If the prediction fails, we avoid
calling the function.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index cb1619aa0ae7de72..2ee39e2c492f792e 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -137,7 +137,7 @@ siftdown (void *base, size_t size, size_t k, size_t n,
if (j < n && cmp (base + (j * size), base + ((j + 1) * size), arg) < 0)
j++;
- if (cmp (base + (k * size), base + (j * size), arg) >= 0)
+ if (j == k || cmp (base + (k * size), base + (j * size), arg) >= 0)
break;
do_swap (base + (size * j), base + (k * size), size, swap_type);
@@ -333,10 +333,12 @@ __qsort_r (void *const pbase, size_t total_elems, size_t size,
that this algorithm runs much faster than others. */
do
{
- while ((*cmp) ((void *) left_ptr, (void *) mid, arg) < 0)
+ while (left_ptr != mid
+ && (*cmp) ((void *) left_ptr, (void *) mid, arg) < 0)
left_ptr += size;
- while ((*cmp) ((void *) mid, (void *) right_ptr, arg) < 0)
+ while (right_ptr != mid
+ && (*cmp) ((void *) mid, (void *) right_ptr, arg) < 0)
right_ptr -= size;
if (left_ptr < right_ptr)

View File

@ -0,0 +1,25 @@
commit e4d8117b82065dc72e8df80097360e7c05a349b9
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Nov 21 16:45:35 2023 +0100
stdlib: Avoid another self-comparison in qsort
In the insertion phase, we could run off the start of the array if the
comparison function never runs zero. In that case, it never finds the
initial element that terminates the iteration.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 2ee39e2c492f792e..0d5f8b92e8072965 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -218,7 +218,7 @@ insertion_sort_qsort_partitions (void *const pbase, size_t total_elems,
while ((run_ptr += size) <= end_ptr)
{
tmp_ptr = run_ptr - size;
- while (cmp (run_ptr, tmp_ptr, arg) < 0)
+ while (run_ptr != tmp_ptr && cmp (run_ptr, tmp_ptr, arg) < 0)
tmp_ptr -= size;
tmp_ptr += size;

View File

@ -0,0 +1,273 @@
commit 55364e1f7dfab372f0710513c4d1c967c4965f71
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Nov 21 16:45:35 2023 +0100
stdlib: Handle various corner cases in the fallback heapsort for qsort
The previous implementation did not consistently apply the rule that
the child nodes of node K are at 2 * K + 1 and 2 * K + 2, or
that the parent node is at (K - 1) / 2.
Add an internal test that targets the heapsort implementation
directly.
Reported-by: Stepan Golosunov <stepan@golosunov.pp.ru>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 4039e5395eeea2b0..ee005ce8caa48abe 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -254,6 +254,7 @@ tests := \
# tests
tests-internal := \
+ tst-qsort4 \
tst-strtod1i \
tst-strtod3 \
tst-strtod4 \
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 0d5f8b92e8072965..b207c12d2f0a38cc 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -126,29 +126,44 @@ pop (stack_node *top, char **lo, char **hi, size_t *depth)
return top;
}
-/* NB: N is inclusive bound for BASE. */
+/* Establish the heap condition at index K, that is, the key at K will
+ not be less than either of its children, at 2 * K + 1 and 2 * K + 2
+ (if they exist). N is the last valid index. */
static inline void
siftdown (void *base, size_t size, size_t k, size_t n,
enum swap_type_t swap_type, __compar_d_fn_t cmp, void *arg)
{
- while (k <= n / 2)
+ /* There can only be a heap condition violation if there are
+ children. */
+ while (2 * k + 1 <= n)
{
- size_t j = 2 * k;
+ /* Left child. */
+ size_t j = 2 * k + 1;
+ /* If the right child is larger, use it. */
if (j < n && cmp (base + (j * size), base + ((j + 1) * size), arg) < 0)
j++;
+ /* If k is already >= to its children, we are done. */
if (j == k || cmp (base + (k * size), base + (j * size), arg) >= 0)
break;
+ /* Heal the violation. */
do_swap (base + (size * j), base + (k * size), size, swap_type);
+
+ /* Swapping with j may have introduced a violation at j. Fix
+ it in the next loop iteration. */
k = j;
}
}
+/* Establish the heap condition for the indices 0 to N (inclusive). */
static inline void
heapify (void *base, size_t size, size_t n, enum swap_type_t swap_type,
__compar_d_fn_t cmp, void *arg)
{
+ /* If n is odd, k = n / 2 has a left child at n, so this is the
+ largest index that can have a heap condition violation regarding
+ its children. */
size_t k = n / 2;
while (1)
{
@@ -158,32 +173,38 @@ heapify (void *base, size_t size, size_t n, enum swap_type_t swap_type,
}
}
-/* A non-recursive heapsort, used on introsort implementation as a fallback
- routine with worst-case performance of O(nlog n) and worst-case space
- complexity of O(1). It sorts the array starting at BASE and ending at
- END, with each element of SIZE bytes. The SWAP_TYPE is the callback
- function used to swap elements, and CMP is the function used to compare
- elements. */
+/* A non-recursive heapsort, used on introsort implementation as a
+ fallback routine with worst-case performance of O(nlog n) and
+ worst-case space complexity of O(1). It sorts the array starting
+ at BASE and ending at END (inclusive), with each element of SIZE
+ bytes. The SWAP_TYPE is the callback function used to swap
+ elements, and CMP is the function used to compare elements. */
static void
heapsort_r (void *base, void *end, size_t size, enum swap_type_t swap_type,
__compar_d_fn_t cmp, void *arg)
{
- const size_t count = ((uintptr_t) end - (uintptr_t) base) / size;
-
- if (count < 2)
+ size_t n = ((uintptr_t) end - (uintptr_t) base) / size;
+ if (n <= 1)
+ /* Handled by insertion sort. */
return;
- size_t n = count - 1;
-
/* Build the binary heap, largest value at the base[0]. */
heapify (base, size, n, swap_type, cmp, arg);
- /* On each iteration base[0:n] is the binary heap, while base[n:count]
- is sorted. */
- while (n > 0)
+ while (true)
{
+ /* Indices 0 .. n contain the binary heap. Extract the largest
+ element put it into the final position in the array. */
do_swap (base, base + (n * size), size, swap_type);
+
+ /* The heap is now one element shorter. */
n--;
+ if (n == 0)
+ break;
+
+ /* By swapping in elements 0 and the previous value of n (now at
+ n + 1), we likely introduced a heap condition violation. Fix
+ it for the reduced heap. */
siftdown (base, size, 0, n, swap_type, cmp, arg);
}
}
diff --git a/stdlib/tst-qsort4.c b/stdlib/tst-qsort4.c
new file mode 100644
index 0000000000000000..a7abaa1a37461666
--- /dev/null
+++ b/stdlib/tst-qsort4.c
@@ -0,0 +1,134 @@
+/* Test the heapsort implementation behind qsort.
+ Copyright (C) 2023 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
+ <http://www.gnu.org/licenses/>. */
+
+#include "qsort.c"
+
+#include <stdio.h>
+#include <support/check.h>
+#include <support/support.h>
+
+static int
+cmp (const void *a1, const void *b1, void *closure)
+{
+ const signed char *a = a1;
+ const signed char *b = b1;
+ return *a - *b;
+}
+
+/* Wrapper around heapsort_r that set ups the required variables. */
+static void
+heapsort_wrapper (void *const pbase, size_t total_elems, size_t size,
+ __compar_d_fn_t cmp, void *arg)
+{
+ char *base_ptr = (char *) pbase;
+ char *lo = base_ptr;
+ char *hi = &lo[size * (total_elems - 1)];
+
+ if (total_elems <= 1)
+ /* Avoid lossage with unsigned arithmetic below. */
+ return;
+
+ enum swap_type_t swap_type;
+ if (is_aligned (pbase, size, 8))
+ swap_type = SWAP_WORDS_64;
+ else if (is_aligned (pbase, size, 4))
+ swap_type = SWAP_WORDS_32;
+ else
+ swap_type = SWAP_BYTES;
+ heapsort_r (lo, hi, size, swap_type, cmp, arg);
+}
+
+static void
+check_one_sort (signed char *array, int length)
+{
+ signed char *copy = xmalloc (length);
+ memcpy (copy, array, length);
+ heapsort_wrapper (copy, length, 1, cmp, NULL);
+
+ /* Verify that the result is sorted. */
+ for (int i = 1; i < length; ++i)
+ if (copy[i] < copy[i - 1])
+ {
+ support_record_failure ();
+ printf ("error: sorting failure for length %d at offset %d\n",
+ length, i - 1);
+ printf ("input:");
+ for (int i = 0; i < length; ++i)
+ printf (" %d", array[i]);
+ printf ("\noutput:");
+ for (int i = 0; i < length; ++i)
+ printf (" %d", copy[i]);
+ putchar ('\n');
+ break;
+ }
+
+ /* Verify that no elements went away or were added. */
+ {
+ int expected_counts[256];
+ for (int i = 0; i < length; ++i)
+ ++expected_counts[array[i] & 0xff];
+ int actual_counts[256];
+ for (int i = 0; i < length; ++i)
+ ++actual_counts[copy[i] & 0xff];
+ for (int i = 0; i < 256; ++i)
+ TEST_COMPARE (expected_counts[i], expected_counts[i]);
+ }
+
+ free (copy);
+}
+
+/* Enumerate all possible combinations of LENGTH elements. */
+static void
+check_combinations (int length, signed char *start, int offset)
+{
+ if (offset == length)
+ check_one_sort (start, length);
+ else
+ for (int i = 0; i < length; ++i)
+ {
+ start[offset] = i;
+ check_combinations(length, start, offset + 1);
+ }
+}
+
+static int
+do_test (void)
+{
+ /* A random permutation of 20 values. */
+ check_one_sort ((signed char[20]) {5, 12, 16, 10, 14, 11, 9, 13, 8, 15,
+ 0, 17, 3, 7, 1, 18, 2, 19, 4, 6}, 20);
+
+
+ /* A permutation that appeared during adversarial testing for the
+ quicksort pass. */
+ check_one_sort ((signed char[16]) {15, 3, 4, 2, 1, 0, 8, 7, 6, 5, 14,
+ 13, 12, 11, 10, 9}, 16);
+
+ /* Array lengths 2 and less are not handled by heapsort_r and
+ deferred to insertion sort. */
+ for (int i = 3; i <= 8; ++i)
+ {
+ signed char *buf = xmalloc (i);
+ check_combinations (i, buf, 0);
+ free (buf);
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,248 @@
commit 64e4acf24da15c11cb83f933947df3b2e8a700cd
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Nov 21 16:45:35 2023 +0100
stdlib: The qsort implementation needs to use heapsort in more cases
The existing logic avoided internal stack overflow. To avoid
a denial-of-service condition with adversarial input, it is necessary
to fall over to heapsort if tail-recursing deeply, too, which does
not result in a deep stack of pending partitions.
The new test stdlib/tst-qsort5 is based on Douglas McIlroy's paper
on this subject.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
stdlib/Makefile: Adjust for getenv tests in glibc-RHEL-67692-4.patch.
diff --git a/stdlib/Makefile b/stdlib/Makefile
index ee005ce8caa48abe..a1a511da37f0c18e 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -212,6 +212,7 @@ tests := \
tst-qsort \
tst-qsort2 \
tst-qsort3 \
+ tst-qsort5 \
tst-quick_exit \
tst-rand48 \
tst-rand48-2 \
@@ -483,6 +484,7 @@ $(objpfx)tst-setcontext3.out: tst-setcontext3.sh $(objpfx)tst-setcontext3
$(common-objpfx)stdlib/; \
$(evaluate-test)
+$(objpfx)tst-qsort5: $(libm)
$(objpfx)tst-getenv-signal: $(shared-thread-library)
$(objpfx)tst-getenv-thread: $(shared-thread-library)
$(objpfx)tst-getenv-unsetenv: $(shared-thread-library)
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index b207c12d2f0a38cc..df8d0012c759e509 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -390,14 +390,23 @@ __qsort_r (void *const pbase, size_t total_elems, size_t size,
{
if ((size_t) (hi - left_ptr) <= max_thresh)
/* Ignore both small partitions. */
- top = pop (top, &lo, &hi, &depth);
+ {
+ top = pop (top, &lo, &hi, &depth);
+ --depth;
+ }
else
- /* Ignore small left partition. */
- lo = left_ptr;
+ {
+ /* Ignore small left partition. */
+ lo = left_ptr;
+ --depth;
+ }
}
else if ((size_t) (hi - left_ptr) <= max_thresh)
/* Ignore small right partition. */
- hi = right_ptr;
+ {
+ hi = right_ptr;
+ --depth;
+ }
else if ((right_ptr - lo) > (hi - left_ptr))
{
/* Push larger left partition indices. */
diff --git a/stdlib/tst-qsort5.c b/stdlib/tst-qsort5.c
new file mode 100644
index 0000000000000000..d3a88c30f8ffb135
--- /dev/null
+++ b/stdlib/tst-qsort5.c
@@ -0,0 +1,171 @@
+/* Adversarial test for qsort_r.
+ Copyright (C) 2023 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
+ <http://www.gnu.org/licenses/>. */
+
+/* The approach follows Douglas McIlroy, A Killer Adversary for
+ Quicksort. Software—Practice and Experience 29 (1999) 341-344.
+ Downloaded <http://www.cs.dartmouth.edu/~doug/mdmspe.pdf>
+ (2023-11-17). */
+
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <support/check.h>
+#include <support/support.h>
+
+struct context
+{
+ /* Called the gas value in the paper. This value is larger than all
+ other values (length minus one will do), so comparison with any
+ decided value has a known result. */
+ int undecided_value;
+
+ /* If comparing undecided values, one of them as to be assigned a
+ value to ensure consistency with future comparisons. This is the
+ value that will be used. Starts out at zero. */
+ int next_decided;
+
+ /* Used to trick pivot selection. Deciding the value for the last
+ seen undcided value in a decided/undecided comparison happens
+ to trick the many qsort implementations. */
+ int last_undecided_index;
+
+ /* This array contains the actually asigned values. The call to
+ qsort_r sorts a different array that contains indices into this
+ array. */
+ int *decided_values;
+};
+
+static int
+compare_opponent (const void *l1, const void *r1, void *ctx1)
+{
+ const int *l = l1;
+ const int *r = r1;
+ struct context *ctx = ctx1;
+ int rvalue = ctx->decided_values[*r];
+ int lvalue = ctx->decided_values[*l];
+
+ if (lvalue == ctx->undecided_value)
+ {
+ if (rvalue == ctx->undecided_value)
+ {
+ /* Both values are undecided. In this case, make a decision
+ for the last-used undecided value. This is tweak is very
+ specific to quicksort. */
+ if (*l == ctx->last_undecided_index)
+ {
+ ctx->decided_values[*l] = ctx->next_decided;
+ ++ctx->next_decided;
+ /* The undecided value or *r is greater. */
+ return -1;
+ }
+ else
+ {
+ ctx->decided_values[*r] = ctx->next_decided;
+ ++ctx->next_decided;
+ /* The undecided value for *l is greater. */
+ return 1;
+ }
+ }
+ else
+ {
+ ctx->last_undecided_index = *l;
+ return 1;
+ }
+ }
+ else
+ {
+ /* *l is a decided value. */
+ if (rvalue == ctx->undecided_value)
+ {
+ ctx->last_undecided_index = *r;
+ /* The undecided value for *r is greater. */
+ return -1;
+ }
+ else
+ return lvalue - rvalue;
+ }
+}
+
+/* Return a pointer to the adversarial permutation of length N. */
+static int *
+create_permutation (size_t n)
+{
+ struct context ctx =
+ {
+ .undecided_value = n - 1, /* Larger than all other values. */
+ .decided_values = xcalloc (n, sizeof (int)),
+ };
+ for (size_t i = 0; i < n; ++i)
+ ctx.decided_values[i] = ctx.undecided_value;
+ int *scratch = xcalloc (n, sizeof (int));
+ for (size_t i = 0; i < n; ++i)
+ scratch[i] = i;
+ qsort_r (scratch, n, sizeof (*scratch), compare_opponent, &ctx);
+ free (scratch);
+ return ctx.decided_values;
+}
+
+/* Callback function for qsort which counts the number of invocations
+ in *CLOSURE. */
+static int
+compare_counter (const void *l1, const void *r1, void *closure)
+{
+ const int *l = l1;
+ const int *r = r1;
+ unsigned long long int *counter = closure;
+ ++*counter;
+ return *l - *r;
+}
+
+/* Count the comparisons required for an adversarial permutation of
+ length N. */
+static unsigned long long int
+count_comparisons (size_t n)
+{
+ int *array = create_permutation (n);
+ unsigned long long int counter = 0;
+ qsort_r (array, n, sizeof (*array), compare_counter, &counter);
+ free (array);
+ return counter;
+}
+
+/* Check the scaling factor for one adversarial permutation of length
+ N, and report some statistics. */
+static void
+check_one_n (size_t n)
+{
+ unsigned long long int count = count_comparisons (n);
+ double factor = count / (n * log (count));
+ printf ("info: length %zu: %llu comparisons ~ %f * n * log (n)\n",
+ n, count, factor);
+ /* This is an arbitrary factor which is true for the current
+ implementation across a wide range of sizes. */
+ TEST_VERIFY (factor <= 4.5);
+}
+
+static int
+do_test (void)
+{
+ check_one_n (100);
+ check_one_n (1000);
+ for (int i = 1; i <= 15; ++i)
+ check_one_n (i * 10 * 1000);
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,105 @@
commit b9390ba93676c4b1e87e218af5e7e4bb596312ac
Author: Florian Weimer <fweimer@redhat.com>
Date: Mon Dec 4 06:35:56 2023 +0100
stdlib: Fix array bounds protection in insertion sort phase of qsort
The previous check did not do anything because tmp_ptr already
points before run_ptr due to the way it is initialized.
Fixes commit e4d8117b82065dc72e8df80097360e7c05a349b9
("stdlib: Avoid another self-comparison in qsort").
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/stdlib/Makefile b/stdlib/Makefile
index a1a511da37f0c18e..82d9d909890853b7 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -213,6 +213,7 @@ tests := \
tst-qsort2 \
tst-qsort3 \
tst-qsort5 \
+ tst-qsort6 \
tst-quick_exit \
tst-rand48 \
tst-rand48-2 \
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index df8d0012c759e509..3d5405705862ddf0 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -239,7 +239,7 @@ insertion_sort_qsort_partitions (void *const pbase, size_t total_elems,
while ((run_ptr += size) <= end_ptr)
{
tmp_ptr = run_ptr - size;
- while (run_ptr != tmp_ptr && cmp (run_ptr, tmp_ptr, arg) < 0)
+ while (tmp_ptr != base_ptr && cmp (run_ptr, tmp_ptr, arg) < 0)
tmp_ptr -= size;
tmp_ptr += size;
diff --git a/stdlib/tst-qsort6.c b/stdlib/tst-qsort6.c
new file mode 100644
index 0000000000000000..8ec0a6b633bc8398
--- /dev/null
+++ b/stdlib/tst-qsort6.c
@@ -0,0 +1,60 @@
+/* Test qsort with invalid comparison functions.
+ Copyright (C) 2023 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <array_length.h>
+#include <stdlib.h>
+#include <support/check.h>
+
+/* Invalid comparison function that always returns -1. */
+static int
+invalid_compare_1 (const void *a1, const void *b1)
+{
+ const int *a = a1;
+ const int *b = b1;
+ /* Check that the marker value matches, which means that we are
+ likely within the array. */
+ TEST_COMPARE (*a, 842523635);
+ TEST_COMPARE (*b, 842523635);
+ TEST_VERIFY_EXIT (*a == 842523635);
+ TEST_VERIFY_EXIT (*b == 842523635);
+ return -1;
+}
+
+/* Invalid comparison function that always returns 1. */
+static int
+invalid_compare_2 (const void *a1, const void *b1)
+{
+ const int *a = a1;
+ const int *b = b1;
+ TEST_COMPARE (*a, 842523635);
+ TEST_COMPARE (*b, 842523635);
+ TEST_VERIFY_EXIT (*a == 842523635);
+ TEST_VERIFY_EXIT (*b == 842523635);
+ return 1;
+}
+
+static int
+do_test (void)
+{
+ int array[] = {842523635, 842523635, 842523635, 842523635, 842523635};
+ qsort (array, array_length (array), sizeof (array[0]), invalid_compare_1);
+ qsort (array, array_length (array), sizeof (array[0]), invalid_compare_2);
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,876 @@
commit 709fbd3ec3595f2d1076b4fec09a739327459288
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Mon Jan 15 11:07:21 2024 -0300
stdlib: Reinstate stable mergesort implementation on qsort
The mergesort removal from qsort implementation (commit 03bf8357e8)
had the side-effect of making sorting nonstable. Although neither
POSIX nor C standard specify that qsort should be stable, it seems
that it has become an instance of Hyrum's law where multiple programs
expect it.
Also, the resulting introsort implementation is not faster than
the previous mergesort (which makes the change even less appealing).
This patch restores the previous mergesort implementation, with the
exception of machinery that checks the resulting allocation against
the _SC_PHYS_PAGES (it only adds complexity and the heuristic not
always make sense depending on the system configuration and load).
The alloca usage was replaced with a fixed-size buffer.
For the fallback mechanism, the implementation uses heapsort. It is
simpler than quicksort, and it does not suffer from adversarial
inputs. With memory overcommit, it should be rarely triggered.
The drawback is mergesort requires O(n) extra space, and since it is
allocated with malloc the function is AS-signal-unsafe. It should be
feasible to change it to use mmap, although I am not sure how urgent
it is. The heapsort is also nonstable, so programs that require a
stable sort would still be subject to this latent issue.
The tst-qsort5 is removed since it will not create quicksort adversarial
inputs with the current qsort_r implementation.
Checked on x86_64-linux-gnu and aarch64-linux-gnu.
Reviewed-by: Florian Weimer <fweimer@redhat.com>
Conflicts:
stdlib/tst-qsort5.c: Deletion had conflicts due to copyright update.
diff --git a/manual/argp.texi b/manual/argp.texi
index b77ad68285ecb732..0023441812d4e584 100644
--- a/manual/argp.texi
+++ b/manual/argp.texi
@@ -735,7 +735,7 @@ for options, bad phase of the moon, etc.
@c hol_set_group ok
@c hol_find_entry ok
@c hol_sort @mtslocale @acucorrupt
-@c qsort dup
+@c qsort dup @acucorrupt
@c hol_entry_qcmp @mtslocale
@c hol_entry_cmp @mtslocale
@c group_cmp ok
diff --git a/manual/locale.texi b/manual/locale.texi
index f6afa5dc44a2a016..1b3f97839bb5d068 100644
--- a/manual/locale.texi
+++ b/manual/locale.texi
@@ -253,7 +253,7 @@ The symbols in this section are defined in the header file @file{locale.h}.
@c calculate_head_size ok
@c __munmap ok
@c compute_hashval ok
-@c qsort dup
+@c qsort dup @acucorrupt
@c rangecmp ok
@c malloc @ascuheap @acsmem
@c strdup @ascuheap @acsmem
diff --git a/manual/search.texi b/manual/search.texi
index a550858478f7fc83..ffaadc46f51b18f9 100644
--- a/manual/search.texi
+++ b/manual/search.texi
@@ -159,7 +159,7 @@ To sort an array using an arbitrary comparison function, use the
@deftypefun void qsort (void *@var{array}, size_t @var{count}, size_t @var{size}, comparison_fn_t @var{compare})
@standards{ISO, stdlib.h}
-@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
+@safety{@prelim{}@mtsafe{}@assafe{}@acunsafe{@acucorrupt{}}}
The @code{qsort} function sorts the array @var{array}. The array
contains @var{count} elements, each of which is of size @var{size}.
@@ -199,8 +199,9 @@ Functions}):
The @code{qsort} function derives its name from the fact that it was
originally implemented using the ``quick sort'' algorithm.
-The implementation of @code{qsort} in this library is an in-place sort
-and uses a constant extra space (allocated on the stack).
+The implementation of @code{qsort} attempts to allocate auxiliary storage
+and use the merge sort algorithm, without violating C standard requirement
+that arguments passed to the comparison function point within the array.
@end deftypefun
@node Search/Sort Example
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 82d9d909890853b7..a9d91a57c08ac506 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -212,7 +212,6 @@ tests := \
tst-qsort \
tst-qsort2 \
tst-qsort3 \
- tst-qsort5 \
tst-qsort6 \
tst-quick_exit \
tst-rand48 \
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 3d5405705862ddf0..b95889047ba31193 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -20,6 +20,7 @@
Engineering a sort function; Jon Bentley and M. Douglas McIlroy;
Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993. */
+#include <errno.h>
#include <limits.h>
#include <memswap.h>
#include <stdlib.h>
@@ -33,9 +34,13 @@ enum swap_type_t
{
SWAP_WORDS_64,
SWAP_WORDS_32,
+ SWAP_VOID_ARG,
SWAP_BYTES
};
+typedef uint32_t __attribute__ ((__may_alias__)) u32_alias_t;
+typedef uint64_t __attribute__ ((__may_alias__)) u64_alias_t;
+
/* If this function returns true, elements can be safely copied using word
loads and stores. Otherwise, it might not be safe. BASE (as an integer)
must be a multiple of the word alignment. SIZE must be a multiple of
@@ -52,7 +57,6 @@ is_aligned (const void *base, size_t size, size_t wordsize)
static inline void
swap_words_64 (void * restrict a, void * restrict b, size_t n)
{
- typedef uint64_t __attribute__ ((__may_alias__)) u64_alias_t;
do
{
n -= 8;
@@ -65,7 +69,6 @@ swap_words_64 (void * restrict a, void * restrict b, size_t n)
static inline void
swap_words_32 (void * restrict a, void * restrict b, size_t n)
{
- typedef uint32_t __attribute__ ((__may_alias__)) u32_alias_t;
do
{
n -= 4;
@@ -89,43 +92,6 @@ do_swap (void * restrict a, void * restrict b, size_t size,
__memswap (a, b, size);
}
-/* Discontinue quicksort algorithm when partition gets below this size.
- This particular magic number was chosen to work best on a Sun 4/260. */
-#define MAX_THRESH 4
-
-/* Stack node declarations used to store unfulfilled partition obligations. */
-typedef struct
- {
- char *lo;
- char *hi;
- size_t depth;
- } stack_node;
-
-/* The stack needs log (total_elements) entries (we could even subtract
- log(MAX_THRESH)). Since total_elements has type size_t, we get as
- upper bound for log (total_elements):
- bits per byte (CHAR_BIT) * sizeof(size_t). */
-enum { STACK_SIZE = CHAR_BIT * sizeof (size_t) };
-
-static inline stack_node *
-push (stack_node *top, char *lo, char *hi, size_t depth)
-{
- top->lo = lo;
- top->hi = hi;
- top->depth = depth;
- return ++top;
-}
-
-static inline stack_node *
-pop (stack_node *top, char **lo, char **hi, size_t *depth)
-{
- --top;
- *lo = top->lo;
- *hi = top->hi;
- *depth = top->depth;
- return top;
-}
-
/* Establish the heap condition at index K, that is, the key at K will
not be less than either of its children, at 2 * K + 1 and 2 * K + 2
(if they exist). N is the last valid index. */
@@ -173,21 +139,35 @@ heapify (void *base, size_t size, size_t n, enum swap_type_t swap_type,
}
}
-/* A non-recursive heapsort, used on introsort implementation as a
- fallback routine with worst-case performance of O(nlog n) and
- worst-case space complexity of O(1). It sorts the array starting
- at BASE and ending at END (inclusive), with each element of SIZE
- bytes. The SWAP_TYPE is the callback function used to swap
- elements, and CMP is the function used to compare elements. */
+static enum swap_type_t
+get_swap_type (void *const pbase, size_t size)
+{
+ if ((size & (sizeof (uint32_t) - 1)) == 0
+ && ((uintptr_t) pbase) % __alignof__ (uint32_t) == 0)
+ {
+ if (size == sizeof (uint32_t))
+ return SWAP_WORDS_32;
+ else if (size == sizeof (uint64_t)
+ && ((uintptr_t) pbase) % __alignof__ (uint64_t) == 0)
+ return SWAP_WORDS_64;
+ }
+ return SWAP_BYTES;
+}
+
+
+/* A non-recursive heapsort with worst-case performance of O(nlog n) and
+ worst-case space complexity of O(1). It sorts the array starting at
+ BASE with n + 1 elements of SIZE bytes. The SWAP_TYPE is the callback
+ function used to swap elements, and CMP is the function used to compare
+ elements. */
static void
-heapsort_r (void *base, void *end, size_t size, enum swap_type_t swap_type,
- __compar_d_fn_t cmp, void *arg)
+heapsort_r (void *base, size_t n, size_t size, __compar_d_fn_t cmp, void *arg)
{
- size_t n = ((uintptr_t) end - (uintptr_t) base) / size;
if (n <= 1)
- /* Handled by insertion sort. */
return;
+ enum swap_type_t swap_type = get_swap_type (base, size);
+
/* Build the binary heap, largest value at the base[0]. */
heapify (base, size, n, swap_type, cmp, arg);
@@ -209,226 +189,226 @@ heapsort_r (void *base, void *end, size_t size, enum swap_type_t swap_type,
}
}
-static inline void
-insertion_sort_qsort_partitions (void *const pbase, size_t total_elems,
- size_t size, enum swap_type_t swap_type,
- __compar_d_fn_t cmp, void *arg)
+/* The maximum size in bytes required by mergesort that will be provided
+ through a buffer allocated in the stack. */
+#define QSORT_STACK_SIZE 1024
+
+/* Elements larger than this value will be sorted through indirect sorting
+ to minimize the need to memory swap calls. */
+#define INDIRECT_SORT_SIZE_THRES 32
+
+struct msort_param
{
- char *base_ptr = (char *) pbase;
- char *const end_ptr = &base_ptr[size * (total_elems - 1)];
- char *tmp_ptr = base_ptr;
-#define min(x, y) ((x) < (y) ? (x) : (y))
- const size_t max_thresh = MAX_THRESH * size;
- char *thresh = min(end_ptr, base_ptr + max_thresh);
- char *run_ptr;
+ size_t s;
+ enum swap_type_t var;
+ __compar_d_fn_t cmp;
+ void *arg;
+ char *t;
+};
- /* Find smallest element in first threshold and place it at the
- array's beginning. This is the smallest array element,
- and the operation speeds up insertion sort's inner loop. */
+static void
+msort_with_tmp (const struct msort_param *p, void *b, size_t n)
+{
+ char *b1, *b2;
+ size_t n1, n2;
- for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size)
- if (cmp (run_ptr, tmp_ptr, arg) < 0)
- tmp_ptr = run_ptr;
+ if (n <= 1)
+ return;
- if (tmp_ptr != base_ptr)
- do_swap (tmp_ptr, base_ptr, size, swap_type);
+ n1 = n / 2;
+ n2 = n - n1;
+ b1 = b;
+ b2 = (char *) b + (n1 * p->s);
- /* Insertion sort, running from left-hand-side up to right-hand-side. */
+ msort_with_tmp (p, b1, n1);
+ msort_with_tmp (p, b2, n2);
- run_ptr = base_ptr + size;
- while ((run_ptr += size) <= end_ptr)
+ char *tmp = p->t;
+ const size_t s = p->s;
+ __compar_d_fn_t cmp = p->cmp;
+ void *arg = p->arg;
+ switch (p->var)
{
- tmp_ptr = run_ptr - size;
- while (tmp_ptr != base_ptr && cmp (run_ptr, tmp_ptr, arg) < 0)
- tmp_ptr -= size;
-
- tmp_ptr += size;
- if (tmp_ptr != run_ptr)
- {
- char *trav;
-
- trav = run_ptr + size;
- while (--trav >= run_ptr)
- {
- char c = *trav;
- char *hi, *lo;
-
- for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo)
- *hi = *lo;
- *hi = c;
- }
- }
+ case SWAP_WORDS_32:
+ while (n1 > 0 && n2 > 0)
+ {
+ if (cmp (b1, b2, arg) <= 0)
+ {
+ *(u32_alias_t *) tmp = *(u32_alias_t *) b1;
+ b1 += sizeof (u32_alias_t);
+ --n1;
+ }
+ else
+ {
+ *(u32_alias_t *) tmp = *(u32_alias_t *) b2;
+ b2 += sizeof (u32_alias_t);
+ --n2;
+ }
+ tmp += sizeof (u32_alias_t);
+ }
+ break;
+ case SWAP_WORDS_64:
+ while (n1 > 0 && n2 > 0)
+ {
+ if (cmp (b1, b2, arg) <= 0)
+ {
+ *(u64_alias_t *) tmp = *(u64_alias_t *) b1;
+ b1 += sizeof (u64_alias_t);
+ --n1;
+ }
+ else
+ {
+ *(u64_alias_t *) tmp = *(u64_alias_t *) b2;
+ b2 += sizeof (u64_alias_t);
+ --n2;
+ }
+ tmp += sizeof (u64_alias_t);
+ }
+ break;
+ case SWAP_VOID_ARG:
+ while (n1 > 0 && n2 > 0)
+ {
+ if ((*cmp) (*(const void **) b1, *(const void **) b2, arg) <= 0)
+ {
+ *(void **) tmp = *(void **) b1;
+ b1 += sizeof (void *);
+ --n1;
+ }
+ else
+ {
+ *(void **) tmp = *(void **) b2;
+ b2 += sizeof (void *);
+ --n2;
+ }
+ tmp += sizeof (void *);
+ }
+ break;
+ default:
+ while (n1 > 0 && n2 > 0)
+ {
+ if (cmp (b1, b2, arg) <= 0)
+ {
+ tmp = (char *) __mempcpy (tmp, b1, s);
+ b1 += s;
+ --n1;
+ }
+ else
+ {
+ tmp = (char *) __mempcpy (tmp, b2, s);
+ b2 += s;
+ --n2;
+ }
+ }
+ break;
}
-}
-
-/* Order size using quicksort. This implementation incorporates
- four optimizations discussed in Sedgewick:
- 1. Non-recursive, using an explicit stack of pointer that store the
- next array partition to sort. To save time, this maximum amount
- of space required to store an array of SIZE_MAX is allocated on the
- stack. Assuming a 32-bit (64 bit) integer for size_t, this needs
- only 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes).
- Pretty cheap, actually.
-
- 2. Chose the pivot element using a median-of-three decision tree.
- This reduces the probability of selecting a bad pivot value and
- eliminates certain extraneous comparisons.
+ if (n1 > 0)
+ memcpy (tmp, b1, n1 * s);
+ memcpy (b, p->t, (n - n2) * s);
+}
- 3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leaving
- insertion sort to order the MAX_THRESH items within each partition.
- This is a big win, since insertion sort is faster for small, mostly
- sorted array segments.
+static void
+__attribute_used__
+indirect_msort_with_tmp (const struct msort_param *p, void *b, size_t n,
+ size_t s)
+{
+ /* Indirect sorting. */
+ char *ip = (char *) b;
+ void **tp = (void **) (p->t + n * sizeof (void *));
+ void **t = tp;
+ void *tmp_storage = (void *) (tp + n);
- 4. The larger of the two sub-partitions is always pushed onto the
- stack first, with the algorithm then concentrating on the
- smaller partition. This *guarantees* no more than log (total_elems)
- stack size is needed (actually O(1) in this case)! */
+ while ((void *) t < tmp_storage)
+ {
+ *t++ = ip;
+ ip += s;
+ }
+ msort_with_tmp (p, p->t + n * sizeof (void *), n);
+
+ /* tp[0] .. tp[n - 1] is now sorted, copy around entries of
+ the original array. Knuth vol. 3 (2nd ed.) exercise 5.2-10. */
+ char *kp;
+ size_t i;
+ for (i = 0, ip = (char *) b; i < n; i++, ip += s)
+ if ((kp = tp[i]) != ip)
+ {
+ size_t j = i;
+ char *jp = ip;
+ memcpy (tmp_storage, ip, s);
+
+ do
+ {
+ size_t k = (kp - (char *) b) / s;
+ tp[j] = jp;
+ memcpy (jp, kp, s);
+ j = k;
+ jp = kp;
+ kp = tp[k];
+ }
+ while (kp != ip);
+
+ tp[j] = jp;
+ memcpy (jp, tmp_storage, s);
+ }
+}
void
__qsort_r (void *const pbase, size_t total_elems, size_t size,
__compar_d_fn_t cmp, void *arg)
{
- char *base_ptr = (char *) pbase;
-
- const size_t max_thresh = MAX_THRESH * size;
-
if (total_elems <= 1)
- /* Avoid lossage with unsigned arithmetic below. */
return;
- enum swap_type_t swap_type;
- if (is_aligned (pbase, size, 8))
- swap_type = SWAP_WORDS_64;
- else if (is_aligned (pbase, size, 4))
- swap_type = SWAP_WORDS_32;
- else
- swap_type = SWAP_BYTES;
+ /* Align to the maximum size used by the swap optimization. */
+ _Alignas (uint64_t) char tmp[QSORT_STACK_SIZE];
+ size_t total_size = total_elems * size;
+ char *buf;
- /* Maximum depth before quicksort switches to heapsort. */
- size_t depth = 2 * (sizeof (size_t) * CHAR_BIT - 1
- - __builtin_clzl (total_elems));
+ if (size > INDIRECT_SORT_SIZE_THRES)
+ total_size = 2 * total_elems * sizeof (void *) + size;
- if (total_elems > MAX_THRESH)
+ if (total_size < sizeof buf)
+ buf = tmp;
+ else
{
- char *lo = base_ptr;
- char *hi = &lo[size * (total_elems - 1)];
- stack_node stack[STACK_SIZE];
- stack_node *top = push (stack, NULL, NULL, depth);
-
- while (stack < top)
- {
- if (depth == 0)
- {
- heapsort_r (lo, hi, size, swap_type, cmp, arg);
- top = pop (top, &lo, &hi, &depth);
- continue;
- }
-
- char *left_ptr;
- char *right_ptr;
-
- /* Select median value from among LO, MID, and HI. Rearrange
- LO and HI so the three values are sorted. This lowers the
- probability of picking a pathological pivot value and
- skips a comparison for both the LEFT_PTR and RIGHT_PTR in
- the while loops. */
-
- char *mid = lo + size * ((hi - lo) / size >> 1);
-
- if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)
- do_swap (mid, lo, size, swap_type);
- if ((*cmp) ((void *) hi, (void *) mid, arg) < 0)
- do_swap (mid, hi, size, swap_type);
- else
- goto jump_over;
- if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)
- do_swap (mid, lo, size, swap_type);
- jump_over:;
-
- left_ptr = lo + size;
- right_ptr = hi - size;
-
- /* Here's the famous ``collapse the walls'' section of quicksort.
- Gotta like those tight inner loops! They are the main reason
- that this algorithm runs much faster than others. */
- do
- {
- while (left_ptr != mid
- && (*cmp) ((void *) left_ptr, (void *) mid, arg) < 0)
- left_ptr += size;
-
- while (right_ptr != mid
- && (*cmp) ((void *) mid, (void *) right_ptr, arg) < 0)
- right_ptr -= size;
-
- if (left_ptr < right_ptr)
- {
- do_swap (left_ptr, right_ptr, size, swap_type);
- if (mid == left_ptr)
- mid = right_ptr;
- else if (mid == right_ptr)
- mid = left_ptr;
- left_ptr += size;
- right_ptr -= size;
- }
- else if (left_ptr == right_ptr)
- {
- left_ptr += size;
- right_ptr -= size;
- break;
- }
- }
- while (left_ptr <= right_ptr);
-
- /* Set up pointers for next iteration. First determine whether
- left and right partitions are below the threshold size. If so,
- ignore one or both. Otherwise, push the larger partition's
- bounds on the stack and continue sorting the smaller one. */
-
- if ((size_t) (right_ptr - lo) <= max_thresh)
- {
- if ((size_t) (hi - left_ptr) <= max_thresh)
- /* Ignore both small partitions. */
- {
- top = pop (top, &lo, &hi, &depth);
- --depth;
- }
- else
- {
- /* Ignore small left partition. */
- lo = left_ptr;
- --depth;
- }
- }
- else if ((size_t) (hi - left_ptr) <= max_thresh)
- /* Ignore small right partition. */
- {
- hi = right_ptr;
- --depth;
- }
- else if ((right_ptr - lo) > (hi - left_ptr))
- {
- /* Push larger left partition indices. */
- top = push (top, lo, right_ptr, depth - 1);
- lo = left_ptr;
- }
- else
- {
- /* Push larger right partition indices. */
- top = push (top, left_ptr, hi, depth - 1);
- hi = right_ptr;
- }
- }
+ int save = errno;
+ buf = malloc (total_size);
+ __set_errno (save);
+ if (buf == NULL)
+ {
+ /* Fallback to heapsort in case of memory failure. */
+ heapsort_r (pbase, total_elems - 1, size, cmp, arg);
+ return;
+ }
+ }
+
+ if (size > INDIRECT_SORT_SIZE_THRES)
+ {
+ const struct msort_param msort_param =
+ {
+ .s = sizeof (void *),
+ .cmp = cmp,
+ .arg = arg,
+ .var = SWAP_VOID_ARG,
+ .t = buf,
+ };
+ indirect_msort_with_tmp (&msort_param, pbase, total_elems, size);
+ }
+ else
+ {
+ const struct msort_param msort_param =
+ {
+ .s = size,
+ .cmp = cmp,
+ .arg = arg,
+ .var = get_swap_type (pbase, size),
+ .t = buf,
+ };
+ msort_with_tmp (&msort_param, pbase, total_elems);
}
- /* Once the BASE_PTR array is partially sorted by quicksort the rest
- is completely sorted using insertion sort, since this is efficient
- for partitions below MAX_THRESH size. BASE_PTR points to the beginning
- of the array to sort, and END_PTR points at the very last element in
- the array (*not* one beyond it!). */
- insertion_sort_qsort_partitions (pbase, total_elems, size, swap_type, cmp,
- arg);
+ if (buf != tmp)
+ free (buf);
}
libc_hidden_def (__qsort_r)
weak_alias (__qsort_r, qsort_r)
diff --git a/stdlib/tst-qsort4.c b/stdlib/tst-qsort4.c
index a7abaa1a37461666..4cf373f22e28fade 100644
--- a/stdlib/tst-qsort4.c
+++ b/stdlib/tst-qsort4.c
@@ -30,35 +30,12 @@ cmp (const void *a1, const void *b1, void *closure)
return *a - *b;
}
-/* Wrapper around heapsort_r that set ups the required variables. */
-static void
-heapsort_wrapper (void *const pbase, size_t total_elems, size_t size,
- __compar_d_fn_t cmp, void *arg)
-{
- char *base_ptr = (char *) pbase;
- char *lo = base_ptr;
- char *hi = &lo[size * (total_elems - 1)];
-
- if (total_elems <= 1)
- /* Avoid lossage with unsigned arithmetic below. */
- return;
-
- enum swap_type_t swap_type;
- if (is_aligned (pbase, size, 8))
- swap_type = SWAP_WORDS_64;
- else if (is_aligned (pbase, size, 4))
- swap_type = SWAP_WORDS_32;
- else
- swap_type = SWAP_BYTES;
- heapsort_r (lo, hi, size, swap_type, cmp, arg);
-}
-
static void
check_one_sort (signed char *array, int length)
{
signed char *copy = xmalloc (length);
memcpy (copy, array, length);
- heapsort_wrapper (copy, length, 1, cmp, NULL);
+ heapsort_r (copy, length - 1, 1, cmp, NULL);
/* Verify that the result is sorted. */
for (int i = 1; i < length; ++i)
diff --git a/stdlib/tst-qsort5.c b/stdlib/tst-qsort5.c
deleted file mode 100644
index d3a88c30f8ffb135..0000000000000000
--- a/stdlib/tst-qsort5.c
+++ /dev/null
@@ -1,171 +0,0 @@
-/* Adversarial test for qsort_r.
- Copyright (C) 2023 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
- <http://www.gnu.org/licenses/>. */
-
-/* The approach follows Douglas McIlroy, A Killer Adversary for
- Quicksort. Software—Practice and Experience 29 (1999) 341-344.
- Downloaded <http://www.cs.dartmouth.edu/~doug/mdmspe.pdf>
- (2023-11-17). */
-
-#include <math.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <support/check.h>
-#include <support/support.h>
-
-struct context
-{
- /* Called the gas value in the paper. This value is larger than all
- other values (length minus one will do), so comparison with any
- decided value has a known result. */
- int undecided_value;
-
- /* If comparing undecided values, one of them as to be assigned a
- value to ensure consistency with future comparisons. This is the
- value that will be used. Starts out at zero. */
- int next_decided;
-
- /* Used to trick pivot selection. Deciding the value for the last
- seen undcided value in a decided/undecided comparison happens
- to trick the many qsort implementations. */
- int last_undecided_index;
-
- /* This array contains the actually asigned values. The call to
- qsort_r sorts a different array that contains indices into this
- array. */
- int *decided_values;
-};
-
-static int
-compare_opponent (const void *l1, const void *r1, void *ctx1)
-{
- const int *l = l1;
- const int *r = r1;
- struct context *ctx = ctx1;
- int rvalue = ctx->decided_values[*r];
- int lvalue = ctx->decided_values[*l];
-
- if (lvalue == ctx->undecided_value)
- {
- if (rvalue == ctx->undecided_value)
- {
- /* Both values are undecided. In this case, make a decision
- for the last-used undecided value. This is tweak is very
- specific to quicksort. */
- if (*l == ctx->last_undecided_index)
- {
- ctx->decided_values[*l] = ctx->next_decided;
- ++ctx->next_decided;
- /* The undecided value or *r is greater. */
- return -1;
- }
- else
- {
- ctx->decided_values[*r] = ctx->next_decided;
- ++ctx->next_decided;
- /* The undecided value for *l is greater. */
- return 1;
- }
- }
- else
- {
- ctx->last_undecided_index = *l;
- return 1;
- }
- }
- else
- {
- /* *l is a decided value. */
- if (rvalue == ctx->undecided_value)
- {
- ctx->last_undecided_index = *r;
- /* The undecided value for *r is greater. */
- return -1;
- }
- else
- return lvalue - rvalue;
- }
-}
-
-/* Return a pointer to the adversarial permutation of length N. */
-static int *
-create_permutation (size_t n)
-{
- struct context ctx =
- {
- .undecided_value = n - 1, /* Larger than all other values. */
- .decided_values = xcalloc (n, sizeof (int)),
- };
- for (size_t i = 0; i < n; ++i)
- ctx.decided_values[i] = ctx.undecided_value;
- int *scratch = xcalloc (n, sizeof (int));
- for (size_t i = 0; i < n; ++i)
- scratch[i] = i;
- qsort_r (scratch, n, sizeof (*scratch), compare_opponent, &ctx);
- free (scratch);
- return ctx.decided_values;
-}
-
-/* Callback function for qsort which counts the number of invocations
- in *CLOSURE. */
-static int
-compare_counter (const void *l1, const void *r1, void *closure)
-{
- const int *l = l1;
- const int *r = r1;
- unsigned long long int *counter = closure;
- ++*counter;
- return *l - *r;
-}
-
-/* Count the comparisons required for an adversarial permutation of
- length N. */
-static unsigned long long int
-count_comparisons (size_t n)
-{
- int *array = create_permutation (n);
- unsigned long long int counter = 0;
- qsort_r (array, n, sizeof (*array), compare_counter, &counter);
- free (array);
- return counter;
-}
-
-/* Check the scaling factor for one adversarial permutation of length
- N, and report some statistics. */
-static void
-check_one_n (size_t n)
-{
- unsigned long long int count = count_comparisons (n);
- double factor = count / (n * log (count));
- printf ("info: length %zu: %llu comparisons ~ %f * n * log (n)\n",
- n, count, factor);
- /* This is an arbitrary factor which is true for the current
- implementation across a wide range of sizes. */
- TEST_VERIFY (factor <= 4.5);
-}
-
-static int
-do_test (void)
-{
- check_one_n (100);
- check_one_n (1000);
- for (int i = 1; i <= 15; ++i)
- check_one_n (i * 10 * 1000);
- return 0;
-}
-
-#include <support/test-driver.c>

View File

@ -0,0 +1,27 @@
commit 74d2731a5fb2676b64092bc25e7f193db1b17b2b
Author: Kuan-Wei Chiu <visitorckw@gmail.com>
Date: Tue Jan 16 10:16:56 2024 +0800
stdlib: Fix heapsort for cases with exactly two elements
When malloc fails to allocate a buffer and falls back to heapsort, the
current heapsort implementation does not perform sorting when there are
exactly two elements. Heapsort is now skipped only when there is
exactly one element.
Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index b95889047ba31193..7b6c7e1f79974157 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -163,7 +163,7 @@ get_swap_type (void *const pbase, size_t size)
static void
heapsort_r (void *base, size_t n, size_t size, __compar_d_fn_t cmp, void *arg)
{
- if (n <= 1)
+ if (n == 0)
return;
enum swap_type_t swap_type = get_swap_type (base, size);

View File

@ -0,0 +1,27 @@
commit 1bb28b7b4f01709b841c86850e1bb83b554feafe
Author: Kuan-Wei Chiu <visitorckw@gmail.com>
Date: Tue Jan 16 10:16:57 2024 +0800
stdlib: Verify heapsort for two-element cases
Adjust the testing approach to start from scenarios with only 2
elements, as insertion sort no longer handles such cases.
Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/stdlib/tst-qsort4.c b/stdlib/tst-qsort4.c
index 4cf373f22e28fade..7909793d9eb3edc7 100644
--- a/stdlib/tst-qsort4.c
+++ b/stdlib/tst-qsort4.c
@@ -96,9 +96,7 @@ do_test (void)
check_one_sort ((signed char[16]) {15, 3, 4, 2, 1, 0, 8, 7, 6, 5, 14,
13, 12, 11, 10, 9}, 16);
- /* Array lengths 2 and less are not handled by heapsort_r and
- deferred to insertion sort. */
- for (int i = 3; i <= 8; ++i)
+ for (int i = 2; i <= 8; ++i)
{
signed char *buf = xmalloc (i);
check_combinations (i, buf, 0);

View File

@ -0,0 +1,32 @@
commit 31bd548650673e8b5ae1a31f1c596ff8305a5d4c
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Wed Jan 17 08:08:01 2024 -0300
stdlib: Remove unused is_aligned function from qsort.c
Checked on x86_64-linux-gnu.
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 7b6c7e1f79974157..8db8a81d182dd1fc 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -41,19 +41,6 @@ enum swap_type_t
typedef uint32_t __attribute__ ((__may_alias__)) u32_alias_t;
typedef uint64_t __attribute__ ((__may_alias__)) u64_alias_t;
-/* If this function returns true, elements can be safely copied using word
- loads and stores. Otherwise, it might not be safe. BASE (as an integer)
- must be a multiple of the word alignment. SIZE must be a multiple of
- WORDSIZE. Since WORDSIZE must be a multiple of the word alignment, and
- WORDSIZE is a power of two on all supported platforms, this function for
- speed merely checks that BASE and SIZE are both multiples of the word
- size. */
-static inline bool
-is_aligned (const void *base, size_t size, size_t wordsize)
-{
- return (((uintptr_t) base | size) & (wordsize - 1)) == 0;
-}
-
static inline void
swap_words_64 (void * restrict a, void * restrict b, size_t n)
{

View File

@ -0,0 +1,51 @@
commit dfa3394a605c8f6f25e4f827789bc89eca1d206c
Author: Xi Ruoyao <xry111@xry111.site>
Date: Tue Jan 23 04:29:18 2024 +0800
qsort: Fix a typo causing unnecessary malloc/free (BZ 31276)
In qsort_r we allocate a buffer sized QSORT_STACK_SIZE (1024) on stack
and we intend to use it if all elements can fit into it. But there is a
typo:
if (total_size < sizeof buf)
buf = tmp;
else
/* allocate a buffer on heap and use it ... */
Here "buf" is a pointer, thus sizeof buf is just 4 or 8, instead of
1024. There is also a minor issue that we should use "<=" instead of
"<".
This bug is detected debugging some strange heap corruption running the
Ruby-3.3.0 test suite (on an experimental Linux From Scratch build using
Binutils-2.41.90 and Glibc trunk, and also Fedora Rawhide [1]). It
seems Ruby is doing some wild "optimization" by jumping into somewhere
in qsort_r instead of calling it normally, resulting in a double free of
buf if we allocate it on heap. The issue can be reproduced
deterministically with:
LD_PRELOAD=/usr/lib/libc_malloc_debug.so MALLOC_CHECK_=3 \
LD_LIBRARY_PATH=. ./ruby test/runner.rb test/ruby/test_enum.rb
in Ruby-3.3.0 tree after building it. This change would hide the issue
for Ruby, but Ruby is likely still buggy (if using this "optimization"
sorting larger arrays).
[1]:https://kojipkgs.fedoraproject.org/work/tasks/9729/111889729/build.log
Signed-off-by: Xi Ruoyao <xry111@xry111.site>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 8db8a81d182dd1fc..2cdd5c1fe790f55c 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -354,7 +354,7 @@ __qsort_r (void *const pbase, size_t total_elems, size_t size,
if (size > INDIRECT_SORT_SIZE_THRES)
total_size = 2 * total_elems * sizeof (void *) + size;
- if (total_size < sizeof buf)
+ if (total_size <= sizeof tmp)
buf = tmp;
else
{

View File

@ -0,0 +1,344 @@
commit d275970ab56f8ba6a3ca598aba75db4daabe5924
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Fri Apr 8 09:57:57 2022 -0300
stdlib: Reflow and sort most variable assignments
diff --git a/stdlib/Makefile b/stdlib/Makefile
index fe43bec0f9d581d5..03f8478c64408ed3 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -22,49 +22,145 @@ subdir := stdlib
include ../Makeconfig
-headers := stdlib.h bits/stdlib.h bits/stdlib-ldbl.h bits/stdlib-float.h \
- monetary.h bits/monetary-ldbl.h \
- inttypes.h stdint.h bits/wordsize.h bits/timesize.h \
- errno.h sys/errno.h bits/errno.h bits/types/error_t.h \
- ucontext.h sys/ucontext.h bits/indirect-return.h \
- alloca.h fmtmsg.h \
- bits/stdlib-bsearch.h sys/random.h bits/stdint-intn.h \
- bits/stdint-uintn.h bits/time64.h \
-
-routines := \
- atof atoi atol atoll \
- abort \
- bsearch qsort msort \
- getenv putenv setenv secure-getenv \
- exit on_exit atexit cxa_atexit cxa_finalize old_atexit \
- quick_exit at_quick_exit cxa_at_quick_exit cxa_thread_atexit_impl \
- abs labs llabs \
- div ldiv lldiv \
- mblen mbstowcs mbtowc wcstombs wctomb \
- random random_r rand rand_r \
- drand48 erand48 lrand48 nrand48 mrand48 jrand48 \
- srand48 seed48 lcong48 \
- drand48_r erand48_r lrand48_r nrand48_r mrand48_r jrand48_r \
- srand48_r seed48_r lcong48_r \
- drand48-iter getrandom getentropy \
- strfromf strfromd strfroml \
- strtol strtoul strtoll strtoull \
- strtol_l strtoul_l strtoll_l strtoull_l \
- strtof strtod strtold \
- strtof_l strtod_l strtold_l \
- strtof_nan strtod_nan strtold_nan \
- system canonicalize \
- a64l l64a \
- rpmatch strfmon strfmon_l getsubopt xpg_basename fmtmsg \
- getcontext setcontext makecontext swapcontext
-aux = grouping groupingwc tens_in_limb
+headers := \
+ alloca.h \
+ bits/errno.h \
+ bits/indirect-return.h \
+ bits/monetary-ldbl.h \
+ bits/stdint-intn.h \
+ bits/stdint-uintn.h \
+ bits/stdlib-bsearch.h \
+ bits/stdlib-float.h \
+ bits/stdlib.h \
+ bits/stdlib-ldbl.h \
+ bits/time64.h \
+ bits/timesize.h \
+ bits/types/error_t.h \
+ bits/wordsize.h \
+ errno.h \
+ fmtmsg.h \
+ inttypes.h \
+ monetary.h \
+ stdint.h \
+ stdlib.h \
+ sys/errno.h \
+ sys/random.h \
+ sys/ucontext.h \
+ ucontext.h \
+ # headers
+
+routines := \
+ a64l \
+ abort \
+ abs \
+ at_quick_exit \
+ atof \
+ atoi \
+ atol\
+ atoll \
+ bsearch \
+ canonicalize \
+ cxa_at_quick_exit \
+ cxa_atexit \
+ cxa_finalize \
+ cxa_thread_atexit_impl \
+ div \
+ drand48 \
+ drand48-iter \
+ drand48_r \
+ erand48 \
+ erand48_r \
+ exit \
+ fmtmsg \
+ getcontext \
+ getentropy \
+ getenv \
+ getrandom \
+ getsubopt \
+ jrand48 \
+ jrand48_r \
+ l64a \
+ labs \
+ lcong48 \
+ lcong48_r \
+ ldiv \
+ llabs \
+ lldiv \
+ lrand48 \
+ lrand48_r \
+ makecontext \
+ mblen \
+ mbstowcs \
+ mbtowc \
+ mrand48 \
+ mrand48_r \
+ msort \
+ nrand48 \
+ nrand48_r \
+ old_atexit \
+ on_exit atexit \
+ putenv \
+ qsort \
+ quick_exit \
+ rand \
+ rand_r \
+ random \
+ random_r \
+ rpmatch \
+ secure-getenv \
+ seed48 \
+ seed48_r \
+ setcontext \
+ setenv \
+ srand48 \
+ srand48_r \
+ strfmon \
+ strfmon_l \
+ strfromd \
+ strfromf \
+ strfroml \
+ strtod \
+ strtod_l \
+ strtod_nan \
+ strtof \
+ strtof_l \
+ strtof_nan \
+ strtol \
+ strtol_l \
+ strtold \
+ strtold_l \
+ strtold_nan \
+ strtoll \
+ strtoll_l \
+ strtoul \
+ strtoul_l \
+ strtoull \
+ strtoull_l \
+ swapcontext \
+ system \
+ wcstombs \
+ wctomb \
+ xpg_basename \
+ # routines
+
+aux = \
+ grouping \
+ groupingwc \
+ tens_in_limb \
+ # aux
# These routines will be omitted from the libc shared object.
# Instead the static object files will be included in a special archive
# linked against when the shared library will be used.
-static-only-routines = atexit at_quick_exit
+static-only-routines = \
+ atexit \
+ at_quick_exit \
+ # static-only-routines
+
+test-srcs := \
+ tst-fmtmsg \
+ #test-srcs
-test-srcs := tst-fmtmsg
tests := \
bug-fmtmsg1 \
bug-getcontext \
@@ -155,15 +251,29 @@ tests := \
tst-width \
tst-width-stdint \
tst-xpg-basename \
-# tests
+ # tests
+
+tests-internal := \
+ tst-strtod1i \
+ tst-strtod3 \
+ tst-strtod4 \
+ tst-strtod5i \
+ tst-tls-atexit \
+ tst-tls-atexit-nodelete \
+ # tests-internal
+
+tests-static := \
+ tst-secure-getenv \
+ # tests-static
-tests-internal := tst-strtod1i tst-strtod3 tst-strtod4 tst-strtod5i \
- tst-tls-atexit tst-tls-atexit-nodelete
-tests-static := tst-secure-getenv
-tests-container := tst-system
+tests-container := \
+ tst-system \
+ #tests-container
ifeq ($(build-hardcoded-path-in-tests),yes)
-tests += tst-empty-env
+tests += \
+ tst-empty-env \
+ # tests
endif
LDLIBS-test-atexit-race = $(shared-thread-library)
@@ -188,30 +298,76 @@ CFLAGS-tst-thread-quick_exit.o = -std=c++11
LDLIBS-tst-thread-quick_exit = -lstdc++
$(objpfx)tst-thread-quick_exit: $(shared-thread-library)
else
-tests-unsupported += tst-quick_exit tst-thread-quick_exit
+tests-unsupported += \
+ tst-quick_exit \
+ tst-thread-quick_exit \
+ # tests-unsupported
endif
-modules-names = tst-tls-atexit-lib test-dlclose-exit-race-helper
+modules-names = \
+ test-dlclose-exit-race-helper \
+ tst-tls-atexit-lib \
+ # modules-names
extra-test-objs += $(addsuffix .os, $(modules-names))
ifeq ($(build-shared),yes)
-tests += tst-putenv
+tests += \
+ tst-putenv \
+ # tests
endif
# Several mpn functions from GNU MP are used by the strtod function.
-mpn-routines := inlines add_n addmul_1 cmp divmod_1 divrem udiv_qrnnd \
- lshift rshift mod_1 mul mul_1 mul_n sub_n submul_1
-mpn-headers = longlong.h gmp.h gmp-impl.h gmp-mparam.h asm-syntax.h
-
-routines := $(strip $(routines) $(mpn-routines)) \
- dbl2mpn ldbl2mpn \
- mpn2flt mpn2dbl mpn2ldbl
-aux += fpioconst mp_clz_tab
-
-tests-extras += tst-putenvmod
-extra-test-objs += tst-putenvmod.os
-
-generated += isomac isomac.out tst-putenvmod.so
+mpn-routines := \
+ add_n \
+ addmul_1 \
+ cmp \
+ divmod_1 \
+ divrem \
+ inlines \
+ lshift \
+ mod_1 \
+ mul \
+ mul_1 \
+ mul_n \
+ rshift \
+ sub_n \
+ submul_1 \
+ udiv_qrnnd \
+ # mpn-routines
+mpn-headers = \
+ asm-syntax.h \
+ gmp-impl.h \
+ gmp-mparam.h \
+ gmp.h \
+ longlong.h \
+ # mpn-headers
+
+routines := \
+ $(strip $(routines) $(mpn-routines)) \
+ dbl2mpn \
+ ldbl2mpn \
+ mpn2dbl \
+ mpn2flt \
+ mpn2ldbl \
+ # routines
+aux += \
+ fpioconst \
+ mp_clz_tab \
+ # aux
+
+tests-extras += \
+ tst-putenvmod \
+ # tests-extras
+
+extra-test-objs += \
+ tst-putenvmod.os \
+ # extra-test-objs
+
+generated += \
+ isomac \
+ isomac.out \
+ tst-putenvmod.so \
+ # generated
CFLAGS-bsearch.c += $(uses-callbacks)
CFLAGS-msort.c += $(uses-callbacks)
@@ -247,9 +403,17 @@ endif
include ../Rules
ifeq ($(run-built-tests),yes)
-LOCALES := cs_CZ.UTF-8 de_DE.UTF-8 en_US.ISO-8859-1 tr_TR.UTF-8 \
- tr_TR.ISO-8859-9 tg_TJ.UTF-8 hr_HR.UTF-8 hi_IN.UTF-8 \
- el_GR.UTF-8
+LOCALES := \
+ cs_CZ.UTF-8 \
+ de_DE.UTF-8 \
+ el_GR.UTF-8 \
+ en_US.ISO-8859-1 \
+ hi_IN.UTF-8 \
+ hr_HR.UTF-8 \
+ tg_TJ.UTF-8 \
+ tr_TR.ISO-8859-9 \
+ tr_TR.UTF-8 \
+ # LOCALES
include ../gen-locales.mk
$(objpfx)bug-strtod2.out: $(gen-locales)

View File

@ -0,0 +1,65 @@
commit e7b90e6e605cf236d4bd79e4930cd6a46f9932c7
Author: Paul Eggert <eggert@cs.ucla.edu>
Date: Thu Feb 1 11:52:46 2024 -0800
stdlib: fix qsort example in manual
* manual/search.texi (Comparison Functions, Array Sort Function):
Sort an array of long ints, not doubles, to avoid hassles
with NaNs.
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
diff --git a/manual/search.texi b/manual/search.texi
index ffaadc46f51b18f9..db577a5332651c36 100644
--- a/manual/search.texi
+++ b/manual/search.texi
@@ -35,19 +35,22 @@ second, zero if they are ``equal'', and positive if the first argument
is ``greater''.
Here is an example of a comparison function which works with an array of
-numbers of type @code{double}:
+numbers of type @code{long int}:
@smallexample
int
-compare_doubles (const void *a, const void *b)
+compare_long_ints (const void *a, const void *b)
@{
- const double *da = (const double *) a;
- const double *db = (const double *) b;
+ const long int *la = a;
+ const long int *lb = b;
- return (*da > *db) - (*da < *db);
+ return (*la > *lb) - (*la < *lb);
@}
@end smallexample
+(The code would have to be more complicated for an array of @code{double},
+to handle NaNs correctly.)
+
The header file @file{stdlib.h} defines a name for the data type of
comparison functions. This type is a GNU extension.
@@ -183,16 +186,16 @@ in the array before making some comparisons. The only way to perform
a stable sort with @code{qsort} is to first augment the objects with a
monotonic counter of some kind.
-Here is a simple example of sorting an array of doubles in numerical
+Here is a simple example of sorting an array of @code{long int} in numerical
order, using the comparison function defined above (@pxref{Comparison
Functions}):
@smallexample
@{
- double *array;
- int size;
+ long int *array;
+ size_t nmemb;
@dots{}
- qsort (array, size, sizeof (double), compare_doubles);
+ qsort (array, nmemb, sizeof *array, compare_long_ints);
@}
@end smallexample

View File

@ -0,0 +1,142 @@
commit 57581acd9559217e859fdac693145ce6399f4d70
Author: Paul Eggert <eggert@cs.ucla.edu>
Date: Sat Apr 6 08:44:01 2024 -0700
Fix bsearch, qsort doc to match POSIX better
* manual/search.texi (Array Search Function):
Correct the statement about lfinds mean runtime:
it is proportional to a number (not that number),
and this is true only if random elements are searched for.
Relax the constraint on bsearchs array argument:
POSIX says it need not be sorted, only partially sorted.
Say that the first arg passed to bsearchs comparison function
is the key, and the second arg is an array element, as
POSIX requires. For bsearch and qsort, say that the
comparison function should not alter the array, as POSIX
requires. For qsort, say that the comparison function
must define a total order, as POSIX requires, that
it should not depend on element addresses, that
the original array index can be used for stable sorts,
and that if qsort still works if memory allocation fails.
Be more consistent in calling the array elements
“elements” rather than “objects”.
Co-authored-by: Zack Weinberg <zack@owlfolio.org>
diff --git a/manual/search.texi b/manual/search.texi
index db577a5332651c36..cb08c494092ef77f 100644
--- a/manual/search.texi
+++ b/manual/search.texi
@@ -84,8 +84,9 @@ The return value is a pointer to the matching element in the array
starting at @var{base} if it is found. If no matching element is
available @code{NULL} is returned.
-The mean runtime of this function is @code{*@var{nmemb}}/2. This
-function should only be used if elements often get added to or deleted from
+The mean runtime of this function is proportional to @code{*@var{nmemb}/2},
+assuming random elements of the array are searched for. This
+function should be used only if elements often get added to or deleted from
the array in which case it might not be useful to sort the array before
searching.
@end deftypefun
@@ -122,26 +123,34 @@ bytes. If one is sure the element is in the array it is better to use
calling @code{lsearch}.
@end deftypefun
-To search a sorted array for an element matching the key, use the
-@code{bsearch} function. The prototype for this function is in
+To search a sorted or partially sorted array for an element matching the key,
+use the @code{bsearch} function. The prototype for this function is in
the header file @file{stdlib.h}.
@pindex stdlib.h
@deftypefun {void *} bsearch (const void *@var{key}, const void *@var{array}, size_t @var{count}, size_t @var{size}, comparison_fn_t @var{compare})
@standards{ISO, stdlib.h}
@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
-The @code{bsearch} function searches the sorted array @var{array} for an object
+The @code{bsearch} function searches @var{array} for an element
that is equivalent to @var{key}. The array contains @var{count} elements,
each of which is of size @var{size} bytes.
The @var{compare} function is used to perform the comparison. This
-function is called with two pointer arguments and should return an
+function is called with arguments that point to the key and to an
+array element, in that order, and should return an
integer less than, equal to, or greater than zero corresponding to
-whether its first argument is considered less than, equal to, or greater
-than its second argument. The elements of the @var{array} must already
-be sorted in ascending order according to this comparison function.
-
-The return value is a pointer to the matching array element, or a null
+whether the key is considered less than, equal to, or greater than
+the array element. The function should not alter the array's contents,
+and the same array element should always compare the same way with the key.
+
+Although the array need not be completely sorted, it should be
+partially sorted with respect to @var{key}. That is, the array should
+begin with elements that compare less than @var{key}, followed by
+elements that compare equal to @var{key}, and ending with elements
+that compare greater than @var{key}. Any or all of these element
+sequences can be empty.
+
+The return value is a pointer to a matching array element, or a null
pointer if no match is found. If the array contains more than one element
that matches, the one that is returned is unspecified.
@@ -171,20 +180,22 @@ array elements. This function is called with two pointer arguments and
should return an integer less than, equal to, or greater than zero
corresponding to whether its first argument is considered less than,
equal to, or greater than its second argument.
+The function must not alter the array's contents, and must define a
+total ordering on the array elements, including any unusual values
+such as floating-point NaN (@pxref{Infinity and NaN}).
+Because the sorting process can move elements,
+the function's return value must not depend on the element addresses
+or the relative positions of elements within the array,
+as these are meaningless while @code{qsort} is running.
@cindex stable sorting
-@strong{Warning:} If two objects compare as equal, their order after
+@strong{Warning:} If two elements compare equal, their order after
sorting is unpredictable. That is to say, the sorting is not stable.
This can make a difference when the comparison considers only part of
-the elements. Two elements with the same sort key may differ in other
-respects.
-
-Although the object addresses passed to the comparison function lie
-within the array, they need not correspond with the original locations
-of those objects because the sorting algorithm may swap around objects
-in the array before making some comparisons. The only way to perform
-a stable sort with @code{qsort} is to first augment the objects with a
-monotonic counter of some kind.
+the elements and two elements that compare equal may differ in other
+respects. To ensure a stable sort in this situation, you can augment
+each element with an appropriate tie-breaking value, such as its
+original array index.
Here is a simple example of sorting an array of @code{long int} in numerical
order, using the comparison function defined above (@pxref{Comparison
@@ -202,18 +213,19 @@ Functions}):
The @code{qsort} function derives its name from the fact that it was
originally implemented using the ``quick sort'' algorithm.
-The implementation of @code{qsort} attempts to allocate auxiliary storage
+The implementation of @code{qsort} attempts to allocate auxiliary memory
and use the merge sort algorithm, without violating C standard requirement
that arguments passed to the comparison function point within the array.
+If the memory allocation fails, @code{qsort} resorts to a slower algorithm.
@end deftypefun
@node Search/Sort Example
@section Searching and Sorting Example
Here is an example showing the use of @code{qsort} and @code{bsearch}
-with an array of structures. The objects in the array are sorted
+with an array of structures. The elements of the array are sorted
by comparing their @code{name} fields with the @code{strcmp} function.
-Then, we can look up individual objects based on their names.
+Then, we can look up individual elements based on their names.
@comment This example is dedicated to the memory of Jim Henson. RIP.
@smallexample

View File

@ -0,0 +1,26 @@
commit 7eed691cc2b6c5dbb6066ee1251606a744c7f05c
Author: Arjun Shankar <arjun@redhat.com>
Date: Wed Jul 2 17:11:32 2025 +0200
stdlib/Makefile: Remove deleted test's libm dependency
tst-qsort5 was deleted in 709fbd3ec3595f2d1076b4fec09a739327459288.
Therefore remove its redundant libm dependency.
Reviewed-by: Florian Weimer <fweimer@redhat.com>
Conflicts:
stdlib/Makefile: Context line mismatch due to missing tests.
diff --git a/stdlib/Makefile b/stdlib/Makefile
index a9d91a57c08ac506..e517e306b868c432 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -484,7 +484,6 @@ $(objpfx)tst-setcontext3.out: tst-setcontext3.sh $(objpfx)tst-setcontext3
$(common-objpfx)stdlib/; \
$(evaluate-test)
-$(objpfx)tst-qsort5: $(libm)
$(objpfx)tst-getenv-signal: $(shared-thread-library)
$(objpfx)tst-getenv-thread: $(shared-thread-library)
$(objpfx)tst-getenv-unsetenv: $(shared-thread-library)

View File

@ -0,0 +1,286 @@
commit fccf38c51746e0817c2409bb361398f9465e0760
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:45 2023 -0300
string: Add internal memswap implementation
The prototype is:
void __memswap (void *restrict p1, void *restrict p2, size_t n)
The function swaps the content of two memory blocks P1 and P2 of
len N. Memory overlap is NOT handled.
It will be used on qsort optimization.
Checked on x86_64-linux-gnu and aarch64-linux-gnu.
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
diff --git a/string/Makefile b/string/Makefile
index 3e4331113f08424c..3ebf7597aad75bfe 100644
--- a/string/Makefile
+++ b/string/Makefile
@@ -66,6 +66,18 @@ tests := tester inl-tester noinl-tester testcopy test-ffs \
test-sig_np tst-strerror-fail \
test-strdup test-strndup
+tests-static-internal := \
+ test-memswap \
+# tests-static-internal
+
+tests-internal := \
+ $(tests-static-internal) \
+ # tests-internal
+
+tests-static := \
+ $(tests-static-internal) \
+ # tests-static
+
# Both tests require the .mo translation files generated by msgfmt.
tests-translation := tst-strsignal \
tst-strerror
diff --git a/string/test-memswap.c b/string/test-memswap.c
new file mode 100644
index 0000000000000000..162beb91e3e96c23
--- /dev/null
+++ b/string/test-memswap.c
@@ -0,0 +1,192 @@
+/* Test and measure memcpy functions.
+ Copyright (C) 2023 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 <string.h>
+#include <support/check.h>
+#include <memswap.h>
+
+#define TEST_MAIN
+#define BUF1PAGES 3
+#include "test-string.h"
+
+static unsigned char *ref1;
+static unsigned char *ref2;
+
+static void
+do_one_test (unsigned char *p1, unsigned char *ref1, unsigned char *p2,
+ unsigned char *ref2, size_t len)
+{
+ __memswap (p1, p2, len);
+
+ TEST_COMPARE_BLOB (p1, len, ref2, len);
+ TEST_COMPARE_BLOB (p2, len, ref1, len);
+}
+
+static inline void
+do_test (size_t align1, size_t align2, size_t len)
+{
+ align1 &= page_size;
+ if (align1 + len >= page_size)
+ return;
+
+ align2 &= page_size;
+ if (align2 + len >= page_size)
+ return;
+
+ unsigned char *p1 = buf1 + align1;
+ unsigned char *p2 = buf2 + align2;
+ for (size_t repeats = 0; repeats < 2; ++repeats)
+ {
+ size_t i, j;
+ for (i = 0, j = 1; i < len; i++, j += 23)
+ {
+ ref1[i] = p1[i] = j;
+ ref2[i] = p2[i] = UCHAR_MAX - j;
+ }
+
+ do_one_test (p1, ref1, p2, ref2, len);
+ }
+}
+
+static void
+do_random_tests (void)
+{
+ for (size_t n = 0; n < ITERATIONS; n++)
+ {
+ size_t len, size, size1, size2, align1, align2;
+
+ if (n == 0)
+ {
+ len = getpagesize ();
+ size = len + 512;
+ size1 = size;
+ size2 = size;
+ align1 = 512;
+ align2 = 512;
+ }
+ else
+ {
+ if ((random () & 255) == 0)
+ size = 65536;
+ else
+ size = 768;
+ if (size > page_size)
+ size = page_size;
+ size1 = size;
+ size2 = size;
+ size_t i = random ();
+ if (i & 3)
+ size -= 256;
+ if (i & 1)
+ size1 -= 256;
+ if (i & 2)
+ size2 -= 256;
+ if (i & 4)
+ {
+ len = random () % size;
+ align1 = size1 - len - (random () & 31);
+ align2 = size2 - len - (random () & 31);
+ if (align1 > size1)
+ align1 = 0;
+ if (align2 > size2)
+ align2 = 0;
+ }
+ else
+ {
+ align1 = random () & 63;
+ align2 = random () & 63;
+ len = random () % size;
+ if (align1 + len > size1)
+ align1 = size1 - len;
+ if (align2 + len > size2)
+ align2 = size2 - len;
+ }
+ }
+ unsigned char *p1 = buf1 + page_size - size1;
+ unsigned char *p2 = buf2 + page_size - size2;
+ size_t j = align1 + len + 256;
+ if (j > size1)
+ j = size1;
+ for (size_t i = 0; i < j; ++i)
+ ref1[i] = p1[i] = random () & 255;
+
+ j = align2 + len + 256;
+ if (j > size2)
+ j = size2;
+
+ for (size_t i = 0; i < j; ++i)
+ ref2[i] = p2[i] = random () & 255;
+
+ do_one_test (p1 + align1, ref1 + align1, p2 + align2, ref2 + align2, len);
+ }
+}
+
+static int
+test_main (void)
+{
+ test_init ();
+ /* Use the start of buf1 for reference buffers. */
+ ref1 = buf1;
+ ref2 = buf1 + page_size;
+ buf1 = ref2 + page_size;
+
+ printf ("%23s", "");
+ printf ("\t__memswap\n");
+
+ for (size_t i = 0; i < 18; ++i)
+ {
+ do_test (0, 0, 1 << i);
+ do_test (i, 0, 1 << i);
+ do_test (0, i, 1 << i);
+ do_test (i, i, 1 << i);
+ }
+
+ for (size_t i = 0; i < 32; ++i)
+ {
+ do_test (0, 0, i);
+ do_test (i, 0, i);
+ do_test (0, i, i);
+ do_test (i, i, i);
+ }
+
+ for (size_t i = 3; i < 32; ++i)
+ {
+ if ((i & (i - 1)) == 0)
+ continue;
+ do_test (0, 0, 16 * i);
+ do_test (i, 0, 16 * i);
+ do_test (0, i, 16 * i);
+ do_test (i, i, 16 * i);
+ }
+
+ for (size_t i = 19; i <= 25; ++i)
+ {
+ do_test (255, 0, 1 << i);
+ do_test (0, 4000, 1 << i);
+ do_test (0, 255, i);
+ do_test (0, 4000, i);
+ }
+
+ do_test (0, 0, getpagesize ());
+
+ do_random_tests ();
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/generic/memswap.h b/sysdeps/generic/memswap.h
new file mode 100644
index 0000000000000000..f09dae1ebbc2ec0f
--- /dev/null
+++ b/sysdeps/generic/memswap.h
@@ -0,0 +1,41 @@
+/* Swap the content of two memory blocks, overlap is NOT handled.
+ Copyright (C) 2023 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 <string.h>
+
+static inline void
+__memswap (void *__restrict p1, void *__restrict p2, size_t n)
+{
+ /* Use multiple small memcpys with constant size to enable inlining on most
+ targets. */
+ enum { SWAP_GENERIC_SIZE = 32 };
+ unsigned char tmp[SWAP_GENERIC_SIZE];
+ while (n > SWAP_GENERIC_SIZE)
+ {
+ memcpy (tmp, p1, SWAP_GENERIC_SIZE);
+ p1 = __mempcpy (p1, p2, SWAP_GENERIC_SIZE);
+ p2 = __mempcpy (p2, tmp, SWAP_GENERIC_SIZE);
+ n -= SWAP_GENERIC_SIZE;
+ }
+ while (n > 0)
+ {
+ unsigned char t = ((unsigned char *)p1)[--n];
+ ((unsigned char *)p1)[n] = ((unsigned char *)p2)[n];
+ ((unsigned char *)p2)[n] = t;
+ }
+}

View File

@ -0,0 +1,157 @@
commit 21d30c774c7f9f5878f0bf9438736c702b0a58a3
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:46 2023 -0300
stdlib: Optimization qsort{_r} swap implementation
The optimization takes in consideration both the most common elements
are either 32 or 64 bit in size and inputs are aligned to the word
boundary. This is similar to what msort does.
For large buffer the swap operation uses memcpy/mempcpy with a
small fixed size buffer (so compiler might inline the operations).
Checked on x86_64-linux-gnu.
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 23f2d283147073ac..59b220ba1c375ca3 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -22,22 +22,73 @@
#include <alloca.h>
#include <limits.h>
+#include <memswap.h>
#include <stdlib.h>
#include <string.h>
+#include <stdbool.h>
-/* Byte-wise swap two items of size SIZE. */
-#define SWAP(a, b, size) \
- do \
- { \
- size_t __size = (size); \
- char *__a = (a), *__b = (b); \
- do \
- { \
- char __tmp = *__a; \
- *__a++ = *__b; \
- *__b++ = __tmp; \
- } while (--__size > 0); \
- } while (0)
+/* Swap SIZE bytes between addresses A and B. These helpers are provided
+ along the generic one as an optimization. */
+
+enum swap_type_t
+ {
+ SWAP_WORDS_64,
+ SWAP_WORDS_32,
+ SWAP_BYTES
+ };
+
+/* If this function returns true, elements can be safely copied using word
+ loads and stores. Otherwise, it might not be safe. BASE (as an integer)
+ must be a multiple of the word alignment. SIZE must be a multiple of
+ WORDSIZE. Since WORDSIZE must be a multiple of the word alignment, and
+ WORDSIZE is a power of two on all supported platforms, this function for
+ speed merely checks that BASE and SIZE are both multiples of the word
+ size. */
+static inline bool
+is_aligned (const void *base, size_t size, size_t wordsize)
+{
+ return (((uintptr_t) base | size) & (wordsize - 1)) == 0;
+}
+
+static inline void
+swap_words_64 (void * restrict a, void * restrict b, size_t n)
+{
+ typedef uint64_t __attribute__ ((__may_alias__)) u64_alias_t;
+ do
+ {
+ n -= 8;
+ u64_alias_t t = *(u64_alias_t *)(a + n);
+ *(u64_alias_t *)(a + n) = *(u64_alias_t *)(b + n);
+ *(u64_alias_t *)(b + n) = t;
+ } while (n);
+}
+
+static inline void
+swap_words_32 (void * restrict a, void * restrict b, size_t n)
+{
+ typedef uint32_t __attribute__ ((__may_alias__)) u32_alias_t;
+ do
+ {
+ n -= 4;
+ u32_alias_t t = *(u32_alias_t *)(a + n);
+ *(u32_alias_t *)(a + n) = *(u32_alias_t *)(b + n);
+ *(u32_alias_t *)(b + n) = t;
+ } while (n);
+}
+
+/* Replace the indirect call with a serie of if statements. It should help
+ the branch predictor. */
+static void
+do_swap (void * restrict a, void * restrict b, size_t size,
+ enum swap_type_t swap_type)
+{
+ if (swap_type == SWAP_WORDS_64)
+ swap_words_64 (a, b, size);
+ else if (swap_type == SWAP_WORDS_32)
+ swap_words_32 (a, b, size);
+ else
+ __memswap (a, b, size);
+}
/* Discontinue quicksort algorithm when partition gets below this size.
This particular magic number was chosen to work best on a Sun 4/260. */
@@ -97,6 +148,14 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
/* Avoid lossage with unsigned arithmetic below. */
return;
+ enum swap_type_t swap_type;
+ if (is_aligned (pbase, size, 8))
+ swap_type = SWAP_WORDS_64;
+ else if (is_aligned (pbase, size, 4))
+ swap_type = SWAP_WORDS_32;
+ else
+ swap_type = SWAP_BYTES;
+
if (total_elems > MAX_THRESH)
{
char *lo = base_ptr;
@@ -120,13 +179,13 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
char *mid = lo + size * ((hi - lo) / size >> 1);
if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)
- SWAP (mid, lo, size);
+ do_swap (mid, lo, size, swap_type);
if ((*cmp) ((void *) hi, (void *) mid, arg) < 0)
- SWAP (mid, hi, size);
+ do_swap (mid, hi, size, swap_type);
else
goto jump_over;
if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)
- SWAP (mid, lo, size);
+ do_swap (mid, lo, size, swap_type);
jump_over:;
left_ptr = lo + size;
@@ -145,7 +204,7 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
if (left_ptr < right_ptr)
{
- SWAP (left_ptr, right_ptr, size);
+ do_swap (left_ptr, right_ptr, size, swap_type);
if (mid == left_ptr)
mid = right_ptr;
else if (mid == right_ptr)
@@ -217,7 +276,7 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
tmp_ptr = run_ptr;
if (tmp_ptr != base_ptr)
- SWAP (tmp_ptr, base_ptr, size);
+ do_swap (tmp_ptr, base_ptr, size, swap_type);
/* Insertion sort, running from left-hand-side up to right-hand-side. */

View File

@ -0,0 +1,125 @@
commit a035a9857e11faf16ed021b5e80faf215262afd1
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:47 2023 -0300
stdlib: Move insertion sort out qsort
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 59b220ba1c375ca3..35020e4c00e5fce3 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -112,6 +112,58 @@ typedef struct
#define STACK_NOT_EMPTY (stack < top)
+static inline void
+insertion_sort_qsort_partitions (void *const pbase, size_t total_elems,
+ size_t size, enum swap_type_t swap_type,
+ __compar_d_fn_t cmp, void *arg)
+{
+ char *base_ptr = (char *) pbase;
+ char *const end_ptr = &base_ptr[size * (total_elems - 1)];
+ char *tmp_ptr = base_ptr;
+#define min(x, y) ((x) < (y) ? (x) : (y))
+ const size_t max_thresh = MAX_THRESH * size;
+ char *thresh = min(end_ptr, base_ptr + max_thresh);
+ char *run_ptr;
+
+ /* Find smallest element in first threshold and place it at the
+ array's beginning. This is the smallest array element,
+ and the operation speeds up insertion sort's inner loop. */
+
+ for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size)
+ if (cmp (run_ptr, tmp_ptr, arg) < 0)
+ tmp_ptr = run_ptr;
+
+ if (tmp_ptr != base_ptr)
+ do_swap (tmp_ptr, base_ptr, size, swap_type);
+
+ /* Insertion sort, running from left-hand-side up to right-hand-side. */
+
+ run_ptr = base_ptr + size;
+ while ((run_ptr += size) <= end_ptr)
+ {
+ tmp_ptr = run_ptr - size;
+ while (cmp (run_ptr, tmp_ptr, arg) < 0)
+ tmp_ptr -= size;
+
+ tmp_ptr += size;
+ if (tmp_ptr != run_ptr)
+ {
+ char *trav;
+
+ trav = run_ptr + size;
+ while (--trav >= run_ptr)
+ {
+ char c = *trav;
+ char *hi, *lo;
+
+ for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo)
+ *hi = *lo;
+ *hi = c;
+ }
+ }
+ }
+}
+
/* Order size using quicksort. This implementation incorporates
four optimizations discussed in Sedgewick:
@@ -258,51 +310,6 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
for partitions below MAX_THRESH size. BASE_PTR points to the beginning
of the array to sort, and END_PTR points at the very last element in
the array (*not* one beyond it!). */
-
-#define min(x, y) ((x) < (y) ? (x) : (y))
-
- {
- char *const end_ptr = &base_ptr[size * (total_elems - 1)];
- char *tmp_ptr = base_ptr;
- char *thresh = min(end_ptr, base_ptr + max_thresh);
- char *run_ptr;
-
- /* Find smallest element in first threshold and place it at the
- array's beginning. This is the smallest array element,
- and the operation speeds up insertion sort's inner loop. */
-
- for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size)
- if ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0)
- tmp_ptr = run_ptr;
-
- if (tmp_ptr != base_ptr)
- do_swap (tmp_ptr, base_ptr, size, swap_type);
-
- /* Insertion sort, running from left-hand-side up to right-hand-side. */
-
- run_ptr = base_ptr + size;
- while ((run_ptr += size) <= end_ptr)
- {
- tmp_ptr = run_ptr - size;
- while ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0)
- tmp_ptr -= size;
-
- tmp_ptr += size;
- if (tmp_ptr != run_ptr)
- {
- char *trav;
-
- trav = run_ptr + size;
- while (--trav >= run_ptr)
- {
- char c = *trav;
- char *hi, *lo;
-
- for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo)
- *hi = *lo;
- *hi = c;
- }
- }
- }
- }
+ insertion_sort_qsort_partitions (pbase, total_elems, size, swap_type, cmp,
+ arg);
}

View File

@ -0,0 +1,85 @@
commit d097f3c79be55d646d86efb7ce876bf84d5ebe4e
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:48 2023 -0300
stdlib: qsort: Move some macros to inline function
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 35020e4c00e5fce3..821a87420638c5a5 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -101,15 +101,28 @@ typedef struct
char *hi;
} stack_node;
-/* The next 4 #defines implement a very fast in-line stack abstraction. */
/* The stack needs log (total_elements) entries (we could even subtract
log(MAX_THRESH)). Since total_elements has type size_t, we get as
upper bound for log (total_elements):
bits per byte (CHAR_BIT) * sizeof(size_t). */
-#define STACK_SIZE (CHAR_BIT * sizeof (size_t))
-#define PUSH(low, high) ((void) ((top->lo = (low)), (top->hi = (high)), ++top))
-#define POP(low, high) ((void) (--top, (low = top->lo), (high = top->hi)))
-#define STACK_NOT_EMPTY (stack < top)
+enum { STACK_SIZE = CHAR_BIT * sizeof (size_t) };
+
+static inline stack_node *
+push (stack_node *top, char *lo, char *hi)
+{
+ top->lo = lo;
+ top->hi = hi;
+ return ++top;
+}
+
+static inline stack_node *
+pop (stack_node *top, char **lo, char **hi)
+{
+ --top;
+ *lo = top->lo;
+ *hi = top->hi;
+ return top;
+}
static inline void
@@ -213,11 +226,9 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
char *lo = base_ptr;
char *hi = &lo[size * (total_elems - 1)];
stack_node stack[STACK_SIZE];
- stack_node *top = stack;
-
- PUSH (NULL, NULL);
+ stack_node *top = stack + 1;
- while (STACK_NOT_EMPTY)
+ while (stack < top)
{
char *left_ptr;
char *right_ptr;
@@ -282,7 +293,7 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
{
if ((size_t) (hi - left_ptr) <= max_thresh)
/* Ignore both small partitions. */
- POP (lo, hi);
+ top = pop (top, &lo, &hi);
else
/* Ignore small left partition. */
lo = left_ptr;
@@ -293,13 +304,13 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
else if ((right_ptr - lo) > (hi - left_ptr))
{
/* Push larger left partition indices. */
- PUSH (lo, right_ptr);
+ top = push (top, lo, right_ptr);
lo = left_ptr;
}
else
{
/* Push larger right partition indices. */
- PUSH (left_ptr, hi);
+ top = push (top, left_ptr, hi);
hi = right_ptr;
}
}

View File

@ -0,0 +1,176 @@
commit 274a46c9b25ab733a1fb9fb1497f1beecae30193
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:49 2023 -0300
stdlib: Implement introsort for qsort (BZ 19305)
This patch makes the quicksort implementation to acts as introsort, to
avoid worse-case performance (and thus making it O(nlog n)). It switch
to heapsort when the depth level reaches 2*log2(total elements). The
heapsort is a textbook implementation.
Checked on x86_64-linux-gnu and aarch64-linux-gnu.
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 821a87420638c5a5..db299eb333cf0302 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -99,6 +99,7 @@ typedef struct
{
char *lo;
char *hi;
+ size_t depth;
} stack_node;
/* The stack needs log (total_elements) entries (we could even subtract
@@ -108,22 +109,85 @@ typedef struct
enum { STACK_SIZE = CHAR_BIT * sizeof (size_t) };
static inline stack_node *
-push (stack_node *top, char *lo, char *hi)
+push (stack_node *top, char *lo, char *hi, size_t depth)
{
top->lo = lo;
top->hi = hi;
+ top->depth = depth;
return ++top;
}
static inline stack_node *
-pop (stack_node *top, char **lo, char **hi)
+pop (stack_node *top, char **lo, char **hi, size_t *depth)
{
--top;
*lo = top->lo;
*hi = top->hi;
+ *depth = top->depth;
return top;
}
+/* NB: N is inclusive bound for BASE. */
+static inline void
+siftdown (void *base, size_t size, size_t k, size_t n,
+ enum swap_type_t swap_type, __compar_d_fn_t cmp, void *arg)
+{
+ while (k <= n / 2)
+ {
+ size_t j = 2 * k;
+ if (j < n && cmp (base + (j * size), base + ((j + 1) * size), arg) < 0)
+ j++;
+
+ if (cmp (base + (k * size), base + (j * size), arg) >= 0)
+ break;
+
+ do_swap (base + (size * j), base + (k * size), size, swap_type);
+ k = j;
+ }
+}
+
+static inline void
+heapify (void *base, size_t size, size_t n, enum swap_type_t swap_type,
+ __compar_d_fn_t cmp, void *arg)
+{
+ size_t k = n / 2;
+ while (1)
+ {
+ siftdown (base, size, k, n, swap_type, cmp, arg);
+ if (k-- == 0)
+ break;
+ }
+}
+
+/* A non-recursive heapsort, used on introsort implementation as a fallback
+ routine with worst-case performance of O(nlog n) and worst-case space
+ complexity of O(1). It sorts the array starting at BASE and ending at
+ END, with each element of SIZE bytes. The SWAP_TYPE is the callback
+ function used to swap elements, and CMP is the function used to compare
+ elements. */
+static void
+heapsort_r (void *base, void *end, size_t size, enum swap_type_t swap_type,
+ __compar_d_fn_t cmp, void *arg)
+{
+ const size_t count = ((uintptr_t) end - (uintptr_t) base) / size;
+
+ if (count < 2)
+ return;
+
+ size_t n = count - 1;
+
+ /* Build the binary heap, largest value at the base[0]. */
+ heapify (base, size, n, swap_type, cmp, arg);
+
+ /* On each iteration base[0:n] is the binary heap, while base[n:count]
+ is sorted. */
+ while (n > 0)
+ {
+ do_swap (base, base + (n * size), size, swap_type);
+ n--;
+ siftdown (base, size, 0, n, swap_type, cmp, arg);
+ }
+}
static inline void
insertion_sort_qsort_partitions (void *const pbase, size_t total_elems,
@@ -209,7 +273,7 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
const size_t max_thresh = MAX_THRESH * size;
- if (total_elems == 0)
+ if (total_elems <= 1)
/* Avoid lossage with unsigned arithmetic below. */
return;
@@ -221,15 +285,26 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
else
swap_type = SWAP_BYTES;
+ /* Maximum depth before quicksort switches to heapsort. */
+ size_t depth = 2 * (sizeof (size_t) * CHAR_BIT - 1
+ - __builtin_clzl (total_elems));
+
if (total_elems > MAX_THRESH)
{
char *lo = base_ptr;
char *hi = &lo[size * (total_elems - 1)];
stack_node stack[STACK_SIZE];
- stack_node *top = stack + 1;
+ stack_node *top = push (stack, NULL, NULL, depth);
while (stack < top)
{
+ if (depth == 0)
+ {
+ heapsort_r (lo, hi, size, swap_type, cmp, arg);
+ top = pop (top, &lo, &hi, &depth);
+ continue;
+ }
+
char *left_ptr;
char *right_ptr;
@@ -293,7 +368,7 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
{
if ((size_t) (hi - left_ptr) <= max_thresh)
/* Ignore both small partitions. */
- top = pop (top, &lo, &hi);
+ top = pop (top, &lo, &hi, &depth);
else
/* Ignore small left partition. */
lo = left_ptr;
@@ -304,13 +379,13 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
else if ((right_ptr - lo) > (hi - left_ptr))
{
/* Push larger left partition indices. */
- top = push (top, lo, right_ptr);
+ top = push (top, lo, right_ptr, depth - 1);
lo = left_ptr;
}
else
{
/* Push larger right partition indices. */
- top = push (top, left_ptr, hi);
+ top = push (top, left_ptr, hi, depth - 1);
hi = right_ptr;
}
}

View File

@ -0,0 +1,491 @@
commit 03bf8357e8291857a435afcc3048e0b697b6cc04
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:50 2023 -0300
stdlib: Remove use of mergesort on qsort (BZ 21719)
This patch removes the mergesort optimization on qsort implementation
and uses the introsort instead. The mergesort implementation has some
issues:
- It is as-safe only for certain types sizes (if total size is less
than 1 KB with large element sizes also forcing memory allocation)
which contradicts the function documentation. Although not required
by the C standard, it is preferable and doable to have an O(1) space
implementation.
- The malloc for certain element size and element number adds
arbitrary latency (might even be worse if malloc is interposed).
- To avoid trigger swap from memory allocation the implementation
relies on system information that might be virtualized (for instance
VMs with overcommit memory) which might lead to potentially use of
swap even if system advertise more memory than actually has. The
check also have the downside of issuing syscalls where none is
expected (although only once per execution).
- The mergesort is suboptimal on an already sorted array (BZ#21719).
The introsort implementation is already optimized to use constant extra
space (due to the limit of total number of elements from maximum VM
size) and thus can be used to avoid the malloc usage issues.
Resulting performance is slower due the usage of qsort, specially in the
worst-case scenario (partialy or sorted arrays) and due the fact
mergesort uses a slight improved swap operations.
This change also renders the BZ#21719 fix unrequired (since it is meant
to fix the sorted input performance degradation for mergesort). The
manual is also updated to indicate the function is now async-cancel
safe.
Checked on x86_64-linux-gnu.
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
Conflicts:
stdlib/msort.c: Deletion had conflicts due to skipped backports.
diff --git a/include/stdlib.h b/include/stdlib.h
index 22c9fb65c3074765..fd108df58ddf8b89 100644
--- a/include/stdlib.h
+++ b/include/stdlib.h
@@ -107,8 +107,6 @@ extern int __posix_openpt (int __oflag) attribute_hidden;
extern int __add_to_environ (const char *name, const char *value,
const char *combines, int replace)
attribute_hidden;
-extern void _quicksort (void *const pbase, size_t total_elems,
- size_t size, __compar_d_fn_t cmp, void *arg);
extern int __on_exit (void (*__func) (int __status, void *__arg), void *__arg);
diff --git a/manual/argp.texi b/manual/argp.texi
index 0023441812d4e584..b77ad68285ecb732 100644
--- a/manual/argp.texi
+++ b/manual/argp.texi
@@ -735,7 +735,7 @@ for options, bad phase of the moon, etc.
@c hol_set_group ok
@c hol_find_entry ok
@c hol_sort @mtslocale @acucorrupt
-@c qsort dup @acucorrupt
+@c qsort dup
@c hol_entry_qcmp @mtslocale
@c hol_entry_cmp @mtslocale
@c group_cmp ok
diff --git a/manual/locale.texi b/manual/locale.texi
index 720e0ca952a665bd..f6afa5dc44a2a016 100644
--- a/manual/locale.texi
+++ b/manual/locale.texi
@@ -253,7 +253,7 @@ The symbols in this section are defined in the header file @file{locale.h}.
@c calculate_head_size ok
@c __munmap ok
@c compute_hashval ok
-@c qsort dup @acucorrupt
+@c qsort dup
@c rangecmp ok
@c malloc @ascuheap @acsmem
@c strdup @ascuheap @acsmem
@@ -275,7 +275,6 @@ The symbols in this section are defined in the header file @file{locale.h}.
@c realloc @ascuheap @acsmem
@c realloc @ascuheap @acsmem
@c fclose @ascuheap @asulock @acsmem @acsfd @aculock
-@c qsort @ascuheap @acsmem
@c alias_compare dup
@c libc_lock_unlock @aculock
@c _nl_explode_name @ascuheap @acsmem
diff --git a/manual/search.texi b/manual/search.texi
index 5691bf2f2b2bb861..a550858478f7fc83 100644
--- a/manual/search.texi
+++ b/manual/search.texi
@@ -159,7 +159,7 @@ To sort an array using an arbitrary comparison function, use the
@deftypefun void qsort (void *@var{array}, size_t @var{count}, size_t @var{size}, comparison_fn_t @var{compare})
@standards{ISO, stdlib.h}
-@safety{@prelim{}@mtsafe{}@assafe{}@acunsafe{@acucorrupt{}}}
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
The @code{qsort} function sorts the array @var{array}. The array
contains @var{count} elements, each of which is of size @var{size}.
@@ -199,9 +199,8 @@ Functions}):
The @code{qsort} function derives its name from the fact that it was
originally implemented using the ``quick sort'' algorithm.
-The implementation of @code{qsort} in this library might not be an
-in-place sort and might thereby use an extra amount of memory to store
-the array.
+The implementation of @code{qsort} in this library is an in-place sort
+and uses a constant extra space (allocated on the stack).
@end deftypefun
@node Search/Sort Example
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 03f8478c64408ed3..3b89bc2aa0307321 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -94,7 +94,6 @@ routines := \
mbtowc \
mrand48 \
mrand48_r \
- msort \
nrand48 \
nrand48_r \
old_atexit \
@@ -370,7 +369,6 @@ generated += \
# generated
CFLAGS-bsearch.c += $(uses-callbacks)
-CFLAGS-msort.c += $(uses-callbacks)
CFLAGS-qsort.c += $(uses-callbacks)
CFLAGS-system.c += -fexceptions
CFLAGS-system.os = -fomit-frame-pointer
diff --git a/stdlib/msort.c b/stdlib/msort.c
deleted file mode 100644
index 8750cc59db2337cf..0000000000000000
--- a/stdlib/msort.c
+++ /dev/null
@@ -1,310 +0,0 @@
-/* An alternative to qsort, with an identical interface.
- This file is part of the GNU C Library.
- Copyright (C) 1992-2021 Free Software Foundation, Inc.
- Written by Mike Haertel, September 1988.
-
- 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 <alloca.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <memcopy.h>
-#include <errno.h>
-#include <atomic.h>
-
-struct msort_param
-{
- size_t s;
- size_t var;
- __compar_d_fn_t cmp;
- void *arg;
- char *t;
-};
-static void msort_with_tmp (const struct msort_param *p, void *b, size_t n);
-
-static void
-msort_with_tmp (const struct msort_param *p, void *b, size_t n)
-{
- char *b1, *b2;
- size_t n1, n2;
-
- if (n <= 1)
- return;
-
- n1 = n / 2;
- n2 = n - n1;
- b1 = b;
- b2 = (char *) b + (n1 * p->s);
-
- msort_with_tmp (p, b1, n1);
- msort_with_tmp (p, b2, n2);
-
- char *tmp = p->t;
- const size_t s = p->s;
- __compar_d_fn_t cmp = p->cmp;
- void *arg = p->arg;
- switch (p->var)
- {
- case 0:
- while (n1 > 0 && n2 > 0)
- {
- if ((*cmp) (b1, b2, arg) <= 0)
- {
- *(uint32_t *) tmp = *(uint32_t *) b1;
- b1 += sizeof (uint32_t);
- --n1;
- }
- else
- {
- *(uint32_t *) tmp = *(uint32_t *) b2;
- b2 += sizeof (uint32_t);
- --n2;
- }
- tmp += sizeof (uint32_t);
- }
- break;
- case 1:
- while (n1 > 0 && n2 > 0)
- {
- if ((*cmp) (b1, b2, arg) <= 0)
- {
- *(uint64_t *) tmp = *(uint64_t *) b1;
- b1 += sizeof (uint64_t);
- --n1;
- }
- else
- {
- *(uint64_t *) tmp = *(uint64_t *) b2;
- b2 += sizeof (uint64_t);
- --n2;
- }
- tmp += sizeof (uint64_t);
- }
- break;
- case 2:
- while (n1 > 0 && n2 > 0)
- {
- unsigned long *tmpl = (unsigned long *) tmp;
- unsigned long *bl;
-
- tmp += s;
- if ((*cmp) (b1, b2, arg) <= 0)
- {
- bl = (unsigned long *) b1;
- b1 += s;
- --n1;
- }
- else
- {
- bl = (unsigned long *) b2;
- b2 += s;
- --n2;
- }
- while (tmpl < (unsigned long *) tmp)
- *tmpl++ = *bl++;
- }
- break;
- case 3:
- while (n1 > 0 && n2 > 0)
- {
- if ((*cmp) (*(const void **) b1, *(const void **) b2, arg) <= 0)
- {
- *(void **) tmp = *(void **) b1;
- b1 += sizeof (void *);
- --n1;
- }
- else
- {
- *(void **) tmp = *(void **) b2;
- b2 += sizeof (void *);
- --n2;
- }
- tmp += sizeof (void *);
- }
- break;
- default:
- while (n1 > 0 && n2 > 0)
- {
- if ((*cmp) (b1, b2, arg) <= 0)
- {
- tmp = (char *) __mempcpy (tmp, b1, s);
- b1 += s;
- --n1;
- }
- else
- {
- tmp = (char *) __mempcpy (tmp, b2, s);
- b2 += s;
- --n2;
- }
- }
- break;
- }
-
- if (n1 > 0)
- memcpy (tmp, b1, n1 * s);
- memcpy (b, p->t, (n - n2) * s);
-}
-
-
-void
-__qsort_r (void *b, size_t n, size_t s, __compar_d_fn_t cmp, void *arg)
-{
- size_t size = n * s;
- char *tmp = NULL;
- struct msort_param p;
-
- /* For large object sizes use indirect sorting. */
- if (s > 32)
- size = 2 * n * sizeof (void *) + s;
-
- if (size < 1024)
- /* The temporary array is small, so put it on the stack. */
- p.t = __alloca (size);
- else
- {
- /* We should avoid allocating too much memory since this might
- have to be backed up by swap space. */
- static long int phys_pages;
- static int pagesize;
-
- if (pagesize == 0)
- {
- phys_pages = __sysconf (_SC_PHYS_PAGES);
-
- if (phys_pages == -1)
- /* Error while determining the memory size. So let's
- assume there is enough memory. Otherwise the
- implementer should provide a complete implementation of
- the `sysconf' function. */
- phys_pages = (long int) (~0ul >> 1);
-
- /* The following determines that we will never use more than
- a quarter of the physical memory. */
- phys_pages /= 4;
-
- /* Make sure phys_pages is written to memory. */
- atomic_write_barrier ();
-
- pagesize = __sysconf (_SC_PAGESIZE);
- }
-
- /* Just a comment here. We cannot compute
- phys_pages * pagesize
- and compare the needed amount of memory against this value.
- The problem is that some systems might have more physical
- memory then can be represented with a `size_t' value (when
- measured in bytes. */
-
- /* If the memory requirements are too high don't allocate memory. */
- if (size / pagesize > (size_t) phys_pages)
- {
- _quicksort (b, n, s, cmp, arg);
- return;
- }
-
- /* It's somewhat large, so malloc it. */
- int save = errno;
- tmp = malloc (size);
- __set_errno (save);
- if (tmp == NULL)
- {
- /* Couldn't get space, so use the slower algorithm
- that doesn't need a temporary array. */
- _quicksort (b, n, s, cmp, arg);
- return;
- }
- p.t = tmp;
- }
-
- p.s = s;
- p.var = 4;
- p.cmp = cmp;
- p.arg = arg;
-
- if (s > 32)
- {
- /* Indirect sorting. */
- char *ip = (char *) b;
- void **tp = (void **) (p.t + n * sizeof (void *));
- void **t = tp;
- void *tmp_storage = (void *) (tp + n);
-
- while ((void *) t < tmp_storage)
- {
- *t++ = ip;
- ip += s;
- }
- p.s = sizeof (void *);
- p.var = 3;
- msort_with_tmp (&p, p.t + n * sizeof (void *), n);
-
- /* tp[0] .. tp[n - 1] is now sorted, copy around entries of
- the original array. Knuth vol. 3 (2nd ed.) exercise 5.2-10. */
- char *kp;
- size_t i;
- for (i = 0, ip = (char *) b; i < n; i++, ip += s)
- if ((kp = tp[i]) != ip)
- {
- size_t j = i;
- char *jp = ip;
- memcpy (tmp_storage, ip, s);
-
- do
- {
- size_t k = (kp - (char *) b) / s;
- tp[j] = jp;
- memcpy (jp, kp, s);
- j = k;
- jp = kp;
- kp = tp[k];
- }
- while (kp != ip);
-
- tp[j] = jp;
- memcpy (jp, tmp_storage, s);
- }
- }
- else
- {
- if ((s & (sizeof (uint32_t) - 1)) == 0
- && ((char *) b - (char *) 0) % __alignof__ (uint32_t) == 0)
- {
- if (s == sizeof (uint32_t))
- p.var = 0;
- else if (s == sizeof (uint64_t)
- && ((char *) b - (char *) 0) % __alignof__ (uint64_t) == 0)
- p.var = 1;
- else if ((s & (sizeof (unsigned long) - 1)) == 0
- && ((char *) b - (char *) 0)
- % __alignof__ (unsigned long) == 0)
- p.var = 2;
- }
- msort_with_tmp (&p, b, n);
- }
- free (tmp);
-}
-libc_hidden_def (__qsort_r)
-weak_alias (__qsort_r, qsort_r)
-
-
-void
-qsort (void *b, size_t n, size_t s, __compar_fn_t cmp)
-{
- return __qsort_r (b, n, s, (__compar_d_fn_t) cmp, NULL);
-}
-libc_hidden_def (qsort)
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index db299eb333cf0302..cb1619aa0ae7de72 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -20,7 +20,6 @@
Engineering a sort function; Jon Bentley and M. Douglas McIlroy;
Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993. */
-#include <alloca.h>
#include <limits.h>
#include <memswap.h>
#include <stdlib.h>
@@ -266,8 +265,8 @@ insertion_sort_qsort_partitions (void *const pbase, size_t total_elems,
stack size is needed (actually O(1) in this case)! */
void
-_quicksort (void *const pbase, size_t total_elems, size_t size,
- __compar_d_fn_t cmp, void *arg)
+__qsort_r (void *const pbase, size_t total_elems, size_t size,
+ __compar_d_fn_t cmp, void *arg)
{
char *base_ptr = (char *) pbase;
@@ -399,3 +398,12 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
insertion_sort_qsort_partitions (pbase, total_elems, size, swap_type, cmp,
arg);
}
+libc_hidden_def (__qsort_r)
+weak_alias (__qsort_r, qsort_r)
+
+void
+qsort (void *b, size_t n, size_t s, __compar_fn_t cmp)
+{
+ return __qsort_r (b, n, s, (__compar_d_fn_t) cmp, NULL);
+}
+libc_hidden_def (qsort)

View File

@ -0,0 +1,399 @@
commit bc888a3976700a3607f6ec4a36dbf3030161cb3e
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:51 2023 -0300
stdlib: Add more qsort{_r} coverage
This patch adds a qsort and qsort_r to trigger the worst case
scenario for the quicksort (which glibc current lacks coverage).
The test is done with random input, dfferent internal types (uint8_t,
uint16_t, uint32_t, uint64_t, large size), and with
different set of element numbers.
Checked on x86_64-linux-gnu and i686-linux-gnu.
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 3b89bc2aa0307321..4039e5395eeea2b0 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -211,6 +211,7 @@ tests := \
tst-on_exit \
tst-qsort \
tst-qsort2 \
+ tst-qsort3 \
tst-quick_exit \
tst-rand48 \
tst-rand48-2 \
diff --git a/stdlib/tst-qsort3.c b/stdlib/tst-qsort3.c
new file mode 100644
index 0000000000000000..421560d74434a116
--- /dev/null
+++ b/stdlib/tst-qsort3.c
@@ -0,0 +1,366 @@
+/* qsort(_r) tests to trigger worst case for quicksort.
+ Copyright (C) 2023 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <array_length.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+
+typedef enum
+{
+ Sorted,
+ Random,
+ Repeated,
+ Bitonic,
+ Duplicated,
+} arraytype_t;
+
+/* Ratio of total of elements which will be repeated. */
+static const double RepeatedRatio = 0.2;
+
+/* Ratio of duplicated element . */
+static const double DuplicatedRatio = 0.4;
+
+struct array_t
+{
+ arraytype_t type;
+ const char *name;
+} static const arraytypes[] =
+{
+ { Sorted, "Sorted" },
+ { Random, "Random" },
+ { Repeated, "Repeated" },
+ { Bitonic, "Bitonic" },
+ { Duplicated, "Duplicated" },
+};
+
+/* Return the index of BASE as interpreted as an array of elements
+ of size SIZE. */
+static inline void *
+arr (void *base, size_t idx, size_t size)
+{
+ return (void*)((uintptr_t)base + (idx * size));
+}
+
+/* Functions used to check qsort. */
+static int
+uint8_t_cmp (const void *a, const void *b)
+{
+ uint8_t ia = *(uint8_t*)a;
+ uint8_t ib = *(uint8_t*)b;
+ return (ia > ib) - (ia < ib);
+}
+
+static int
+uint16_t_cmp (const void *a, const void *b)
+{
+ uint16_t ia = *(uint16_t*)a;
+ uint16_t ib = *(uint16_t*)b;
+ return (ia > ib) - (ia < ib);
+}
+
+static int
+uint32_t_cmp (const void *a, const void *b)
+{
+ uint32_t ia = *(uint32_t*)a;
+ uint32_t ib = *(uint32_t*)b;
+ return (ia > ib) - (ia < ib);
+}
+
+static int
+uint64_t_cmp (const void *a, const void *b)
+{
+ uint64_t ia = *(uint64_t*)a;
+ uint64_t ib = *(uint64_t*)b;
+ return (ia > ib) - (ia < ib);
+}
+
+#define LARGE_SIZE 47
+
+static int
+large_cmp (const void *a, const void *b)
+{
+ return memcmp (a, b, LARGE_SIZE);
+}
+
+/* Function used to check qsort_r. */
+typedef enum
+{
+ UINT8_CMP_T,
+ UINT16_CMP_T,
+ UINT32_CMP_T,
+ UINT64_CMP_T,
+ LARGE_CMP_T
+} type_cmp_t;
+
+static type_cmp_t
+uint_t_cmp_type (size_t sz)
+{
+ switch (sz)
+ {
+ case sizeof (uint8_t): return UINT8_CMP_T;
+ case sizeof (uint16_t): return UINT16_CMP_T;
+ case sizeof (uint64_t): return UINT64_CMP_T;
+ case sizeof (uint32_t): return UINT32_CMP_T;
+ default: return LARGE_CMP_T;
+ }
+}
+
+static int
+uint_t_cmp (const void *a, const void *b, void *arg)
+{
+ type_cmp_t type = *(type_cmp_t*) arg;
+ switch (type)
+ {
+ case UINT8_CMP_T: return uint8_t_cmp (a, b);
+ case UINT32_CMP_T: return uint32_t_cmp (a, b);
+ case UINT16_CMP_T: return uint16_t_cmp (a, b);
+ case UINT64_CMP_T: return uint64_t_cmp (a, b);
+ default: return large_cmp (a, b);
+ }
+}
+
+static void
+seq (void *elem, size_t type_size, int value)
+{
+ if (type_size == sizeof (uint8_t))
+ *(uint8_t*)elem = value;
+ else if (type_size == sizeof (uint16_t))
+ *(uint16_t*)elem = value;
+ else if (type_size == sizeof (uint32_t))
+ *(uint32_t*)elem = value;
+ else if (type_size == sizeof (uint64_t))
+ *(uint64_t*)elem = value;
+ else
+ memset (elem, value, type_size);
+}
+
+static void
+fill_array (void *array, void *refarray, size_t nmemb, size_t type_size,
+ arraytype_t type)
+{
+ size_t size = nmemb * type_size;
+
+ switch (type)
+ {
+ case Sorted:
+ for (size_t i = 0; i < nmemb; i++)
+ seq (arr (array, i, type_size), type_size, i);
+ break;
+
+ case Random:
+ arc4random_buf (array, size);
+ break;
+
+ case Repeated:
+ {
+ arc4random_buf (array, size);
+
+ void *randelem = xmalloc (type_size);
+ arc4random_buf (randelem, type_size);
+
+ /* Repeat REPEATED elements (based on RepeatRatio ratio) in the random
+ array. */
+ size_t repeated = (size_t)(nmemb * RepeatedRatio);
+ for (size_t i = 0; i < repeated; i++)
+ {
+ size_t pos = arc4random_uniform (nmemb - 1);
+ memcpy (arr (array, pos, type_size), randelem, type_size);
+ }
+ free (randelem);
+ }
+ break;
+
+ case Bitonic:
+ {
+ size_t i;
+ for (i = 0; i < nmemb / 2; i++)
+ seq (arr (array, i, type_size), type_size, i);
+ for ( ; i < nmemb; i++)
+ seq (arr (array, i, type_size), type_size, (nmemb - 1) - i);
+ }
+ break;
+
+ case Duplicated:
+ {
+ int randelem1 = arc4random ();
+ for (size_t i = 0; i < nmemb; i++)
+ seq (arr (array, i, type_size), type_size, randelem1);
+
+ size_t duplicates = (size_t)(nmemb * DuplicatedRatio);
+ int randelem2 = arc4random ();
+ for (size_t i = 0; i < duplicates; i++)
+ {
+ size_t pos = arc4random_uniform (nmemb - 1);
+ seq (arr (array, pos, type_size), type_size, randelem2);
+ }
+ }
+ break;
+ }
+
+ memcpy (refarray, array, size);
+}
+
+typedef int (*cmpfunc_t)(const void *, const void *);
+
+/* Simple insertion sort to use as reference sort. */
+static void
+qsort_r_ref (void *p, size_t n, size_t s, __compar_d_fn_t cmp, void *arg)
+{
+ if (n <= 1)
+ return;
+
+ int i = 1;
+ char tmp[s];
+ while (i < n)
+ {
+ memcpy (tmp, arr (p, i, s), s);
+ int j = i - 1;
+ while (j >= 0 && cmp (arr (p, j, s), tmp, arg) > 0)
+ {
+ memcpy (arr (p, j + 1, s), arr (p, j, s), s);
+ j = j - 1;
+ }
+ memcpy (arr (p, j + 1, s), tmp, s);
+ i = i + 1;
+ }
+}
+
+static void
+qsort_ref (void *b, size_t n, size_t s, __compar_fn_t cmp)
+{
+ return qsort_r_ref (b, n, s, (__compar_d_fn_t) cmp, NULL);
+}
+
+/* Check if ARRAY of total NMEMB element of size SIZE is sorted
+ based on CMPFUNC. */
+static void
+check_array (void *array, void *refarray, size_t nmemb, size_t type_size,
+ cmpfunc_t cmpfunc)
+{
+ for (size_t i = 1; i < nmemb; i++)
+ {
+ int ret = cmpfunc (arr (array, i, type_size),
+ arr (array, i-1, type_size));
+ TEST_VERIFY_EXIT (ret >= 0);
+ }
+
+ size_t size = nmemb * type_size;
+ TEST_COMPARE_BLOB (array, size, refarray, size);
+}
+
+static void
+check_qsort (void *buf, void *refbuf, size_t nelem, size_t type_size,
+ arraytype_t type, cmpfunc_t cmpfunc)
+{
+ fill_array (buf, refbuf, nelem, type_size, type);
+
+ qsort (buf, nelem, type_size, cmpfunc);
+ qsort_ref (refbuf, nelem, type_size, cmpfunc);
+
+ check_array (buf, refbuf, nelem, type_size, cmpfunc);
+}
+
+static void
+check_qsort_r (void *buf, void *refbuf, size_t nelem, size_t type_size,
+ arraytype_t type, cmpfunc_t cmpfunc)
+{
+ fill_array (buf, refbuf, nelem, type_size, type);
+
+ type_cmp_t typecmp = uint_t_cmp_type (type_size);
+
+ qsort_r (buf, nelem, type_size, uint_t_cmp, &typecmp);
+ qsort_r_ref (refbuf, nelem, type_size, uint_t_cmp, &typecmp);
+
+ check_array (buf, refbuf, nelem, type_size, cmpfunc);
+}
+
+static int
+do_test (void)
+{
+ /* Some random sizes. */
+ static const size_t nelems[] = { 0, 1, 7, 20, 32, 100, 256, 1024, 4256 };
+ size_t max_nelems = 0;
+ for (int i = 0; i < array_length (nelems); i++)
+ if (nelems[i] > max_nelems)
+ max_nelems = nelems[i];
+
+ static const struct test_t
+ {
+ size_t type_size;
+ cmpfunc_t cmpfunc;
+ }
+ tests[] =
+ {
+ { sizeof (uint8_t), uint8_t_cmp },
+ { sizeof (uint16_t), uint16_t_cmp },
+ { sizeof (uint32_t), uint32_t_cmp },
+ { sizeof (uint64_t), uint64_t_cmp },
+ /* Test swap with large elements. */
+ { LARGE_SIZE, large_cmp },
+ };
+ size_t max_type_size = 0;
+ for (int i = 0; i < array_length (tests); i++)
+ if (tests[i].type_size > max_type_size)
+ max_type_size = tests[i].type_size;
+
+ void *buf = reallocarray (NULL, max_nelems, max_type_size);
+ TEST_VERIFY_EXIT (buf != NULL);
+ void *refbuf = reallocarray (NULL, max_nelems, max_type_size);
+ TEST_VERIFY_EXIT (refbuf != NULL);
+
+ for (const struct test_t *test = tests; test < array_end (tests); ++test)
+ {
+ if (test_verbose > 0)
+ printf ("info: testing qsort with type_size=%zu\n", test->type_size);
+ for (const struct array_t *arraytype = arraytypes;
+ arraytype < array_end (arraytypes);
+ ++arraytype)
+ {
+ if (test_verbose > 0)
+ printf (" distribution=%s\n", arraytype->name);
+ for (const size_t *nelem = nelems;
+ nelem < array_end (nelems);
+ ++nelem)
+ {
+ if (test_verbose > 0)
+ printf (" nelem=%zu, total size=%zu\n", *nelem,
+ *nelem * test->type_size);
+
+ check_qsort (buf, refbuf, *nelem, test->type_size,
+ arraytype->type, test->cmpfunc);
+ check_qsort_r (buf, refbuf, *nelem, test->type_size,
+ arraytype->type, test->cmpfunc);
+ }
+ }
+ }
+
+ free (buf);
+ free (refbuf);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -22,7 +22,7 @@ Date: Fri Mar 15 19:08:24 2024 +0100
Reviewed-by: Arjun Shankar <arjun@redhat.com>
diff --git a/sysdeps/unix/sysv/linux/sched_getcpu.c b/sysdeps/unix/sysv/linux/sched_getcpu.c
index dfb884568d..72a3360550 100644
index 6f78edaea1495342..a33e30a1cb8e161b 100644
--- a/sysdeps/unix/sysv/linux/sched_getcpu.c
+++ b/sysdeps/unix/sysv/linux/sched_getcpu.c
@@ -33,17 +33,9 @@ vsyscall_sched_getcpu (void)

View File

@ -6,7 +6,7 @@ Date: Sat May 20 13:37:47 2023 +0000
Note on the changes:
- Partial backport, the only file modified is `resolv/inet_pton.c` to
ease further backport for RHEL-83984.
ease further backport for RHEL-44920.
diff --git a/resolv/inet_pton.c b/resolv/inet_pton.c
index f1d5db75d0d47501..835f364794c1be96 100644

View File

@ -26,11 +26,6 @@ Date: Tue Mar 25 09:40:20 2025 +0000
Reviewed-by: Joseph Myers <josmyers@redhat.com>
Conflicts:
stdio-common/Makefile
(mising tst-fwrite-bz29459 downstream)
diff --git a/Makerules b/Makerules
index 689842ba56c71b0d..2b5deadced0f8c08 100644
--- a/Makerules
@ -46,7 +41,7 @@ index 689842ba56c71b0d..2b5deadced0f8c08 100644
# and 64-bit time support.
include $(o-iterator)
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index fe69e48849cb9819..df7afefc7a1f9828 100644
index 077c1062756971e3..15525a0f768244c6 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -51,6 +51,33 @@ nonfmt-xprintf-stems := \
@ -83,23 +78,23 @@ index fe69e48849cb9819..df7afefc7a1f9828 100644
headers := \
bits/printf-ldbl.h \
bits/stdio_lim.h \
@@ -274,6 +301,7 @@ endif
@@ -279,6 +306,7 @@ endif
test-srcs = \
$(xprintf-srcs) \
+ $(xscanf-srcs) \
tst-fwrite-bz29459 \
tst-printf \
tst-printfsz-islongdouble \
tst-unbputc \
@@ -282,6 +310,7 @@ test-srcs = \
@@ -288,6 +316,7 @@ test-srcs = \
ifeq ($(run-built-tests),yes)
tests-special += \
$(foreach f,$(xprintf-stems),$(objpfx)$(f).out) \
+ $(foreach f,$(xscanf-stems),$(objpfx)$(f).out) \
$(objpfx)tst-fwrite-bz29459.out \
$(objpfx)tst-printf.out \
$(objpfx)tst-printfsz-islongdouble.out \
$(objpfx)tst-setvbuf1-cmp.out \
@@ -293,6 +322,7 @@ ifeq (yes,$(build-shared))
@@ -300,6 +329,7 @@ ifeq (yes,$(build-shared))
ifneq ($(PERL),no)
tests-special += \
$(foreach f,$(xprintf-stems),$(objpfx)$(f)-mem.out) \
@ -107,7 +102,7 @@ index fe69e48849cb9819..df7afefc7a1f9828 100644
$(objpfx)tst-freopen2-mem.out \
$(objpfx)tst-freopen3-mem.out \
$(objpfx)tst-freopen4-mem.out \
@@ -314,6 +344,8 @@ tests-special += \
@@ -321,6 +351,8 @@ tests-special += \
generated += \
$(foreach f,$(xprintf-stems),$(f)-mem.out) \
$(foreach f,$(xprintf-stems),$(f).mtrace) \
@ -116,7 +111,7 @@ index fe69e48849cb9819..df7afefc7a1f9828 100644
tst-freopen2-mem.out \
tst-freopen2.mtrace \
tst-freopen3-mem.out \
@@ -457,6 +489,26 @@ $(objpfx)tst-printf-format-%.out: \
@@ -468,6 +500,26 @@ $(objpfx)tst-printf-format-%.out: \
$(make-tst-printf-format-out) > $@; \
$(evaluate-test)
@ -143,7 +138,7 @@ index fe69e48849cb9819..df7afefc7a1f9828 100644
$(objpfx)tst-printfsz-islongdouble.out: \
tst-printfsz-islongdouble.sh $(objpfx)tst-printfsz-islongdouble
$(SHELL) $^ '$(test-program-prefix)' $@; \
@@ -542,5 +594,13 @@ $(objpfx)tst-setvbuf1-cmp.out: tst-setvbuf1.expect $(objpfx)tst-setvbuf1.out
@@ -553,5 +605,13 @@ $(objpfx)tst-setvbuf1-cmp.out: tst-setvbuf1.expect $(objpfx)tst-setvbuf1.out
$(objpfx)tst-printf-round: $(libm)
$(objpfx)tst-scanf-round: $(libm)

View File

@ -10,7 +10,7 @@ Date: Fri Mar 28 12:35:52 2025 +0000
Reviewed-by: Joseph Myers <josmyers@redhat.com>
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index df7afefc7a1f9828..e8e06f194e6592b6 100644
index 15525a0f768244c6..74d5e02c7206cabc 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -52,7 +52,7 @@ nonfmt-xprintf-stems := \

View File

@ -10,7 +10,7 @@ Date: Fri Mar 28 12:35:52 2025 +0000
Reviewed-by: Joseph Myers <josmyers@redhat.com>
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index e8e06f194e6592b6..2892f680c069836a 100644
index 74d5e02c7206cabc..3e165685af09a1c3 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -52,7 +52,7 @@ nonfmt-xprintf-stems := \

View File

@ -10,7 +10,7 @@ Date: Fri Mar 28 12:35:52 2025 +0000
Reviewed-by: Joseph Myers <josmyers@redhat.com>
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 2892f680c069836a..7cad1ddeb6f57998 100644
index 3e165685af09a1c3..3a09b5bfbc930b45 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -52,7 +52,7 @@ nonfmt-xprintf-stems := \

View File

@ -10,7 +10,7 @@ Date: Fri Mar 28 12:35:53 2025 +0000
Reviewed-by: Joseph Myers <josmyers@redhat.com>
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 7cad1ddeb6f57998..fd3c86cc7df68e57 100644
index 3a09b5bfbc930b45..13c50f07ccfc86c3 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -52,7 +52,7 @@ nonfmt-xprintf-stems := \

View File

@ -10,7 +10,7 @@ Date: Fri Mar 28 12:35:53 2025 +0000
Reviewed-by: Joseph Myers <josmyers@redhat.com>
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index fd3c86cc7df68e57..3cb9da43d20bb31a 100644
index 13c50f07ccfc86c3..1aefe3702800bd73 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -52,7 +52,7 @@ nonfmt-xprintf-stems := \

View File

@ -6,7 +6,7 @@ Also exclude failing nan(...) floating-point input patterns because
swbz#30647 has not been fixed downstream.
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 3cb9da43d20bb31a..a18c87741ae42a61 100644
index 1aefe3702800bd73..7573dee90d264e16 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -64,7 +64,7 @@ fmts-xscanf-int := d i

View File

@ -0,0 +1,562 @@
commit a93d9e03a31ec14405cb3a09aa95413b67067380
Author: H.J. Lu <hjl.tools@gmail.com>
Date: Tue Aug 17 19:35:48 2021 -0700
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>
Conflicts:
elf/Makefile (fixup context due to reordering)
elf/dl-open.c (modified line didn't exist downstream)
diff --git a/csu/Makefile b/csu/Makefile
index 3054329cea4a276d..e2390e4a7deb6941 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 0000000000000000..13924d5efdbaa248
--- /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 15bec14364266c77..b074cc29664b3e20 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -54,6 +54,7 @@ dl-routines = \
dl-catch \
dl-close \
dl-debug \
+ dl-debug-symbols \
dl-deps \
dl-exception \
dl-execstack \
@@ -399,6 +400,7 @@ tests += \
tst-dlmodcount \
tst-dlmopen1 \
tst-dlmopen3 \
+ tst-dlmopen4 \
tst-dlmopen-dlerror \
tst-dlmopen-gethostbyname \
tst-dlmopen-twice \
@@ -1973,6 +1975,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 9d158c25498fd8ae..bf699e58d753a1e2 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -469,7 +469,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 0000000000000000..b7e9f5d9470c4da2
--- /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 2cd5f0975380445c..f637d4bb8de3db8c 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 eb6b658b698f5694..5b0734c816b351f0 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -950,7 +950,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 80af6518d4395906..eef724f7e9b2211d 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -596,7 +596,7 @@ dl_open_worker_begin (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;
}
@@ -634,7 +634,7 @@ dl_open_worker_begin (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);
@@ -840,7 +840,7 @@ no more namespaces available for dlmopen()"));
}
GL(dl_ns)[nsid].libc_map = NULL;
- _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
@@ -916,7 +916,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 757205affe65d9e1..5b85df8a2eed1888 100644
--- a/elf/dl-reloc-static-pie.c
+++ b/elf/dl-reloc-static-pie.c
@@ -62,7 +62,7 @@ _dl_relocate_static_pie (void)
ELF_DYNAMIC_RELOCATE (main_map, NULL, 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 21a351686b9bf7c8..0906bbe33fbd9f8f 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 61bc99e4b086612f..f3476d8308616f84 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 d3d9e6b904ac78fd..7d8ed0ac1188d527 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1725,7 +1725,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;
@@ -2531,7 +2531,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 0000000000000000..3fe150e50bc259f0
--- /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 0dc63ef37d6a5666..0cf130ddb8af2e89 100644
--- a/include/link.h
+++ b/include/link.h
@@ -362,6 +362,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
# define LA_SYMBIND "la_symbind32"
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index dc4e0555e4ed7f3c..695f3910234e87da 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -376,7 +376,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;
@@ -1128,12 +1128,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

View File

@ -0,0 +1,158 @@
commit 7278d11f3a0cd528188c719bab75575b0aea2c6e
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Jul 4 21:46:05 2025 +0200
elf: Introduce separate _r_debug_array variable
It replaces the ns_debug member of the namespaces. Previously,
the base namespace had an unused ns_debug member.
This change also fixes a concurrency issue: Now _dl_debug_initialize
only updates r_next of the previous namespace's r_debug after the new
r_debug is initialized, so that only the initialized version is
observed. (Client code accessing _r_debug will benefit from load
dependency tracking in CPUs even without explicit barriers.)
Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
diff --git a/elf/dl-debug.c b/elf/dl-debug.c
index f637d4bb8de3db8c..649386d5a6b885ed 100644
--- a/elf/dl-debug.c
+++ b/elf/dl-debug.c
@@ -30,17 +30,37 @@ extern const int verify_link_map_members[(VERIFY_MEMBER (l_addr)
&& VERIFY_MEMBER (l_prev))
? 1 : -1];
+#ifdef SHARED
+/* r_debug structs for secondary namespaces. The first namespace is
+ handled separately because its r_debug structure must overlap with
+ the public _r_debug symbol, so the first array element corresponds
+ to LM_ID_BASE + 1. See elf/dl-debug-symbols.S. */
+struct r_debug_extended _r_debug_array[DL_NNS - 1];
+
+/* Return the r_debug object for the namespace NS. */
+static inline struct r_debug_extended *
+get_rdebug (Lmid_t ns)
+{
+ if (ns == LM_ID_BASE)
+ return &_r_debug_extended;
+ else
+ return &_r_debug_array[ns - 1];
+}
+#else /* !SHARED */
+static inline struct r_debug_extended *
+get_rdebug (Lmid_t ns)
+{
+ return &_r_debug_extended; /* There is just one namespace. */
+}
+#endif /* !SHARED */
+
/* 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;
+ struct r_debug_extended *r = get_rdebug (ns);
if (r->base.r_map == NULL)
atomic_store_release (&r->base.r_map,
(void *) GL(dl_ns)[ns]._ns_loaded);
@@ -54,34 +74,7 @@ _dl_debug_update (Lmid_t ns)
struct r_debug *
_dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
{
- struct r_debug_extended *r, **pp = NULL;
-
- if (ns == LM_ID_BASE)
- {
- 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;
- }
- }
-
+ struct r_debug_extended *r = get_rdebug (ns);
if (r->base.r_brk == 0)
{
/* Tell the debugger where to find the map of loaded objects.
@@ -89,20 +82,36 @@ _dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
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;
+
+#ifdef SHARED
+ /* Add the new namespace to the linked list. This assumes that
+ namespaces are allocated in increasing order. 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. */
+
+ if (ns != LM_ID_BASE)
+ {
+ r->base.r_version = 2;
+ if (ns - 1 == LM_ID_BASE)
+ {
+ atomic_store_release (&_r_debug_extended.r_next, r);
+ /* Now there are multiple namespaces. */
+ atomic_store_release (&_r_debug_extended.base.r_version, 2);
+ }
+ else
+ /* Update r_debug_extended of the previous namespace. */
+ atomic_store_release (&_r_debug_array[ns - 2].r_next, r);
+ }
+ else
+#endif /* SHARED */
+ r->base.r_version = 1;
}
if (r->base.r_map == NULL)
atomic_store_release (&r->base.r_map,
(void *) GL(dl_ns)[ns]._ns_loaded);
- if (pp != NULL)
- {
- 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->base;
}
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 695f3910234e87da..e09edb01da3b5b90 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -375,8 +375,6 @@ struct rtld_global
size_t n_elements;
void (*free) (void *);
} _ns_unique_sym_table;
- /* Keep track of changes to each namespace' list. */
- struct r_debug_extended _ns_debug;
} _dl_ns[DL_NNS];
/* One higher than index of last used namespace. */
EXTERN size_t _dl_nns;

View File

@ -0,0 +1,17 @@
Downstream patch to keep ABI stable.
Bring back a dummy `struct r_debug` member in `GL (dl_ns)`, to preserve
`_rtld_global` layout.
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index e09edb01da3b5b90..3d9b90a22bfa6a7d 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -375,6 +375,8 @@ struct rtld_global
size_t n_elements;
void (*free) (void *);
} _ns_unique_sym_table;
+ /* Dummy structure to keep the ABI stable. */
+ struct r_debug _ns_debug_unused;
} _dl_ns[DL_NNS];
/* One higher than index of last used namespace. */
EXTERN size_t _dl_nns;

View File

@ -0,0 +1,26 @@
commit 7e84ac3a3ac9e7c4dc10de2ce65db971b9650e4d
Author: Florian Weimer <fweimer@redhat.com>
Date: Mon Sep 20 15:50:00 2021 +0200
elf: Include <sysdep.h> in elf/dl-debug-symbols.S
This is necessary to generate assembler marker sections on some
targets.
Reviewed-by: Szabolcs Nagy <szabolcs.nagy@arm.com>
diff --git a/elf/dl-debug-symbols.S b/elf/dl-debug-symbols.S
index b7e9f5d9470c4da2..28456ab1f237ea87 100644
--- a/elf/dl-debug-symbols.S
+++ b/elf/dl-debug-symbols.S
@@ -18,6 +18,10 @@
<https://www.gnu.org/licenses/>. */
#include <rtld-sizes.h>
+#include <sysdep.h>
+
+/* Some targets define a macro to denote the zero register. */
+#undef zero
/* 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. */

View File

@ -0,0 +1,32 @@
commit 1b5e65ef6a442fdccf88d43c3048f98292d85631
Author: Paul Pluzhnikov <ppluzhnikov@google.com>
Date: Sat Mar 25 21:27:01 2023 +0000
Minor: don't call _dl_debug_update (which can have side effects) inside assert
diff --git a/elf/dl-open.c b/elf/dl-open.c
index eef724f7e9b2211d..0d2b4cd4785a226a 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -596,7 +596,9 @@ dl_open_worker_begin (void *a)
if ((mode & RTLD_GLOBAL) && new->l_global == 0)
add_to_global_update (new);
- assert (_dl_debug_update (args->nsid)->r_state == RT_CONSISTENT);
+ const int r_state __attribute__ ((unused))
+ = _dl_debug_update (args->nsid)->r_state;
+ assert (r_state == RT_CONSISTENT);
return;
}
@@ -916,7 +918,9 @@ no more namespaces available for dlmopen()"));
_dl_signal_exception (errcode, &exception, NULL);
}
- assert (_dl_debug_update (args.nsid)->r_state == RT_CONSISTENT);
+ const int r_state __attribute__ ((unused))
+ = _dl_debug_update (args.nsid)->r_state;
+ assert (r_state == RT_CONSISTENT);
/* Release the lock. */
__rtld_lock_unlock_recursive (GL(dl_load_lock));

View File

@ -0,0 +1,275 @@
commit 9897ced8e78db5d813166a7ccccfd5a42c69ef20
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Oct 25 16:50:10 2024 +0200
elf: Run constructors on cyclic recursive dlopen (bug 31986)
This is conceptually similar to the reported bug, but does not
depend on auditing. The fix is simple: just complete execution
of the constructors. This exposed the fact that the link map
for statically linked executables does not have l_init_called
set, even though constructors have run.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
elf/Makefile (fix local test re-ordering)
diff --git a/elf/Makefile b/elf/Makefile
index b074cc29664b3e20..dc774b083eda202b 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -398,12 +398,15 @@ tests += \
tst-dl-is_dso \
tst-dlclose-lazy \
tst-dlmodcount \
- tst-dlmopen1 \
- tst-dlmopen3 \
- tst-dlmopen4 \
tst-dlmopen-dlerror \
tst-dlmopen-gethostbyname \
tst-dlmopen-twice \
+ tst-dlmopen1 \
+ tst-dlmopen3 \
+ tst-dlmopen4 \
+ tst-dlopen-recurse \
+ tst-dlopen-self \
+ tst-dlopen-tlsmodid \
tst-dlopen-tlsreinit1 \
tst-dlopen-tlsreinit2 \
tst-dlopen-tlsreinit3 \
@@ -411,8 +414,6 @@ tests += \
tst-dlopenfail \
tst-dlopenfail-2 \
tst-dlopenrpath \
- tst-dlopen-self \
- tst-dlopen-tlsmodid \
tst-dlsym-error \
tst-filterobj \
tst-filterobj-dlopen \
@@ -775,13 +776,15 @@ modules-names = \
tst-deep1mod1 \
tst-deep1mod2 \
tst-deep1mod3 \
- tst-dlmopen1mod \
tst-dlclose-lazy-mod1 \
tst-dlclose-lazy-mod2 \
tst-dlmopen-dlerror-mod \
tst-dlmopen-gethostbyname-mod \
tst-dlmopen-twice-mod1 \
tst-dlmopen-twice-mod2 \
+ tst-dlmopen1mod \
+ tst-dlopen-recursemod1 \
+ tst-dlopen-recursemod2 \
tst-dlopen-sgid-mod \
tst-dlopen-tlsreinitmod1 \
tst-dlopen-tlsreinitmod2 \
@@ -2856,6 +2859,9 @@ tst-dlopen-tlsreinit3-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
$(objpfx)tst-dlopen-tlsreinit4.out: $(objpfx)tst-auditmod1.so
tst-dlopen-tlsreinit4-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
+$(objpfx)tst-dlopen-recurse.out: $(objpfx)tst-dlopen-recursemod1.so
+$(objpfx)tst-dlopen-recursemod1.so: $(objpfx)tst-dlopen-recursemod2.so
+
LDFLAGS-tst-hash-collision1-mod.so = -Wl,--hash-style=both
$(objpfx)tst-hash-collision1: $(objpfx)tst-hash-collision1-mod.so
LDFLAGS-tst-hash-collision1-mod-gnu.so = -Wl,--hash-style=gnu
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 0d2b4cd4785a226a..0b0bfb8acda28caa 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -600,6 +600,14 @@ dl_open_worker_begin (void *a)
= _dl_debug_update (args->nsid)->r_state;
assert (r_state == RT_CONSISTENT);
+ /* Do not return without calling the (supposedly new) map's
+ constructor. This case occurs if a dependency of a directly
+ opened map has a constructor that calls dlopen again on the
+ initially opened map. The new map is initialized last, so
+ checking only it is enough. */
+ if (!new->l_init_called)
+ _dl_catch_exception (NULL, call_dl_init, args);
+
return;
}
diff --git a/elf/dl-support.c b/elf/dl-support.c
index 00abc2d8056c78b0..f4dc9c61a2637f8b 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -103,6 +103,7 @@ static struct link_map _dl_main_map =
.l_used = 1,
.l_tls_offset = NO_TLS_OFFSET,
.l_serial = 1,
+ .l_init_called = 1,
};
/* Namespace information. */
diff --git a/elf/tst-dlopen-recurse.c b/elf/tst-dlopen-recurse.c
new file mode 100644
index 0000000000000000..c7fb379d373c6e77
--- /dev/null
+++ b/elf/tst-dlopen-recurse.c
@@ -0,0 +1,34 @@
+/* Test that recursive dlopen runs constructors before return (bug 31986).
+ Copyright (C) 2024 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 <support/check.h>
+#include <support/xdlfcn.h>
+
+static int
+do_test (void)
+{
+ void *handle = xdlopen ("tst-dlopen-recursemod1.so", RTLD_NOW);
+ int *status = dlsym (handle, "recursemod1_status");
+ printf ("info: recursemod1_status == %d (from main)\n", *status);
+ TEST_COMPARE (*status, 2);
+ xdlclose (handle);
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-dlopen-recursemod1.c b/elf/tst-dlopen-recursemod1.c
new file mode 100644
index 0000000000000000..5e0cc0eb8c32d6d4
--- /dev/null
+++ b/elf/tst-dlopen-recursemod1.c
@@ -0,0 +1,50 @@
+/* Directly opened test module that gets recursively opened again.
+ Copyright (C) 2024 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 <stdlib.h>
+#include <support/xdlfcn.h>
+
+int recursemod1_status;
+
+/* Force linking against st-dlopen-recursemod2.so. Also allows
+ checking for relocation. */
+extern int recursemod2_status;
+int *force_recursemod2_reference = &recursemod2_status;
+
+static void __attribute__ ((constructor))
+init (void)
+{
+ ++recursemod1_status;
+ printf ("info: tst-dlopen-recursemod1.so constructor called (status %d)\n",
+ recursemod1_status);
+}
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+ /* The recursemod1_status variable was incremented in the
+ tst-dlopen-recursemod2.so constructor. */
+ printf ("info: tst-dlopen-recursemod1.so destructor called (status %d)\n",
+ recursemod1_status);
+ if (recursemod1_status != 2)
+ {
+ puts ("error: recursemod1_status == 2 expected");
+ exit (1);
+ }
+}
diff --git a/elf/tst-dlopen-recursemod2.c b/elf/tst-dlopen-recursemod2.c
new file mode 100644
index 0000000000000000..edd2f2526b877810
--- /dev/null
+++ b/elf/tst-dlopen-recursemod2.c
@@ -0,0 +1,66 @@
+/* Indirectly opened module that recursively opens the directly opened module.
+ Copyright (C) 2024 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 <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int recursemod2_status;
+
+static void __attribute__ ((constructor))
+init (void)
+{
+ ++recursemod2_status;
+ printf ("info: tst-dlopen-recursemod2.so constructor called (status %d)\n",
+ recursemod2_status);
+ void *handle = dlopen ("tst-dlopen-recursemod1.so", RTLD_NOW);
+ if (handle == NULL)
+ {
+ printf ("error: dlopen: %s\n", dlerror ());
+ exit (1);
+ }
+ int *status = dlsym (handle, "recursemod1_status");
+ if (status == NULL)
+ {
+ printf ("error: dlsym: %s\n", dlerror ());
+ exit (1);
+ }
+ printf ("info: recursemod1_status == %d\n", *status);
+ if (*status != 1)
+ {
+ puts ("error: recursemod1_status == 1 expected");
+ exit (1);
+ }
+ ++*status;
+ printf ("info: recursemod1_status == %d\n", *status);
+
+ int **mod2_status = dlsym (handle, "force_recursemod2_reference");
+ if (mod2_status == NULL || *mod2_status != &recursemod2_status)
+ {
+ puts ("error: invalid recursemod2_status address in"
+ " tst-dlopen-recursemod1.so");
+ exit (1);
+ }
+}
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+ printf ("info: tst-dlopen-recursemod2.so destructor called (status %d)\n",
+ recursemod2_status);
+}

View File

@ -0,0 +1,103 @@
commit e096b7a1896886eb7dd2732ccbf1184b0eec9a63
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Oct 25 16:50:10 2024 +0200
elf: Signal LA_ACT_CONSISTENT to auditors after RT_CONSISTENT switch
Auditors can call into the dynamic loader again if
LA_ACT_CONSISTENT, and those recursive calls could observe
r_state != RT_CONSISTENT.
We should consider failing dlopen/dlmopen/dlclose if
r_state != RT_CONSISTENT. The dynamic linker is probably not
in a state in which it can handle reentrant calls. This
needs further investigation.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
elf/rtld.c (kept SHARED guard downstream)
diff --git a/elf/dl-close.c b/elf/dl-close.c
index bf699e58d753a1e2..8a4c3528a124d4e7 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -759,6 +759,11 @@ _dl_close_worker (struct link_map *map, bool force)
/* TLS is cleaned up for the unloaded modules. */
__rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
+ /* Notify the debugger those objects are finalized and gone. */
+ r->r_state = RT_CONSISTENT;
+ _dl_debug_state ();
+ LIBC_PROBE (unmap_complete, 2, nsid, r);
+
#ifdef SHARED
/* Auditing checkpoint: we have deleted all objects. Also, do not notify
auditors of the cleanup of a failed audit module loading attempt. */
@@ -771,11 +776,6 @@ _dl_close_worker (struct link_map *map, bool force)
--GL(dl_nns);
while (GL(dl_ns)[GL(dl_nns) - 1]._ns_loaded == NULL);
- /* Notify the debugger those objects are finalized and gone. */
- r->r_state = RT_CONSISTENT;
- _dl_debug_state ();
- LIBC_PROBE (unmap_complete, 2, nsid, r);
-
/* Recheck if we need to retry, release the lock. */
out:
if (dl_close_state == rerun)
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 0b0bfb8acda28caa..5095ea4f96b6cf49 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -638,17 +638,17 @@ dl_open_worker_begin (void *a)
#endif
}
-#ifdef SHARED
- /* Auditing checkpoint: we have added all objects. */
- _dl_audit_activity_nsid (new->l_ns, LA_ACT_CONSISTENT);
-#endif
-
/* Notify the debugger all new objects are now ready to go. */
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);
+#ifdef SHARED
+ /* Auditing checkpoint: we have added all objects. */
+ _dl_audit_activity_nsid (new->l_ns, LA_ACT_CONSISTENT);
+#endif
+
_dl_open_check (new);
/* Print scope information. */
diff --git a/elf/rtld.c b/elf/rtld.c
index 7d8ed0ac1188d527..cd233174c9d944b2 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2524,11 +2524,6 @@ dl_main (const ElfW(Phdr) *phdr,
_dl_relocate_object might need to call `mprotect' for DT_TEXTREL. */
_dl_sysdep_start_cleanup ();
-#ifdef SHARED
- /* Auditing checkpoint: we have added all objects. */
- _dl_audit_activity_nsid (LM_ID_BASE, LA_ACT_CONSISTENT);
-#endif
-
/* 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_update (LM_ID_BASE);
@@ -2536,6 +2531,11 @@ dl_main (const ElfW(Phdr) *phdr,
_dl_debug_state ();
LIBC_PROBE (init_complete, 2, LM_ID_BASE, r);
+#ifdef SHARED
+ /* Auditing checkpoint: we have added all objects. */
+ _dl_audit_activity_nsid (LM_ID_BASE, LA_ACT_CONSISTENT);
+#endif
+
#if defined USE_LDCONFIG && !defined MAP_COPY
/* We must munmap() the cache file. */
_dl_unload_cache ();

View File

@ -0,0 +1,342 @@
commit 43db5e2c0672cae7edea7c9685b22317eae25471
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Oct 25 16:50:10 2024 +0200
elf: Signal RT_CONSISTENT after relocation processing in dlopen (bug 31986)
Previously, a la_activity audit event was generated before
relocation processing completed. This does did not match what
happened during initial startup in elf/rtld.c (towards the end
of dl_main). It also caused various problems if an auditor
tried to open the same shared object again using dlmopen:
If it was the directly loaded object, it had a search scope
associated with it, so the early exit in dl_open_worker_begin
was taken even though the object was unrelocated. This caused
the r_state == RT_CONSISTENT assert to fail. Avoidance of the
assert also depends on reversing the order of r_state update
and auditor event (already implemented in a previous commit).
At the later point, args->map can be NULL due to failure,
so use the assigned namespace ID instead if that is available.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
elf/Makefile (fixup context)
diff --git a/elf/Makefile b/elf/Makefile
index dc774b083eda202b..73deb69f5a3c9150 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -404,6 +404,7 @@ tests += \
tst-dlmopen1 \
tst-dlmopen3 \
tst-dlmopen4 \
+ tst-dlopen-auditdup \
tst-dlopen-recurse \
tst-dlopen-self \
tst-dlopen-tlsmodid \
@@ -783,6 +784,8 @@ modules-names = \
tst-dlmopen-twice-mod1 \
tst-dlmopen-twice-mod2 \
tst-dlmopen1mod \
+ tst-dlopen-auditdup-auditmod \
+ tst-dlopen-auditdupmod \
tst-dlopen-recursemod1 \
tst-dlopen-recursemod2 \
tst-dlopen-sgid-mod \
@@ -2861,6 +2864,9 @@ tst-dlopen-tlsreinit4-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
$(objpfx)tst-dlopen-recurse.out: $(objpfx)tst-dlopen-recursemod1.so
$(objpfx)tst-dlopen-recursemod1.so: $(objpfx)tst-dlopen-recursemod2.so
+tst-dlopen-auditdup-ENV = LD_AUDIT=$(objpfx)tst-dlopen-auditdup-auditmod.so
+$(objpfx)tst-dlopen-auditdup.out: \
+ $(objpfx)tst-dlopen-auditdupmod.so $(objpfx)tst-dlopen-auditdup-auditmod.so
LDFLAGS-tst-hash-collision1-mod.so = -Wl,--hash-style=both
$(objpfx)tst-hash-collision1: $(objpfx)tst-hash-collision1-mod.so
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 5095ea4f96b6cf49..6ec1ca033bbe7859 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -575,6 +575,14 @@ dl_open_worker_begin (void *a)
_dl_debug_printf ("opening file=%s [%lu]; direct_opencount=%u\n\n",
new->l_name, new->l_ns, new->l_direct_opencount);
+#ifdef SHARED
+ /* No relocation processing on this execution path. But
+ relocation has not been performed for static
+ position-dependent executables, so disable the assert for
+ static linking. */
+ assert (new->l_relocated);
+#endif
+
/* If the user requested the object to be in the global
namespace but it is not so far, prepare to add it now. This
can raise an exception to do a malloc failure. */
@@ -596,10 +604,6 @@ dl_open_worker_begin (void *a)
if ((mode & RTLD_GLOBAL) && new->l_global == 0)
add_to_global_update (new);
- const int r_state __attribute__ ((unused))
- = _dl_debug_update (args->nsid)->r_state;
- assert (r_state == RT_CONSISTENT);
-
/* Do not return without calling the (supposedly new) map's
constructor. This case occurs if a dependency of a directly
opened map has a constructor that calls dlopen again on the
@@ -638,17 +642,6 @@ dl_open_worker_begin (void *a)
#endif
}
- /* Notify the debugger all new objects are now ready to go. */
- 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);
-
-#ifdef SHARED
- /* Auditing checkpoint: we have added all objects. */
- _dl_audit_activity_nsid (new->l_ns, LA_ACT_CONSISTENT);
-#endif
-
_dl_open_check (new);
/* Print scope information. */
@@ -695,6 +688,7 @@ dl_open_worker_begin (void *a)
created dlmopen namespaces. Do not do this for static dlopen
because libc has relocations against ld.so, which may not have
been relocated at this point. */
+ struct r_debug *r = _dl_debug_update (args->nsid);
#ifdef SHARED
if (GL(dl_ns)[args->nsid].libc_map != NULL)
_dl_open_relocate_one_object (args, r, GL(dl_ns)[args->nsid].libc_map,
@@ -782,6 +776,26 @@ dl_open_worker (void *a)
__rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
+ /* Auditing checkpoint and debugger signalling. Do this even on
+ error, so that dlopen exists with consistent state. */
+ if (args->nsid >= 0 || args->map != NULL)
+ {
+ Lmid_t nsid = args->map != NULL ? args->map->l_ns : args->nsid;
+ struct r_debug *r = _dl_debug_update (nsid);
+#ifdef SHARED
+ bool was_not_consistent = r->r_state != RT_CONSISTENT;
+#endif
+ r->r_state = RT_CONSISTENT;
+ _dl_debug_state ();
+ LIBC_PROBE (map_complete, 3, nsid, r, new);
+
+#ifdef SHARED
+ if (was_not_consistent)
+ /* Avoid redudant/recursive signalling. */
+ _dl_audit_activity_nsid (nsid, LA_ACT_CONSISTENT);
+#endif
+ }
+
if (__glibc_unlikely (ex.errstring != NULL))
/* Reraise the error. */
_dl_signal_exception (err, &ex, NULL);
diff --git a/elf/tst-dlopen-auditdup-auditmod.c b/elf/tst-dlopen-auditdup-auditmod.c
new file mode 100644
index 0000000000000000..9b67295e94d03e7a
--- /dev/null
+++ b/elf/tst-dlopen-auditdup-auditmod.c
@@ -0,0 +1,100 @@
+/* Auditor that opens again an object that just has been opened.
+ Copyright (C) 2024 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 <dlfcn.h>
+#include <link.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+unsigned int
+la_version (unsigned int v)
+{
+ return LAV_CURRENT;
+}
+
+static bool trigger_on_la_activity;
+
+unsigned int
+la_objopen (struct link_map *map, Lmid_t lmid, uintptr_t *cookie)
+{
+ printf ("info: la_objopen: \"%s\"\n", map->l_name);
+ if (strstr (map->l_name, "/tst-dlopen-auditdupmod.so") != NULL)
+ trigger_on_la_activity = true;
+ return 0;
+}
+
+void
+la_activity (uintptr_t *cookie, unsigned int flag)
+{
+ static unsigned int calls;
+ ++calls;
+ printf ("info: la_activity: call %u (flag %u)\n", calls, flag);
+ fflush (stdout);
+ if (trigger_on_la_activity)
+ {
+ /* Avoid triggering on the dlmopen call below. */
+ static bool recursion;
+ if (recursion)
+ return;
+ recursion = true;
+
+ puts ("info: about to dlmopen tst-dlopen-auditdupmod.so");
+ fflush (stdout);
+ void *handle = dlmopen (LM_ID_BASE, "tst-dlopen-auditdupmod.so",
+ RTLD_NOW);
+ if (handle == NULL)
+ {
+ printf ("error: dlmopen: %s\n", dlerror ());
+ fflush (stdout);
+ _exit (1);
+ }
+
+ /* Check that the constructor has run. */
+ int *status = dlsym (handle, "auditdupmod_status");
+ if (status == NULL)
+ {
+ printf ("error: dlsym: %s\n", dlerror ());
+ fflush (stdout);
+ _exit (1);
+ }
+ printf ("info: auditdupmod_status == %d\n", *status);
+ if (*status != 1)
+ {
+ puts ("error: auditdupmod_status == 1 expected");
+ fflush (stdout);
+ _exit (1);
+ }
+ /* Checked in the destructor and the main program. */
+ ++*status;
+ printf ("info: auditdupmod_status == %d\n", *status);
+
+ /* Check that the module has been relocated. */
+ int **status_address = dlsym (handle, "auditdupmod_status_address");
+ if (status_address == NULL || *status_address != status)
+ {
+ puts ("error: invalid auditdupmod_status address in"
+ " tst-dlopen-auditdupmod.so");
+ fflush (stdout);
+ _exit (1);
+ }
+
+ fflush (stdout);
+ }
+}
diff --git a/elf/tst-dlopen-auditdup.c b/elf/tst-dlopen-auditdup.c
new file mode 100644
index 0000000000000000..d022c58ae3091da1
--- /dev/null
+++ b/elf/tst-dlopen-auditdup.c
@@ -0,0 +1,36 @@
+/* Test that recursive dlopen from auditor works (bug 31986).
+ Copyright (C) 2024 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 <support/check.h>
+#include <support/xdlfcn.h>
+
+static int
+do_test (void)
+{
+ puts ("info: about to dlopen tst-dlopen-auditdupmod.so");
+ fflush (stdout);
+ void *handle = xdlopen ("tst-dlopen-auditdupmod.so", RTLD_NOW);
+ int *status = xdlsym (handle, "auditdupmod_status");
+ printf ("info: auditdupmod_status == %d (from main)\n", *status);
+ TEST_COMPARE (*status, 2);
+ xdlclose (handle);
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-dlopen-auditdupmod.c b/elf/tst-dlopen-auditdupmod.c
new file mode 100644
index 0000000000000000..59b7e21daa8212df
--- /dev/null
+++ b/elf/tst-dlopen-auditdupmod.c
@@ -0,0 +1,48 @@
+/* Directly opened test module that gets reopened from the auditor.
+ Copyright (C) 2024 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 <stdlib.h>
+#include <support/xdlfcn.h>
+
+int auditdupmod_status;
+
+/* Used to check for successful relocation processing. */
+int *auditdupmod_status_address = &auditdupmod_status;
+
+static void __attribute__ ((constructor))
+init (void)
+{
+ ++auditdupmod_status;
+ printf ("info: tst-dlopen-auditdupmod.so constructor called (status %d)\n",
+ auditdupmod_status);
+}
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+ /* The tst-dlopen-auditdup-auditmod.so auditor incremented
+ auditdupmod_status. */
+ printf ("info: tst-dlopen-auditdupmod.so destructor called (status %d)\n",
+ auditdupmod_status);
+ if (auditdupmod_status != 2)
+ {
+ puts ("error: auditdupmod_status == 2 expected");
+ exit (1);
+ }
+}

View File

@ -0,0 +1,272 @@
commit 95129e6b8fabdaa8cd8a4a5cc20be0f4cb0ba59f
Author: Florian Weimer <fweimer@redhat.com>
Date: Mon Oct 28 14:45:30 2024 +0100
Revert "elf: Run constructors on cyclic recursive dlopen (bug 31986)"
This reverts commit 9897ced8e78db5d813166a7ccccfd5a42c69ef20.
Adjust the test expectations in elf/tst-dlopen-auditdup-auditmod.c
accordingly.
Conflicts:
elf/Makefile (fixup context)
diff --git a/elf/Makefile b/elf/Makefile
index 73deb69f5a3c9150..a358ad7ff0eb2af7 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -405,7 +405,6 @@ tests += \
tst-dlmopen3 \
tst-dlmopen4 \
tst-dlopen-auditdup \
- tst-dlopen-recurse \
tst-dlopen-self \
tst-dlopen-tlsmodid \
tst-dlopen-tlsreinit1 \
@@ -786,8 +785,6 @@ modules-names = \
tst-dlmopen1mod \
tst-dlopen-auditdup-auditmod \
tst-dlopen-auditdupmod \
- tst-dlopen-recursemod1 \
- tst-dlopen-recursemod2 \
tst-dlopen-sgid-mod \
tst-dlopen-tlsreinitmod1 \
tst-dlopen-tlsreinitmod2 \
@@ -2862,8 +2859,6 @@ tst-dlopen-tlsreinit3-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
$(objpfx)tst-dlopen-tlsreinit4.out: $(objpfx)tst-auditmod1.so
tst-dlopen-tlsreinit4-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
-$(objpfx)tst-dlopen-recurse.out: $(objpfx)tst-dlopen-recursemod1.so
-$(objpfx)tst-dlopen-recursemod1.so: $(objpfx)tst-dlopen-recursemod2.so
tst-dlopen-auditdup-ENV = LD_AUDIT=$(objpfx)tst-dlopen-auditdup-auditmod.so
$(objpfx)tst-dlopen-auditdup.out: \
$(objpfx)tst-dlopen-auditdupmod.so $(objpfx)tst-dlopen-auditdup-auditmod.so
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 6ec1ca033bbe7859..6557c2fd7ca0bbfe 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -604,14 +604,6 @@ dl_open_worker_begin (void *a)
if ((mode & RTLD_GLOBAL) && new->l_global == 0)
add_to_global_update (new);
- /* Do not return without calling the (supposedly new) map's
- constructor. This case occurs if a dependency of a directly
- opened map has a constructor that calls dlopen again on the
- initially opened map. The new map is initialized last, so
- checking only it is enough. */
- if (!new->l_init_called)
- _dl_catch_exception (NULL, call_dl_init, args);
-
return;
}
diff --git a/elf/dl-support.c b/elf/dl-support.c
index f4dc9c61a2637f8b..00abc2d8056c78b0 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -103,7 +103,6 @@ static struct link_map _dl_main_map =
.l_used = 1,
.l_tls_offset = NO_TLS_OFFSET,
.l_serial = 1,
- .l_init_called = 1,
};
/* Namespace information. */
diff --git a/elf/tst-dlopen-auditdup-auditmod.c b/elf/tst-dlopen-auditdup-auditmod.c
index 9b67295e94d03e7a..270a595ec4de1439 100644
--- a/elf/tst-dlopen-auditdup-auditmod.c
+++ b/elf/tst-dlopen-auditdup-auditmod.c
@@ -66,7 +66,11 @@ la_activity (uintptr_t *cookie, unsigned int flag)
_exit (1);
}
- /* Check that the constructor has run. */
+ /* Check that the constructor has not run. Running the
+ constructor would require constructing its dependencies, but
+ the constructor call that triggered this auditing activity
+ has not completed, and constructors among the dependencies
+ may not be able to deal with that. */
int *status = dlsym (handle, "auditdupmod_status");
if (status == NULL)
{
@@ -75,9 +79,9 @@ la_activity (uintptr_t *cookie, unsigned int flag)
_exit (1);
}
printf ("info: auditdupmod_status == %d\n", *status);
- if (*status != 1)
+ if (*status != 0)
{
- puts ("error: auditdupmod_status == 1 expected");
+ puts ("error: auditdupmod_status == 0 expected");
fflush (stdout);
_exit (1);
}
diff --git a/elf/tst-dlopen-recurse.c b/elf/tst-dlopen-recurse.c
deleted file mode 100644
index c7fb379d373c6e77..0000000000000000
--- a/elf/tst-dlopen-recurse.c
+++ /dev/null
@@ -1,34 +0,0 @@
-/* Test that recursive dlopen runs constructors before return (bug 31986).
- Copyright (C) 2024 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 <support/check.h>
-#include <support/xdlfcn.h>
-
-static int
-do_test (void)
-{
- void *handle = xdlopen ("tst-dlopen-recursemod1.so", RTLD_NOW);
- int *status = dlsym (handle, "recursemod1_status");
- printf ("info: recursemod1_status == %d (from main)\n", *status);
- TEST_COMPARE (*status, 2);
- xdlclose (handle);
- return 0;
-}
-
-#include <support/test-driver.c>
diff --git a/elf/tst-dlopen-recursemod1.c b/elf/tst-dlopen-recursemod1.c
deleted file mode 100644
index 5e0cc0eb8c32d6d4..0000000000000000
--- a/elf/tst-dlopen-recursemod1.c
+++ /dev/null
@@ -1,50 +0,0 @@
-/* Directly opened test module that gets recursively opened again.
- Copyright (C) 2024 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 <stdlib.h>
-#include <support/xdlfcn.h>
-
-int recursemod1_status;
-
-/* Force linking against st-dlopen-recursemod2.so. Also allows
- checking for relocation. */
-extern int recursemod2_status;
-int *force_recursemod2_reference = &recursemod2_status;
-
-static void __attribute__ ((constructor))
-init (void)
-{
- ++recursemod1_status;
- printf ("info: tst-dlopen-recursemod1.so constructor called (status %d)\n",
- recursemod1_status);
-}
-
-static void __attribute__ ((destructor))
-fini (void)
-{
- /* The recursemod1_status variable was incremented in the
- tst-dlopen-recursemod2.so constructor. */
- printf ("info: tst-dlopen-recursemod1.so destructor called (status %d)\n",
- recursemod1_status);
- if (recursemod1_status != 2)
- {
- puts ("error: recursemod1_status == 2 expected");
- exit (1);
- }
-}
diff --git a/elf/tst-dlopen-recursemod2.c b/elf/tst-dlopen-recursemod2.c
deleted file mode 100644
index edd2f2526b877810..0000000000000000
--- a/elf/tst-dlopen-recursemod2.c
+++ /dev/null
@@ -1,66 +0,0 @@
-/* Indirectly opened module that recursively opens the directly opened module.
- Copyright (C) 2024 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 <dlfcn.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-int recursemod2_status;
-
-static void __attribute__ ((constructor))
-init (void)
-{
- ++recursemod2_status;
- printf ("info: tst-dlopen-recursemod2.so constructor called (status %d)\n",
- recursemod2_status);
- void *handle = dlopen ("tst-dlopen-recursemod1.so", RTLD_NOW);
- if (handle == NULL)
- {
- printf ("error: dlopen: %s\n", dlerror ());
- exit (1);
- }
- int *status = dlsym (handle, "recursemod1_status");
- if (status == NULL)
- {
- printf ("error: dlsym: %s\n", dlerror ());
- exit (1);
- }
- printf ("info: recursemod1_status == %d\n", *status);
- if (*status != 1)
- {
- puts ("error: recursemod1_status == 1 expected");
- exit (1);
- }
- ++*status;
- printf ("info: recursemod1_status == %d\n", *status);
-
- int **mod2_status = dlsym (handle, "force_recursemod2_reference");
- if (mod2_status == NULL || *mod2_status != &recursemod2_status)
- {
- puts ("error: invalid recursemod2_status address in"
- " tst-dlopen-recursemod1.so");
- exit (1);
- }
-}
-
-static void __attribute__ ((destructor))
-fini (void)
-{
- printf ("info: tst-dlopen-recursemod2.so destructor called (status %d)\n",
- recursemod2_status);
-}

View File

@ -0,0 +1,218 @@
commit d604f9c500570e80febfcc6a52b63a002b466f35
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Mar 11 15:30:52 2025 +0100
elf: Test dlopen (NULL, RTLD_LAZY) from an ELF constructor
This call must not complete initialization of all shared objects
in the global scope because the ELF constructor which makes the call
likely has not finished initialization. Calling more constructors
at this point would expose those to a partially constructed
dependency.
This completes the revert of commit 9897ced8e78db5d813166a7ccccfd5a
("elf: Run constructors on cyclic recursive dlopen (bug 31986)").
Conflicts:
elf/Makefile (fixup context)
diff --git a/elf/Makefile b/elf/Makefile
index a358ad7ff0eb2af7..3a50ca90366aec94 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -405,6 +405,7 @@ tests += \
tst-dlmopen3 \
tst-dlmopen4 \
tst-dlopen-auditdup \
+ tst-dlopen-constructor-null \
tst-dlopen-self \
tst-dlopen-tlsmodid \
tst-dlopen-tlsreinit1 \
@@ -785,6 +786,8 @@ modules-names = \
tst-dlmopen1mod \
tst-dlopen-auditdup-auditmod \
tst-dlopen-auditdupmod \
+ tst-dlopen-constructor-null-mod1 \
+ tst-dlopen-constructor-null-mod2 \
tst-dlopen-sgid-mod \
tst-dlopen-tlsreinitmod1 \
tst-dlopen-tlsreinitmod2 \
@@ -2937,3 +2940,9 @@ $(objpfx)tst-nolink-libc-2: $(objpfx)tst-nolink-libc.o
-Wl,--dynamic-linker=$(objpfx)ld.so
$(objpfx)tst-nolink-libc-2.out: $(objpfx)tst-nolink-libc-2 $(objpfx)ld.so
$< > $@ 2>&1; $(evaluate-test)
+
+$(objpfx)tst-dlopen-constructor-null: \
+ $(objpfx)tst-dlopen-constructor-null-mod1.so \
+ $(objpfx)tst-dlopen-constructor-null-mod2.so
+$(objpfx)tst-dlopen-constructor-null-mod2.so: \
+ $(objpfx)tst-dlopen-constructor-null-mod1.so
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 6557c2fd7ca0bbfe..c225654822ee3520 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -604,6 +604,16 @@ dl_open_worker_begin (void *a)
if ((mode & RTLD_GLOBAL) && new->l_global == 0)
add_to_global_update (new);
+ /* It is not possible to run the ELF constructor for the new
+ link map if it has not executed yet: If this dlopen call came
+ from an ELF constructor that has not put that object into a
+ consistent state, completing initialization for the entire
+ scope will expose objects that have this partially
+ constructed object among its dependencies to this
+ inconsistent state. This could happen even with a benign
+ dlopen (NULL, RTLD_LAZY) call from a constructor of an
+ initially loaded shared object. */
+
return;
}
diff --git a/elf/tst-dlopen-constructor-null-mod1.c b/elf/tst-dlopen-constructor-null-mod1.c
new file mode 100644
index 0000000000000000..70a7a0ad46a1a666
--- /dev/null
+++ b/elf/tst-dlopen-constructor-null-mod1.c
@@ -0,0 +1,55 @@
+/* Module calling dlopen (NULL, RTLD_LAZY) to obtain the global scope.
+ Copyright (C) 2024 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 <dlfcn.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int mod1_status;
+
+static void __attribute__ ((constructor))
+init (void)
+{
+ puts ("info: tst-dlopen-constructor-null-mod1.so constructor");
+
+ void *handle = dlopen (NULL, RTLD_LAZY);
+ if (handle == NULL)
+ {
+ printf ("error: %s\n", dlerror ());
+ exit (1);
+ }
+ puts ("info: dlopen returned");
+ if (dlsym (handle, "malloc") != malloc)
+ {
+ puts ("error: dlsym did not produce expected result");
+ exit (1);
+ }
+ dlclose (handle);
+
+ /* Check that the second module's constructor has not executed. */
+ if (getenv ("mod2_status") != NULL)
+ {
+ printf ("error: mod2_status environment variable set: %s\n",
+ getenv ("mod2_status"));
+ exit (1);
+ }
+
+ /* Communicate to the second module that the constructor executed. */
+ mod1_status = 1;
+}
diff --git a/elf/tst-dlopen-constructor-null-mod2.c b/elf/tst-dlopen-constructor-null-mod2.c
new file mode 100644
index 0000000000000000..d6e945beaec04815
--- /dev/null
+++ b/elf/tst-dlopen-constructor-null-mod2.c
@@ -0,0 +1,37 @@
+/* Module whose constructor should not be invoked by dlopen (NULL, RTLD_LAZY).
+ Copyright (C) 2024 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 <stdlib.h>
+
+extern int mod1_status;
+int mod2_status;
+
+static void __attribute__ ((constructor))
+init (void)
+{
+ printf ("info: tst-dlopen-constructor-null-mod2.so constructor"
+ " (mod1_status=%d)", mod1_status);
+ if (!(mod1_status == 1 && mod2_status == 0))
+ {
+ puts ("error: mod1_status == 1 && mod2_status == 0 expected");
+ exit (1);
+ }
+ setenv ("mod2_status", "constructed", 1);
+ mod2_status = 1;
+}
diff --git a/elf/tst-dlopen-constructor-null.c b/elf/tst-dlopen-constructor-null.c
new file mode 100644
index 0000000000000000..db90643325c5235f
--- /dev/null
+++ b/elf/tst-dlopen-constructor-null.c
@@ -0,0 +1,38 @@
+/* Verify that dlopen (NULL, RTLD_LAZY) does not complete initialization.
+ Copyright (C) 2024 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/>. */
+
+/* This test mimics what the glvndSetupPthreads function in libglvnd
+ does. */
+
+#include <stdlib.h>
+#include <support/check.h>
+
+/* Defined and initialized in the shared objects. */
+extern int mod1_status;
+extern int mod2_status;
+
+static int
+do_test (void)
+{
+ TEST_COMPARE (mod1_status, 1);
+ TEST_COMPARE (mod2_status, 1);
+ TEST_COMPARE_STRING (getenv ("mod2_status"), "constructed");
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,24 @@
commit ac73067cb7a328bf106ecd041c020fc61be7e087
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Oct 25 17:41:53 2024 +0200
elf: Fix map_complete Systemtap probe in dl_open_worker
The refactoring did not take the change of variable into account.
Fixes commit 43db5e2c0672cae7edea7c9685b22317eae25471
("elf: Signal RT_CONSISTENT after relocation processing in dlopen
(bug 31986)").
diff --git a/elf/dl-open.c b/elf/dl-open.c
index c225654822ee3520..1e61e402455da666 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -789,7 +789,7 @@ dl_open_worker (void *a)
#endif
r->r_state = RT_CONSISTENT;
_dl_debug_state ();
- LIBC_PROBE (map_complete, 3, nsid, r, new);
+ LIBC_PROBE (map_complete, 3, nsid, r, args->map);
#ifdef SHARED
if (was_not_consistent)

View File

@ -0,0 +1,120 @@
commit 8f8dd904c4a2207699bb666f30acceb5209c8d3f
Author: Florian Weimer <fweimer@redhat.com>
Date: Wed Nov 6 10:33:44 2024 +0100
elf: rtld_multiple_ref is always true
For a long time, libc.so.6 has dependend on ld.so, which
means that there is a reference to ld.so in all processes,
and rtld_multiple_ref is always true. In fact, if
rtld_multiple_ref were false, some of the ld.so setup code
would not run.
Reviewed-by: DJ Delorie <dj@redhat.com>
Conflicts:
elf/rtld.c (
- prelink support not removed downstream
- "elf: Add _dl_find_object function" not ported
downstream
)
diff --git a/elf/rtld.c b/elf/rtld.c
index d02ecc834c9a4d43..711bb77d70da6563 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2002,43 +2002,37 @@ dl_main (const ElfW(Phdr) *phdr,
if (main_map->l_searchlist.r_list[i] == &GL(dl_rtld_map))
break;
- bool rtld_multiple_ref = false;
- if (__glibc_likely (i < main_map->l_searchlist.r_nlist))
- {
- /* Some DT_NEEDED entry referred to the interpreter object itself, so
- put it back in the list of visible objects. We insert it into the
- chain in symbol search order because gdb uses the chain's order as
- its symbol search order. */
- rtld_multiple_ref = true;
+ /* Insert the link map for the dynamic loader into the chain in
+ symbol search order because gdb uses the chain's order as its
+ symbol search order. */
- GL(dl_rtld_map).l_prev = main_map->l_searchlist.r_list[i - 1];
- if (__glibc_likely (state.mode == rtld_mode_normal))
- {
- GL(dl_rtld_map).l_next = (i + 1 < main_map->l_searchlist.r_nlist
- ? main_map->l_searchlist.r_list[i + 1]
- : NULL);
+ GL(dl_rtld_map).l_prev = main_map->l_searchlist.r_list[i - 1];
+ if (__glibc_likely (state.mode == rtld_mode_normal))
+ {
+ GL(dl_rtld_map).l_next = (i + 1 < main_map->l_searchlist.r_nlist
+ ? main_map->l_searchlist.r_list[i + 1]
+ : NULL);
#ifdef NEED_DL_SYSINFO_DSO
- if (GLRO(dl_sysinfo_map) != NULL
- && GL(dl_rtld_map).l_prev->l_next == GLRO(dl_sysinfo_map)
- && GL(dl_rtld_map).l_next != GLRO(dl_sysinfo_map))
- GL(dl_rtld_map).l_prev = GLRO(dl_sysinfo_map);
+ if (GLRO(dl_sysinfo_map) != NULL
+ && GL(dl_rtld_map).l_prev->l_next == GLRO(dl_sysinfo_map)
+ && GL(dl_rtld_map).l_next != GLRO(dl_sysinfo_map))
+ GL(dl_rtld_map).l_prev = GLRO(dl_sysinfo_map);
#endif
- }
- else
- /* In trace mode there might be an invisible object (which we
- could not find) after the previous one in the search list.
- In this case it doesn't matter much where we put the
- interpreter object, so we just initialize the list pointer so
- that the assertion below holds. */
- GL(dl_rtld_map).l_next = GL(dl_rtld_map).l_prev->l_next;
-
- assert (GL(dl_rtld_map).l_prev->l_next == GL(dl_rtld_map).l_next);
- GL(dl_rtld_map).l_prev->l_next = &GL(dl_rtld_map);
- if (GL(dl_rtld_map).l_next != NULL)
- {
- assert (GL(dl_rtld_map).l_next->l_prev == GL(dl_rtld_map).l_prev);
- GL(dl_rtld_map).l_next->l_prev = &GL(dl_rtld_map);
- }
+ }
+ else
+ /* In trace mode there might be an invisible object (which we
+ could not find) after the previous one in the search list.
+ In this case it doesn't matter much where we put the
+ interpreter object, so we just initialize the list pointer so
+ that the assertion below holds. */
+ GL(dl_rtld_map).l_next = GL(dl_rtld_map).l_prev->l_next;
+
+ assert (GL(dl_rtld_map).l_prev->l_next == GL(dl_rtld_map).l_next);
+ GL(dl_rtld_map).l_prev->l_next = &GL(dl_rtld_map);
+ if (GL(dl_rtld_map).l_next != NULL)
+ {
+ assert (GL(dl_rtld_map).l_next->l_prev == GL(dl_rtld_map).l_prev);
+ GL(dl_rtld_map).l_next->l_prev = &GL(dl_rtld_map);
}
/* Now let us see whether all libraries are available in the
@@ -2212,8 +2206,7 @@ dl_main (const ElfW(Phdr) *phdr,
}
}
- if ((GLRO(dl_debug_mask) & DL_DEBUG_PRELINK)
- && rtld_multiple_ref)
+ if (GLRO(dl_debug_mask) & DL_DEBUG_PRELINK)
{
/* Mark the link map as not yet relocated again. */
GL(dl_rtld_map).l_relocated = 0;
@@ -2500,10 +2493,9 @@ dl_main (const ElfW(Phdr) *phdr,
/* Make sure no new search directories have been added. */
assert (GLRO(dl_init_all_dirs) == GL(dl_all_dirs));
- if (! prelinked && rtld_multiple_ref)
+ if (! prelinked)
{
- /* There was an explicit ref to the dynamic linker as a shared lib.
- Re-relocate ourselves with user-controlled symbol definitions.
+ /* Re-relocate ourselves with user-controlled symbol definitions.
We must do this after TLS initialization in case after this
re-relocation, we might call a user-supplied function

View File

@ -0,0 +1,52 @@
commit a79642204537dec8a1e1c58d1e0a074b3c624f46
Author: Florian Weimer <fweimer@redhat.com>
Date: Wed Nov 6 10:33:44 2024 +0100
elf: Do not define consider_profiling, consider_symbind as macros
This avoids surprises when refactoring the code if these identifiers
are re-used later in the file.
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c
index 0254e589c06fbf4c..ded506da9e180eac 100644
--- a/elf/dl-reloc.c
+++ b/elf/dl-reloc.c
@@ -207,8 +207,8 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
int lazy = reloc_mode & RTLD_LAZY;
int skip_ifunc = reloc_mode & __RTLD_NOIFUNC;
-#ifdef SHARED
bool consider_symbind = false;
+#ifdef SHARED
/* If we are auditing, install the same handlers we need for profiling. */
if ((reloc_mode & __RTLD_AUDIT) == 0)
{
@@ -227,9 +227,7 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
}
#elif defined PROF
/* Never use dynamic linker profiling for gprof profiling code. */
-# define consider_profiling 0
-#else
-# define consider_symbind 0
+ consider_profiling = 0;
#endif
/* If DT_BIND_NOW is set relocate all references in this object. We
@@ -287,7 +285,6 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
ELF_DYNAMIC_RELOCATE (l, scope, lazy, consider_profiling, skip_ifunc);
-#ifndef PROF
if ((consider_profiling || consider_symbind)
&& l->l_info[DT_PLTRELSZ] != NULL)
{
@@ -308,7 +305,6 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
_dl_fatal_printf (errstring, RTLD_PROGNAME, l->l_name);
}
}
-#endif
}
/* Mark the object so we know this work has been done. */

View File

@ -0,0 +1,77 @@
commit f2326c2ec0a0a8db7bc7f4db8cce3002768fc3b6
Author: Florian Weimer <fweimer@redhat.com>
Date: Wed Nov 6 10:33:44 2024 +0100
elf: Introduce _dl_relocate_object_no_relro
And make _dl_protect_relro apply RELRO conditionally.
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c
index ded506da9e180eac..239f5505f805b008 100644
--- a/elf/dl-reloc.c
+++ b/elf/dl-reloc.c
@@ -189,12 +189,9 @@ _dl_nothread_init_static_tls (struct link_map *map)
#include "dynamic-link.h"
void
-_dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
- int reloc_mode, int consider_profiling)
+_dl_relocate_object_no_relro (struct link_map *l, struct r_scope_elem *scope[],
+ int reloc_mode, int consider_profiling)
{
- if (l->l_relocated)
- return;
-
struct textrels
{
caddr_t start;
@@ -325,17 +322,24 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
textrels = textrels->next;
}
-
- /* In case we can protect the data now that the relocations are
- done, do it. */
- if (l->l_relro_size != 0)
- _dl_protect_relro (l);
}
+void
+_dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
+ int reloc_mode, int consider_profiling)
+{
+ if (l->l_relocated)
+ return;
+ _dl_relocate_object_no_relro (l, scope, reloc_mode, consider_profiling);
+ _dl_protect_relro (l);
+}
void
_dl_protect_relro (struct link_map *l)
{
+ if (l->l_relro_size == 0)
+ return;
+
ElfW(Addr) start = ALIGN_DOWN((l->l_addr
+ l->l_relro_addr),
GLRO(dl_pagesize));
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 537d1293c7b5543b..dc4e0555e4ed7f3c 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -1073,6 +1073,13 @@ extern void _dl_relocate_object (struct link_map *map,
int reloc_mode, int consider_profiling)
attribute_hidden;
+/* Perform relocation, but do not apply RELRO. Does not check
+ L->relocated. Otherwise the same as _dl_relocate_object. */
+void _dl_relocate_object_no_relro (struct link_map *map,
+ struct r_scope_elem *scope[],
+ int reloc_mode, int consider_profiling)
+ attribute_hidden;
+
/* Protect PT_GNU_RELRO area. */
extern void _dl_protect_relro (struct link_map *map) attribute_hidden;

View File

@ -0,0 +1,203 @@
commit c1560f3f75c0e892b5522c16f91b4e303f677094
Author: Florian Weimer <fweimer@redhat.com>
Date: Wed Nov 6 10:33:44 2024 +0100
elf: Switch to main malloc after final ld.so self-relocation
Before commit ee1ada1bdb8074de6e1bdc956ab19aef7b6a7872
("elf: Rework exception handling in the dynamic loader
[BZ #25486]"), the previous order called the main calloc
to allocate a shadow GOT/PLT array for auditing support.
This happened before libc.so.6 ELF constructors were run, so
a user malloc could run without libc.so.6 having been
initialized fully. One observable effect was that
environ was NULL at this point.
It does not seem to be possible at present to trigger such
an allocation, but it seems more robust to delay switching
to main malloc after ld.so self-relocation is complete.
The elf/tst-rtld-no-malloc-audit test case fails with a
2.34-era glibc that does not have this fix.
Reviewed-by: DJ Delorie <dj@redhat.com>
Conflicts:
elf/Makefile (fixup context)
elf/rtld.c (Align change with glibc-RHEL-48820-1)
diff --git a/elf/Makefile b/elf/Makefile
index 41adea8d1c6d13ca..0c8e0d794bac640f 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -439,6 +439,9 @@ tests += \
tst-recursive-tls \
tst-relsort1 \
tst-ro-dynamic \
+ tst-rtld-no-malloc \
+ tst-rtld-no-malloc-audit \
+ tst-rtld-no-malloc-preload \
tst-rtld-run-static \
tst-single_threaded \
tst-single_threaded-pthread \
@@ -2896,3 +2899,9 @@ tst-tls22-mod2.so-no-z-defs = yes
tst-tls22-mod2-gnu2.so-no-z-defs = yes
$(objpfx)tst-dlopen-sgid.out: $(objpfx)tst-dlopen-sgid-mod.so
+
+# Reuse an audit module which provides ample debug logging.
+tst-rtld-no-malloc-audit-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
+
+# Any shared object should do.
+tst-rtld-no-malloc-preload-ENV = LD_PRELOAD=$(objpfx)tst-auditmod1.so
diff --git a/elf/dl-support.c b/elf/dl-support.c
index 1fea55c443505890..00abc2d8056c78b0 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -353,8 +353,7 @@ _dl_non_dynamic_init (void)
}
/* Setup relro on the binary itself. */
- if (_dl_main_map.l_relro_size != 0)
- _dl_protect_relro (&_dl_main_map);
+ _dl_protect_relro (&_dl_main_map);
}
#ifdef DL_SYSINFO_IMPLEMENTATION
diff --git a/elf/rtld.c b/elf/rtld.c
index 711bb77d70da6563..3436dd918e699080 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2495,26 +2495,23 @@ dl_main (const ElfW(Phdr) *phdr,
if (! prelinked)
{
- /* Re-relocate ourselves with user-controlled symbol definitions.
-
- We must do this after TLS initialization in case after this
- re-relocation, we might call a user-supplied function
- (e.g. calloc from _dl_relocate_object) that uses TLS data. */
-
- /* The malloc implementation has been relocated, so resolving
- its symbols (and potentially calling IFUNC resolvers) is safe
- at this point. */
- __rtld_malloc_init_real (main_map);
-
/* Likewise for the locking implementation. */
__rtld_mutex_init ();
+ /* Re-relocate ourselves with user-controlled symbol definitions. */
+
RTLD_TIMING_VAR (start);
rtld_timer_start (&start);
- /* Mark the link map as not yet relocated again. */
- GL(dl_rtld_map).l_relocated = 0;
- _dl_relocate_object (&GL(dl_rtld_map), main_map->l_scope, 0, 0);
+ _dl_relocate_object_no_relro (&GL(dl_rtld_map), main_map->l_scope, 0, 0);
+
+ /* The malloc implementation has been relocated, so resolving
+ its symbols (and potentially calling IFUNC resolvers) is safe
+ at this point. */
+ __rtld_malloc_init_real (main_map);
+
+ if (GL(dl_rtld_map).l_relro_size != 0)
+ _dl_protect_relro (&GL(dl_rtld_map));
rtld_timer_accum (&relocate_time, start);
}
diff --git a/elf/tst-rtld-no-malloc-audit.c b/elf/tst-rtld-no-malloc-audit.c
new file mode 100644
index 0000000000000000..a028377ad1fea027
--- /dev/null
+++ b/elf/tst-rtld-no-malloc-audit.c
@@ -0,0 +1 @@
+#include "tst-rtld-no-malloc.c"
diff --git a/elf/tst-rtld-no-malloc-preload.c b/elf/tst-rtld-no-malloc-preload.c
new file mode 100644
index 0000000000000000..a028377ad1fea027
--- /dev/null
+++ b/elf/tst-rtld-no-malloc-preload.c
@@ -0,0 +1 @@
+#include "tst-rtld-no-malloc.c"
diff --git a/elf/tst-rtld-no-malloc.c b/elf/tst-rtld-no-malloc.c
new file mode 100644
index 0000000000000000..5f24d4bd72c4af0c
--- /dev/null
+++ b/elf/tst-rtld-no-malloc.c
@@ -0,0 +1,76 @@
+/* Test that program loading does not call malloc.
+ Copyright (C) 2024 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 <string.h>
+#include <unistd.h>
+
+static void
+print (const char *s)
+{
+ const char *end = s + strlen (s);
+ while (s < end)
+ {
+ ssize_t ret = write (STDOUT_FILENO, s, end - s);
+ if (ret <= 0)
+ _exit (2);
+ s += ret;
+ }
+}
+
+static void __attribute__ ((noreturn))
+unexpected_call (const char *function)
+{
+ print ("error: unexpected call to ");
+ print (function);
+ print ("\n");
+ _exit (1);
+}
+
+/* These are the malloc functions implement in elf/dl-minimal.c. */
+
+void
+free (void *ignored)
+{
+ unexpected_call ("free");
+}
+
+void *
+calloc (size_t ignored1, size_t ignored2)
+{
+ unexpected_call ("calloc");
+}
+
+void *
+malloc (size_t ignored)
+{
+ unexpected_call ("malloc");
+}
+
+void *
+realloc (void *ignored1, size_t ignored2)
+{
+ unexpected_call ("realloc");
+}
+
+int
+main (void)
+{
+ /* Do not use the test wrapper, to avoid spurious malloc calls from it. */
+ return 0;
+}

View File

@ -0,0 +1,228 @@
commit 706209867f1ba89c458033408d419e92d8055f58
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Jan 7 09:18:07 2025 +0100
elf: Second ld.so relocation only if libc.so has been loaded
Commit 8f8dd904c4a2207699bb666f30acceb5209c8d3f (“elf:
rtld_multiple_ref is always true”) removed some code that happened
to enable compatibility with programs that do not link against
libc.so. Such programs cannot call dlopen or any dynamic linker
functions (except __tls_get_addr), so this is not really useful.
Still ld.so should not crash with a null-pointer dereference
or undefined symbol reference in these cases.
In the main relocation loop, call _dl_relocate_object unconditionally
because it already checks if the object has been relocated.
If libc.so was loaded, self-relocate ld.so against it and call
__rtld_mutex_init and __rtld_malloc_init_real to activate the full
implementations. Those are available only if libc.so is there,
so skip these initialization steps if libc.so is absent. Without
libc.so, the global scope can be completely empty. This can cause
ld.so self-relocation to fail because if it uses symbol-based
relocations, which is why the second ld.so self-relocation is not
performed if libc.so is missing.
The previous concern regarding GOT updates through self-relocation
no longer applies because function pointers are updated
explicitly through __rtld_mutex_init and __rtld_malloc_init_real,
and not through relocation. However, the second ld.so self-relocation
is still delayed, in case there are other symbols being used.
Fixes commit 8f8dd904c4a2207699bb666f30acceb5209c8d3f (“elf:
rtld_multiple_ref is always true”).
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
elf/Makefile (fixup context)
elf/rtld.c (
- Patch off due to prelink code
- "elf: Move _dl_rtld_map, _dl_rtld_audit_state out of GL" not
ported downstream
)
diff --git a/elf/Makefile b/elf/Makefile
index 0c8e0d794bac640f..d30f7f67e73a646e 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -2905,3 +2905,20 @@ tst-rtld-no-malloc-audit-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
# Any shared object should do.
tst-rtld-no-malloc-preload-ENV = LD_PRELOAD=$(objpfx)tst-auditmod1.so
+
+# These rules link and run the special elf/tst-nolink-libc-* tests if
+# a port adds them to the tests variables. Neither test variant is
+# linked against libc.so, but tst-nolink-libc-1 is linked against
+# ld.so. The test is always run directly, not under the dynamic
+# linker.
+CFLAGS-tst-nolink-libc.c += $(no-stack-protector)
+$(objpfx)tst-nolink-libc-1: $(objpfx)tst-nolink-libc.o $(objpfx)ld.so
+ $(LINK.o) -nostdlib -nostartfiles -o $@ $< \
+ -Wl,--dynamic-linker=$(objpfx)ld.so,--no-as-needed $(objpfx)ld.so
+$(objpfx)tst-nolink-libc-1.out: $(objpfx)tst-nolink-libc-1 $(objpfx)ld.so
+ $< > $@ 2>&1; $(evaluate-test)
+$(objpfx)tst-nolink-libc-2: $(objpfx)tst-nolink-libc.o
+ $(LINK.o) -nostdlib -nostartfiles -o $@ $< \
+ -Wl,--dynamic-linker=$(objpfx)ld.so
+$(objpfx)tst-nolink-libc-2.out: $(objpfx)tst-nolink-libc-2 $(objpfx)ld.so
+ $< > $@ 2>&1; $(evaluate-test)
diff --git a/elf/rtld.c b/elf/rtld.c
index 3436dd918e699080..d3d9e6b904ac78fd 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2410,25 +2410,25 @@ dl_main (const ElfW(Phdr) *phdr,
}
else
{
- /* Now we have all the objects loaded. Relocate them all except for
- the dynamic linker itself. We do this in reverse order so that copy
- relocs of earlier objects overwrite the data written by later
- objects. We do not re-relocate the dynamic linker itself in this
- loop because that could result in the GOT entries for functions we
- call being changed, and that would break us. It is safe to relocate
- the dynamic linker out of order because it has no copy relocations.
- Likewise for libc, which is relocated early to ensure that IFUNC
- resolvers in libc work. */
+ /* Now we have all the objects loaded. */
int consider_profiling = GLRO(dl_profile) != NULL;
/* If we are profiling we also must do lazy reloaction. */
GLRO(dl_lazy) |= consider_profiling;
+ /* If libc.so has been loaded, relocate it early, after the dynamic
+ loader itself. The initial self-relocation of ld.so should be
+ sufficient for IFUNC resolvers in libc.so. */
if (GL(dl_ns)[LM_ID_BASE].libc_map != NULL)
- _dl_relocate_object (GL(dl_ns)[LM_ID_BASE].libc_map,
- GL(dl_ns)[LM_ID_BASE].libc_map->l_scope,
- GLRO(dl_lazy) ? RTLD_LAZY : 0, consider_profiling);
+ {
+ RTLD_TIMING_VAR (start);
+ rtld_timer_start (&start);
+ _dl_relocate_object (GL(dl_ns)[LM_ID_BASE].libc_map,
+ GL(dl_ns)[LM_ID_BASE].libc_map->l_scope,
+ GLRO(dl_lazy) ? RTLD_LAZY : 0, consider_profiling);
+ rtld_timer_accum (&relocate_time, start);
+ }
RTLD_TIMING_VAR (start);
rtld_timer_start (&start);
@@ -2450,9 +2450,8 @@ dl_main (const ElfW(Phdr) *phdr,
/* Also allocated with the fake malloc(). */
l->l_free_initfini = 0;
- if (l != &GL(dl_rtld_map))
- _dl_relocate_object (l, l->l_scope, GLRO(dl_lazy) ? RTLD_LAZY : 0,
- consider_profiling);
+ _dl_relocate_object (l, l->l_scope, GLRO(dl_lazy) ? RTLD_LAZY : 0,
+ consider_profiling);
/* Add object to slot information data if necessasy. */
if (l->l_tls_blocksize != 0 && __rtld_tls_init_tp_called)
@@ -2495,25 +2494,22 @@ dl_main (const ElfW(Phdr) *phdr,
if (! prelinked)
{
- /* Likewise for the locking implementation. */
- __rtld_mutex_init ();
-
- /* Re-relocate ourselves with user-controlled symbol definitions. */
-
- RTLD_TIMING_VAR (start);
- rtld_timer_start (&start);
-
- _dl_relocate_object_no_relro (&GL(dl_rtld_map), main_map->l_scope, 0, 0);
-
- /* The malloc implementation has been relocated, so resolving
- its symbols (and potentially calling IFUNC resolvers) is safe
- at this point. */
- __rtld_malloc_init_real (main_map);
+ /* If libc.so was loaded, relocate ld.so against it. Complete ld.so
+ initialization with mutex symbols from libc.so and malloc symbols
+ from the global scope. */
+ if (GL(dl_ns)[LM_ID_BASE].libc_map != NULL)
+ {
+ RTLD_TIMING_VAR (start);
+ rtld_timer_start (&start);
+ _dl_relocate_object_no_relro (&GL(dl_rtld_map), main_map->l_scope, 0, 0);
+ rtld_timer_accum (&relocate_time, start);
- if (GL(dl_rtld_map).l_relro_size != 0)
- _dl_protect_relro (&GL(dl_rtld_map));
+ __rtld_mutex_init ();
+ __rtld_malloc_init_real (main_map);
+ }
- rtld_timer_accum (&relocate_time, start);
+ /* All ld.so initialization is complete. Apply RELRO. */
+ _dl_protect_relro (&GL(dl_rtld_map));
}
/* Relocation is complete. Perform early libc initialization. This
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index 460ba54a8afcc515..94eb2665b27371a4 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -393,7 +393,15 @@ libof-lddlibc4 = lddlibc4
others += pldd
install-bin += pldd
$(objpfx)pldd: $(objpfx)xmalloc.o
+
+test-internal-extras += tst-nolink-libc
+ifeq ($(run-built-tests),yes)
+tests-special += \
+ $(objpfx)tst-nolink-libc-1.out \
+ $(objpfx)tst-nolink-libc-2.out \
+ # tests-special
endif
+endif # $(subdir) == elf
ifeq ($(subdir),rt)
CFLAGS-mq_send.c += -fexceptions
diff --git a/sysdeps/unix/sysv/linux/arm/Makefile b/sysdeps/unix/sysv/linux/arm/Makefile
index 32db854cbd6bdfd6..ab7ced85408ee515 100644
--- a/sysdeps/unix/sysv/linux/arm/Makefile
+++ b/sysdeps/unix/sysv/linux/arm/Makefile
@@ -1,5 +1,8 @@
ifeq ($(subdir),elf)
sysdep-rtld-routines += aeabi_read_tp libc-do-syscall
+# The test uses INTERNAL_SYSCALL_CALL. In thumb mode, this uses
+# an undefined reference to __libc_do_syscall.
+CFLAGS-tst-nolink-libc.c += -marm
endif
ifeq ($(subdir),misc)
diff --git a/sysdeps/unix/sysv/linux/tst-nolink-libc.c b/sysdeps/unix/sysv/linux/tst-nolink-libc.c
new file mode 100644
index 0000000000000000..817f37784b4080f9
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-nolink-libc.c
@@ -0,0 +1,25 @@
+/* Test program not linked against libc.so and not using any glibc functions.
+ Copyright (C) 2024 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 <sysdep.h>
+
+void
+_start (void)
+{
+ INTERNAL_SYSCALL_CALL (exit_group, 0);
+}

View File

@ -0,0 +1,28 @@
commit 39183f47d8bc9eda711c9797b18d69d7a02af91c
Author: Florian Weimer <fweimer@redhat.com>
Date: Wed Jan 8 16:55:31 2025 +0100
elf: Minimize library dependencies of tst-nolink-libc.c
On 32-bit Arm, -fasynchronous-unwind-tables creates a reference
to the symbol __aeabi_unwind_cpp_pr0. Compile the tests without
this flag even if it is passed as part of CC, to avoid linker
failures.
diff --git a/elf/Makefile b/elf/Makefile
index d30f7f67e73a646e..dc93f631a682a006 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -2910,8 +2910,10 @@ tst-rtld-no-malloc-preload-ENV = LD_PRELOAD=$(objpfx)tst-auditmod1.so
# a port adds them to the tests variables. Neither test variant is
# linked against libc.so, but tst-nolink-libc-1 is linked against
# ld.so. The test is always run directly, not under the dynamic
-# linker.
-CFLAGS-tst-nolink-libc.c += $(no-stack-protector)
+# linker. It is necessary to minimize run-time dependencies, by
+# disabling stack protection and unwinding.
+CFLAGS-tst-nolink-libc.c += $(no-stack-protector) \
+ -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables
$(objpfx)tst-nolink-libc-1: $(objpfx)tst-nolink-libc.o $(objpfx)ld.so
$(LINK.o) -nostdlib -nostartfiles -o $@ $< \
-Wl,--dynamic-linker=$(objpfx)ld.so,--no-as-needed $(objpfx)ld.so

View File

@ -0,0 +1,30 @@
commit d30f41d2c9031b0540641af692e56002eab5599f
Author: Florian Weimer <fweimer@redhat.com>
Date: Thu Jun 26 11:38:00 2025 +0200
elf: Add missing DSO dependencies for tst-rtld-no-malloc-{audit,preload}
Fixes commit c1560f3f75c0e892b5522c16f91b4e303f677094
("elf: Switch to main malloc after final ld.so self-relocation").
Reviewed-by: Frédéric Bérat <fberat@redhat.com>
Conflicts:
elf/Makefile (fixup context)
diff --git a/elf/Makefile b/elf/Makefile
index dc93f631a682a006..15bec14364266c77 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -2902,9 +2902,11 @@ $(objpfx)tst-dlopen-sgid.out: $(objpfx)tst-dlopen-sgid-mod.so
# Reuse an audit module which provides ample debug logging.
tst-rtld-no-malloc-audit-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
+$(objpfx)tst-rtld-no-malloc-audit.out: $(objpfx)tst-auditmod1.so
# Any shared object should do.
tst-rtld-no-malloc-preload-ENV = LD_PRELOAD=$(objpfx)tst-auditmod1.so
+$(objpfx)tst-rtld-no-malloc-preload.out: $(objpfx)tst-auditmod1.so
# These rules link and run the special elf/tst-nolink-libc-* tests if
# a port adds them to the tests variables. Neither test variant is

View File

@ -0,0 +1,55 @@
commit 152f863926e77c6f9c9a8b8779c8084eb844ec44
Author: Andreas Schwab <schwab@suse.de>
Date: Thu Mar 23 16:18:50 2023 +0100
_dl_map_object_from_fd: Remove unnecessary debugger notification in error path
After commit ed3ce71f5c ("elf: Move la_activity (LA_ACT_ADD) after
_dl_add_to_namespace_list() (BZ #28062)") it is no longer necessary to
reset the debugger state in the error case, since the debugger
notification only happens after no more errors can occur.
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 5b0734c816b351f0..98a91d40b74cb76b 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -950,8 +950,6 @@ _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_update (nsid);
- bool make_consistent = false;
/* Get file information. To match the kernel behavior, do not fill
in this information for the executable in case of an explicit
@@ -983,14 +981,6 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
free ((void *) l->l_phdr);
free (l);
free (realname);
-
- if (make_consistent && r != NULL)
- {
- r->r_state = RT_CONSISTENT;
- _dl_debug_state ();
- LIBC_PROBE (map_failed, 2, nsid, r);
- }
-
_dl_signal_error (errval, name, NULL, errstring);
}
@@ -1476,6 +1466,7 @@ cannot enable executable stack as shared object requires");
_dl_add_to_namespace_list (l, nsid);
/* Signal that we are going to add new objects. */
+ struct r_debug *r = _dl_debug_update (nsid);
if (r->r_state == RT_CONSISTENT)
{
#ifdef SHARED
@@ -1492,7 +1483,6 @@ cannot enable executable stack as shared object requires");
r->r_state = RT_ADD;
_dl_debug_state ();
LIBC_PROBE (map_start, 2, nsid, r);
- make_consistent = true;
}
else
assert (r->r_state == RT_ADD);

View File

@ -0,0 +1,174 @@
commit ab5aa2ee3d3f978e474803cbbc5fe805ad30e293
Author: Andreas Schwab <schwab@suse.de>
Date: Thu Mar 23 16:46:20 2023 +0100
dlopen: skip debugger notification for DSO loaded from sprof (bug 30258)
Avoid inconsistent state in the debugger interface.
Conflicts:
elf/Makefile: Test differences.
diff --git a/elf/Makefile b/elf/Makefile
index 86d4dd9960088ee1..721f254d121118c0 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -528,6 +528,7 @@ tests-container += \
test-srcs = \
tst-pathopt \
+ tst-sprof-basic \
# tests-srcs
ifeq (yes,$(have-fpie))
@@ -860,6 +861,7 @@ modules-names = \
tst-sonamemove-linkmod1 \
tst-sonamemove-runmod1 \
tst-sonamemove-runmod2 \
+ tst-sprof-mod \
tst-tls19mod1 \
tst-tls19mod2 \
tst-tls19mod3 \
@@ -1114,6 +1116,7 @@ tests-special += \
$(objpfx)tst-rtld-help.out \
$(objpfx)tst-rtld-load-self.out \
$(objpfx)tst-rtld-preload.out \
+ $(objpfx)tst-sprof-basic.out \
# tests-special
endif
tests-special += \
@@ -2946,3 +2949,11 @@ $(objpfx)tst-dlopen-constructor-null: \
$(objpfx)tst-dlopen-constructor-null-mod2.so
$(objpfx)tst-dlopen-constructor-null-mod2.so: \
$(objpfx)tst-dlopen-constructor-null-mod1.so
+
+LDFLAGS-tst-sprof-mod.so = -Wl,-soname,tst-sprof-mod.so
+$(objpfx)tst-sprof-basic: $(objpfx)tst-sprof-mod.so
+$(objpfx)tst-sprof-basic.out: tst-sprof-basic.sh $(objpfx)tst-sprof-basic
+ $(SHELL) $< $(common-objpfx) '$(test-wrapper-env)' \
+ '$(run-program-env)' > $@; \
+ $(evaluate-test)
+generated += tst-sprof-mod.so.profile
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 98a91d40b74cb76b..5c1be6e80cd52e2f 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -1465,6 +1465,10 @@ cannot enable executable stack as shared object requires");
/* Now that the object is fully initialized add it to the object list. */
_dl_add_to_namespace_list (l, nsid);
+ /* Skip auditing and debugger notification when called from 'sprof'. */
+ if (mode & __RTLD_SPROF)
+ return l;
+
/* Signal that we are going to add new objects. */
struct r_debug *r = _dl_debug_update (nsid);
if (r->r_state == RT_CONSISTENT)
diff --git a/elf/tst-sprof-basic.c b/elf/tst-sprof-basic.c
new file mode 100644
index 0000000000000000..5e4083305a4acdd6
--- /dev/null
+++ b/elf/tst-sprof-basic.c
@@ -0,0 +1,25 @@
+/* Copyright (C) 2023 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/>. */
+
+void hello (void);
+
+int
+main (void)
+{
+ hello ();
+ return 0;
+}
diff --git a/elf/tst-sprof-basic.sh b/elf/tst-sprof-basic.sh
new file mode 100755
index 0000000000000000..901db61708c1b8ec
--- /dev/null
+++ b/elf/tst-sprof-basic.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+# Test basic functionality of sprof
+# Copyright (C) 2023 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/>.
+
+set -e
+
+common_objpfx=$1
+test_wrapper_env=$2
+run_program_env=$3
+
+sprof_mod=tst-sprof-mod.so
+
+${test_wrapper_env} \
+${run_program_env} \
+LD_PROFILE=$sprof_mod \
+LD_PROFILE_OUTPUT=${common_objpfx}elf \
+LD_LIBRARY_PATH=${common_objpfx}.:${common_objpfx}elf \
+ ${common_objpfx}elf/ld.so ${common_objpfx}elf/tst-sprof-basic
+
+${test_wrapper_env} \
+${run_program_env} \
+LD_LIBRARY_PATH=${common_objpfx}.:${common_objpfx}elf \
+ ${common_objpfx}elf/ld.so ${common_objpfx}elf/sprof -p $sprof_mod \
+ ${common_objpfx}elf/${sprof_mod}.profile
+
+exit $?
diff --git a/elf/tst-sprof-mod.c b/elf/tst-sprof-mod.c
new file mode 100644
index 0000000000000000..8b3f5e8e07f42ae6
--- /dev/null
+++ b/elf/tst-sprof-mod.c
@@ -0,0 +1,24 @@
+/* Copyright (C) 2023 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>
+
+void
+hello (void)
+{
+ printf ("Hello World\n");
+}

View File

@ -0,0 +1,30 @@
commit f563971b5bf7191acfdd5702fe00878752c2056d
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Sep 26 11:40:12 2023 +0200
elf: Add dummy declaration of _dl_audit_objclose for !SHARED
This allows us to avoid some #ifdef SHARED conditionals.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 3d9b90a22bfa6a7d..21dbe2d21ed8e605 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -1457,7 +1457,14 @@ void DL_ARCH_FIXUP_ATTRIBUTE _dl_audit_pltexit (struct link_map *l,
const void *inregs,
void *outregs)
attribute_hidden;
-#endif /* SHARED */
+
+#else /* !SHARED */
+static inline void
+_dl_audit_objclose (struct link_map *l)
+{
+ /* No audit implementation for !SHARED. */
+}
+#endif /* !SHARED */
#if PTHREAD_IN_LIBC && defined SHARED
/* Recursive locking implementation for use within the dynamic loader.

View File

@ -0,0 +1,102 @@
commit ed2b8d3a866eb37e069f6a71bdf10421cd4c5e54
Author: Adam Sampson <ats@offog.org>
Date: Mon May 6 18:16:32 2024 +0100
ldconfig: Move endswithn into a new header file
is_gdb_python_file is doing a similar test, so it can use this helper
function as well.
Signed-off-by: Adam Sampson <ats@offog.org>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/elf/endswith.h b/elf/endswith.h
new file mode 100644
index 0000000000000000..c6430c48be0c1071
--- /dev/null
+++ b/elf/endswith.h
@@ -0,0 +1,33 @@
+/* Copyright (C) 2023-2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; version 2 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses/>. */
+
+#ifndef _ENDSWITH_H
+#define _ENDSWITH_H
+
+#include <string.h>
+
+/* Return true if the N bytes at NAME end with with the characters in
+ the string SUFFIX. (NAME[N + 1] does not have to be a null byte.)
+ Expected to be called with a string literal for SUFFIX. */
+static inline bool
+endswithn (const char *name, size_t n, const char *suffix)
+{
+ return (n >= strlen (suffix)
+ && memcmp (name + n - strlen (suffix), suffix,
+ strlen (suffix)) == 0);
+}
+
+#endif /* _ENDSWITH_H */
diff --git a/elf/ldconfig.c b/elf/ldconfig.c
index 4a96c409994d96c8..185c8115ae6b4060 100644
--- a/elf/ldconfig.c
+++ b/elf/ldconfig.c
@@ -41,6 +41,7 @@
#include <libgen.h>
#include <ldconfig.h>
+#include <endswith.h>
#include <dl-cache.h>
#include <dl-hwcaps.h>
#include <dl-is_dso.h>
@@ -778,17 +779,6 @@ struct dlib_entry
struct dlib_entry *next;
};
-/* Return true if the N bytes at NAME end with with the characters in
- the string SUFFIX. (NAME[N + 1] does not have to be a null byte.)
- Expected to be called with a string literal for SUFFIX. */
-static inline bool
-endswithn (const char *name, size_t n, const char *suffix)
-{
- return (n >= strlen (suffix)
- && memcmp (name + n - strlen (suffix), suffix,
- strlen (suffix)) == 0);
-}
-
/* Skip some temporary DSO files. These files may be partially written
and lead to ldconfig crashes when examined. */
static bool
diff --git a/elf/readlib.c b/elf/readlib.c
index 8901de2684835653..2701535e3f24a201 100644
--- a/elf/readlib.c
+++ b/elf/readlib.c
@@ -35,6 +35,7 @@
#include <gnu/lib-names.h>
#include <ldconfig.h>
+#include <endswith.h>
#define Elf32_CLASS ELFCLASS32
#define Elf64_CLASS ELFCLASS64
@@ -68,7 +69,7 @@ static bool
is_gdb_python_file (const char *name)
{
size_t len = strlen (name);
- return len > 7 && strcmp (name + len - 7, "-gdb.py") == 0;
+ return endswithn (name, len, "-gdb.py");
}
/* Returns 0 if everything is ok, != 0 in case of error. */

View File

@ -0,0 +1,76 @@
commit 4a50fdf8b2c1106b50cd9056b4c6f3a72cdeed5f
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Sep 3 17:52:47 2024 +0200
elf: Update DSO list, write audit log to elf/tst-audit23.out
After commit 1d5024f4f052c12e404d42d3b5bfe9c3e9fd27c4
("support: Build with exceptions and asynchronous unwind tables
[BZ #30587]"), libgcc_s is expected to show up in the DSO
list on 32-bit Arm. Do not update max_objs because vdso is not
tracked (and which is the reason why the test currently passes
even with libgcc_s present).
Also write the log output from the auditor to standard output,
for easier test debugging.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/elf/tst-audit23.c b/elf/tst-audit23.c
index 4904cf1340a97ee1..6ac1f20af60a2ebb 100644
--- a/elf/tst-audit23.c
+++ b/elf/tst-audit23.c
@@ -85,13 +85,28 @@ do_test (int argc, char *argv[])
= support_capture_subprogram (spargv[0], spargv);
support_capture_subprocess_check (&result, "tst-audit22", 0, sc_allow_stderr);
+ {
+ FILE *fp = fmemopen (result.err.buffer, result.err.length, "r");
+ TEST_VERIFY (fp != NULL);
+ unsigned int line = 0;
+ char *buffer = NULL;
+ size_t buffer_length = 0;
+ puts ("info: *** audit log start ***");
+ while (xgetline (&buffer, &buffer_length, fp))
+ printf ("%6u\t%s", ++line, buffer);
+ puts ("info: *** audit log end ***");
+ free (buffer);
+ xfclose (fp);
+ }
+
/* The expected la_objopen/la_objclose:
1. executable
2. loader
3. libc.so
- 4. tst-audit23mod.so
- 5. libc.so (LM_ID_NEWLM).
- 6. vdso (optional and ignored). */
+ 4. libgcc_s.so (one some architectures, for libsupport)
+ 5. tst-audit23mod.so
+ 6. libc.so (LM_ID_NEWLM).
+ vdso (optional and ignored). */
enum { max_objs = 6 };
struct la_obj_t
{
@@ -115,8 +130,10 @@ do_test (int argc, char *argv[])
TEST_VERIFY (out != NULL);
char *buffer = NULL;
size_t buffer_length = 0;
+ unsigned int line = 0;
while (xgetline (&buffer, &buffer_length, out))
{
+ ++line;
if (startswith (buffer, "la_activity: "))
{
uintptr_t cookie;
@@ -174,8 +191,8 @@ do_test (int argc, char *argv[])
if (is_vdso (lname))
continue;
if (nobjs == max_objs)
- FAIL_EXIT1 ("non expected la_objopen: %s %"PRIxPTR" %ld",
- lname, laddr, lmid);
+ FAIL_EXIT1 ("(line %u) non expected la_objopen: %s %"PRIxPTR" %ld",
+ line, lname, laddr, lmid);
objs[nobjs].lname = lname;
objs[nobjs].laddr = laddr;
objs[nobjs].lmid = lmid;

View File

@ -0,0 +1,34 @@
commit a20bc2f6233a726c7df8eaa332b6e498bd59321f
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Nov 29 15:36:40 2024 +0100
elf: Add the endswith function to <endswith.h>
And include <stdbool.h> for a definition of bool.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/elf/endswith.h b/elf/endswith.h
index c6430c48be0c1071..3954e57f8eff0faa 100644
--- a/elf/endswith.h
+++ b/elf/endswith.h
@@ -17,6 +17,7 @@
#ifndef _ENDSWITH_H
#define _ENDSWITH_H
+#include <stdbool.h>
#include <string.h>
/* Return true if the N bytes at NAME end with with the characters in
@@ -30,4 +31,11 @@ endswithn (const char *name, size_t n, const char *suffix)
strlen (suffix)) == 0);
}
+/* Same as endswithn, but uses the entire SUBJECT for matching. */
+static inline bool
+endswith (const char *subject, const char *suffix)
+{
+ return endswithn (subject, strlen (subject), suffix);
+}
+
#endif /* _ENDSWITH_H */

View File

@ -0,0 +1,131 @@
commit 8f36b1469677afe37168f9af1b77402d7a70c673
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Aug 9 15:31:18 2024 +0200
elf: Signal la_objopen for the proxy link map in dlmopen (bug 31985)
Previously, the ld.so link map was silently added to the namespace.
This change produces an auditing event for it.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 5c1be6e80cd52e2f..6714807946b60188 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -928,6 +928,37 @@ _dl_process_pt_gnu_property (struct link_map *l, int fd, const ElfW(Phdr) *ph)
}
}
+static void
+_dl_notify_new_object (int mode, Lmid_t nsid, struct link_map *l)
+{
+ /* Signal that we are going to add new objects. */
+ struct r_debug *r = _dl_debug_update (nsid);
+ if (r->r_state == RT_CONSISTENT)
+ {
+#ifdef SHARED
+ /* Auditing checkpoint: we are going to add new objects. Since this
+ is called after _dl_add_to_namespace_list the namespace is guaranteed
+ to not be empty. */
+ if ((mode & __RTLD_AUDIT) == 0)
+ _dl_audit_activity_nsid (nsid, LA_ACT_ADD);
+#endif
+
+ /* Notify the debugger we have added some objects. We need to
+ call _dl_debug_initialize in a static program in case dynamic
+ linking has not been used before. */
+ r->r_state = RT_ADD;
+ _dl_debug_state ();
+ LIBC_PROBE (map_start, 2, nsid, r);
+ }
+ else
+ assert (r->r_state == RT_ADD);
+
+#ifdef SHARED
+ /* Auditing checkpoint: we have a new object. */
+ if (!GL(dl_ns)[l->l_ns]._ns_loaded->l_auditing)
+ _dl_audit_objopen (l, nsid);
+#endif
+}
/* Map in the shared object NAME, actually located in REALNAME, and already
opened on FD. */
@@ -1024,6 +1055,8 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
/* Add the map for the mirrored object to the object list. */
_dl_add_to_namespace_list (l, nsid);
+ _dl_notify_new_object (mode, nsid, l);
+
return l;
}
#endif
@@ -1469,33 +1502,7 @@ cannot enable executable stack as shared object requires");
if (mode & __RTLD_SPROF)
return l;
- /* Signal that we are going to add new objects. */
- struct r_debug *r = _dl_debug_update (nsid);
- if (r->r_state == RT_CONSISTENT)
- {
-#ifdef SHARED
- /* Auditing checkpoint: we are going to add new objects. Since this
- is called after _dl_add_to_namespace_list the namespace is guaranteed
- to not be empty. */
- if ((mode & __RTLD_AUDIT) == 0)
- _dl_audit_activity_nsid (nsid, LA_ACT_ADD);
-#endif
-
- /* Notify the debugger we have added some objects. We need to
- call _dl_debug_initialize in a static program in case dynamic
- linking has not been used before. */
- r->r_state = RT_ADD;
- _dl_debug_state ();
- LIBC_PROBE (map_start, 2, nsid, r);
- }
- else
- assert (r->r_state == RT_ADD);
-
-#ifdef SHARED
- /* Auditing checkpoint: we have a new object. */
- if (!GL(dl_ns)[l->l_ns]._ns_loaded->l_auditing)
- _dl_audit_objopen (l, nsid);
-#endif
+ _dl_notify_new_object (mode, nsid, l);
return l;
}
diff --git a/elf/tst-audit23.c b/elf/tst-audit23.c
index 6ac1f20af60a2ebb..0c7ca3112381a31e 100644
--- a/elf/tst-audit23.c
+++ b/elf/tst-audit23.c
@@ -17,6 +17,7 @@
<https://www.gnu.org/licenses/>. */
#include <array_length.h>
+#include <endswith.h>
#include <errno.h>
#include <getopt.h>
#include <link.h>
@@ -106,8 +107,9 @@ do_test (int argc, char *argv[])
4. libgcc_s.so (one some architectures, for libsupport)
5. tst-audit23mod.so
6. libc.so (LM_ID_NEWLM).
+ 7. loader (proxy link map in new namespace)
vdso (optional and ignored). */
- enum { max_objs = 6 };
+ enum { max_objs = 7 };
struct la_obj_t
{
char *lname;
@@ -236,7 +238,9 @@ do_test (int argc, char *argv[])
for (size_t i = 0; i < nobjs; i++)
{
- TEST_COMPARE (objs[i].closed, true);
+ /* This subtest currently does not pass because of bug 32065. */
+ if (! (endswith (objs[i].lname, LD_SO) && objs[i].lmid != LM_ID_BASE))
+ TEST_COMPARE (objs[i].closed, true);
free (objs[i].lname);
}

View File

@ -0,0 +1,76 @@
commit c4b160744cb39eca20dc36b39c7fa6e10352706c
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Aug 9 16:06:40 2024 +0200
elf: Call la_objclose for proxy link maps in _dl_fini (bug 32065)
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/elf/dl-fini.c b/elf/dl-fini.c
index fa876da0ffa1cf97..133dbac385b34fbb 100644
--- a/elf/dl-fini.c
+++ b/elf/dl-fini.c
@@ -74,6 +74,7 @@ _dl_fini (void)
unsigned int i;
struct link_map *l;
+ struct link_map *proxy_link_map = NULL;
assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL);
for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)
/* Do not handle ld.so in secondary namespaces. */
@@ -89,6 +90,11 @@ _dl_fini (void)
are not dlclose()ed from underneath us. */
++l->l_direct_opencount;
}
+ else
+ /* Used below to call la_objclose for the ld.so proxy
+ link map. */
+ proxy_link_map = l;
+
assert (ns != LM_ID_BASE || i == nloaded);
assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1);
unsigned int nmaps = i;
@@ -158,6 +164,9 @@ _dl_fini (void)
--l->l_direct_opencount;
}
+ if (proxy_link_map != NULL)
+ _dl_audit_objclose (proxy_link_map);
+
#ifdef SHARED
_dl_audit_activity_nsid (ns, LA_ACT_CONSISTENT);
#endif
diff --git a/elf/tst-audit23.c b/elf/tst-audit23.c
index 0c7ca3112381a31e..d5137fd44dacde3c 100644
--- a/elf/tst-audit23.c
+++ b/elf/tst-audit23.c
@@ -236,13 +236,26 @@ do_test (int argc, char *argv[])
}
}
+ Lmid_t lmid_other = LM_ID_NEWLM;
+ unsigned int other_namespace_count = 0;
for (size_t i = 0; i < nobjs; i++)
{
- /* This subtest currently does not pass because of bug 32065. */
- if (! (endswith (objs[i].lname, LD_SO) && objs[i].lmid != LM_ID_BASE))
- TEST_COMPARE (objs[i].closed, true);
+ if (objs[i].lmid != LM_ID_BASE)
+ {
+ if (lmid_other == LM_ID_NEWLM)
+ lmid_other = objs[i].lmid;
+ TEST_COMPARE (objs[i].lmid, lmid_other);
+ ++other_namespace_count;
+ if (!(endswith (objs[i].lname, "/" LIBC_SO)
+ || endswith (objs[i].lname, "/" LD_SO)))
+ FAIL ("unexpected object in secondary namespace: %s",
+ objs[i].lname);
+ }
+ TEST_COMPARE (objs[i].closed, true);
free (objs[i].lname);
}
+ /* Both libc.so and ld.so should be present. */
+ TEST_COMPARE (other_namespace_count, 2);
/* la_activity(LA_ACT_CONSISTENT) should be the last callback received.
Since only one link map may be not-CONSISTENT at a time, this also

View File

@ -0,0 +1,140 @@
commit 495b96e064da605630a23092d1e484ade4bdc093
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Sep 3 17:57:46 2024 +0200
elf: Reorder audit events in dlcose to match _dl_fini (bug 32066)
This was discovered after extending elf/tst-audit23 to cover
dlclose of the dlmopen namespace.
Auditors already experience the new order during process
shutdown (_dl_fini), so no LAV_CURRENT bump or backwards
compatibility code seems necessary.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/elf/dl-close.c b/elf/dl-close.c
index 8a4c3528a124d4e7..236d89f67f3bf410 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -303,6 +303,12 @@ _dl_close_worker (struct link_map *map, bool force)
}
#ifdef SHARED
+ /* Auditing checkpoint: we will start deleting objects.
+ This is supposed to happen before la_objclose (see _dl_fini),
+ but only once per non-recursive dlclose call. */
+ if (!unload_any)
+ _dl_audit_activity_nsid (nsid, LA_ACT_DELETE);
+
/* Auditing checkpoint: we remove an object. */
_dl_audit_objclose (imap);
#endif
@@ -463,12 +469,8 @@ _dl_close_worker (struct link_map *map, bool force)
if (!unload_any)
goto out;
-#ifdef SHARED
- /* Auditing checkpoint: we will start deleting objects. */
- _dl_audit_activity_nsid (nsid, LA_ACT_DELETE);
-#endif
-
- /* Notify the debugger we are about to remove some loaded objects. */
+ /* Notify the debugger we are about to remove some loaded objects.
+ LA_ACT_DELETE has already been signalled above for !unload_any. */
struct r_debug *r = _dl_debug_update (nsid);
r->r_state = RT_DELETE;
_dl_debug_state ();
diff --git a/elf/tst-audit23.c b/elf/tst-audit23.c
index d5137fd44dacde3c..5e3afd397bb2d3d1 100644
--- a/elf/tst-audit23.c
+++ b/elf/tst-audit23.c
@@ -31,16 +31,21 @@
#include <support/xstdio.h>
#include <support/xdlfcn.h>
#include <support/support.h>
+#include <support/test-driver.h>
static int restart;
+static int do_dlclose;
#define CMDLINE_OPTIONS \
- { "restart", no_argument, &restart, 1 },
+ { "restart", no_argument, &restart, 1 }, \
+ { "dlclose", no_argument, &do_dlclose, 1 }, \
static int
handle_restart (void)
{
xdlopen ("tst-audit23mod.so", RTLD_NOW);
- xdlmopen (LM_ID_NEWLM, LIBC_SO, RTLD_NOW);
+ void *handle = xdlmopen (LM_ID_NEWLM, LIBC_SO, RTLD_NOW);
+ if (do_dlclose)
+ xdlclose (handle);
return 0;
}
@@ -60,8 +65,8 @@ is_vdso (const char *str)
|| startswith (str, "linux-vdso");
}
-static int
-do_test (int argc, char *argv[])
+static void
+do_one_test (int argc, char *argv[], bool pass_dlclose_flag)
{
/* We must have either:
- One or four parameters left if called initially:
@@ -69,16 +74,15 @@ do_test (int argc, char *argv[])
+ "--library-path" optional
+ the library path optional
+ the application name */
- if (restart)
- return handle_restart ();
-
- char *spargv[9];
+ char *spargv[10];
TEST_VERIFY_EXIT (((argc - 1) + 3) < array_length (spargv));
int i = 0;
for (; i < argc - 1; i++)
spargv[i] = argv[i + 1];
spargv[i++] = (char *) "--direct";
spargv[i++] = (char *) "--restart";
+ if (pass_dlclose_flag)
+ spargv[i++] = (char *) "--dlclose";
spargv[i] = NULL;
setenv ("LD_AUDIT", "tst-auditmod23.so", 0);
@@ -146,8 +150,14 @@ do_test (int argc, char *argv[])
/* The cookie identifies the object at the head of the link map,
so we only add a new namespace if it changes from the previous
- one. This works since dlmopen is the last in the test body. */
- if (cookie != last_act_cookie && last_act_cookie != -1)
+ one. This works since dlmopen is the last in the test body.
+
+ Currently, this does not work as expected because there
+ is no head link map if a namespace is completely deleted.
+ No LA_ACT_CONSISTENT event is generated in that case.
+ See the comment in _dl_audit_activity_nsid and bug 32068. */
+ if (cookie != last_act_cookie && last_act_cookie != -1
+ && !pass_dlclose_flag)
TEST_COMPARE (last_act, LA_ACT_CONSISTENT);
if (this_act == LA_ACT_ADD && acts[nacts] != cookie)
@@ -265,7 +275,16 @@ do_test (int argc, char *argv[])
free (buffer);
xfclose (out);
+}
+
+static int
+do_test (int argc, char *argv[])
+{
+ if (restart)
+ return handle_restart ();
+ do_one_test (argc, argv, false);
+ do_one_test (argc, argv, true);
return 0;
}

View File

@ -0,0 +1,647 @@
commit 4f6dae219578d6df475864e273da40bde9d30806
Author: Stefan Liebler <stli@linux.ibm.com>
Date: Tue Apr 29 13:28:58 2025 +0200
S390: Add new s390 platform z17.
The glibc-hwcaps subdirectories are extended by "z17". Libraries are loaded if
the z17 facility bits are active:
- Miscellaneous-instruction-extensions facility 4
- Vector-enhancements-facility 3
- Vector-Packed-Decimal-Enhancement Facility 3
- CPU: Concurrent-Functions Facility
tst-glibc-hwcaps.c is extended in order to test z17 via new marker6.
In case of running on a z17 with a kernel not recognizing z17 yet,
AT_PLATFORM will be z900 but vector-bit in AT_HWCAP is set. This situation
is now recognized and this testcase does not fail.
A fatal glibc error is dumped if glibc was build with architecture
level set for z17, but run on an older machine (See dl-hwcap-check.h).
Note, you might get an SIGILL before this check if you don't use:
configure --with-rtld-early-cflags=-march=<older-machine>
ld.so --list-diagnostics now also dumps information about s390.cpu_features.
Independent from z17, the s390x kernel won't introduce new HWCAP-Bits if there
is no special handling needed in kernel itself. For z17, we don't have new
HWCAP flags, but have to check the facility bits retrieved by
stfle-instruction.
Instead of storing all the stfle-bits (currently four 64bit values) in the
cpu_features struct, we now only store those bits, which are needed within
glibc itself. Note that we have this list twice, one with original values and
the other one which can be filtered with GLIBC_TUNABLES=glibc.cpu.hwcaps.
Those new fields are stored in so far reserved space in cpu_features struct.
Thus processes started in between the update of glibc package and we e.g. have
a new ld.so and an old libc.so, won't crash. The glibc internal ifunc-resolvers
would not select the best optimized variant.
The users of stfle-bits are also updated:
- parsing of GLIBC_TUNABLES=glibc.cpu.hwcaps
- glibc internal ifunc-resolvers
- __libc_ifunc_impl_list
- sysconf
Note: Conflicts were resolved by Stefan Liebler.
Conflicts:
elf/Makefile (fixup context)
sysdeps/s390/cpu-features.c (partial re-implement downstream)
sysdeps/s390/multiarch/ifunc-impl-list.c (fixup context)
sysdeps/s390/s390-64/dl-hwcaps-subdirs.c (fixup context)
diff --git a/elf/Makefile b/elf/Makefile
index ba11f3a8b81e7218..84151f2e59704a43 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -667,6 +667,12 @@ modules-names = \
libmarkermod5-3 \
libmarkermod5-4 \
libmarkermod5-5 \
+ libmarkermod6-1 \
+ libmarkermod6-2 \
+ libmarkermod6-3 \
+ libmarkermod6-4 \
+ libmarkermod6-5 \
+ libmarkermod6-6 \
ltglobmod1 \
ltglobmod2 \
neededobj1 \
@@ -2624,6 +2630,7 @@ LDFLAGS-libmarkermod2-1.so += -Wl,-soname,libmarkermod2.so
LDFLAGS-libmarkermod3-1.so += -Wl,-soname,libmarkermod3.so
LDFLAGS-libmarkermod4-1.so += -Wl,-soname,libmarkermod4.so
LDFLAGS-libmarkermod5-1.so += -Wl,-soname,libmarkermod5.so
+LDFLAGS-libmarkermod6-1.so += -Wl,-soname,libmarkermod6.so
$(objpfx)libmarkermod%.os : markermodMARKER-VALUE.c
$(compile-command.c) \
-DMARKER=marker$(firstword $(subst -, ,$*)) \
@@ -2638,6 +2645,8 @@ $(objpfx)libmarkermod4.so: $(objpfx)libmarkermod4-1.so
cp $< $@
$(objpfx)libmarkermod5.so: $(objpfx)libmarkermod5-1.so
cp $< $@
+$(objpfx)libmarkermod6.so: $(objpfx)libmarkermod6-1.so
+ cp $< $@
# tst-glibc-hwcaps-prepend checks that --glibc-hwcaps-prepend is
# preferred over auto-detected subdirectories.
diff --git a/elf/tst-glibc-hwcaps-cache.script b/elf/tst-glibc-hwcaps-cache.script
index d58fc8c5de3c5198..af89e9c6f811f483 100644
--- a/elf/tst-glibc-hwcaps-cache.script
+++ b/elf/tst-glibc-hwcaps-cache.script
@@ -5,6 +5,7 @@ cp $B/elf/libmarkermod2-1.so $L/libmarkermod2.so
cp $B/elf/libmarkermod3-1.so $L/libmarkermod3.so
cp $B/elf/libmarkermod4-1.so $L/libmarkermod4.so
cp $B/elf/libmarkermod5-1.so $L/libmarkermod5.so
+cp $B/elf/libmarkermod6-1.so $L/libmarkermod6.so
mkdirp 0770 $L/glibc-hwcaps/power9
cp $B/elf/libmarkermod2-2.so $L/glibc-hwcaps/power9/libmarkermod2.so
@@ -26,6 +27,12 @@ cp $B/elf/libmarkermod5-2.so $L/glibc-hwcaps/z13/libmarkermod5.so
cp $B/elf/libmarkermod5-3.so $L/glibc-hwcaps/z14/libmarkermod5.so
cp $B/elf/libmarkermod5-4.so $L/glibc-hwcaps/z15/libmarkermod5.so
cp $B/elf/libmarkermod5-5.so $L/glibc-hwcaps/z16/libmarkermod5.so
+mkdirp 0770 $L/glibc-hwcaps/z17
+cp $B/elf/libmarkermod6-2.so $L/glibc-hwcaps/z13/libmarkermod6.so
+cp $B/elf/libmarkermod6-3.so $L/glibc-hwcaps/z14/libmarkermod6.so
+cp $B/elf/libmarkermod6-4.so $L/glibc-hwcaps/z15/libmarkermod6.so
+cp $B/elf/libmarkermod6-5.so $L/glibc-hwcaps/z16/libmarkermod6.so
+cp $B/elf/libmarkermod6-6.so $L/glibc-hwcaps/z17/libmarkermod6.so
mkdirp 0770 $L/glibc-hwcaps/x86-64-v2
cp $B/elf/libmarkermod2-2.so $L/glibc-hwcaps/x86-64-v2/libmarkermod2.so
diff --git a/sysdeps/s390/cpu-features.c b/sysdeps/s390/cpu-features.c
index afeb9b56382efa96..e9e7e726b71fb09a 100644
--- a/sysdeps/s390/cpu-features.c
+++ b/sysdeps/s390/cpu-features.c
@@ -28,7 +28,7 @@ extern __typeof (memcmp) MEMCMP_DEFAULT;
#if HAVE_TUNABLES
# define S390_COPY_CPU_FEATURES(SRC_PTR, DEST_PTR) \
(DEST_PTR)->hwcap = (SRC_PTR)->hwcap; \
- (DEST_PTR)->stfle_bits[0] = (SRC_PTR)->stfle_bits[0];
+ (DEST_PTR)->stfle_filtered = (SRC_PTR)->stfle_filtered;
static void
TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
@@ -100,7 +100,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
disable = true;
hwcap_mask = HWCAP_S390_VXRS | HWCAP_S390_VXRS_EXT
| HWCAP_S390_VXRS_EXT2;
- stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
+ stfle_bits0_mask = S390_STFLE_BIT61_ARCH13_MIE3;
}
else if ((feature_len == 3 && *feature == 'z'
&& MEMCMP_DEFAULT (feature, "z13", 3) == 0)
@@ -110,7 +110,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
reset_features = true;
disable = true;
hwcap_mask = HWCAP_S390_VXRS_EXT | HWCAP_S390_VXRS_EXT2;
- stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
+ stfle_bits0_mask = S390_STFLE_BIT61_ARCH13_MIE3;
}
else if ((feature_len == 3 && *feature == 'z'
&& MEMCMP_DEFAULT (feature, "z14", 3) == 0)
@@ -120,14 +120,16 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
reset_features = true;
disable = true;
hwcap_mask = HWCAP_S390_VXRS_EXT2;
- stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
+ stfle_bits0_mask = S390_STFLE_BIT61_ARCH13_MIE3;
}
else if ((feature_len == 3 && *feature == 'z'
&& (MEMCMP_DEFAULT (feature, "z15", 3) == 0
- || MEMCMP_DEFAULT (feature, "z16", 3) == 0))
+ || MEMCMP_DEFAULT (feature, "z16", 3) == 0
+ || MEMCMP_DEFAULT (feature, "z17", 3) == 0))
|| (feature_len == 6
&& (MEMCMP_DEFAULT (feature, "arch13", 6) == 0
- || MEMCMP_DEFAULT (feature, "arch14", 6) == 0)))
+ || MEMCMP_DEFAULT (feature, "arch14", 6) == 0
+ || MEMCMP_DEFAULT (feature, "arch15", 6) == 0)))
{
/* For z15 or newer we don't have to disable something,
but we have to reset to the original values. */
@@ -165,7 +167,7 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
if (feature_len == 10
&& MEMCMP_DEFAULT (feature, "STFLE_MIE3", 10) == 0)
{
- stfle_bits0_mask = S390_STFLE_MASK_ARCH13_MIE3;
+ stfle_bits0_mask = S390_STFLE_BIT61_ARCH13_MIE3;
}
}
@@ -186,9 +188,9 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
if (stfle_bits0_mask != 0ULL)
{
if (disable)
- cpu_features_curr.stfle_bits[0] &= ~stfle_bits0_mask;
+ cpu_features_curr.stfle_filtered &= ~stfle_bits0_mask;
else
- cpu_features_curr.stfle_bits[0] |= stfle_bits0_mask;
+ cpu_features_curr.stfle_filtered |= stfle_bits0_mask;
}
/* Jump over current token ... */
@@ -202,14 +204,18 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *valp)
/* Copy back the features after checking that no unsupported features were
enabled by user. */
cpu_features->hwcap = cpu_features_curr.hwcap & cpu_features_orig.hwcap;
- cpu_features->stfle_bits[0] = cpu_features_curr.stfle_bits[0]
- & cpu_features_orig.stfle_bits[0];
+ cpu_features->stfle_filtered = cpu_features_curr.stfle_filtered
+ & cpu_features_orig.stfle_filtered;
}
#endif
static inline void
-init_cpu_features (struct cpu_features *cpu_features)
+init_cpu_features_no_tunables (struct cpu_features *cpu_features)
{
+ /* Only initialize once. */
+ if (cpu_features->hwcap != 0)
+ return;
+
/* Fill cpu_features as passed by kernel and machine. */
cpu_features->hwcap = GLRO(dl_hwcap);
@@ -218,20 +224,57 @@ init_cpu_features (struct cpu_features *cpu_features)
&& (cpu_features->hwcap & HWCAP_S390_ZARCH)
&& (cpu_features->hwcap & HWCAP_S390_HIGH_GPRS)))
{
- register unsigned long reg0 __asm__("0") = 0;
+ unsigned long long stfle_bits[4] = { 0 };
+ register unsigned long reg0 __asm__("0") = 3;
__asm__ __volatile__(".machine push" "\n\t"
".machine \"z9-109\"" "\n\t"
".machinemode \"zarch_nohighgprs\"\n\t"
"stfle %0" "\n\t"
".machine pop" "\n"
- : "=QS" (cpu_features->stfle_bits[0]),
+ : "=QS" (stfle_bits[0]),
"+d" (reg0)
: : "cc");
+
+ unsigned long long internal_stfle_bits = 0;
+
+ /* Facility bit 34: z10: General instructions extension. */
+ if ((stfle_bits[0] & (1ULL << (63 - 34))) != 0)
+ internal_stfle_bits |= S390_STFLE_BIT34_Z10;
+
+ /* Facility bit 45: z196: Distinct operands, popcount, ... */
+ if ((stfle_bits[0] & (1ULL << (63 - 45))) != 0)
+ internal_stfle_bits |= S390_STFLE_BIT45_Z196;
+
+ /* Facility bit 61: arch13/z15: Miscellaneous-Instruction-Extensions
+ Facility 3, e.g. mvcrl. */
+ if ((stfle_bits[0] & (1ULL << (63 - 61))) != 0)
+ internal_stfle_bits |= S390_STFLE_BIT61_ARCH13_MIE3;
+
+ /* Facility bit 84: arch15/z17: Miscellaneous-instruction-extensions 4 */
+ if ((stfle_bits[1] & (1ULL << (127 - 84))) != 0)
+ internal_stfle_bits |= S390_STFLE_BIT84_ARCH15_MIE4;
+
+ /* Facility bit 198: arch15/z17: Vector-enhancements-facility 3 */
+ if ((stfle_bits[3] & (1ULL << (255 - 198))) != 0)
+ internal_stfle_bits |= S390_STFLE_BIT198_ARCH15_VXRS_EXT3;
+
+ /* Facility bit 199: arch15/z17: Vector-Packed-Decimal-Enhancement 3 */
+ if ((stfle_bits[3] & (1ULL << (255 - 199))) != 0)
+ internal_stfle_bits |= S390_STFLE_BIT199_ARCH15_VXRS_PDE3;
+
+ /* Facility bit 201: arch15/z17: CPU: Concurrent-Functions Facility */
+ if ((stfle_bits[3] & (1ULL << (255 - 201))) != 0)
+ internal_stfle_bits |= S390_STFLE_BIT201_ARCH15_CON;
+
+ cpu_features->stfle_orig = internal_stfle_bits;
+ cpu_features->stfle_filtered = internal_stfle_bits;
}
- else
- {
- cpu_features->stfle_bits[0] = 0ULL;
- }
+}
+
+static inline void
+init_cpu_features (struct cpu_features *cpu_features)
+{
+ init_cpu_features_no_tunables (cpu_features);
#if HAVE_TUNABLES
TUNABLE_GET (glibc, cpu, hwcaps, tunable_val_t *, TUNABLE_CALLBACK (set_hwcaps));
diff --git a/sysdeps/s390/cpu-features.h b/sysdeps/s390/cpu-features.h
index 5e6b58f7c5bb07e4..f98654a7223cfc5b 100644
--- a/sysdeps/s390/cpu-features.h
+++ b/sysdeps/s390/cpu-features.h
@@ -18,29 +18,58 @@
#ifndef __CPU_FEATURES_S390X_H
# define __CPU_FEATURES_S390X_H
-#define S390_STFLE_BITS_Z10 34 /* General instructions extension */
-#define S390_STFLE_BITS_Z196 45 /* Distinct operands, pop ... */
-#define S390_STFLE_BITS_ARCH13_MIE3 61 /* Miscellaneous-Instruction-Extensions
- Facility 3, e.g. mvcrl. */
+/* The following stfle bit definitions are intended to be used for the
+ glibc internal stfle_orig and stfle_filtered fields in cpu_features
+ struct. They can't be used on the double words retrieved by the
+ stfle-instruction. */
-#define S390_STFLE_MASK_ARCH13_MIE3 (1ULL << (63 - S390_STFLE_BITS_ARCH13_MIE3))
+/* Facility bit 34: z10: General instructions extension. */
+#define S390_STFLE_BIT34_Z10 (1ULL << 0)
+/* Facility bit 45: z196: Distinct operands, popcount, ... */
+#define S390_STFLE_BIT45_Z196 (1ULL << 1)
-#define S390_IS_ARCH13_MIE3(STFLE_BITS_ARRAY) \
- (((STFLE_BITS_ARRAY)[0] & S390_STFLE_MASK_ARCH13_MIE3) != 0)
+/* Facility bit 61: arch13/z15: Miscellaneous-Instruction-Extensions
+ Facility 3, e.g. mvcrl. */
+#define S390_STFLE_BIT61_ARCH13_MIE3 (1ULL << 2)
-#define S390_IS_Z196(STFLE_BITS_ARRAY) \
- (((STFLE_BITS_ARRAY)[0] & (1ULL << (63 - S390_STFLE_BITS_Z196))) != 0)
+/* Facility bit 84: arch15/z17: Miscellaneous-instruction-extensions
+ facility 4 */
+#define S390_STFLE_BIT84_ARCH15_MIE4 (1ULL << 3)
-#define S390_IS_Z10(STFLE_BITS_ARRAY) \
- (((STFLE_BITS_ARRAY)[0] & (1ULL << (63 - S390_STFLE_BITS_Z10))) != 0)
+/* Facility bit 198: arch15/z17: Vector-enhancements-facility 3 */
+#define S390_STFLE_BIT198_ARCH15_VXRS_EXT3 (1ULL << 4)
+
+/* Facility bit 199: arch15/z17: Vector-Packed-Decimal-Enhancement
+ Facility 3 */
+#define S390_STFLE_BIT199_ARCH15_VXRS_PDE3 (1ULL << 5)
+
+/* Facility bit 201: arch15/z17: CPU: Concurrent-Functions Facility */
+#define S390_STFLE_BIT201_ARCH15_CON (1ULL << 6)
+
+#define S390_IS_ARCH15(STFLE_BITS) \
+ ((((STFLE_BITS) & S390_STFLE_BIT84_ARCH15_MIE4) != 0) \
+ && (((STFLE_BITS) & S390_STFLE_BIT198_ARCH15_VXRS_EXT3) != 0) \
+ && (((STFLE_BITS) & S390_STFLE_BIT199_ARCH15_VXRS_PDE3) != 0) \
+ && (((STFLE_BITS) & S390_STFLE_BIT201_ARCH15_CON) != 0))
+
+#define S390_IS_ARCH13_MIE3(STFLE_BITS) \
+ (((STFLE_BITS) & S390_STFLE_BIT61_ARCH13_MIE3) != 0)
+
+#define S390_IS_Z196(STFLE_BITS) \
+ (((STFLE_BITS) & S390_STFLE_BIT45_Z196) != 0)
+
+#define S390_IS_Z10(STFLE_BITS) \
+ (((STFLE_BITS) & S390_STFLE_BIT34_Z10) != 0)
struct cpu_features
{
unsigned long int hwcap;
unsigned long int __reserved_hwcap2;
- unsigned long long stfle_bits[3];
- unsigned long long __reserved[11];
+ unsigned long long __reserved;
+ unsigned long long stfle_orig;
+ unsigned long long stfle_filtered;
+ unsigned long long __reserved2[11];
};
#endif /* __CPU_FEATURES_S390X_H */
diff --git a/sysdeps/s390/dl-diagnostics-cpu.c b/sysdeps/s390/dl-diagnostics-cpu.c
new file mode 100644
index 0000000000000000..426af2df7a34b55e
--- /dev/null
+++ b/sysdeps/s390/dl-diagnostics-cpu.c
@@ -0,0 +1,37 @@
+/* Print CPU diagnostics data in ld.so. s390 version.
+ Copyright (C) 2025 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-diagnostics.h>
+#include <ldsodefs.h>
+#include <cpu-features.h>
+
+static void
+print_cpu_features_value (const char *label, uint64_t value)
+{
+ _dl_printf ("s390.cpu_features.");
+ _dl_diagnostics_print_labeled_value (label, value);
+}
+
+void
+_dl_diagnostics_cpu (void)
+{
+ const struct cpu_features *cpu_features = &GLRO(dl_s390_cpu_features);
+ print_cpu_features_value ("hwcap", cpu_features->hwcap);
+ print_cpu_features_value ("stfle_orig", cpu_features->stfle_orig);
+ print_cpu_features_value ("stfle_filtered", cpu_features->stfle_filtered);
+}
diff --git a/sysdeps/s390/multiarch/ifunc-impl-list.c b/sysdeps/s390/multiarch/ifunc-impl-list.c
index af2c75f5df7c7e1d..4c5fdf6069c4027b 100644
--- a/sysdeps/s390/multiarch/ifunc-impl-list.c
+++ b/sysdeps/s390/multiarch/ifunc-impl-list.c
@@ -83,9 +83,9 @@ __libc_ifunc_impl_list (const char *name, struct libc_ifunc_impl *array,
/* Get hardware information. */
const struct cpu_features *features = &GLRO(dl_s390_cpu_features);
- unsigned long int dl_hwcap = features->hwcap;
- const unsigned long long * __attribute__((unused)) stfle_bits
- = features->stfle_bits;
+ unsigned long int dl_hwcap __attribute__ ((unused)) = features->hwcap;
+ const unsigned long long __attribute__((unused)) stfle_bits
+ = features->stfle_filtered;
#if HAVE_MEMSET_IFUNC
IFUNC_IMPL (i, name, memset,
diff --git a/sysdeps/s390/multiarch/ifunc-resolve.h b/sysdeps/s390/multiarch/ifunc-resolve.h
index c22d59d2a341fff7..16b358ec2e895a4c 100644
--- a/sysdeps/s390/multiarch/ifunc-resolve.h
+++ b/sysdeps/s390/multiarch/ifunc-resolve.h
@@ -22,7 +22,7 @@
#include <cpu-features.h>
#define s390_libc_ifunc_expr_stfle_init() \
- const unsigned long long *stfle_bits = features->stfle_bits;
+ const unsigned long long stfle_bits = features->stfle_filtered;
#define s390_libc_ifunc_expr_init() \
const struct cpu_features *features = &GLRO(dl_s390_cpu_features); \
diff --git a/sysdeps/s390/s390-64/Makefile b/sysdeps/s390/s390-64/Makefile
index 66ed844e68df5159..991025cd2a97c203 100644
--- a/sysdeps/s390/s390-64/Makefile
+++ b/sysdeps/s390/s390-64/Makefile
@@ -11,7 +11,8 @@ $(objpfx)tst-glibc-hwcaps: \
$(objpfx)libmarkermod2-1.so \
$(objpfx)libmarkermod3-1.so \
$(objpfx)libmarkermod4-1.so \
- $(objpfx)libmarkermod5-1.so
+ $(objpfx)libmarkermod5-1.so \
+ $(objpfx)libmarkermod6-1.so
$(objpfx)tst-glibc-hwcaps.out: \
$(objpfx)libmarkermod2.so \
$(objpfx)glibc-hwcaps/z13/libmarkermod2.so \
@@ -26,7 +27,14 @@ $(objpfx)tst-glibc-hwcaps.out: \
$(objpfx)glibc-hwcaps/z13/libmarkermod5.so \
$(objpfx)glibc-hwcaps/z14/libmarkermod5.so \
$(objpfx)glibc-hwcaps/z15/libmarkermod5.so \
- $(objpfx)glibc-hwcaps/z16/libmarkermod5.so
+ $(objpfx)glibc-hwcaps/z16/libmarkermod5.so \
+ $(objpfx)libmarkermod6.so \
+ $(objpfx)glibc-hwcaps/z13/libmarkermod6.so \
+ $(objpfx)glibc-hwcaps/z14/libmarkermod6.so \
+ $(objpfx)glibc-hwcaps/z15/libmarkermod6.so \
+ $(objpfx)glibc-hwcaps/z16/libmarkermod6.so \
+ $(objpfx)glibc-hwcaps/z17/libmarkermod6.so
+
$(objpfx)glibc-hwcaps/z13/libmarkermod2.so: $(objpfx)libmarkermod2-2.so
$(make-target-directory)
@@ -58,6 +66,21 @@ $(objpfx)glibc-hwcaps/z15/libmarkermod5.so: $(objpfx)libmarkermod5-4.so
$(objpfx)glibc-hwcaps/z16/libmarkermod5.so: $(objpfx)libmarkermod5-5.so
$(make-target-directory)
cp $< $@
+$(objpfx)glibc-hwcaps/z13/libmarkermod6.so: $(objpfx)libmarkermod6-2.so
+ $(make-target-directory)
+ cp $< $@
+$(objpfx)glibc-hwcaps/z14/libmarkermod6.so: $(objpfx)libmarkermod6-3.so
+ $(make-target-directory)
+ cp $< $@
+$(objpfx)glibc-hwcaps/z15/libmarkermod6.so: $(objpfx)libmarkermod6-4.so
+ $(make-target-directory)
+ cp $< $@
+$(objpfx)glibc-hwcaps/z16/libmarkermod6.so: $(objpfx)libmarkermod6-5.so
+ $(make-target-directory)
+ cp $< $@
+$(objpfx)glibc-hwcaps/z17/libmarkermod6.so: $(objpfx)libmarkermod6-6.so
+ $(make-target-directory)
+ cp $< $@
ifeq (no,$(build-hardcoded-path-in-tests))
diff --git a/sysdeps/s390/s390-64/dl-hwcap-check.h b/sysdeps/s390/s390-64/dl-hwcap-check.h
index 52c609571b32f4ab..d52444419b9a54c1 100644
--- a/sysdeps/s390/s390-64/dl-hwcap-check.h
+++ b/sysdeps/s390/s390-64/dl-hwcap-check.h
@@ -25,8 +25,23 @@
static inline void
dl_hwcap_check (void)
{
-#if defined __ARCH__
-# if GCCMACRO__ARCH__ >= 14
+ /* Note: The s390x kernel won't introduce new HWCAP-Bits if there is
+ no special handling needed in kernel itself. Thus we have have
+ to check the facility-list retrieved with the stfle instruction.
+ We already have a common storage of this list in cpu-features.c.
+ This dl-hwcap-check.h file is included in
+ sysdeps/unix/sysv/linux/dl-sysdep.c, where also dl-machine.h and
+ cpu-features.c is included. Therefore we don't have a special
+ include here. */
+
+#if defined GCCMACRO__ARCH__
+# if GCCMACRO__ARCH__ >= 15
+ init_cpu_features_no_tunables (&GLRO(dl_s390_cpu_features));
+ if (!(S390_IS_ARCH15 (GLRO(dl_s390_cpu_features).stfle_orig)))
+ _dl_fatal_printf ("\
+Fatal glibc error: CPU lacks VXRS_EXT3/VXRS_PDE3/MIE4/Concurrent-functions \
+support (z17 or later required)\n");
+# elif GCCMACRO__ARCH__ >= 14
if (!(GLRO(dl_hwcap) & HWCAP_S390_VXRS_PDE2))
_dl_fatal_printf ("\
Fatal glibc error: CPU lacks VXRS_PDE2 support (z16 or later required)\n");
@@ -39,7 +54,7 @@ Fatal glibc error: CPU lacks VXRS_EXT2 support (z15 or later required)\n");
_dl_fatal_printf ("\
Fatal glibc error: CPU lacks VXE support (z14 or later required)\n");
# endif
-#endif /* __ARCH__ */
+#endif /* GCCMACRO__ARCH__ */
}
#endif /* _DL_HWCAP_CHECK_H */
diff --git a/sysdeps/s390/s390-64/dl-hwcaps-subdirs.c b/sysdeps/s390/s390-64/dl-hwcaps-subdirs.c
index 187d732d560c4a62..0e4aa067c0b87439 100644
--- a/sysdeps/s390/s390-64/dl-hwcaps-subdirs.c
+++ b/sysdeps/s390/s390-64/dl-hwcaps-subdirs.c
@@ -18,9 +18,10 @@
#include <dl-hwcaps.h>
#include <ldsodefs.h>
+#include <cpu-features.h>
-const char _dl_hwcaps_subdirs[] = "z16:z15:z14:z13";
-enum { subdirs_count = 4 }; /* Number of components in _dl_hwcaps_subdirs. */
+const char _dl_hwcaps_subdirs[] = "z17:z16:z15:z14:z13";
+enum { subdirs_count = 5 }; /* Number of components in _dl_hwcaps_subdirs. */
uint32_t
_dl_hwcaps_subdirs_active (void)
@@ -57,5 +58,12 @@ _dl_hwcaps_subdirs_active (void)
return _dl_hwcaps_subdirs_build_bitmask (subdirs_count, active);
++active;
+ /* z17.
+ Note: The kernel has not introduced new HWCAP bits as the new facilities do
+ not require kernel interaction. Thus we check the features via stfle. */
+ if (!(S390_IS_ARCH15 (GLRO(dl_s390_cpu_features).stfle_orig)))
+ return _dl_hwcaps_subdirs_build_bitmask (subdirs_count, active);
+ ++active;
+
return _dl_hwcaps_subdirs_build_bitmask (subdirs_count, active);
}
diff --git a/sysdeps/s390/s390-64/tst-glibc-hwcaps.c b/sysdeps/s390/s390-64/tst-glibc-hwcaps.c
index f3b8ef3dec80d2d1..211aaf3052a1e12c 100644
--- a/sysdeps/s390/s390-64/tst-glibc-hwcaps.c
+++ b/sysdeps/s390/s390-64/tst-glibc-hwcaps.c
@@ -26,35 +26,53 @@ extern int marker2 (void);
extern int marker3 (void);
extern int marker4 (void);
extern int marker5 (void);
+extern int marker6 (void);
/* Return the arch level, 10 for the baseline libmarkermod*.so's. */
static int
compute_level (void)
{
const char *platform = (const char *) getauxval (AT_PLATFORM);
+ const unsigned long int hwcap = getauxval (AT_HWCAP);
+ const int latest_level = 15;
/* The arch* versions refer to the edition of the Principles of
Operation, and they are off by two when compared with the recent
product names. (The code below should not be considered an
accurate mapping to Principles of Operation editions for earlier
AT_PLATFORM strings). */
- if (strcmp (platform, "z900") == 0)
- return 10;
- if (strcmp (platform, "z990") == 0)
- return 10;
- if (strcmp (platform, "z9-109") == 0)
- return 10;
- if (strcmp (platform, "z10") == 0)
- return 10;
- if (strcmp (platform, "z196") == 0)
- return 10;
- if (strcmp (platform, "zEC12") == 0)
- return 10;
+ if ((strcmp (platform, "z900") == 0)
+ || (strcmp (platform, "z990") == 0)
+ || (strcmp (platform, "z9-109") == 0)
+ || (strcmp (platform, "z10") == 0)
+ || (strcmp (platform, "z196") == 0)
+ || (strcmp (platform, "zEC12") == 0))
+ {
+ if ((hwcap & HWCAP_S390_VX) == 0)
+ {
+ /* As vector-support was introduced with the newer z13
+ architecture, we are really on one of the tested older
+ architectures. */
+ return 10;
+ }
+ else
+ {
+ /* According to AT_PLATFORM we are on an older architecture
+ without vector-support, but according to HWCAPs vector
+ registers are supported. This means we are running on a
+ new architecture which is not yet known by the kernel.
+ Thus the default AT_PLATFORM string is used, which is the
+ oldest supported one. For this test, assume we are on
+ the latest known architecture. See
+ <kernel>/arch/s390/kernel/processor.c:setup_elf_platform().
+ */
+ return latest_level;
+ }
+ }
/* If we are running on z13 or newer and the kernel was booted with novx,
then AT_PLATFORM is z13 or newer, but _dl_hwcaps_subdirs_active will
return zero and the _dl_hwcaps_subdirs are not searched. */
- const unsigned long int hwcap = getauxval (AT_HWCAP);
if ((hwcap & HWCAP_S390_VX) == 0)
return 10;
@@ -66,9 +84,12 @@ compute_level (void)
return 13;
if (strcmp (platform, "z16") == 0)
return 14;
+ if (strcmp (platform, "z17") == 0)
+ return latest_level;
+
printf ("warning: unrecognized AT_PLATFORM value: %s\n", platform);
- /* Assume that the new platform supports z16. */
- return 14;
+ /* Assume that the new platform supports the latest known architecture. */
+ return latest_level;
}
static int
@@ -80,6 +101,7 @@ do_test (void)
TEST_COMPARE (marker3 (), MIN (level - 9, 3));
TEST_COMPARE (marker4 (), MIN (level - 9, 4));
TEST_COMPARE (marker5 (), MIN (level - 9, 5));
+ TEST_COMPARE (marker6 (), MIN (level - 9, 6));
return 0;
}
diff --git a/sysdeps/unix/sysv/linux/s390/sysconf.c b/sysdeps/unix/sysv/linux/s390/sysconf.c
index 14821b5f248cd991..fc04a7ddedea0d7e 100644
--- a/sysdeps/unix/sysv/linux/s390/sysconf.c
+++ b/sysdeps/unix/sysv/linux/s390/sysconf.c
@@ -65,7 +65,7 @@ get_cache_info (int level, int attr, int type)
return 0L;
}
- if (!S390_IS_Z10 (features->stfle_bits))
+ if (!S390_IS_Z10 (features->stfle_orig))
{
/* We are at least on a z9 machine.
Return 256byte for LINESIZE for L1 d/i-cache,

View File

@ -0,0 +1,42 @@
Downstream only.
From: Stefan Liebler <stli@linux.ibm.com>
Date: Tue, 6 May 2025 14:49:22 +0200
Subject: [PATCH 2/2] S390: Add z17 to legacy hwcap/platform mechanism
As glibc 2.34 is still generating ld.so.cache entries for libraries
in /usr/lib64/z16/libtest.so, do the same for z17 to have the same behavior.
See upstream glibc commits:
commit b78ff5a25dc8ba9d8c6df10bb0a533254bdd193f
'elf: Remove legacy hwcaps support from ldconfig'
https://sourceware.org/git/?p=glibc.git;a=commit;h=b78ff5a25dc8ba9d8c6df10bb0a533254bdd193f
commit c5aa5fd40adc81c4f0b18e01f329aeaf86518c7b
'elf: Remove loading legacy hwcaps/platform entries in dynamic loader'
https://sourceware.org/git/?p=glibc.git;a=commit;h=c5aa5fd40adc81c4f0b18e01f329aeaf86518c7b
diff --git a/sysdeps/s390/dl-procinfo-s390.c b/sysdeps/s390/dl-procinfo-s390.c
index 559f3827936cd017..7842f7742a829e16 100644
--- a/sysdeps/s390/dl-procinfo-s390.c
+++ b/sysdeps/s390/dl-procinfo-s390.c
@@ -28,5 +28,5 @@ const char _dl_s390_cap_flags[_DL_HWCAP_COUNT][9] =
const char _dl_s390_platforms[_DL_PLATFORMS_COUNT][7] =
{
"g5", "z900", "z990", "z9-109", "z10", "z196", "zEC12", "z13", "z14", "z15",
- "z16"
+ "z16", "z17"
};
diff --git a/sysdeps/s390/dl-procinfo.h b/sysdeps/s390/dl-procinfo.h
index eb782fc3014cd012..3d78cacc98d79f7f 100644
--- a/sysdeps/s390/dl-procinfo.h
+++ b/sysdeps/s390/dl-procinfo.h
@@ -24,7 +24,7 @@
#define _DL_HWCAP_COUNT 23
extern const char _dl_s390_cap_flags[_DL_HWCAP_COUNT][9] attribute_hidden;
-#define _DL_PLATFORMS_COUNT 11
+#define _DL_PLATFORMS_COUNT 12
extern const char _dl_s390_platforms[_DL_PLATFORMS_COUNT][7] attribute_hidden;
/* The kernel provides up to 32 capability bits with elf_hwcap. */

View File

@ -0,0 +1,67 @@
commit 3b21166c4d34ee032093bcf599ffac42ad8a4371
Author: Arjun Shankar <arjun@redhat.com>
Date: Wed Jun 4 13:08:53 2025 +0200
manual: Expand Descriptor-Relative Access section
Improve the clarity of the paragraphs describing common flags and add a
list of common error conditions for descriptor-relative functions.
Reviewed-by: Florian Weimer <fweimer@redhat.com>
diff --git a/manual/filesys.texi b/manual/filesys.texi
index 450d175e614d8834..28d38f23fc58c51f 100644
--- a/manual/filesys.texi
+++ b/manual/filesys.texi
@@ -310,12 +310,17 @@ This is a GNU extension.
The flags argument in @code{@dots{}at} functions can be a combination of
the following flags, defined in @file{fcntl.h}. Not all such functions
support all flags, and some (such as @code{openat}) do not accept a
-flags argument at all.
-
-In the flag descriptions below, the @dfn{effective final path component}
-refers to the final component (basename) of the full path constructed
-from the descriptor and file name arguments, using file name lookup, as
-described above.
+flags argument at all. Although the flags specific to each function have
+distinct values from each other, some flags (relevant to different
+functions) might share the same value and therefore are not guaranteed to
+have unique values.
+
+A non-exhaustive list of common flags and their descriptions follows. Flags
+specific to a function are described alongside the function itself. In
+these flag descriptions, the @dfn{effective final path component} refers to
+the final component (basename) of the full path constructed from the
+descriptor and file name arguments, using file name lookup, as described
+above.
@vtable @code
@item AT_EMPTY_PATH
@@ -353,6 +358,28 @@ a non-final component of the file name are still followed.
argument to the @code{getauxval} function (with @code{AT_@dots{}}
constants defined in @file{elf.h}). @xref{Auxiliary Vector}.
+@cindex common errors in descriptor-relative functions
+@cindex common errors in @code{@dots{}at} functions
+
+The @code{@dots{}at} functions have some common error conditions due to the
+nature of descriptor-relative access. A list of common errors and their
+descriptions follows. Errors specific to a function are described alongside
+the function itself.
+
+@table @code
+@item EBADF
+The file name argument is a relative path but the descriptor argument
+is neither @code{AT_FDCWD} nor a valid file descriptor.
+
+@item EINVAL
+If the function accepts a @var{flags} argument, the flag combination passed
+is not valid for the function.
+
+@item ENOTDIR
+The file name argument is a relative file name but the descriptor
+argument is associated with a file that is not a directory.
+@end table
+
@node Accessing Directories
@section Accessing Directories
@cindex accessing directories

View File

@ -0,0 +1,50 @@
commit 941157dbcdf1c410960bde991206bfb6d9bb292f
Author: Arjun Shankar <arjun@redhat.com>
Date: Wed Jun 4 13:08:54 2025 +0200
manual: Document faccessat
Reviewed-by: Florian Weimer <fweimer@redhat.com>
diff --git a/manual/filesys.texi b/manual/filesys.texi
index 28d38f23fc58c51f..17c15b54037e719d 100644
--- a/manual/filesys.texi
+++ b/manual/filesys.texi
@@ -3069,6 +3069,29 @@ Flag meaning test for execute/search permission.
Flag meaning test for existence of the file.
@end deftypevr
+@deftypefun int faccessat (int @var{filedes}, const char *@var{filename}, int @var{how}, int @var{flags})
+@standards{POSIX.1-2008, unistd.h}
+@comment Unaudited and therefore marked AC-Unsafe and AS-Unsafe by default
+@safety{@mtsafe{}@asunsafe{@asucorrupt{}}@acunsafe{@acucorrupt{}}}
+This function is a descriptor-relative version of the @code{access}
+function above. @xref{Descriptor-Relative Access}. The @var{flags}
+argument can contain a combination of the flags @code{AT_EACCESS} described
+below, @code{AT_EMPTY_PATH}, and @code{AT_SYMLINK_NOFOLLOW}.
+
+@vtable @code
+@item AT_EACCESS
+This flag when passed to the @code{faccessat} function causes it to perform
+access checks using effective user and group IDs instead of real IDs, which
+is the default and matches the @code{access} function.
+@end vtable
+
+Compared to @code{access}, some additional error conditions can occur.
+@xref{Descriptor-Relative Access}.
+
+This function may not work correctly on older kernels missing the
+@code{faccessat2} system call.
+@end deftypefun
+
@node File Times
@subsection File Times
@@ -3849,7 +3872,6 @@ creation always works like @code{open} with @code{O_EXCL}.
The @code{mkdtemp} function comes from OpenBSD.
@c FIXME these are undocumented:
-@c faccessat
@c fchmodat
@c fchownat
@c futimesat

View File

@ -0,0 +1,38 @@
commit 49766eb1a5b93d093bd0fada55ca7a42dfdb10d6
Author: Arjun Shankar <arjun@redhat.com>
Date: Wed Jun 4 13:08:55 2025 +0200
manual: Document mkdirat
Reviewed-by: Florian Weimer <fweimer@redhat.com>
diff --git a/manual/filesys.texi b/manual/filesys.texi
index 17c15b54037e719d..d8f362f3beda9b28 100644
--- a/manual/filesys.texi
+++ b/manual/filesys.texi
@@ -1964,6 +1964,17 @@ To use this function, your program should include the header file
@pindex sys/stat.h
@end deftypefun
+@deftypefun int mkdirat (int @var{filedes}, const char *@var{filename}, mode_t @var{mode})
+@standards{POSIX.1-2008, sys/stat.h}
+@comment Unaudited and therefore marked AC-Unsafe and AS-Unsafe by default
+@safety{@mtsafe{}@asunsafe{@asucorrupt{}}@acunsafe{@acucorrupt{}}}
+This function is a descriptor-relative version of the @code{mkdir}
+function above. @xref{Descriptor-Relative Access}.
+
+Compared to @code{mkdir}, some additional error conditions can occur.
+@xref{Descriptor-Relative Access}.
+@end deftypefun
+
@node File Attributes
@section File Attributes
@@ -3877,7 +3888,6 @@ The @code{mkdtemp} function comes from OpenBSD.
@c futimesat
@c fstatat (there's a commented-out safety assessment for this one)
@c statx
-@c mkdirat
@c mkfifoat
@c name_to_handle_at
@c openat

View File

@ -0,0 +1,38 @@
commit 60f86c9cd062882cbeb04b2944c3dfb7457ee5c5
Author: Arjun Shankar <arjun@redhat.com>
Date: Wed Jun 4 13:08:56 2025 +0200
manual: Document renameat
Reviewed-by: Florian Weimer <fweimer@redhat.com>
diff --git a/manual/filesys.texi b/manual/filesys.texi
index d8f362f3beda9b28..8a173c562fa71f83 100644
--- a/manual/filesys.texi
+++ b/manual/filesys.texi
@@ -1910,6 +1910,17 @@ file systems.
@end table
@end deftypefun
+@deftypefun int renameat (int @var{oldfiledes}, const char *@var{oldname}, int @var{newfiledes}, const char *@var{newname})
+@standards{POSIX.1-2008, stdio.h}
+@comment Unaudited and therefore marked AC-Unsafe and AS-Unsafe by default
+@safety{@mtsafe{}@asunsafe{@asucorrupt{}}@acunsafe{@acucorrupt{}}}
+This function is a descriptor-relative version of the @code{rename}
+function above. @xref{Descriptor-Relative Access}.
+
+Compared to @code{rename}, some additional error conditions can occur.
+@xref{Descriptor-Relative Access}.
+@end deftypefun
+
@node Creating Directories
@section Creating Directories
@cindex creating a directory
@@ -3893,7 +3904,6 @@ The @code{mkdtemp} function comes from OpenBSD.
@c openat
@c open_by_handle_at
@c readlinkat
-@c renameat
@c renameat2
@c scandirat
@c symlinkat

View File

@ -0,0 +1,51 @@
commit 75b725717ff23d0ae38fc7f4a0361cb1bdffbe2e
Author: Arjun Shankar <arjun@redhat.com>
Date: Wed Jun 4 13:08:57 2025 +0200
manual: Document unlinkat
Reviewed-by: Florian Weimer <fweimer@redhat.com>
diff --git a/manual/filesys.texi b/manual/filesys.texi
index 8a173c562fa71f83..396d68c32925c501 100644
--- a/manual/filesys.texi
+++ b/manual/filesys.texi
@@ -1779,6 +1779,31 @@ file system and can't be modified.
@end table
@end deftypefun
+@deftypefun int unlinkat (int @var{filedes}, const char *@var{filename}, int @var{flags})
+@standards{POSIX.1-2008, unistd.h}
+@comment Unaudited and therefore marked AC-Unsafe and AS-Unsafe by default
+@safety{@mtsafe{}@asunsafe{@asucorrupt{}}@acunsafe{@acucorrupt{}}}
+This function is a descriptor-relative version of the @code{unlink}
+function above. @xref{Descriptor-Relative Access}. The @var{flags}
+argument may either be @code{0} or contain the flag @code{AT_REMOVEDIR}:
+
+@table @code
+@item AT_REMOVEDIR
+This flag causes @code{unlinkat} to perform an @code{rmdir} operation on
+@code{filename} instead of performing the equivalent of @code{unlink}.
+@end table
+
+Compared to @code{unlink}, some additional error conditions can occur due to
+descriptor-relative access. @xref{Descriptor-Relative Access}. In
+addition to this, the following other errors can also occur:
+
+@table @code
+@item EISDIR
+The effective final path derived from @var{filename} and @var{filedes} is a
+directory but @code{AT_REMOVEDIR} was not passed in @code{flags}.
+@end table
+@end deftypefun
+
@deftypefun int rmdir (const char *@var{filename})
@standards{POSIX.1, unistd.h}
@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
@@ -3907,6 +3932,5 @@ The @code{mkdtemp} function comes from OpenBSD.
@c renameat2
@c scandirat
@c symlinkat
-@c unlinkat
@c utimensat
@c mknodat

View File

@ -0,0 +1,97 @@
commit 25f1d945766a3a757d9b54eb48fe7c3c48c0f791
Author: Arjun Shankar <arjun@redhat.com>
Date: Wed Jun 4 13:08:58 2025 +0200
manual: Document futimens and utimensat
Document futimens and utimensat. Also document the EINVAL error
condition for futimes. It is inherited by futimens and utimensat as
well.
Reviewed-by: Florian Weimer <fweimer@redhat.com>
diff --git a/manual/filesys.texi b/manual/filesys.texi
index 396d68c32925c501..f21f21804251e480 100644
--- a/manual/filesys.texi
+++ b/manual/filesys.texi
@@ -3307,6 +3307,10 @@ permission for the file, or be a privileged user.
@item EBADF
The @var{filedes} argument is not a valid file descriptor.
+@item EINVAL
+At least one of the fields in the @code{tvp} array passed has an invalid
+value.
+
@item EPERM
If the @var{times} argument is not a null pointer, you must either be
the owner of the file or be a privileged user.
@@ -3316,6 +3320,64 @@ The file lives on a read-only file system.
@end table
@end deftypefun
+@deftypefun int futimens (int @var{filedes}, const struct timespec @var{tsp}@t{[2]})
+@standards{POSIX.1-2008, sys/stat.h}
+@comment Unaudited and therefore marked AC-Unsafe and AS-Unsafe by default
+@safety{@mtsafe{}@asunsafe{@asucorrupt{}}@acunsafe{@acucorrupt{}}}
+This function is like @code{futimes}, except that it sets the file access
+and modification timestamps with nanosecond precision. The argument
+@code{tsp} is used similarly to @code{futimes}' @code{tvp}, but has a
+@code{const struct timespec} type that can express calendar time with
+nanosecond precision. @xref{Time Types}.
+@end deftypefun
+
+@deftypefun int utimensat (int @var{filedes}, const char *@var{filename}, const struct timespec @var{tsp}@t{[2]}, int @var{flags})
+@standards{POSIX.1-2008, sys/stat.h}
+@comment Unaudited and therefore marked AC-Unsafe and AS-Unsafe by default
+@safety{@mtsafe{}@asunsafe{@asucorrupt{}}@acunsafe{@acucorrupt{}}}
+This function is a descriptor-relative version of the @code{futimens}
+function above. @xref{Descriptor-Relative Access}. The @var{flags}
+argument can contain a combination of the flags @code{AT_EMPTY_PATH}, and
+@code{AT_SYMLINK_NOFOLLOW}. The call:
+
+@smallexample
+futimens (@var{filedes}, @var{tsp})
+@end smallexample
+
+is equivalent to:
+
+@smallexample
+utimensat (@var{filedes}, @code{NULL}, @var{tsp}, 0)
+@end smallexample
+
+Compared to @code{futimens}, some additional error conditions can occur due
+to descriptor-relative access. @xref{Descriptor-Relative Access}. In
+addition to this, the following other errors can also occur:
+
+@table @code
+@item EINVAL
+The @var{filename} argument is NULL, @var{filedes} is not @code{AT_FDCWD},
+and @var{flags} is not @code{0}.
+
+@item ELOOP
+There are too many levels of indirection. This can be the result of
+circular symbolic links to directories.
+
+@item ENAMETOOLONG
+The resulting path is too long. This error only occurs on systems which
+have a limit on the file name length.
+
+@item ENOENT
+The @var{filename} argument is an empty string and @var{flags} does not
+contain @code{AT_EMPTY_PATH}, or @var{filename} does not refer to an
+existing file.
+
+@item ESRCH
+Search permission was denied for one of the prefix components of the the
+@var{filename} argument.
+@end table
+@end deftypefun
+
@node File Size
@subsection File Size
@@ -3932,5 +3994,4 @@ The @code{mkdtemp} function comes from OpenBSD.
@c renameat2
@c scandirat
@c symlinkat
-@c utimensat
@c mknodat

View File

@ -0,0 +1,148 @@
commit 2eee835eca960c9d4119279804214b7a1ed5d156
Author: DJ Delorie <dj@redhat.com>
Date: Thu Aug 8 22:44:56 2024 -0400
inet: test if_nametoindex and if_indextoname
Tests for if_nameindex, if_name2index, and if_index2name
Tests that valid results are consistent.
Tests that invalid parameters fail correctly.
Reviewed-by: Florian Weimer <fweimer@redhat.com>
diff --git a/inet/Makefile b/inet/Makefile
index ef6b94ed0b519d6d..dbdcea9e71fdd765 100644
--- a/inet/Makefile
+++ b/inet/Makefile
@@ -136,6 +136,7 @@ tests := \
tst-getni1 \
tst-getni2 \
tst-if_index-long \
+ tst-if_nameindex \
tst-inet6_rth \
tst-network \
tst-ntoa \
diff --git a/inet/tst-if_nameindex.c b/inet/tst-if_nameindex.c
new file mode 100644
index 0000000000000000..b025cdb3a7c6b68c
--- /dev/null
+++ b/inet/tst-if_nameindex.c
@@ -0,0 +1,116 @@
+/* Tests for if_nameindex et al.
+ Copyright (C) 2024 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 <stdlib.h>
+#include <errno.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/descriptors.h>
+#include <support/support.h>
+
+static char *buffer;
+
+static const char *test_names[] = {
+ "testing",
+ "invalid",
+ "dont-match",
+ "",
+ "\001\001\001\177",
+ NULL
+};
+
+static void
+checki (int i)
+{
+ char *ifname;
+
+ /* Test that a known-invalid index returns NULL. */
+ /* BUFFER should not be accessed. */
+
+ printf ("Testing if_indextoname (%d) == NULL\n", i);
+ ifname = if_indextoname (i, NULL);
+ TEST_VERIFY (ifname == NULL);
+ TEST_VERIFY (errno == ENXIO);
+}
+
+static int
+do_test (void)
+{
+ struct if_nameindex *if_ni, *ifp;
+ int min_idx, max_idx, buflen = 0;
+ int i;
+
+ if_ni = if_nameindex ();
+ TEST_VERIFY (if_ni != NULL);
+
+ min_idx = max_idx = if_ni->if_index;
+
+ for (ifp = if_ni; !(ifp->if_index == 0 && ifp->if_name == NULL); ifp++)
+ {
+ printf ("%u: %s\n", ifp->if_index, ifp->if_name);
+ if (ifp->if_index < min_idx)
+ min_idx = ifp->if_index;
+ if (ifp->if_index > max_idx)
+ max_idx = ifp->if_index;
+ if (strlen (ifp->if_name) + 1 > buflen)
+ buflen = strlen (ifp->if_name) + 1;
+ }
+ buffer = (char *) xmalloc (buflen);
+
+ /* Check normal operation. */
+ for (ifp = if_ni; !(ifp->if_index == 0 && ifp->if_name == NULL); ifp++)
+ {
+ unsigned int idx = if_nametoindex (ifp->if_name);
+ TEST_VERIFY (idx == ifp->if_index);
+
+ char *fn = if_indextoname (ifp->if_index, buffer);
+ TEST_VERIFY (strcmp (fn, ifp->if_name) == 0);
+ }
+
+ for (i=-2; i<min_idx; i++)
+ checki (i);
+ for (i=max_idx+1; i<max_idx+3; i++)
+ checki (i);
+
+ /* Check that a known-invalid name returns 0. */
+
+ for (i=0; test_names[i] != NULL; i++)
+ {
+ /* Make sure our "invalid" name is really invalid. */
+ for (ifp = if_ni; !(ifp->if_index == 0 && ifp->if_name == NULL); ifp++)
+ if (strcmp (test_names[i], ifp->if_name) == 0)
+ goto not_this_one;
+
+ printf ("Testing if_nametoindex (%s) == 0\n", test_names[i]);
+
+ unsigned int idx = if_nametoindex (test_names[i]);
+ TEST_VERIFY (idx == 0);
+ TEST_VERIFY (errno == ENODEV);
+
+ not_this_one:
+ }
+
+
+ if_freenameindex (if_ni);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,20 @@
commit 79f44e1a47e87907fb8e97bbd098e01c4adc26a5
Author: Florian Weimer <fweimer@redhat.com>
Date: Mon Aug 26 16:45:31 2024 +0200
inet: Avoid label at end of compound statement in tst-if_nameindex
This fails to compile with GCC 8.
diff --git a/inet/tst-if_nameindex.c b/inet/tst-if_nameindex.c
index b025cdb3a7c6b68c..5b905601245bef34 100644
--- a/inet/tst-if_nameindex.c
+++ b/inet/tst-if_nameindex.c
@@ -105,6 +105,7 @@ do_test (void)
TEST_VERIFY (errno == ENODEV);
not_this_one:
+ ;
}

Some files were not shown because too many files have changed in this diff Show More