Overwrite target for x86_64_v2

Update patch-git.lua to handle AlmaLinux branches correctly

Add support for AlmaLinux import UBI format
This commit is contained in:
Eduard Abdullin 2026-05-26 12:01:35 +00:00 committed by root
commit 1d0c7dface
10 changed files with 7787 additions and 5762 deletions

180
glibc-RHEL-149709.patch Normal file
View File

@ -0,0 +1,180 @@
commit d8997716a1ca22cf038eac86ed286830ba9818cc
Author: DJ Delorie <dj@redhat.com>
Date: Wed Apr 1 17:52:25 2026 -0400
nss: fix __get_default_domain logic
Fix logic bug in __nss_get_default_domain that prevents
proper initialization.
Because this function is not exposed, the test case must link
against the object directly.
Bug origin commit: 64d1e08ea822bf47cb2796ad0f727136227f983c
Co-authored-by: Florian Weimer <fweimer@redhat.com>
Reviewed-by: Frédéric Bérat <fberat@redhat.com>
diff --git a/nss/Makefile b/nss/Makefile
index 400ee3d5eefe3bd1..a9d0715d885ddb0d 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -317,6 +317,7 @@ tests := \
test-netdb \
test-rpcent \
testgrp \
+ tst-default-domain \
tst-fgetsgent_r \
tst-getaddrinfo \
tst-getaddrinfo2 \
@@ -510,6 +511,8 @@ endif
$(objpfx)tst-nss-files-alias-leak.out: $(objpfx)libnss_files.so
$(objpfx)tst-nss-files-alias-truncated.out: $(objpfx)libnss_files.so
+$(objpfx)tst-default-domain: $(objpfx)nisdomain.os
+
tst-nss-gai-hv2-canonname-ENV = \
MALLOC_TRACE=$(objpfx)tst-nss-gai-hv2-canonname.mtrace \
LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so
diff --git a/nss/nss_compat/nisdomain.c b/nss/nss_compat/nisdomain.c
index 1c9d6423d3dc7e74..90b227f131ac787c 100644
--- a/nss/nss_compat/nisdomain.c
+++ b/nss/nss_compat/nisdomain.c
@@ -36,7 +36,7 @@ __nss_get_default_domain (char **outdomain)
__libc_lock_lock (domainname_lock);
- if (domainname[0] != '\0')
+ if (domainname[0] == '\0')
{
if (getdomainname (domainname, MAXDOMAINNAMELEN) < 0)
result = errno;
diff --git a/nss/tst-default-domain.c b/nss/tst-default-domain.c
new file mode 100644
index 0000000000000000..2fa9153d8802d4c3
--- /dev/null
+++ b/nss/tst-default-domain.c
@@ -0,0 +1,123 @@
+/* Basic test of __nss_get_default_domain
+ Copyright (C) 2026 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 <unistd.h>
+#include <string.h>
+#include <dlfcn.h>
+
+#include "nss_compat/nisdomain.h"
+
+#include <support/test-driver.h>
+#include <support/support.h>
+#include <support/namespace.h>
+#include <support/check.h>
+
+char unset_domain[] = "unset_domain";
+char new_domain[] = "new_domain";
+
+/* This function checks the __nss_get_default_domain() function in
+ nss_compat/nssdomain.c. Because this is an internal function to
+ libnss_compat.so, the Makefile will link that object to this test
+ case directly. */
+
+static int
+do_test (void)
+{
+ char *domain_name;
+ char buf[1024];
+
+ /* We need to be in a network namespace so we can change the domain
+ name without interfering with the host system. */
+ support_become_root ();
+ support_enter_network_namespace ();
+ if (!support_in_uts_namespace ())
+ return EXIT_UNSUPPORTED;
+
+ /* First pass: set an empty domain and make sure it's returned
+ correctly. This should not be cached. */
+
+ /* Set the domain name to a known value. */
+ TEST_VERIFY (setdomainname ("", 0) == 0);
+
+ /* Make sure it got set. */
+ TEST_VERIFY (getdomainname (buf, sizeof(buf)) == 0);
+ TEST_COMPARE_STRING (buf, "");
+
+ /* Set this to a known "unknown" value so we can detect if it's not
+ changed. */
+ domain_name = unset_domain;
+
+ /* This is the function we're testing. */
+ TEST_VERIFY (__nss_get_default_domain (& domain_name) == 0);
+
+ /* Make sure the correct domain name is returned. */
+ TEST_VERIFY (domain_name != NULL);
+ TEST_COMPARE_STRING (domain_name, "");
+
+ /* Second pass: set a non-empty domain and make sure it's returned
+ correctly. This works because the empty domain is not
+ cached. */
+
+ /* Set the domain name to a known value. */
+ TEST_VERIFY (setdomainname (new_domain, strlen (new_domain)) == 0);
+
+ /* Make sure it got set. */
+ TEST_VERIFY (getdomainname (buf, sizeof(buf)) == 0);
+ TEST_COMPARE_STRING (buf, new_domain);
+
+ /* Set this to a known "unknown" value so we can detect if it's not
+ changed. */
+ domain_name = unset_domain;
+
+ /* This is the function we're testing. */
+ TEST_VERIFY (__nss_get_default_domain (& domain_name) == 0);
+
+ /* Make sure the correct domain name is returned. */
+ TEST_VERIFY (domain_name != NULL);
+ TEST_COMPARE_STRING (domain_name, new_domain);
+
+ /* The function caches the name, so check it twice. */
+ TEST_VERIFY (__nss_get_default_domain (& domain_name) == 0);
+
+ TEST_VERIFY (domain_name != NULL);
+ TEST_COMPARE_STRING (domain_name, new_domain);
+
+ /* Third pass: set an empty domain again but expect the cached
+ value. */
+
+ /* Set the domain name to a known value. */
+ TEST_VERIFY (setdomainname ("", 0) == 0);
+
+ /* Make sure it got set. */
+ TEST_VERIFY (getdomainname (buf, sizeof(buf)) == 0);
+ TEST_COMPARE_STRING (buf, "");
+
+ /* Set this to a known "unknown" value so we can detect if it's not
+ changed. */
+ domain_name = unset_domain;
+
+ /* This is the function we're testing. */
+ TEST_VERIFY (__nss_get_default_domain (& domain_name) == 0);
+
+ TEST_VERIFY (domain_name != NULL);
+ TEST_COMPARE_STRING (domain_name, new_domain);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

369
glibc-RHEL-159427-1.patch Normal file
View File

@ -0,0 +1,369 @@
commit 332f8e62afef53492dd8285490bcf7aeef18c80a
Author: Frédéric Bérat <fberat@redhat.com>
Date: Fri Sep 5 16:14:38 2025 +0200
tls: Add debug logging for TLS and TCB management
Introduce the `DL_DEBUG_TLS` debug mask to enable detailed logging for
Thread-Local Storage (TLS) and Thread Control Block (TCB) management.
This change integrates a new `tls` option into the `LD_DEBUG`
environment variable, allowing developers to trace:
- TCB allocation, deallocation, and reuse events in `dl-tls.c`,
`nptl/allocatestack.c`, and `nptl/nptl-stack.c`.
- Thread startup events, including the TID and TCB address, in
`nptl/pthread_create.c`.
A new test, `tst-dl-debug-tid`, has been added to validate the
functionality of this new debug logging, ensuring that relevant messages
are correctly generated for both main and worker threads.
This enhances the debugging capabilities for diagnosing issues related
to TLS allocation and thread lifecycle within the dynamic linker.
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/elf/dl-tls.c b/elf/dl-tls.c
index b13e752358a059a4..85db1969b921d9e6 100644
--- a/elf/dl-tls.c
+++ b/elf/dl-tls.c
@@ -497,6 +497,8 @@ _dl_allocate_tls_storage (void)
result = allocate_dtv (result);
if (result == NULL)
free (allocated);
+ else if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("TCB allocated: 0x%lx\n", (unsigned long int) result);
_dl_tls_allocate_end ();
return result;
@@ -685,6 +687,10 @@ rtld_hidden_def (_dl_allocate_tls)
void
_dl_deallocate_tls (void *tcb, bool dealloc_tcb)
{
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("TCB deallocating: 0x%lx (dealloc_tcb=%d)\n",
+ (unsigned long int) tcb, dealloc_tcb);
+
dtv_t *dtv = GET_DTV (tcb);
/* We need to free the memory allocated for non-static TLS. */
diff --git a/elf/rtld.c b/elf/rtld.c
index 8b189a87f76e7e08..c9082bb31643d9ab 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2469,10 +2469,12 @@ process_dl_debug (struct dl_main_state *state, const char *dl_debug)
DL_DEBUG_VERSIONS | DL_DEBUG_IMPCALLS },
{ LEN_AND_STR ("scopes"), "display scope information",
DL_DEBUG_SCOPES },
+ { LEN_AND_STR ("tls"), "display TLS structures processing",
+ DL_DEBUG_TLS },
{ LEN_AND_STR ("all"), "all previous options combined",
DL_DEBUG_LIBS | DL_DEBUG_RELOC | DL_DEBUG_FILES | DL_DEBUG_SYMBOLS
| DL_DEBUG_BINDINGS | DL_DEBUG_VERSIONS | DL_DEBUG_IMPCALLS
- | DL_DEBUG_SCOPES },
+ | DL_DEBUG_SCOPES | DL_DEBUG_TLS },
{ LEN_AND_STR ("statistics"), "display relocation statistics",
DL_DEBUG_STATISTICS },
{ LEN_AND_STR ("unused"), "determined unused DSOs",
diff --git a/nptl/Makefile b/nptl/Makefile
index 4d3271ba71f0bc65..89e2b8c4523714ab 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -373,6 +373,7 @@ tests-container = tst-pthread-getattr
tests-internal := \
tst-barrier5 \
tst-cond22 \
+ tst-dl-debug-tid \
tst-mutex8 \
tst-mutex8-static \
tst-mutexpi8 \
@@ -559,6 +560,7 @@ xtests-static += tst-setuid1-static
ifeq ($(run-built-tests),yes)
tests-special += \
+ $(objpfx)tst-dl-debug-tid.out \
$(objpfx)tst-oddstacklimit.out \
# tests-special
ifeq ($(build-shared),yes)
@@ -693,6 +695,11 @@ tst-stackguard1-ARGS = --command "$(host-test-program-cmd) --child"
tst-stackguard1-static-ARGS = --command "$(objpfx)tst-stackguard1-static --child"
ifeq ($(run-built-tests),yes)
+$(objpfx)tst-dl-debug-tid.out: tst-dl-debug-tid.sh $(objpfx)tst-dl-debug-tid
+ $(SHELL) $< $(common-objpfx) '$(test-wrapper)' '$(rtld-prefix)' \
+ '$(test-wrapper-env)' '$(run-program-env)' \
+ $(objpfx)tst-dl-debug-tid > $@; $(evaluate-test)
+
$(objpfx)tst-oddstacklimit.out: $(objpfx)tst-oddstacklimit $(objpfx)tst-basic1
$(test-program-prefix) $< --command '$(host-test-program-cmd)' > $@; \
$(evaluate-test)
diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
index d9adb5856cefa533..b26ebc8680d7280a 100644
--- a/nptl/allocatestack.c
+++ b/nptl/allocatestack.c
@@ -116,6 +116,10 @@ get_cached_stack (size_t *sizep, void **memp)
/* Release the lock early. */
lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) ("TLS TCB reused from cache: 0x%lx\n",
+ (unsigned long int) result);
+
/* Report size and location of the stack to the caller. */
*sizep = result->stackblock_size;
*memp = result->stackblock;
@@ -293,6 +297,12 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
stack cache nor will the memory (except the TLS memory) be freed. */
pd->user_stack = true;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) (
+ "TCB for user-supplied stack created: 0x%lx, stack=0x%lx, size=%lu\n",
+ (unsigned long int) pd, (unsigned long int) pd->stackblock,
+ (unsigned long int) pd->stackblock_size);
+
/* This is at least the second thread. */
pd->header.multiple_threads = 1;
@@ -427,6 +437,10 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
/* Don't allow setxid until cloned. */
pd->setxid_futex = -1;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) ("TCB for new stack allocated: 0x%lx\n",
+ (unsigned long int) pd);
+
/* Allocate the DTV for this thread. */
if (_dl_allocate_tls (TLS_TPADJ (pd)) == NULL)
{
diff --git a/nptl/nptl-stack.c b/nptl/nptl-stack.c
index 396f2261411aa079..f634d6a29fbdd624 100644
--- a/nptl/nptl-stack.c
+++ b/nptl/nptl-stack.c
@@ -75,6 +75,11 @@ __nptl_free_stacks (size_t limit)
/* Account for the freed memory. */
GL (dl_stack_cache_actsize) -= curr->stackblock_size;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) (
+ "TCB cache full, deallocating: TID=%ld, TCB=0x%lx\n",
+ (long int) curr->tid, (unsigned long int) curr);
+
/* Free the memory associated with the ELF TLS. */
_dl_deallocate_tls (TLS_TPADJ (curr), false);
@@ -96,6 +101,12 @@ static inline void
__attribute ((always_inline))
queue_stack (struct pthread *stack)
{
+ /* The 'stack' parameter is a pointer to the TCB (struct pthread),
+ not just the stack. */
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) ("TCB deallocated into cache: TID=%ld, TCB=0x%lx\n",
+ (long int) stack->tid, (unsigned long int) stack);
+
/* We unconditionally add the stack to the list. The memory may
still be in use but it will not be reused until the kernel marks
the stack as not used anymore. */
@@ -123,8 +134,16 @@ __nptl_deallocate_stack (struct pthread *pd)
if (__glibc_likely (! pd->user_stack))
(void) queue_stack (pd);
else
- /* Free the memory associated with the ELF TLS. */
- _dl_deallocate_tls (TLS_TPADJ (pd), false);
+ {
+ /* User-provided stack. We must not free it. But we must free
+ the TLS memory. */
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) (
+ "TCB for user-supplied stack deallocated: TID=%ld, TCB=0x%lx\n",
+ (long int) pd->tid, (unsigned long int) pd);
+ /* Free the memory associated with the ELF TLS. */
+ _dl_deallocate_tls (TLS_TPADJ (pd), false);
+ }
lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
}
diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
index a6254b1204ccfc80..b836a3ca9216245d 100644
--- a/nptl/pthread_create.c
+++ b/nptl/pthread_create.c
@@ -364,6 +364,10 @@ start_thread (void *arg)
goto out;
}
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ GLRO (dl_debug_printf) ("Thread starting: TID=%ld, TCB=0x%lx\n",
+ (long int) pd->tid, (unsigned long int) pd);
+
/* Initialize resolver state pointer. */
__resp = &pd->res;
diff --git a/nptl/tst-dl-debug-tid.c b/nptl/tst-dl-debug-tid.c
new file mode 100644
index 0000000000000000..231fa43516b233b3
--- /dev/null
+++ b/nptl/tst-dl-debug-tid.c
@@ -0,0 +1,69 @@
+/* Test for thread ID logging in dynamic linker.
+ 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/>. */
+
+/* This test checks that the dynamic linker correctly logs thread creation
+ and destruction. It creates a detached thread followed by a joinable
+ thread to exercise different code paths. A barrier is used to ensure
+ the detached thread has started before the joinable one is created,
+ making the test more deterministic. The tst-dl-debug-tid.sh shell script
+ wrapper then verifies the LD_DEBUG output. */
+
+#include <pthread.h>
+#include <support/xthread.h>
+#include <stdio.h>
+#include <unistd.h>
+
+static void *
+thread_function (void *arg)
+{
+ if (arg)
+ pthread_barrier_wait ((pthread_barrier_t *) arg);
+ return NULL;
+}
+
+static int
+do_test (void)
+{
+ pthread_t thread1;
+ pthread_attr_t attr;
+ pthread_barrier_t barrier;
+
+ pthread_barrier_init (&barrier, NULL, 2);
+
+ /* A detached thread.
+ * Deallocation is done by the thread itself upon exit. */
+ xpthread_attr_init (&attr);
+ xpthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+ /* We don't need the thread handle for the detached thread. */
+ xpthread_create (&attr, thread_function, &barrier);
+ xpthread_attr_destroy (&attr);
+
+ /* Wait for the detached thread to be executed. */
+ pthread_barrier_wait (&barrier);
+ pthread_barrier_destroy (&barrier);
+
+ /* A joinable thread.
+ * Deallocation is done by the main thread in pthread_join. */
+ thread1 = xpthread_create (NULL, thread_function, NULL);
+
+ xpthread_join (thread1);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/nptl/tst-dl-debug-tid.sh b/nptl/tst-dl-debug-tid.sh
new file mode 100644
index 0000000000000000..93d27134a09eaca7
--- /dev/null
+++ b/nptl/tst-dl-debug-tid.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+# Test for thread ID logging in dynamic linker.
+# 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/>.
+
+# This script runs the tst-dl-debug-tid test case and verifies its
+# LD_DEBUG output. It checks for thread creation/destruction messages
+# to ensure the dynamic linker's thread-aware logging is working.
+
+set -e
+
+# Arguments are from Makefile.
+common_objpfx="$1"
+test_wrapper="$2"
+rtld_prefix="$3"
+test_wrapper_env="$4"
+run_program_env="$5"
+test_program="$6"
+
+debug_output="${common_objpfx}elf/tst-dl-debug-tid.debug"
+rm -f "${debug_output}".*
+
+# Run the test program with LD_DEBUG=tls.
+eval "${test_wrapper_env}" LD_DEBUG=tls LD_DEBUG_OUTPUT="${debug_output}" \
+ "${test_wrapper}" "${rtld_prefix}" "${test_program}"
+
+debug_output=$(ls "${debug_output}".*)
+# Check for the "Thread starting" message.
+if ! grep -q 'Thread starting: TID=' "${debug_output}"; then
+ echo "error: 'Thread starting' message not found"
+ cat "${debug_output}"
+ exit 1
+fi
+
+# Check that we have a message where the PID (from prefix) is different
+# from the TID (in the message). This indicates a worker thread log.
+if ! grep 'Thread starting: TID=' "${debug_output}" | awk -F '[ \t:]+' '{
+ sub(/,/, "", $5);
+ sub(/TID=/, "", $5);
+ if ($1 != $5)
+ exit 0;
+ exit 1
+}'; then
+ echo "error: No 'Thread starting' message from a worker thread found"
+ cat "${debug_output}"
+ exit 1
+fi
+
+# We expect messages from thread creation and destruction.
+if ! grep -q 'TCB allocated\|TCB deallocating\|TCB reused\|TCB deallocated' \
+ "${debug_output}"; then
+ echo "error: Expected TCB allocation/deallocation message not found"
+ cat "${debug_output}"
+ exit 1
+fi
+
+cat "${debug_output}"
+rm -f "${debug_output}"
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 5512c83d536874cb..3b10639e0a8a4de6 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -547,8 +547,9 @@ struct rtld_global_ro
#define DL_DEBUG_STATISTICS (1 << 7)
#define DL_DEBUG_UNUSED (1 << 8)
#define DL_DEBUG_SCOPES (1 << 9)
-/* These two are used only internally. */
+/* DL_DEBUG_HELP is only used internally. */
#define DL_DEBUG_HELP (1 << 10)
+#define DL_DEBUG_TLS (1 << 11)
/* Platform name. */
EXTERN const char *_dl_platform;

107
glibc-RHEL-159427-2.patch Normal file
View File

@ -0,0 +1,107 @@
commit d4d472366ba69df7b14eba22a75f887b99855d70
Author: Frédéric Bérat <fberat@redhat.com>
Date: Thu Oct 2 19:15:29 2025 +0200
docs: Add dynamic linker environment variable docs
The Dynamic Linker chapter now includes a new section detailing
environment variables that influence its behavior.
This new section documents the `LD_DEBUG` environment variable,
explaining how to enable debugging output and listing its various
keywords like `libs`, `reloc`, `files`, `symbols`, `bindings`,
`versions`, `scopes`, `tls`, `all`, `statistics`, `unused`, and `help`.
It also documents `LD_DEBUG_OUTPUT`, which controls where the debug
output is written, allowing redirection to a file with the process ID
appended.
This provides users with essential information for controlling and
debugging the dynamic linker.
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/manual/dynlink.texi b/manual/dynlink.texi
index 86917c7452ca9f96..be6b0193339d3863 100644
--- a/manual/dynlink.texi
+++ b/manual/dynlink.texi
@@ -14,6 +14,8 @@ Dynamic linkers are sometimes called @dfn{dynamic loaders}.
@menu
* Dynamic Linker Invocation:: Explicit invocation of the dynamic linker.
+* Dynamic Linker Environment Variables:: Environment variables that control the
+ dynamic linker.
* Dynamic Linker Introspection:: Interfaces for querying mapping information.
@end menu
@@ -348,6 +350,70 @@ probed CPU are omitted. Nothing is printed if the system does not
support the XGETBV instruction.
@end table
+@node Dynamic Linker Environment Variables
+@section Dynamic Linker Environment Variables
+
+The behavior of the dynamic linker can be modified through various environment
+variables.
+
+@table @code
+@item LD_DEBUG
+@cindex @code{LD_DEBUG} environment variable
+The @env{LD_DEBUG} environment variable can be set to a comma-separated list
+of keywords to enable debugging output from the dynamic linker. Setting it to
+@code{help} will display a list of all available keywords. The output is
+written to standard output by default.
+
+@table @code
+@item libs
+Display library search paths.
+
+@item reloc
+Display relocation processing.
+
+@item files
+Display progress for input file processing.
+
+@item symbols
+Display symbol table processing.
+
+@item bindings
+Display information about symbol binding.
+
+@item versions
+Display version dependencies.
+
+@item scopes
+Display scope information.
+
+@item tls
+Display information about Thread-Local Storage (TLS) handling, including TCB
+allocation, deallocation, and reuse. This is useful for debugging issues
+related to thread creation and lifecycle.
+
+@item all
+All previous options combined.
+
+@item statistics
+Display relocation statistics.
+
+@item unused
+Determined unused DSOs.
+
+@item help
+Display a help message with all available options and exit.
+@end table
+
+@item LD_DEBUG_OUTPUT
+@cindex @code{LD_DEBUG_OUTPUT} environment variable
+If @env{LD_DEBUG} is set, the output is written to standard output by
+default. If @env{LD_DEBUG_OUTPUT} is set, the output is written to the
+file specified by its value, with the process ID appended. For example, if
+@env{LD_DEBUG_OUTPUT} is set to @file{/tmp/glibc.debug}, the output will be
+written to a file named @file{/tmp/glibc.debug.12345}, where @code{12345} is
+the process ID.
+@end table
+
@node Dynamic Linker Introspection
@section Dynamic Linker Introspection

70
glibc-RHEL-159427-3.patch Normal file
View File

@ -0,0 +1,70 @@
commit 20092f2ef601aef57cc184cbacd7cab39bba5a25
Author: Yury Khrustalev <yury.khrustalev@arm.com>
Date: Mon Dec 1 10:09:14 2025 +0000
nptl: tests: Fix test-wrapper use in tst-dl-debug-tid.sh
Test wrapper script was used twice: once to run the test
command and second time within the text command which
seems unnecessary and results in false errors when running
this test.
Fixes 332f8e62afef53492dd8285490bcf7aeef18c80a
Reviewed-by: Frédéric Bérat <fberat@redhat.com>
diff --git a/nptl/Makefile b/nptl/Makefile
index 89e2b8c4523714ab..44bc59a2d3fea89f 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -696,8 +696,8 @@ tst-stackguard1-static-ARGS = --command "$(objpfx)tst-stackguard1-static --child
ifeq ($(run-built-tests),yes)
$(objpfx)tst-dl-debug-tid.out: tst-dl-debug-tid.sh $(objpfx)tst-dl-debug-tid
- $(SHELL) $< $(common-objpfx) '$(test-wrapper)' '$(rtld-prefix)' \
- '$(test-wrapper-env)' '$(run-program-env)' \
+ $(SHELL) $< $(common-objpfx) '$(test-wrapper-env)' '$(rtld-prefix)' \
+ '$(run-program-env)' \
$(objpfx)tst-dl-debug-tid > $@; $(evaluate-test)
$(objpfx)tst-oddstacklimit.out: $(objpfx)tst-oddstacklimit $(objpfx)tst-basic1
diff --git a/nptl/tst-dl-debug-tid.sh b/nptl/tst-dl-debug-tid.sh
index 93d27134a09eaca7..9ee31ac4f241f0b4 100644
--- a/nptl/tst-dl-debug-tid.sh
+++ b/nptl/tst-dl-debug-tid.sh
@@ -25,18 +25,17 @@ set -e
# Arguments are from Makefile.
common_objpfx="$1"
-test_wrapper="$2"
+test_wrapper_env="$2"
rtld_prefix="$3"
-test_wrapper_env="$4"
-run_program_env="$5"
-test_program="$6"
+run_program_env="$4"
+test_program="$5"
debug_output="${common_objpfx}elf/tst-dl-debug-tid.debug"
rm -f "${debug_output}".*
# Run the test program with LD_DEBUG=tls.
eval "${test_wrapper_env}" LD_DEBUG=tls LD_DEBUG_OUTPUT="${debug_output}" \
- "${test_wrapper}" "${rtld_prefix}" "${test_program}"
+ "${rtld_prefix}" "${test_program}"
debug_output=$(ls "${debug_output}".*)
# Check for the "Thread starting" message.
@@ -49,9 +48,9 @@ fi
# Check that we have a message where the PID (from prefix) is different
# from the TID (in the message). This indicates a worker thread log.
if ! grep 'Thread starting: TID=' "${debug_output}" | awk -F '[ \t:]+' '{
- sub(/,/, "", $5);
- sub(/TID=/, "", $5);
- if ($1 != $5)
+ sub(/,/, "", $4);
+ sub(/TID=/, "", $4);
+ if ($1 != $4)
exit 0;
exit 1
}'; then

681
glibc-RHEL-159427-4.patch Normal file
View File

@ -0,0 +1,681 @@
commit 1e47dbcce4d5cf2ce71377e729014e454a3e15ae
Author: Frédéric Bérat <fberat@redhat.com>
Date: Fri Dec 12 16:19:43 2025 +0100
elf(tls): Add debug logging for TLS operations
This commit introduces extensive debug logging for thread-local storage
(TLS) operations within the dynamic linker. When `LD_DEBUG=tls` is
enabled, messages are printed for:
- TLS module assignment and release.
- DTV (Dynamic Thread Vector) resizing events.
- TLS block allocations and deallocations.
- `__tls_get_addr` slow path events (DTV updates, lazy allocations, and
static TLS usage).
The log format is standardized to use a "tls: " prefix and identifies
modules using the "modid %lu" convention. To aid in debugging
multithreaded applications, thread-specific logs include the Thread
Control Block (TCB) address to identify the context of the operation.
A new test module `tst-tls-debug-mod.c` and a corresponding shell script
`tst-tls-debug-recursive.sh` have been added. Additionally, the existing
`tst-dl-debug-tid` NPTL test has been updated to verify these TLS debug
messages in a multithreaded context.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
elf/Makefile
(fixup context)
elf/dl-tls.c
(missing 5e249192cac7354af02a7347a0d8c984e0c88ed3 downstream)
sysdeps/x86_64/dl-tls.c
(missing 5e249192cac7354af02a7347a0d8c984e0c88ed3 downstream)
diff --git a/elf/Makefile b/elf/Makefile
index 73c426ae9c1da05c..8651a96bf260416c 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -1352,6 +1352,7 @@ $(objpfx)tst-glibcelf.out: tst-glibcelf.py elf.h $(..)/scripts/glibcelf.py \
ifeq ($(run-built-tests),yes)
tests-special += $(objpfx)tst-tls-allocation-failure-static-patched.out
+tests-special += $(objpfx)tst-tls-debug-recursive.out
endif
# The test requires shared _and_ PIE because the executable
@@ -3339,3 +3340,15 @@ tst-tls22-mod1.so-no-z-defs = yes
tst-tls22-mod1-gnu2.so-no-z-defs = yes
tst-tls22-mod2.so-no-z-defs = yes
tst-tls22-mod2-gnu2.so-no-z-defs = yes
+
+ifeq ($(run-built-tests),yes)
+$(objpfx)tst-tls-debug-recursive.out: tst-tls-debug-recursive.sh \
+ $(objpfx)tst-recursive-tls \
+ $(objpfx)tst-recursive-tlsmallocmod.so \
+ $(patsubst %,$(objpfx)tst-recursive-tlsmod%.so, \
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)
+ $(SHELL) $< $(common-objpfx) '$(test-wrapper-env)' \
+ '$(rtld-prefix)' '$(run_program_env)' \
+ $(objpfx)tst-recursive-tls > $@; \
+ $(evaluate-test)
+endif
diff --git a/elf/dl-close.c b/elf/dl-close.c
index fb27a1231c1c5b66..708e88c68f7d5482 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -74,6 +74,11 @@ remove_slotinfo (size_t idx, struct dtv_slotinfo_list *listp, size_t disp,
if (__glibc_likely (old_map != NULL))
{
/* Mark the entry as unused. These can be read concurrently. */
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf (
+ "tls: release modid %lu from %s [%ld]\n",
+ (unsigned long int) idx, DSO_FILENAME (old_map->l_name),
+ (long int) old_map->l_ns);
atomic_store_relaxed (&listp->slotinfo[idx - disp].gen,
GL(dl_tls_generation) + 1);
atomic_store_relaxed (&listp->slotinfo[idx - disp].map, NULL);
diff --git a/elf/dl-tls.c b/elf/dl-tls.c
index 85db1969b921d9e6..41f76bff9a1bbebe 100644
--- a/elf/dl-tls.c
+++ b/elf/dl-tls.c
@@ -209,6 +209,12 @@ _dl_assign_tls_modid (struct link_map *l)
}
l->l_tls_modid = result;
+
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: assign modid %lu to %s [%ld]\n",
+ (unsigned long int) result,
+ DSO_FILENAME (l->l_name),
+ (long int) l->l_ns);
}
@@ -498,7 +504,7 @@ _dl_allocate_tls_storage (void)
if (result == NULL)
free (allocated);
else if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
- _dl_debug_printf ("TCB allocated: 0x%lx\n", (unsigned long int) result);
+ _dl_debug_printf ("tls: allocate TCB 0x%lx\n", (unsigned long int) result);
_dl_tls_allocate_end ();
return result;
@@ -511,13 +517,18 @@ extern dtv_t _dl_static_dtv[];
#endif
static dtv_t *
-_dl_resize_dtv (dtv_t *dtv, size_t max_modid)
+_dl_resize_dtv (dtv_t *dtv, size_t max_modid, void *tcb)
{
/* Resize the dtv. */
dtv_t *newp;
size_t newsize = max_modid + DTV_SURPLUS;
size_t oldsize = dtv[-1].counter;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: DTV resized for TCB 0x%lx: oldsize=%lu, newsize=%lu\n",
+ (unsigned long int) tcb,
+ (unsigned long int) oldsize, (unsigned long int) newsize);
+
_dl_tls_allocate_begin ();
if (dtv == GL(dl_initial_dtv))
{
@@ -586,7 +597,7 @@ _dl_allocate_tls_init (void *result, bool main_thread)
if (dtv[-1].counter < GL(dl_tls_max_dtv_idx))
{
/* Resize the dtv. */
- dtv = _dl_resize_dtv (dtv, GL(dl_tls_max_dtv_idx));
+ dtv = _dl_resize_dtv (dtv, GL(dl_tls_max_dtv_idx), result);
/* Install this new dtv in the thread data structures. */
INSTALL_DTV (result, &dtv[-1]);
@@ -677,9 +688,14 @@ rtld_hidden_def (_dl_allocate_tls_init)
void *
_dl_allocate_tls (void *mem)
{
- return _dl_allocate_tls_init (mem == NULL
- ? _dl_allocate_tls_storage ()
- : allocate_dtv (mem), false);
+ void *result = _dl_allocate_tls_init (mem == NULL
+ ? _dl_allocate_tls_storage ()
+ : allocate_dtv (mem), false);
+ if (__glibc_unlikely (result != NULL
+ && (GLRO (dl_debug_mask) & DL_DEBUG_TLS)))
+ _dl_debug_printf ("tls: TLS initialized for TCB 0x%lx\n",
+ (unsigned long int) result);
+ return result;
}
rtld_hidden_def (_dl_allocate_tls)
@@ -688,14 +704,22 @@ void
_dl_deallocate_tls (void *tcb, bool dealloc_tcb)
{
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
- _dl_debug_printf ("TCB deallocating: 0x%lx (dealloc_tcb=%d)\n",
+ _dl_debug_printf ("tls: deallocate TCB 0x%lx (dealloc_tcb=%d)\n",
(unsigned long int) tcb, dealloc_tcb);
dtv_t *dtv = GET_DTV (tcb);
/* We need to free the memory allocated for non-static TLS. */
for (size_t cnt = 0; cnt < dtv[-1].counter; ++cnt)
- free (dtv[1 + cnt].pointer.to_free);
+ {
+ if (dtv[1 + cnt].pointer.to_free != NULL
+ && __glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf (
+ "tls: deallocate block 0x%lx for modid %lu; TCB=0x%lx\n",
+ (unsigned long int) dtv[1 + cnt].pointer.to_free,
+ (unsigned long int) (1 + cnt), (unsigned long int) tcb);
+ free (dtv[1 + cnt].pointer.to_free);
+ }
/* The array starts with dtv[-1]. */
if (dtv != GL(dl_initial_dtv))
@@ -767,6 +791,12 @@ allocate_and_init (struct link_map *map)
(map->l_tls_align, map->l_tls_blocksize);
if (result.val == NULL)
oom ();
+ else if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: allocate block 0x%lx for modid %lu; size=%lu, TCB=0x%lx\n",
+ (unsigned long int) result.to_free,
+ (unsigned long int) map->l_tls_modid,
+ (unsigned long int) map->l_tls_blocksize,
+ (unsigned long int) THREAD_SELF);
/* Initialize the memory. */
memset (__mempcpy (result.val, map->l_tls_initimage,
@@ -868,7 +898,7 @@ _dl_update_slotinfo (unsigned long int req_modid, size_t new_gen)
continue;
/* Resizing the dtv aborts on failure: bug 16134. */
- dtv = _dl_resize_dtv (dtv, max_modid);
+ dtv = _dl_resize_dtv (dtv, max_modid, THREAD_SELF);
assert (modid <= dtv[-1].counter);
@@ -889,6 +919,12 @@ _dl_update_slotinfo (unsigned long int req_modid, size_t new_gen)
least some dynamic TLS usage by interposed mallocs. */
if (dtv[modid].pointer.to_free != NULL)
{
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf (
+ "tls: DTV update for TCB 0x%lx: modid %lu deallocated block 0x%lx\n",
+ (unsigned long int) THREAD_SELF,
+ (unsigned long int) modid,
+ (unsigned long int) dtv[modid].pointer.to_free);
_dl_tls_allocate_begin ();
free (dtv[modid].pointer.to_free);
_dl_tls_allocate_end ();
@@ -969,6 +1005,11 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
dtv[GET_ADDR_MODULE].pointer.to_free = NULL;
dtv[GET_ADDR_MODULE].pointer.val = p;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: modid %lu using static TLS; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE,
+ (unsigned long int) THREAD_SELF);
+
return (char *) p + GET_ADDR_OFFSET;
}
else
@@ -1024,18 +1065,28 @@ __tls_get_addr (GET_ADDR_ARGS)
{
if (_dl_tls_allocate_active ()
&& GET_ADDR_MODULE < _dl_tls_initial_modid_limit)
+ {
/* This is a reentrant __tls_get_addr call, but we can
satisfy it because it's an initially-loaded module ID.
These TLS slotinfo slots do not change, so the
out-of-date generation counter does not matter. However,
if not in a TLS update, still update_get_addr below, to
get off the slow path eventually. */
- ;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: modid %lu reentrant usage; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE,
+ (unsigned long int) THREAD_SELF);
+ }
else
{
/* Update DTV up to the global generation, see CONCURRENCY NOTES
in _dl_update_slotinfo. */
gen = atomic_load_acquire (&GL(dl_tls_generation));
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf (
+ "tls: modid %lu update DTV to generation %lu; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE, (unsigned long int) gen,
+ (unsigned long int) THREAD_SELF);
return update_get_addr (GET_ADDR_PARAM, gen);
}
}
@@ -1043,7 +1094,13 @@ __tls_get_addr (GET_ADDR_ARGS)
void *p = dtv[GET_ADDR_MODULE].pointer.val;
if (__glibc_unlikely (p == TLS_DTV_UNALLOCATED))
- return tls_get_addr_tail (GET_ADDR_PARAM, dtv, NULL);
+ {
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: modid %lu lazy allocation; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE,
+ (unsigned long int) THREAD_SELF);
+ return tls_get_addr_tail (GET_ADDR_PARAM, dtv, NULL);
+ }
return (char *) p + GET_ADDR_OFFSET;
}
@@ -1113,6 +1170,10 @@ _dl_tls_initial_modid_limit_setup (void)
break;
}
_dl_tls_initial_modid_limit = idx;
+
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: initial modid limit set to %lu\n",
+ (unsigned long int) idx);
}
diff --git a/elf/rtld.c b/elf/rtld.c
index c9082bb31643d9ab..8b09d8389a7050d2 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1237,6 +1237,11 @@ rtld_setup_main_map (struct link_map *main_map)
/* This image gets the ID one. */
GL(dl_tls_max_dtv_idx) = main_map->l_tls_modid = 1;
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: assign modid %lu to %s [%ld]\n",
+ (unsigned long int) main_map->l_tls_modid,
+ DSO_FILENAME (main_map->l_name),
+ (long int) main_map->l_ns);
}
break;
diff --git a/elf/tst-recursive-tlsmodN.c b/elf/tst-recursive-tlsmodN.c
index bb7592aee6ed347e..921980605cf32a38 100644
--- a/elf/tst-recursive-tlsmodN.c
+++ b/elf/tst-recursive-tlsmodN.c
@@ -19,10 +19,10 @@
/* Compiled with VAR and FUNC set via -D. FUNC requires some
relocation against TLS variable VAR. */
-__thread int VAR;
+__thread char VAR[32768];
int
FUNC (void)
{
- return VAR;
+ return VAR[0];
}
diff --git a/elf/tst-tls-debug-recursive.sh b/elf/tst-tls-debug-recursive.sh
new file mode 100755
index 0000000000000000..083e716f7216ffd5
--- /dev/null
+++ b/elf/tst-tls-debug-recursive.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+# Test for TLS logging in dynamic linker.
+# Copyright (C) 2026 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 script runs the tst-tls-debug-recursive test case and verifies its
+# LD_DEBUG=tls output. It checks for various TLS-related messages
+# to ensure the dynamic linker's TLS logging is working correctly.
+
+set -e
+common_objpfx="$1"
+test_wrapper_env="$2"
+rtld_prefix="$3"
+run_program_env="$4"
+test_program="$5"
+
+debug_output="${common_objpfx}elf/tst-tls-debug-recursive.debug"
+rm -f "${debug_output}".*
+
+# Run the test program with LD_DEBUG=tls.
+eval "${test_wrapper_env}" LD_DEBUG=tls LD_DEBUG_OUTPUT="${debug_output}" \
+ "${rtld_prefix}" "${test_program}"
+
+debug_output=$(ls "${debug_output}".*)
+
+fail=0
+
+# Check for expected messages
+if ! grep -q 'tls: DTV resized for TCB 0x.*: oldsize' "${debug_output}"; then
+ echo "FAIL: DTV resized message not found"
+ fail=1
+fi
+
+if ! grep -q 'tls: DTV update for TCB 0x.*: modid .* deallocated block' "${debug_output}"; then
+ echo "FAIL: module deallocated during DTV update message not found"
+ fail=1
+fi
+
+if ! grep -q 'tls: assign modid .* to' "${debug_output}"; then
+ echo "FAIL: module assigned message not found"
+ fail=1
+fi
+
+if ! grep -q 'tls: allocate block .* for modid .* size=.*, TCB=0x' "${debug_output}"; then
+ echo "FAIL: module allocated message not found"
+ fail=1
+fi
+
+if ! grep -q 'tls: modid .* update DTV to generation .* TCB=0x' "${debug_output}"; then
+ echo "FAIL: update DTV message not found"
+ fail=1
+fi
+
+if ! grep -q 'tls: initial modid limit set to' "${debug_output}"; then
+ echo "FAIL: initial modid limit message not found"
+ fail=1
+fi
+
+if [ $fail -ne 0 ]; then
+ echo "Test FAILED"
+ cat "${debug_output}"
+ rm -f "${debug_output}"
+ exit 1
+fi
+
+echo "Test PASSED"
+cat "${debug_output}"
+rm -f "${debug_output}"
+exit 0
diff --git a/nptl/Makefile b/nptl/Makefile
index 44bc59a2d3fea89f..fcb0582e3a1c2b1d 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -273,6 +273,7 @@ CFLAGS-tst-thread-exit-clobber.o = -std=gnu++11
LDLIBS-tst-thread-exit-clobber = -lstdc++
CFLAGS-tst-minstack-throw.o = -std=gnu++11
LDLIBS-tst-minstack-throw = -lstdc++
+LDLIBS-tst-dl-debug-tid = $(libdl)
tests = \
tst-attr2 \
@@ -483,6 +484,7 @@ modules-names = \
tst-compat-forwarder-mod \
tst-execstack-threads-mod \
tst-stack4mod \
+ tst-tls-debug-mod \
tst-tls3mod \
tst-tls5mod \
tst-tls5moda \
@@ -695,7 +697,8 @@ tst-stackguard1-ARGS = --command "$(host-test-program-cmd) --child"
tst-stackguard1-static-ARGS = --command "$(objpfx)tst-stackguard1-static --child"
ifeq ($(run-built-tests),yes)
-$(objpfx)tst-dl-debug-tid.out: tst-dl-debug-tid.sh $(objpfx)tst-dl-debug-tid
+$(objpfx)tst-dl-debug-tid.out: tst-dl-debug-tid.sh $(objpfx)tst-dl-debug-tid \
+ $(objpfx)tst-tls-debug-mod.so
$(SHELL) $< $(common-objpfx) '$(test-wrapper-env)' '$(rtld-prefix)' \
'$(run-program-env)' \
$(objpfx)tst-dl-debug-tid > $@; $(evaluate-test)
diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
index b26ebc8680d7280a..2876fd7fe306c639 100644
--- a/nptl/allocatestack.c
+++ b/nptl/allocatestack.c
@@ -117,7 +117,7 @@ get_cached_stack (size_t *sizep, void **memp)
lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
- GLRO (dl_debug_printf) ("TLS TCB reused from cache: 0x%lx\n",
+ GLRO (dl_debug_printf) ("tls: TCB reused from cache: 0x%lx\n",
(unsigned long int) result);
/* Report size and location of the stack to the caller. */
@@ -299,9 +299,9 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
GLRO (dl_debug_printf) (
- "TCB for user-supplied stack created: 0x%lx, stack=0x%lx, size=%lu\n",
- (unsigned long int) pd, (unsigned long int) pd->stackblock,
- (unsigned long int) pd->stackblock_size);
+ "tls: TCB created (user-supplied stack); stack=0x%lx, size=%lu, TCB=0x%lx\n",
+ (unsigned long int) pd->stackblock,
+ (unsigned long int) pd->stackblock_size, (unsigned long int) pd);
/* This is at least the second thread. */
pd->header.multiple_threads = 1;
@@ -438,7 +438,7 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
pd->setxid_futex = -1;
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
- GLRO (dl_debug_printf) ("TCB for new stack allocated: 0x%lx\n",
+ GLRO (dl_debug_printf) ("tls: TCB allocated (new stack): 0x%lx\n",
(unsigned long int) pd);
/* Allocate the DTV for this thread. */
diff --git a/nptl/nptl-stack.c b/nptl/nptl-stack.c
index f634d6a29fbdd624..3993a7a90d14d80d 100644
--- a/nptl/nptl-stack.c
+++ b/nptl/nptl-stack.c
@@ -77,7 +77,7 @@ __nptl_free_stacks (size_t limit)
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
GLRO (dl_debug_printf) (
- "TCB cache full, deallocating: TID=%ld, TCB=0x%lx\n",
+ "tls: TCB deallocating from full cache; TID=%ld, TCB=0x%lx\n",
(long int) curr->tid, (unsigned long int) curr);
/* Free the memory associated with the ELF TLS. */
@@ -104,7 +104,7 @@ queue_stack (struct pthread *stack)
/* The 'stack' parameter is a pointer to the TCB (struct pthread),
not just the stack. */
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
- GLRO (dl_debug_printf) ("TCB deallocated into cache: TID=%ld, TCB=0x%lx\n",
+ GLRO (dl_debug_printf) ("tls: TCB deallocated into cache; TID=%ld, TCB=0x%lx\n",
(long int) stack->tid, (unsigned long int) stack);
/* We unconditionally add the stack to the list. The memory may
@@ -139,7 +139,7 @@ __nptl_deallocate_stack (struct pthread *pd)
the TLS memory. */
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
GLRO (dl_debug_printf) (
- "TCB for user-supplied stack deallocated: TID=%ld, TCB=0x%lx\n",
+ "tls: TCB deallocated (user-supplied stack); TID=%ld, TCB=0x%lx\n",
(long int) pd->tid, (unsigned long int) pd);
/* Free the memory associated with the ELF TLS. */
_dl_deallocate_tls (TLS_TPADJ (pd), false);
diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
index b836a3ca9216245d..381ed68c05f58a1e 100644
--- a/nptl/pthread_create.c
+++ b/nptl/pthread_create.c
@@ -365,7 +365,7 @@ start_thread (void *arg)
}
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
- GLRO (dl_debug_printf) ("Thread starting: TID=%ld, TCB=0x%lx\n",
+ GLRO (dl_debug_printf) ("tls: thread starting; TID=%ld, TCB=0x%lx\n",
(long int) pd->tid, (unsigned long int) pd);
/* Initialize resolver state pointer. */
diff --git a/nptl/tst-dl-debug-tid.c b/nptl/tst-dl-debug-tid.c
index 231fa43516b233b3..f3b443f9786a9a99 100644
--- a/nptl/tst-dl-debug-tid.c
+++ b/nptl/tst-dl-debug-tid.c
@@ -27,12 +27,25 @@
#include <support/xthread.h>
#include <stdio.h>
#include <unistd.h>
+#include <dlfcn.h>
+#include <support/xdlfcn.h>
+#include <support/check.h>
static void *
thread_function (void *arg)
{
if (arg)
pthread_barrier_wait ((pthread_barrier_t *) arg);
+
+ /* Load a module with TLS to verify allocation/deallocation logs. */
+ void *h = xdlopen ("tst-tls-debug-mod.so", RTLD_NOW);
+
+ /* Call a function that accesses TLS. */
+ int (*fp) (void) = (int (*) (void)) xdlsym (h, "in_dso");
+ TEST_COMPARE (fp (), 0);
+
+ xdlclose (h);
+
return NULL;
}
diff --git a/nptl/tst-dl-debug-tid.sh b/nptl/tst-dl-debug-tid.sh
index 9ee31ac4f241f0b4..8708ac9f2ea25c9e 100644
--- a/nptl/tst-dl-debug-tid.sh
+++ b/nptl/tst-dl-debug-tid.sh
@@ -39,7 +39,7 @@ eval "${test_wrapper_env}" LD_DEBUG=tls LD_DEBUG_OUTPUT="${debug_output}" \
debug_output=$(ls "${debug_output}".*)
# Check for the "Thread starting" message.
-if ! grep -q 'Thread starting: TID=' "${debug_output}"; then
+if ! grep -q 'tls: thread starting; TID=.*, TCB=0x' "${debug_output}"; then
echo "error: 'Thread starting' message not found"
cat "${debug_output}"
exit 1
@@ -47,10 +47,10 @@ fi
# Check that we have a message where the PID (from prefix) is different
# from the TID (in the message). This indicates a worker thread log.
-if ! grep 'Thread starting: TID=' "${debug_output}" | awk -F '[ \t:]+' '{
- sub(/,/, "", $4);
- sub(/TID=/, "", $4);
- if ($1 != $4)
+if ! grep 'tls: thread starting; TID=.*, TCB=0x' "${debug_output}" | awk -F '[ \t:]+' '{
+ sub(/TID=/, "", $5);
+ sub(/,/, "", $5);
+ if ($1 != $5)
exit 0;
exit 1
}'; then
@@ -60,12 +60,33 @@ if ! grep 'Thread starting: TID=' "${debug_output}" | awk -F '[ \t:]+' '{
fi
# We expect messages from thread creation and destruction.
-if ! grep -q 'TCB allocated\|TCB deallocating\|TCB reused\|TCB deallocated' \
+if ! grep -q 'tls: allocate TCB 0x\|tls: deallocate TCB 0x\|tls: TCB reused from cache\|tls: TCB deallocated' \
"${debug_output}"; then
echo "error: Expected TCB allocation/deallocation message not found"
cat "${debug_output}"
exit 1
fi
+# Check for TLS module ID assignment.
+if ! grep -q 'tls: assign modid .* to' "${debug_output}"; then
+ echo "error: Expected 'modid ... assigned to' message not found"
+ cat "${debug_output}"
+ exit 1
+fi
+
+# Check for TLS block allocation.
+if ! grep -q 'tls: allocate block .* for modid .* size=.*, TCB=0x' "${debug_output}"; then
+ echo "error: Expected 'modid ... allocated' message not found"
+ cat "${debug_output}"
+ exit 1
+fi
+
+# TLS block deallocation might be skipped due to DTV surplus.
+if grep -q 'tls: deallocate block .* for modid .* TCB=0x' "${debug_output}"; then
+ echo "INFO: module deallocated message found"
+else
+ echo "INFO: module deallocated message not found (may be due to DTV surplus)"
+fi
+
cat "${debug_output}"
rm -f "${debug_output}"
diff --git a/nptl/tst-tls-debug-mod.c b/nptl/tst-tls-debug-mod.c
new file mode 100644
index 0000000000000000..0308c8a3e2d76fe1
--- /dev/null
+++ b/nptl/tst-tls-debug-mod.c
@@ -0,0 +1,26 @@
+/* Test for TLS logging in dynamic linker.
+ Copyright (C) 2026 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/>. */
+
+__thread char tls_var[32768] __attribute__ ((tls_model ("global-dynamic")));
+
+int
+in_dso (void)
+{
+ tls_var[0] = 42;
+ return tls_var[0] - 42;
+}
diff --git a/sysdeps/x86_64/dl-tls.c b/sysdeps/x86_64/dl-tls.c
index b3c1e4fcd7813b8b..8e550fc7d8efe2ff 100644
--- a/sysdeps/x86_64/dl-tls.c
+++ b/sysdeps/x86_64/dl-tls.c
@@ -41,11 +41,36 @@ __tls_get_addr_slow (GET_ADDR_ARGS)
dtv_t *dtv = THREAD_DTV ();
size_t gen = atomic_load_acquire (&GL(dl_tls_generation));
- if (__glibc_unlikely (dtv[0].counter != gen)
+ if (__glibc_unlikely (dtv[0].counter != gen))
+ {
/* See comment in __tls_get_addr in elf/dl-tls.c. */
- && !(_dl_tls_allocate_active ()
- && GET_ADDR_MODULE < _dl_tls_initial_modid_limit))
- return update_get_addr (GET_ADDR_PARAM, gen);
+ if (_dl_tls_allocate_active ()
+ && GET_ADDR_MODULE < _dl_tls_initial_modid_limit)
+ {
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf (
+ "tls: modid %lu reentrant usage; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE,
+ (unsigned long int) THREAD_SELF);
+ }
+ else
+ {
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf (
+ "tls: modid %lu update DTV to generation %lu; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE, (unsigned long int) gen,
+ (unsigned long int) THREAD_SELF);
+ return update_get_addr (GET_ADDR_PARAM, gen);
+ }
+ }
+
+ if (__glibc_unlikely (dtv[GET_ADDR_MODULE].pointer.val == TLS_DTV_UNALLOCATED))
+ {
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_TLS))
+ _dl_debug_printf ("tls: modid %lu lazy allocation; TCB=0x%lx\n",
+ (unsigned long int) GET_ADDR_MODULE,
+ (unsigned long int) THREAD_SELF);
+ }
return tls_get_addr_tail (GET_ADDR_PARAM, dtv, NULL);
}

177
glibc-RHEL-159427-5.patch Normal file
View File

@ -0,0 +1,177 @@
commit 9181dc6eb6084da95d8c14d9defe96189fd0360d
Author: Frédéric Bérat <berat.fred@gmail.com>
Date: Tue Jan 27 23:07:17 2026 +0100
feat(rtld): Allow LD_DEBUG category exclusion
Adds support for excluding specific categories from `LD_DEBUG` output.
The `LD_DEBUG` environment variable now accepts category names prefixed
with a dash (`-`) to disable their debugging output. This allows users
to enable broad categories (e.g., `all`) while suppressing verbose or
irrelevant information from specific sub-categories (e.g., `-tls`).
The `process_dl_debug` function in `rtld.c` has been updated to parse
these exclusion options and unset the corresponding bits in
`GLRO(dl_debug_mask)`. The `LD_DEBUG=help` output has also been updated
to document this new functionality. A new test `tst-dl-debug-exclude.sh`
is added to verify the correct behavior of category exclusion.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/elf/Makefile b/elf/Makefile
index 8651a96bf260416c..46bf893b8f6eb537 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -1353,6 +1353,7 @@ $(objpfx)tst-glibcelf.out: tst-glibcelf.py elf.h $(..)/scripts/glibcelf.py \
ifeq ($(run-built-tests),yes)
tests-special += $(objpfx)tst-tls-allocation-failure-static-patched.out
tests-special += $(objpfx)tst-tls-debug-recursive.out
+tests-special += $(objpfx)tst-dl-debug-exclude.out
endif
# The test requires shared _and_ PIE because the executable
@@ -3351,4 +3352,14 @@ $(objpfx)tst-tls-debug-recursive.out: tst-tls-debug-recursive.sh \
'$(rtld-prefix)' '$(run_program_env)' \
$(objpfx)tst-recursive-tls > $@; \
$(evaluate-test)
+
+$(objpfx)tst-dl-debug-exclude.out: tst-dl-debug-exclude.sh \
+ $(objpfx)tst-recursive-tls \
+ $(objpfx)tst-recursive-tlsmallocmod.so \
+ $(patsubst %,$(objpfx)tst-recursive-tlsmod%.so, \
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)
+ $(SHELL) $< $(common-objpfx) '$(test-wrapper-env)' \
+ '$(rtld-prefix)' '$(run_program_env)' \
+ $(objpfx)tst-recursive-tls > $@; \
+ $(evaluate-test)
endif
diff --git a/elf/rtld.c b/elf/rtld.c
index 8b09d8389a7050d2..e18f70349b337173 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2501,11 +2501,18 @@ process_dl_debug (struct dl_main_state *state, const char *dl_debug)
&& dl_debug[len] != ',' && dl_debug[len] != ':')
++len;
+ bool exclude = *dl_debug == '-';
+ const char *name = exclude ? dl_debug + 1 : dl_debug;
+ size_t name_len = exclude ? len - 1 : len;
+
for (cnt = 0; cnt < ndebopts; ++cnt)
- if (debopts[cnt].len == len
- && memcmp (dl_debug, debopts[cnt].name, len) == 0)
+ if (debopts[cnt].len == name_len
+ && memcmp (name, debopts[cnt].name, name_len) == 0)
{
- GLRO(dl_debug_mask) |= debopts[cnt].mask;
+ if (exclude)
+ GLRO(dl_debug_mask) &= ~debopts[cnt].mask;
+ else
+ GLRO(dl_debug_mask) |= debopts[cnt].mask;
break;
}
@@ -2547,7 +2554,8 @@ Valid options for the LD_DEBUG environment variable are:\n\n");
_dl_printf ("\n\
To direct the debugging output into a file instead of standard output\n\
-a filename can be specified using the LD_DEBUG_OUTPUT environment variable.\n");
+a filename can be specified using the LD_DEBUG_OUTPUT environment variable.\n\
+Categories can be excluded by prefixing them with a dash (-).\n");
_exit (0);
}
}
diff --git a/elf/tst-dl-debug-exclude.sh b/elf/tst-dl-debug-exclude.sh
new file mode 100644
index 0000000000000000..9837ffcea8e07933
--- /dev/null
+++ b/elf/tst-dl-debug-exclude.sh
@@ -0,0 +1,87 @@
+#!/bin/sh
+# Test for LD_DEBUG category exclusion.
+# Copyright (C) 2026 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 script verifies the LD_DEBUG category exclusion functionality.
+# It checks that:
+# 1. Categories can be excluded using the '-' prefix.
+# 2. Options are processed sequentially, meaning the last specified
+# option for a category (enable or exclude) takes precedence.
+
+set -e
+
+common_objpfx="$1"
+test_wrapper_env="$2"
+rtld_prefix="$3"
+run_program_env="$4"
+test_program="$5"
+
+debug_output="${common_objpfx}elf/tst-dl-debug-exclude.debug"
+rm -f "${debug_output}".*
+
+# Run the test program with LD_DEBUG=all,-tls.
+# We expect general logs but no TLS logs.
+eval "${test_wrapper_env}" LD_DEBUG=all,-tls LD_DEBUG_OUTPUT="${debug_output}" \
+ "${rtld_prefix}" "${test_program}"
+
+fail=0
+
+# 1. Check that general logs are present (e.g., file loading)
+if ! grep -q 'file=' "${debug_output}".*; then
+ echo "FAIL: 'file=' message not found (LD_DEBUG=all failed)"
+ fail=1
+fi
+
+# 2. Check that TLS logs are NOT present
+if grep -q 'tls: ' "${debug_output}".*; then
+ echo "FAIL: TLS message found (exclusion of -tls failed)"
+ fail=1
+fi
+
+rm -f "${debug_output}".*
+# 3. Check for LD_DEBUG=all,-tls,tls (ordering verification)
+# We expect TLS logs to BE present
+eval "${test_wrapper_env}" LD_DEBUG=all,-tls,tls LD_DEBUG_OUTPUT="${debug_output}" \
+ "${rtld_prefix}" "${test_program}"
+
+if ! grep -q 'tls: ' "${debug_output}".*; then
+ echo "FAIL: TLS message not found (ordering -tls,tls failed)"
+ fail=1
+fi
+
+rm -f "${debug_output}".*
+# 4. Check for LD_DEBUG=tls,-tls
+# We expect TLS logs to NOT be present
+eval "${test_wrapper_env}" LD_DEBUG=tls,-tls LD_DEBUG_OUTPUT="${debug_output}" \
+ "${rtld_prefix}" "${test_program}"
+
+if grep -q 'tls: ' "${debug_output}".*; then
+ echo "FAIL: TLS message found (ordering tls,-tls failed)"
+ fail=1
+fi
+
+if [ $fail -ne 0 ]; then
+ echo "Test FAILED"
+ cat "${debug_output}".*
+ rm -f "${debug_output}".*
+ exit 1
+fi
+
+echo "Test PASSED"
+rm -f "${debug_output}".*
+exit 0

321
glibc-RHEL-162885.patch Normal file
View File

@ -0,0 +1,321 @@
commit d6f08d1cf027f4eb2ba289a6cc66853722d4badc
Author: Florian Weimer <fweimer@redhat.com>
Date: Thu Apr 16 19:13:43 2026 +0200
Use pending character state in IBM1390, IBM1399 character sets (CVE-2026-4046)
Follow the example in iso-2022-jp-3.c and use the __count state
variable to store the pending character. This avoids restarting
the conversion if the output buffer ends between two 4-byte UCS-4
code points, so that the assert reported in the bug can no longer
happen.
Even though the fix is applied to ibm1364.c, the change is only
effective for the two HAS_COMBINED codecs for IBM1390, IBM1399.
The test case was mostly auto-generated using
claude-4.6-opus-high-thinking, and composer-2-fast shows up in the
log as well. During review, gpt-5.4-xhigh flagged that the original
version of the test case was not exercising the new character
flush logic.
This fixes bug 33980.
Assisted-by: LLM
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
diff --git a/iconvdata/Makefile b/iconvdata/Makefile
index 7196a8744bb66e8c..090ba929b1ec646e 100644
--- a/iconvdata/Makefile
+++ b/iconvdata/Makefile
@@ -76,7 +76,7 @@ tests = bug-iconv1 bug-iconv2 tst-loading tst-e2big tst-iconv4 bug-iconv4 \
tst-iconv6 bug-iconv5 bug-iconv6 tst-iconv7 bug-iconv8 bug-iconv9 \
bug-iconv10 bug-iconv11 bug-iconv12 tst-iconv-big5-hkscs-to-2ucs4 \
bug-iconv13 bug-iconv14 bug-iconv15 \
- tst-iconv-iso-2022-cn-ext
+ tst-iconv-iso-2022-cn-ext tst-bug33980
ifeq ($(have-thread-library),yes)
tests += bug-iconv3
endif
@@ -333,6 +333,8 @@ $(objpfx)bug-iconv15.out: $(addprefix $(objpfx), $(gconv-modules)) \
$(addprefix $(objpfx),$(modules.so))
$(objpfx)tst-iconv-iso-2022-cn-ext.out: $(addprefix $(objpfx), $(gconv-modules)) \
$(addprefix $(objpfx),$(modules.so))
+$(objpfx)tst-bug33980.out: $(addprefix $(objpfx), $(gconv-modules)) \
+ $(addprefix $(objpfx),$(modules.so))
$(objpfx)iconv-test.out: run-iconv-test.sh \
$(addprefix $(objpfx), $(gconv-modules)) \
diff --git a/iconvdata/ibm1364.c b/iconvdata/ibm1364.c
index d6c8ce7f682aa64d..47970008f7ef31ec 100644
--- a/iconvdata/ibm1364.c
+++ b/iconvdata/ibm1364.c
@@ -67,12 +67,29 @@
/* Since this is a stateful encoding we have to provide code which resets
the output state to the initial state. This has to be done during the
- flushing. */
+ flushing. For the to-internal direction (FROM_DIRECTION is true),
+ there may be a pending character that needs flushing. */
#define EMIT_SHIFT_TO_INIT \
if ((data->__statep->__count & ~7) != sb) \
{ \
if (FROM_DIRECTION) \
- data->__statep->__count &= 7; \
+ { \
+ uint32_t ch = data->__statep->__count >> 7; \
+ if (__glibc_unlikely (ch != 0)) \
+ { \
+ if (__glibc_unlikely (outend - outbuf < 4)) \
+ status = __GCONV_FULL_OUTPUT; \
+ else \
+ { \
+ put32 (outbuf, ch); \
+ outbuf += 4; \
+ /* Clear character and db bit. */ \
+ data->__statep->__count &= 7; \
+ } \
+ } \
+ else \
+ data->__statep->__count &= 7; \
+ } \
else \
{ \
/* We are not in the initial state. To switch back we have \
@@ -99,11 +116,13 @@
*curcsp = save_curcs
-/* Current codeset type. */
+/* Current codeset type. The bit is stored in the __count variable of
+ the conversion state. If the db bit is set, bit 7 and above store
+ a pending UCS-4 code point if non-zero. */
enum
{
- sb = 0,
- db = 64
+ sb = 0, /* Single byte mode. */
+ db = 64 /* Double byte mode. */
};
@@ -119,21 +138,29 @@ enum
} \
else \
{ \
- /* This is a combined character. Make sure we have room. */ \
- if (__glibc_unlikely (outptr + 8 > outend)) \
- { \
- result = __GCONV_FULL_OUTPUT; \
- break; \
- } \
- \
const struct divide *cmbp \
= &DB_TO_UCS4_COMB[ch - __TO_UCS4_COMBINED_MIN]; \
assert (cmbp->res1 != 0 && cmbp->res2 != 0); \
\
put32 (outptr, cmbp->res1); \
outptr += 4; \
- put32 (outptr, cmbp->res2); \
- outptr += 4; \
+ \
+ /* See whether we have room for the second character. */ \
+ if (outend - outptr >= 4) \
+ { \
+ put32 (outptr, cmbp->res2); \
+ outptr += 4; \
+ } \
+ else \
+ { \
+ /* Otherwise store only the first character now, and \
+ put the second one into the queue. */ \
+ curcs |= cmbp->res2 << 7; \
+ inptr += 2; \
+ /* Tell the caller why we terminate the loop. */ \
+ result = __GCONV_FULL_OUTPUT; \
+ break; \
+ } \
} \
}
#else
@@ -153,7 +180,20 @@ enum
#define LOOPFCT FROM_LOOP
#define BODY \
{ \
- uint32_t ch = *inptr; \
+ uint32_t ch; \
+ \
+ ch = curcs >> 7; \
+ if (__glibc_unlikely (ch != 0)) \
+ { \
+ put32 (outptr, ch); \
+ outptr += 4; \
+ /* Remove the pending character, but preserve state bits. */ \
+ curcs &= (1 << 7) - 1; \
+ continue; \
+ } \
+ \
+ /* Otherwise read the next input byte. */ \
+ ch = *inptr; \
\
if (__builtin_expect (ch, 0) == SO) \
{ \
diff --git a/iconvdata/tst-bug33980.c b/iconvdata/tst-bug33980.c
new file mode 100644
index 0000000000000000..c9693e0efebe4eae
--- /dev/null
+++ b/iconvdata/tst-bug33980.c
@@ -0,0 +1,153 @@
+/* Test for bug 33980: combining characters in IBM1390/IBM1399.
+ Copyright (C) 2026 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 <alloc_buffer.h>
+#include <errno.h>
+#include <iconv.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <support/check.h>
+#include <support/next_to_fault.h>
+#include <support/support.h>
+
+/* Run iconv in a loop with a small output buffer of OUTBUFSIZE bytes
+ starting at OUTBUF. OUTBUF should be right before an unmapped page
+ so that writing past the end will fault. Skip SHIFT bytes at the
+ start of the input and output, to exercise different buffer
+ alignment. TRUNCATE indicates skipped bytes at the end of
+ input (0 and 1 a valid). */
+static void
+test_one (const char *encoding, unsigned int shift, unsigned int truncate,
+ char *outbuf, size_t outbufsize)
+{
+ /* In IBM1390 and IBM1399, the DBCS code 0xECB5 expands to two
+ Unicode code points when translated. */
+ static char input[] =
+ {
+ /* 8 letters X. */
+ 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7,
+ /* SO, 0xECB5, SI: shift to DBCS, special character, shift back. */
+ 0x0e, 0xec, 0xb5, 0x0f
+ };
+
+ /* Expected output after UTF-8 conversion. */
+ static char expected[] =
+ {
+ 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X',
+ /* U+304B (HIRAGANA LETTER KA). */
+ 0xe3, 0x81, 0x8b,
+ /* U+309A (COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK). */
+ 0xe3, 0x82, 0x9a
+ };
+
+ iconv_t cd = iconv_open ("UTF-8", encoding);
+ TEST_VERIFY_EXIT (cd != (iconv_t) -1);
+
+ char result_storage[64];
+ struct alloc_buffer result_buf
+ = alloc_buffer_create (result_storage, sizeof (result_storage));
+
+ char *inptr = &input[shift];
+ size_t inleft = sizeof (input) - shift - truncate;
+
+ while (inleft > 0)
+ {
+ char *outptr = outbuf;
+ size_t outleft = outbufsize;
+ size_t inleft_before = inleft;
+
+ size_t ret = iconv (cd, &inptr, &inleft, &outptr, &outleft);
+ size_t produced = outptr - outbuf;
+ alloc_buffer_copy_bytes (&result_buf, outbuf, produced);
+
+ if (ret == (size_t) -1 && errno == E2BIG)
+ {
+ if (produced == 0 && inleft == inleft_before)
+ {
+ /* Output buffer too small to make progress. This is
+ expected for very small output buffer sizes. */
+ TEST_VERIFY_EXIT (outbufsize < 3);
+ break;
+ }
+ continue;
+ }
+ if (ret == (size_t) -1)
+ FAIL_EXIT1 ("%s (outbufsize %zu): iconv: %m", encoding, outbufsize);
+ break;
+ }
+
+ /* Flush any pending state (e.g. a buffered combined character).
+ With outbufsize < 3, we could not store the first character, so
+ the second character did not become pending, and there is nothing
+ to flush. */
+ {
+ char *outptr = outbuf;
+ size_t outleft = outbufsize;
+
+ size_t ret = iconv (cd, NULL, NULL, &outptr, &outleft);
+ TEST_VERIFY_EXIT (ret == 0);
+ size_t produced = outptr - outbuf;
+ alloc_buffer_copy_bytes (&result_buf, outbuf, produced);
+
+ /* Second flush does not provide more data. */
+ outptr = outbuf;
+ outleft = outbufsize;
+ ret = iconv (cd, NULL, NULL, &outptr, &outleft);
+ TEST_VERIFY_EXIT (ret == 0);
+ TEST_VERIFY (outptr == outbuf);
+ }
+
+ TEST_VERIFY_EXIT (!alloc_buffer_has_failed (&result_buf));
+ size_t result_used
+ = sizeof (result_storage) - alloc_buffer_size (&result_buf);
+
+ if (outbufsize >= 3)
+ {
+ TEST_COMPARE (inleft, 0);
+ TEST_COMPARE (result_used, sizeof (expected) - shift);
+ TEST_COMPARE_BLOB (result_storage, result_used,
+ &expected[shift], sizeof (expected) - shift);
+ }
+ else
+ /* If the buffer is too small, only the leading X could be converted. */
+ TEST_COMPARE (result_used, 8 - shift);
+
+ TEST_VERIFY_EXIT (iconv_close (cd) == 0);
+}
+
+static int
+do_test (void)
+{
+ struct support_next_to_fault ntf
+ = support_next_to_fault_allocate (8);
+
+ for (int shift = 0; shift <= 8; ++shift)
+ for (int truncate = 0; truncate < 2; ++truncate)
+ for (size_t outbufsize = 1; outbufsize <= 8; outbufsize++)
+ {
+ char *outbuf = ntf.buffer + ntf.length - outbufsize;
+ test_one ("IBM1390", shift, truncate, outbuf, outbufsize);
+ test_one ("IBM1399", shift, truncate, outbuf, outbufsize);
+ }
+
+ support_next_to_fault_free (&ntf);
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -2357,7 +2357,7 @@ update_gconv_modules_cache ()
%endif
%changelog
* Tue May 19 2026 Eduard Abdullin <eabdullin@almalinux.org> - 2.39-121.alma.1
* Tue May 26 2026 Eduard Abdullin <eabdullin@almalinux.org> - 2.39-124.alma.1
- Overwrite target for x86_64_v2
- Update patch-git.lua to handle AlmaLinux branches correctly
- Add support for AlmaLinux import UBI format

View File

@ -1,2 +1,2 @@
3e6989278e3b421cc69180f93e6b3ab73b2ce5fe
8f42d24c37304d4db88237907d86b220cf0ecf3c
v1

File diff suppressed because it is too large Load Diff