diff --git a/glibc-RHEL-49785-1.patch b/glibc-RHEL-49785-1.patch new file mode 100644 index 0000000..b9a8d92 --- /dev/null +++ b/glibc-RHEL-49785-1.patch @@ -0,0 +1,56 @@ +commit a0f9bfc3a5cc10920787d70d0653720a8fa013f3 +Author: Adhemerval Zanella +Date: Mon Nov 6 17:25:48 2023 -0300 + + elf: Remove any_debug from dl_main_state + + Its usage can be implied by the GLRO(dl_debug_mask). + Reviewed-by: Siddhesh Poyarekar + +Conflicts: + elf/dl-main.h + (fixup context) + +diff --git a/elf/dl-main.h b/elf/dl-main.h +index d3820e0063143878..35728d47d463c3fb 100644 +--- a/elf/dl-main.h ++++ b/elf/dl-main.h +@@ -94,9 +94,6 @@ struct dl_main_state + + enum rtld_mode mode; + +- /* True if any of the debugging options is enabled. */ +- bool any_debug; +- + /* True if information about versions has to be printed. */ + bool version_info; + }; +diff --git a/elf/rtld.c b/elf/rtld.c +index b788857924e5a388..629ad7f8f4f75353 100644 +--- a/elf/rtld.c ++++ b/elf/rtld.c +@@ -305,7 +305,6 @@ dl_main_state_init (struct dl_main_state *state) + state->glibc_hwcaps_prepend = NULL; + state->glibc_hwcaps_mask = NULL; + state->mode = rtld_mode_normal; +- state->any_debug = false; + state->version_info = false; + } + +@@ -2707,7 +2706,6 @@ process_dl_debug (struct dl_main_state *state, const char *dl_debug) + && memcmp (dl_debug, debopts[cnt].name, len) == 0) + { + GLRO(dl_debug_mask) |= debopts[cnt].mask; +- state->any_debug = true; + break; + } + +@@ -2954,7 +2952,7 @@ process_envvars (struct dl_main_state *state) + /* If we have to run the dynamic linker in debugging mode and the + LD_DEBUG_OUTPUT environment variable is given, we write the debug + messages to this file. */ +- else if (state->any_debug && debug_output != NULL) ++ else if (GLRO(dl_debug_mask) != 0 && debug_output != NULL) + { + const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW; + size_t name_len = strlen (debug_output); diff --git a/glibc-RHEL-49785-2.patch b/glibc-RHEL-49785-2.patch new file mode 100644 index 0000000..312faf7 --- /dev/null +++ b/glibc-RHEL-49785-2.patch @@ -0,0 +1,374 @@ +commit 332f8e62afef53492dd8285490bcf7aeef18c80a +Author: Frédéric Bérat +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 + +Conflicts: + sysdeps/generic/ldsodefs.h + (PRELINK is still there downstream) + +diff --git a/elf/dl-tls.c b/elf/dl-tls.c +index 1b1bc4292eb24747..20d6036b891f653c 100644 +--- a/elf/dl-tls.c ++++ b/elf/dl-tls.c +@@ -503,6 +503,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; +@@ -691,6 +693,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 629ad7f8f4f75353..ff4c160d8fafd70d 100644 +--- a/elf/rtld.c ++++ b/elf/rtld.c +@@ -2676,10 +2676,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 80c7587f0086677b..7001ecd17b752dba 100644 +--- a/nptl/Makefile ++++ b/nptl/Makefile +@@ -374,6 +374,7 @@ tests-container = tst-pthread-getattr + tests-internal := \ + tst-barrier5 \ + tst-cond22 \ ++ tst-dl-debug-tid \ + tst-mutex8 \ + tst-mutex8-static \ + tst-mutexpi8 \ +@@ -562,6 +563,7 @@ xtests-static += tst-setuid1-static + + ifeq ($(run-built-tests),yes) + tests-special += \ ++ $(objpfx)tst-dl-debug-tid.out \ + $(objpfx)tst-oddstacklimit.out \ + $(objpfx)tst-stack3-mem.out \ + # tests-special +@@ -685,6 +687,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 b0f40afd3f5801f6..9e0bb8f0eeff8e88 100644 +--- a/nptl/allocatestack.c ++++ b/nptl/allocatestack.c +@@ -114,6 +114,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; +@@ -291,6 +295,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; + #ifndef TLS_MULTIPLE_THREADS_IN_TCB +@@ -425,6 +435,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 3f33a4c20b39a4aa..4ef6527ff7e8192b 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 109c5e3dc78c9aa2..37de54eaa570cedb 100644 +--- a/nptl/pthread_create.c ++++ b/nptl/pthread_create.c +@@ -362,6 +362,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 ++ . */ ++ ++/* 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 ++#include ++#include ++#include ++ ++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 +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 ++# . ++ ++# 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 e5860916345487e7..5080db9381c31c4f 100644 +--- a/sysdeps/generic/ldsodefs.h ++++ b/sysdeps/generic/ldsodefs.h +@@ -560,9 +560,10 @@ 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_PRELINK (1 << 11) ++#define DL_DEBUG_TLS (1 << 12) + + /* OS version. */ + EXTERN unsigned int _dl_osversion; diff --git a/glibc-RHEL-49785-3.patch b/glibc-RHEL-49785-3.patch new file mode 100644 index 0000000..3969165 --- /dev/null +++ b/glibc-RHEL-49785-3.patch @@ -0,0 +1,107 @@ +commit d4d472366ba69df7b14eba22a75f887b99855d70 +Author: Frédéric Bérat +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 + +diff --git a/manual/dynlink.texi b/manual/dynlink.texi +index 2b35e5883c3e65c0..469340e1d4ae8a88 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 + diff --git a/glibc-RHEL-49785-4.patch b/glibc-RHEL-49785-4.patch new file mode 100644 index 0000000..6ee9ef6 --- /dev/null +++ b/glibc-RHEL-49785-4.patch @@ -0,0 +1,70 @@ +commit 20092f2ef601aef57cc184cbacd7cab39bba5a25 +Author: Yury Khrustalev +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 + +diff --git a/nptl/Makefile b/nptl/Makefile +index 7001ecd17b752dba..597311bf07223c4f 100644 +--- a/nptl/Makefile ++++ b/nptl/Makefile +@@ -688,8 +688,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 diff --git a/glibc-RHEL-49785-5.patch b/glibc-RHEL-49785-5.patch new file mode 100644 index 0000000..be0b0eb --- /dev/null +++ b/glibc-RHEL-49785-5.patch @@ -0,0 +1,681 @@ +commit 1e47dbcce4d5cf2ce71377e729014e454a3e15ae +Author: Frédéric Bérat +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 + +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 bd12a20435538fdd..2431e7032c44beb8 100644 +--- a/elf/Makefile ++++ b/elf/Makefile +@@ -1252,6 +1252,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 +@@ -3052,3 +3053,15 @@ LDFLAGS-tst-version-hash-zero-linkmod.so = \ + $(objpfx)tst-version-hash-zero-refmod.so: \ + $(objpfx)tst-version-hash-zero-linkmod.so + tst-version-hash-zero-refmod.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 4514c53d2db261c1..8020b0b182d66f4f 100644 +--- a/elf/dl-close.c ++++ b/elf/dl-close.c +@@ -79,6 +79,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 20d6036b891f653c..f22d828a86539057 100644 +--- a/elf/dl-tls.c ++++ b/elf/dl-tls.c +@@ -215,6 +215,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); + } + + +@@ -504,7 +510,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; +@@ -517,13 +523,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)) + { +@@ -592,7 +603,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]); +@@ -683,9 +694,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) + +@@ -694,14 +710,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)) +@@ -773,6 +797,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, +@@ -874,7 +904,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); + +@@ -895,6 +925,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 (); +@@ -975,6 +1011,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 +@@ -1030,18 +1071,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); + } + } +@@ -1049,7 +1100,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; + } +@@ -1119,6 +1176,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 ff4c160d8fafd70d..3221bc8520996097 100644 +--- a/elf/rtld.c ++++ b/elf/rtld.c +@@ -1257,6 +1257,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 ++# . ++ ++# 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 597311bf07223c4f..958de6b0002f63aa 100644 +--- a/nptl/Makefile ++++ b/nptl/Makefile +@@ -274,6 +274,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 \ +@@ -486,6 +487,7 @@ modules-names = \ + tst-compat-forwarder-mod \ + tst-execstack-mod \ + tst-stack4mod \ ++ tst-tls-debug-mod \ + tst-tls3mod \ + tst-tls5mod \ + tst-tls5moda \ +@@ -687,7 +689,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 9e0bb8f0eeff8e88..6c6094e929f927cc 100644 +--- a/nptl/allocatestack.c ++++ b/nptl/allocatestack.c +@@ -115,7 +115,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. */ +@@ -297,9 +297,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; +@@ -436,7 +436,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 4ef6527ff7e8192b..d2d0729035a19dfe 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 37de54eaa570cedb..c69b4108f60f647d 100644 +--- a/nptl/pthread_create.c ++++ b/nptl/pthread_create.c +@@ -363,7 +363,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 + #include + #include ++#include ++#include ++#include + + 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 ++ . */ ++ ++__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 7abfb18413b1d06c..2bd336876a6613c8 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); + } diff --git a/glibc-RHEL-49785-6.patch b/glibc-RHEL-49785-6.patch new file mode 100644 index 0000000..e109ac5 --- /dev/null +++ b/glibc-RHEL-49785-6.patch @@ -0,0 +1,177 @@ +commit 9181dc6eb6084da95d8c14d9defe96189fd0360d +Author: Frédéric Bérat +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 + +diff --git a/elf/Makefile b/elf/Makefile +index 2431e7032c44beb8..ec87811c61565511 100644 +--- a/elf/Makefile ++++ b/elf/Makefile +@@ -1253,6 +1253,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 +@@ -3064,4 +3065,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 3221bc8520996097..a4290890d6bca263 100644 +--- a/elf/rtld.c ++++ b/elf/rtld.c +@@ -2708,11 +2708,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; + } + +@@ -2754,7 +2761,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 ++# . ++ ++# 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