[PATCH v4 1/2] elf(tls): Add debug logging for TLS operations

Frederic Berat fberat@redhat.com
Tue Feb 24 08:09:12 GMT 2026


On Fri, Feb 13, 2026 at 1:57 PM Adhemerval Zanella Netto <
adhemerval.zanella@linaro.org> wrote:

>
>
> On 11/02/26 10:59, Frédéric Bérat wrote:
> > 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.
>
> This version looks good to me, thanks.
>
> Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
>

Thanks, but I'm afraid I'll need to send a new version, I got test failures
on arm, I need to tweak my test.

>
> > ---
> > v4:
> > - Replace __func__ prefix with "tls: " to provide more meaningful
> >   context.
> > - Clarify TLS log messages (e.g., "TLS initialized") to avoid ambiguity.
> > - Remove TCB information from global events (modid assign/release).
> > - Fix Lmid_t printing to use %ld (signed long).
> > - Add missing glibc headers and descriptive comments to new test files.
> > - Update tst-tls-debug-recursive.sh and tst-dl-debug-tid.sh to match the
> >   new format.
> >
> >  elf/Makefile                   | 15 ++++++
> >  elf/dl-close.c                 |  5 ++
> >  elf/dl-tls.c                   | 83 +++++++++++++++++++++++++++++-----
> >  elf/rtld.c                     |  5 ++
> >  elf/tst-tls-debug-recursive.sh | 83 ++++++++++++++++++++++++++++++++++
> >  nptl/Makefile                  |  5 +-
> >  nptl/allocatestack.c           | 10 ++--
> >  nptl/nptl-stack.c              |  6 +--
> >  nptl/pthread_create.c          |  2 +-
> >  nptl/tst-dl-debug-tid.c        | 13 ++++++
> >  nptl/tst-dl-debug-tid.sh       | 33 +++++++++++---
> >  nptl/tst-tls-debug-mod.c       | 26 +++++++++++
> >  sysdeps/x86_64/dl-tls.c        | 33 ++++++++++++--
> >  13 files changed, 288 insertions(+), 31 deletions(-)
> >  create mode 100755 elf/tst-tls-debug-recursive.sh
> >  create mode 100644 nptl/tst-tls-debug-mod.c
> >
> > diff --git a/elf/Makefile b/elf/Makefile
> > index 396e97b5e7..9d4dfcf4ae 100644
> > --- a/elf/Makefile
> > +++ b/elf/Makefile
> > @@ -3539,3 +3539,18 @@ $(objpfx)tst-origin.out: tst-origin.sh
> $(objpfx)tst-origin
> >       $(evaluate-test)
> >
> >  $(objpfx)tst-dlopen-sgid.out: $(objpfx)tst-dlopen-sgid-mod.so
> > +
> > +
> > +ifeq ($(run-built-tests),yes)
> > +tests-special += $(objpfx)tst-tls-debug-recursive.out
> > +
> > +$(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
>
> Ok.
>
> > diff --git a/elf/dl-close.c b/elf/dl-close.c
> > index fca877534e..92bce07c7a 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);
>
> Ok.
>
> > diff --git a/elf/dl-tls.c b/elf/dl-tls.c
> > index 8cef809261..5bfcb184ed 100644
> > --- a/elf/dl-tls.c
> > +++ b/elf/dl-tls.c
> > @@ -220,6 +220,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);
> >  }
> >
> >
> > @@ -538,7 +544,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;
> > @@ -551,13 +557,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))
> >      {
> > @@ -626,7 +637,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]);
> > @@ -717,9 +728,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)
> >
> > @@ -728,14 +744,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))
> > @@ -790,6 +814,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,
> > @@ -891,7 +921,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);
> >
> > @@ -912,6 +942,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 ();
> > @@ -1004,6 +1040,11 @@ tls_get_addr_tail (tls_index *ti, dtv_t *dtv,
> struct link_map *the_map)
> >         dtv[ti->ti_module].pointer.to_free = NULL;
> >         dtv[ti->ti_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) ti->ti_module,
> > +                           (unsigned long int) THREAD_SELF);
> > +
> >         return tls_get_addr_adjust (p, ti);
> >       }
> >        else
> > @@ -1059,18 +1100,28 @@ __tls_get_addr (tls_index *ti)
> >      {
> >        if (_dl_tls_allocate_active ()
> >         && ti->ti_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) ti->ti_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) ti->ti_module, (unsigned long int) gen,
> > +             (unsigned long int) THREAD_SELF);
> >         return update_get_addr (ti, gen);
> >       }
> >      }
> > @@ -1078,7 +1129,13 @@ __tls_get_addr (tls_index *ti)
> >    void *p = dtv[ti->ti_module].pointer.val;
> >
> >    if (__glibc_unlikely (p == TLS_DTV_UNALLOCATED))
> > -    return tls_get_addr_tail (ti, 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) ti->ti_module,
> > +                       (unsigned long int) THREAD_SELF);
> > +      return tls_get_addr_tail (ti, dtv, NULL);
> > +    }
> >
> >    return tls_get_addr_adjust (p, ti);
> >  }
> > @@ -1149,6 +1206,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);
> >  }
> >
> >
>
> OK.
>
> > diff --git a/elf/rtld.c b/elf/rtld.c
> > index 29e7a4ddfa..1fc32ebefa 100644
> > --- a/elf/rtld.c
> > +++ b/elf/rtld.c
> > @@ -1192,6 +1192,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-tls-debug-recursive.sh
> b/elf/tst-tls-debug-recursive.sh
> > new file mode 100755
> > index 0000000000..083e716f72
> > --- /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
>
> Ok.  Myabe harness to check for the expected number of messages for each
> type.
>

Considering that I got a failure on arm for a missing log (due to a
different path taken), I wonder if I should.


>
> > diff --git a/nptl/Makefile b/nptl/Makefile
> > index 08b8ba8a31..85f95dd0cf 100644
> > --- a/nptl/Makefile
> > +++ b/nptl/Makefile
> > @@ -266,6 +266,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 \
> > @@ -485,6 +486,7 @@ modules-names = \
> >    tst-audit-threads-mod2 \
> >    tst-compat-forwarder-mod \
> >    tst-stack4mod \
> > +  tst-tls-debug-mod \
> >    tst-tls3mod \
> >    tst-tls5mod \
> >    tst-tls5moda \
> > @@ -710,7 +712,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 d3a9dbd3d2..b2ecb00113 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.  */
> > @@ -436,9 +436,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;
> > @@ -561,7 +561,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.  */
>
> Ok.
>
> > diff --git a/nptl/nptl-stack.c b/nptl/nptl-stack.c
> > index 4ae081c55e..d327c7f7be 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);
>
> Ok.
>
> > diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
> > index 6e7e0c6435..9a0cefb0f5 100644
> > --- a/nptl/pthread_create.c
> > +++ b/nptl/pthread_create.c
> > @@ -367,7 +367,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 b530d2170a..faa3b795b5 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;
> >  }
> >
>
> Ok.
>
> > diff --git a/nptl/tst-dl-debug-tid.sh b/nptl/tst-dl-debug-tid.sh
> > index 12a4aa2b34..a748865a25 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 0000000000..0786d3755c
> > --- /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 int tls_var __attribute__ ((tls_model ("global-dynamic")));
> > +
> > +int
> > +in_dso (void)
> > +{
> > +  tls_var = 42;
> > +  return tls_var - 42;
> > +}
> > diff --git a/sysdeps/x86_64/dl-tls.c b/sysdeps/x86_64/dl-tls.c
> > index a1877eeaed..6aee0972bb 100644
> > --- a/sysdeps/x86_64/dl-tls.c
> > +++ b/sysdeps/x86_64/dl-tls.c
> > @@ -41,11 +41,36 @@ __tls_get_addr_slow (tls_index *ti)
> >    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 ()
> > -           && ti->ti_module < _dl_tls_initial_modid_limit))
> > -    return update_get_addr (ti, gen);
> > +      if (_dl_tls_allocate_active ()
> > +       && ti->ti_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) ti->ti_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) ti->ti_module, (unsigned long int) gen,
> > +             (unsigned long int) THREAD_SELF);
> > +       return update_get_addr (ti, gen);
> > +     }
> > +    }
> > +
> > +  if (__glibc_unlikely (dtv[ti->ti_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) ti->ti_module,
> > +                       (unsigned long int) THREAD_SELF);
> > +    }
> >
> >    return tls_get_addr_tail (ti, dtv, NULL);
> >  }
> >
> > base-commit: 60a3b44f603f1410a6fd2a63f96da65095269bad
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20260224/3ff7fa12/attachment-0001.htm>


More information about the Libc-alpha mailing list