elf: Fix ld.so SIGSEGV in trace mode with unresolved TLS symbols (BZ 34532)

Message ID 20260817142139.451836-1-adhemerval.zanella@linaro.org (mailing list archive)
State New
Headers
Series elf: Fix ld.so SIGSEGV in trace mode with unresolved TLS symbols (BZ 34532) |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed
redhat-pt-bot/TryBot-32bit success Build for i686

Commit Message

Adhemerval Zanella Netto Aug. 17, 2026, 2:21 p.m. UTC
  On loongarch and hppa, running ld.so in trace mode on an object with
dynamic relocations against undefined TLS symbols crashes with SIGSEGV.

Add the same NULL guards used by all other targets.

Checked on loongarch64-linux-gnu-lp64d and hppa-linux-gnu.
---
 elf/Makefile                   |  5 +++
 elf/tst-trace-tls-mod.c        | 41 +++++++++++++++++
 elf/tst-trace-tls.c            | 82 ++++++++++++++++++++++++++++++++++
 sysdeps/hppa/dl-machine.h      |  4 +-
 sysdeps/loongarch/dl-machine.h | 14 ++++--
 5 files changed, 141 insertions(+), 5 deletions(-)
 create mode 100644 elf/tst-trace-tls-mod.c
 create mode 100644 elf/tst-trace-tls.c
  

Comments

mengqinggang Aug. 20, 2026, 9:56 a.m. UTC | #1
The bug ID in elf/tst-trace-tls.c appears to be a placeholder (XXXXX).


在 2026/8/17 22:21, Adhemerval Zanella 写道:
> On loongarch and hppa, running ld.so in trace mode on an object with
> dynamic relocations against undefined TLS symbols crashes with SIGSEGV.
>
> Add the same NULL guards used by all other targets.
>
> Checked on loongarch64-linux-gnu-lp64d and hppa-linux-gnu.
> ---
>   elf/Makefile                   |  5 +++
>   elf/tst-trace-tls-mod.c        | 41 +++++++++++++++++
>   elf/tst-trace-tls.c            | 82 ++++++++++++++++++++++++++++++++++
>   sysdeps/hppa/dl-machine.h      |  4 +-
>   sysdeps/loongarch/dl-machine.h | 14 ++++--
>   5 files changed, 141 insertions(+), 5 deletions(-)
>   create mode 100644 elf/tst-trace-tls-mod.c
>   create mode 100644 elf/tst-trace-tls.c
>
> diff --git a/elf/Makefile b/elf/Makefile
> index dbc6cfec7fe..d9abbad1cf7 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -529,6 +529,7 @@ tests += \
>     tst-tlsalign \
>     tst-tlsalign-extern \
>     tst-tlsgap \
> +  tst-trace-tls \
>     tst-unique1 \
>     tst-unique2 \
>     tst-unwind-ctor \
> @@ -1108,6 +1109,7 @@ modules-names += \
>     tst-tlsmod7 \
>     tst-tlsmod8 \
>     tst-tlsmod9 \
> +  tst-trace-tls-mod \
>     tst-unique1mod1 \
>     tst-unique1mod2 \
>     tst-unique2mod1 \
> @@ -3544,6 +3546,9 @@ $(objpfx)tst-nodeps2-mod.so: $(common-objpfx)libc.so \
>   $(objpfx)tst-nodeps2.out: \
>     $(objpfx)tst-nodeps1-mod.so $(objpfx)tst-nodeps2-mod.so
>   
> +$(objpfx)tst-trace-tls.out: $(objpfx)tst-trace-tls-mod.so $(objpfx)ld.so
> +tst-trace-tls-mod.so-no-z-defs = yes
> +
>   $(objpfx)tst-tlsgap: $(shared-thread-library)
>   $(objpfx)tst-tlsgap.out: \
>     $(objpfx)tst-tlsgap-mod0.so \
> diff --git a/elf/tst-trace-tls-mod.c b/elf/tst-trace-tls-mod.c
> new file mode 100644
> index 00000000000..7b855fd814c
> --- /dev/null
> +++ b/elf/tst-trace-tls-mod.c
> @@ -0,0 +1,41 @@
> +/* Shared object with relocations against undefined TLS symbols.
> +   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/>.  */
> +
> +extern __thread int missing_tls_gd
> +  __attribute__ ((tls_model ("global-dynamic")));
> +extern __thread int missing_tls_ie
> +  __attribute__ ((tls_model ("initial-exec")));
> +extern int missing_data;
> +
> +int
> +access_tls_gd (void)
> +{
> +  return missing_tls_gd;
> +}
> +
> +int
> +access_tls_ie (void)
> +{
> +  return missing_tls_ie;
> +}
> +
> +int
> +access_data (void)
> +{
> +  return missing_data;
> +}
> diff --git a/elf/tst-trace-tls.c b/elf/tst-trace-tls.c
> new file mode 100644
> index 00000000000..2d541b1a519
> --- /dev/null
> +++ b/elf/tst-trace-tls.c
> @@ -0,0 +1,82 @@
> +/* Check that trace mode handles unresolved TLS symbols (bug XXXXX).
> +   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 <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <support/capture_subprocess.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +
> +/* Check if unresolved TLS symbol in trace mode (LD_TRACE_LOADED_OBJECTS)
> +   is correctly reported.  */
> +
> +static char *mod;
> +
> +static struct support_capture_subprocess
> +run_ldso (const char *env1, const char *env2)
> +{
> +  char *const argv[] =
> +    { (char *) support_objdir_elf_ldso, (char *) mod, NULL };
> +  char *const envp[] =
> +    { (char *) "LD_TRACE_LOADED_OBJECTS=1", (char *) env1, (char *) env2,
> +      NULL };
> +
> +  struct support_capture_subprocess result
> +    = support_capture_subprogram (argv[0], argv, envp);
> +  support_capture_subprocess_check (&result, "tst-trace-tls", 0,
> +				    sc_allow_stdout | sc_allow_stderr);
> +  return result;
> +}
> +
> +static void
> +run_trace (const char *title, const char *env1, const char *env2,
> +	   bool check_undefined)
> +{
> +  printf ("info: checking %s\n", title);
> +
> +  struct support_capture_subprocess result = run_ldso (env1, env2);
> +
> +  if (check_undefined)
> +    {
> +      TEST_VERIFY (strstr (result.err.buffer,
> +			   "undefined symbol: missing_tls_gd") != NULL);
> +      TEST_VERIFY (strstr (result.err.buffer,
> +			   "undefined symbol: missing_tls_ie") != NULL);
> +      TEST_VERIFY (strstr (result.err.buffer,
> +			   "undefined symbol: missing_data") != NULL);
> +    }
> +
> +  support_capture_subprocess_free (&result);
> +}
> +
> +static int
> +do_test (void)
> +{
> +  mod = xasprintf ("%s/elf/tst-trace-tls-mod.so", support_objdir_root);
> +
> +  run_trace ("ldd -d", "LD_WARN=yes", NULL, true);
> +  run_trace ("ldd -r", "LD_WARN=yes", "LD_BIND_NOW=1", true);
> +  run_trace ("ldd -u", "LD_DEBUG=unused", NULL, false);
> +
> +  free (mod);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/sysdeps/hppa/dl-machine.h b/sysdeps/hppa/dl-machine.h
> index 9bdbd85b6b3..ba2e4123c6e 100644
> --- a/sysdeps/hppa/dl-machine.h
> +++ b/sysdeps/hppa/dl-machine.h
> @@ -700,7 +700,9 @@ elf_machine_rela (struct link_map *map, struct r_scope_elem *scope[],
>   
>   #if !defined RTLD_BOOTSTRAP
>       case R_PARISC_TLS_DTPMOD32:
> -      value = sym_map->l_tls_modid;
> +      /* The sym_map may be NULL in trace mode (LD_TRACE_LOADED_OBJECTS).  */
> +      if (sym_map != NULL)
> +	value = sym_map->l_tls_modid;
>         break;
>   
>       case R_PARISC_TLS_DTPOFF32:
> diff --git a/sysdeps/loongarch/dl-machine.h b/sysdeps/loongarch/dl-machine.h
> index ae295264ae9..1fb2635f92e 100644
> --- a/sysdeps/loongarch/dl-machine.h
> +++ b/sysdeps/loongarch/dl-machine.h
> @@ -197,16 +197,22 @@ elf_machine_rela (struct link_map *map, struct r_scope_elem *scope[],
>   
>   #ifndef RTLD_BOOTSTRAP
>       case __WORDSIZE == 64 ? R_LARCH_TLS_DTPMOD64 : R_LARCH_TLS_DTPMOD32:
> -      *addr_field = sym_map->l_tls_modid;
> +      /* The sym_map may be NULL in trace mode (LD_TRACE_LOADED_OBJECTS).  */
> +      if (sym_map != NULL)
> +	*addr_field = sym_map->l_tls_modid;
>         break;
>   
>       case __WORDSIZE == 64 ? R_LARCH_TLS_DTPREL64 : R_LARCH_TLS_DTPREL32:
> -      *addr_field = TLS_DTPREL_VALUE (sym) + reloc->r_addend;
> +      if (sym != NULL)
> +	*addr_field = TLS_DTPREL_VALUE (sym) + reloc->r_addend;
>         break;
>   
>       case __WORDSIZE == 64 ? R_LARCH_TLS_TPREL64 : R_LARCH_TLS_TPREL32:
> -      CHECK_STATIC_TLS (map, sym_map, sym);
> -      *addr_field = TLS_TPREL_VALUE (sym_map, sym) + reloc->r_addend;
> +      if (sym != NULL)
> +	{
> +	  CHECK_STATIC_TLS (map, sym_map, sym);
> +	  *addr_field = TLS_TPREL_VALUE (sym_map, sym) + reloc->r_addend;
> +	}
>         break;
>   
>       case __WORDSIZE == 64 ? R_LARCH_TLS_DESC64 : R_LARCH_TLS_DESC32:
  
Adhemerval Zanella Netto Aug. 20, 2026, 12:12 p.m. UTC | #2
Oops, I forgot to update it.  Fixed locally.

On 20/08/26 06:56, mengqinggang wrote:
> The bug ID in elf/tst-trace-tls.c appears to be a placeholder (XXXXX).
> 
> 
> 在 2026/8/17 22:21, Adhemerval Zanella 写道:
>> On loongarch and hppa, running ld.so in trace mode on an object with
>> dynamic relocations against undefined TLS symbols crashes with SIGSEGV.
>>
>> Add the same NULL guards used by all other targets.
>>
>> Checked on loongarch64-linux-gnu-lp64d and hppa-linux-gnu.
>> ---
>>   elf/Makefile                   |  5 +++
>>   elf/tst-trace-tls-mod.c        | 41 +++++++++++++++++
>>   elf/tst-trace-tls.c            | 82 ++++++++++++++++++++++++++++++++++
>>   sysdeps/hppa/dl-machine.h      |  4 +-
>>   sysdeps/loongarch/dl-machine.h | 14 ++++--
>>   5 files changed, 141 insertions(+), 5 deletions(-)
>>   create mode 100644 elf/tst-trace-tls-mod.c
>>   create mode 100644 elf/tst-trace-tls.c
>>
>> diff --git a/elf/Makefile b/elf/Makefile
>> index dbc6cfec7fe..d9abbad1cf7 100644
>> --- a/elf/Makefile
>> +++ b/elf/Makefile
>> @@ -529,6 +529,7 @@ tests += \
>>     tst-tlsalign \
>>     tst-tlsalign-extern \
>>     tst-tlsgap \
>> +  tst-trace-tls \
>>     tst-unique1 \
>>     tst-unique2 \
>>     tst-unwind-ctor \
>> @@ -1108,6 +1109,7 @@ modules-names += \
>>     tst-tlsmod7 \
>>     tst-tlsmod8 \
>>     tst-tlsmod9 \
>> +  tst-trace-tls-mod \
>>     tst-unique1mod1 \
>>     tst-unique1mod2 \
>>     tst-unique2mod1 \
>> @@ -3544,6 +3546,9 @@ $(objpfx)tst-nodeps2-mod.so: $(common-objpfx)libc.so \
>>   $(objpfx)tst-nodeps2.out: \
>>     $(objpfx)tst-nodeps1-mod.so $(objpfx)tst-nodeps2-mod.so
>>   +$(objpfx)tst-trace-tls.out: $(objpfx)tst-trace-tls-mod.so $(objpfx)ld.so
>> +tst-trace-tls-mod.so-no-z-defs = yes
>> +
>>   $(objpfx)tst-tlsgap: $(shared-thread-library)
>>   $(objpfx)tst-tlsgap.out: \
>>     $(objpfx)tst-tlsgap-mod0.so \
>> diff --git a/elf/tst-trace-tls-mod.c b/elf/tst-trace-tls-mod.c
>> new file mode 100644
>> index 00000000000..7b855fd814c
>> --- /dev/null
>> +++ b/elf/tst-trace-tls-mod.c
>> @@ -0,0 +1,41 @@
>> +/* Shared object with relocations against undefined TLS symbols.
>> +   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/>.  */
>> +
>> +extern __thread int missing_tls_gd
>> +  __attribute__ ((tls_model ("global-dynamic")));
>> +extern __thread int missing_tls_ie
>> +  __attribute__ ((tls_model ("initial-exec")));
>> +extern int missing_data;
>> +
>> +int
>> +access_tls_gd (void)
>> +{
>> +  return missing_tls_gd;
>> +}
>> +
>> +int
>> +access_tls_ie (void)
>> +{
>> +  return missing_tls_ie;
>> +}
>> +
>> +int
>> +access_data (void)
>> +{
>> +  return missing_data;
>> +}
>> diff --git a/elf/tst-trace-tls.c b/elf/tst-trace-tls.c
>> new file mode 100644
>> index 00000000000..2d541b1a519
>> --- /dev/null
>> +++ b/elf/tst-trace-tls.c
>> @@ -0,0 +1,82 @@
>> +/* Check that trace mode handles unresolved TLS symbols (bug XXXXX).
>> +   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 <stdio.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +#include <support/capture_subprocess.h>
>> +#include <support/check.h>
>> +#include <support/support.h>
>> +
>> +/* Check if unresolved TLS symbol in trace mode (LD_TRACE_LOADED_OBJECTS)
>> +   is correctly reported.  */
>> +
>> +static char *mod;
>> +
>> +static struct support_capture_subprocess
>> +run_ldso (const char *env1, const char *env2)
>> +{
>> +  char *const argv[] =
>> +    { (char *) support_objdir_elf_ldso, (char *) mod, NULL };
>> +  char *const envp[] =
>> +    { (char *) "LD_TRACE_LOADED_OBJECTS=1", (char *) env1, (char *) env2,
>> +      NULL };
>> +
>> +  struct support_capture_subprocess result
>> +    = support_capture_subprogram (argv[0], argv, envp);
>> +  support_capture_subprocess_check (&result, "tst-trace-tls", 0,
>> +                    sc_allow_stdout | sc_allow_stderr);
>> +  return result;
>> +}
>> +
>> +static void
>> +run_trace (const char *title, const char *env1, const char *env2,
>> +       bool check_undefined)
>> +{
>> +  printf ("info: checking %s\n", title);
>> +
>> +  struct support_capture_subprocess result = run_ldso (env1, env2);
>> +
>> +  if (check_undefined)
>> +    {
>> +      TEST_VERIFY (strstr (result.err.buffer,
>> +               "undefined symbol: missing_tls_gd") != NULL);
>> +      TEST_VERIFY (strstr (result.err.buffer,
>> +               "undefined symbol: missing_tls_ie") != NULL);
>> +      TEST_VERIFY (strstr (result.err.buffer,
>> +               "undefined symbol: missing_data") != NULL);
>> +    }
>> +
>> +  support_capture_subprocess_free (&result);
>> +}
>> +
>> +static int
>> +do_test (void)
>> +{
>> +  mod = xasprintf ("%s/elf/tst-trace-tls-mod.so", support_objdir_root);
>> +
>> +  run_trace ("ldd -d", "LD_WARN=yes", NULL, true);
>> +  run_trace ("ldd -r", "LD_WARN=yes", "LD_BIND_NOW=1", true);
>> +  run_trace ("ldd -u", "LD_DEBUG=unused", NULL, false);
>> +
>> +  free (mod);
>> +
>> +  return 0;
>> +}
>> +
>> +#include <support/test-driver.c>
>> diff --git a/sysdeps/hppa/dl-machine.h b/sysdeps/hppa/dl-machine.h
>> index 9bdbd85b6b3..ba2e4123c6e 100644
>> --- a/sysdeps/hppa/dl-machine.h
>> +++ b/sysdeps/hppa/dl-machine.h
>> @@ -700,7 +700,9 @@ elf_machine_rela (struct link_map *map, struct r_scope_elem *scope[],
>>     #if !defined RTLD_BOOTSTRAP
>>       case R_PARISC_TLS_DTPMOD32:
>> -      value = sym_map->l_tls_modid;
>> +      /* The sym_map may be NULL in trace mode (LD_TRACE_LOADED_OBJECTS).  */
>> +      if (sym_map != NULL)
>> +    value = sym_map->l_tls_modid;
>>         break;
>>         case R_PARISC_TLS_DTPOFF32:
>> diff --git a/sysdeps/loongarch/dl-machine.h b/sysdeps/loongarch/dl-machine.h
>> index ae295264ae9..1fb2635f92e 100644
>> --- a/sysdeps/loongarch/dl-machine.h
>> +++ b/sysdeps/loongarch/dl-machine.h
>> @@ -197,16 +197,22 @@ elf_machine_rela (struct link_map *map, struct r_scope_elem *scope[],
>>     #ifndef RTLD_BOOTSTRAP
>>       case __WORDSIZE == 64 ? R_LARCH_TLS_DTPMOD64 : R_LARCH_TLS_DTPMOD32:
>> -      *addr_field = sym_map->l_tls_modid;
>> +      /* The sym_map may be NULL in trace mode (LD_TRACE_LOADED_OBJECTS).  */
>> +      if (sym_map != NULL)
>> +    *addr_field = sym_map->l_tls_modid;
>>         break;
>>         case __WORDSIZE == 64 ? R_LARCH_TLS_DTPREL64 : R_LARCH_TLS_DTPREL32:
>> -      *addr_field = TLS_DTPREL_VALUE (sym) + reloc->r_addend;
>> +      if (sym != NULL)
>> +    *addr_field = TLS_DTPREL_VALUE (sym) + reloc->r_addend;
>>         break;
>>         case __WORDSIZE == 64 ? R_LARCH_TLS_TPREL64 : R_LARCH_TLS_TPREL32:
>> -      CHECK_STATIC_TLS (map, sym_map, sym);
>> -      *addr_field = TLS_TPREL_VALUE (sym_map, sym) + reloc->r_addend;
>> +      if (sym != NULL)
>> +    {
>> +      CHECK_STATIC_TLS (map, sym_map, sym);
>> +      *addr_field = TLS_TPREL_VALUE (sym_map, sym) + reloc->r_addend;
>> +    }
>>         break;
>>         case __WORDSIZE == 64 ? R_LARCH_TLS_DESC64 : R_LARCH_TLS_DESC32:
>
  

Patch

diff --git a/elf/Makefile b/elf/Makefile
index dbc6cfec7fe..d9abbad1cf7 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -529,6 +529,7 @@  tests += \
   tst-tlsalign \
   tst-tlsalign-extern \
   tst-tlsgap \
+  tst-trace-tls \
   tst-unique1 \
   tst-unique2 \
   tst-unwind-ctor \
@@ -1108,6 +1109,7 @@  modules-names += \
   tst-tlsmod7 \
   tst-tlsmod8 \
   tst-tlsmod9 \
+  tst-trace-tls-mod \
   tst-unique1mod1 \
   tst-unique1mod2 \
   tst-unique2mod1 \
@@ -3544,6 +3546,9 @@  $(objpfx)tst-nodeps2-mod.so: $(common-objpfx)libc.so \
 $(objpfx)tst-nodeps2.out: \
   $(objpfx)tst-nodeps1-mod.so $(objpfx)tst-nodeps2-mod.so
 
+$(objpfx)tst-trace-tls.out: $(objpfx)tst-trace-tls-mod.so $(objpfx)ld.so
+tst-trace-tls-mod.so-no-z-defs = yes
+
 $(objpfx)tst-tlsgap: $(shared-thread-library)
 $(objpfx)tst-tlsgap.out: \
   $(objpfx)tst-tlsgap-mod0.so \
diff --git a/elf/tst-trace-tls-mod.c b/elf/tst-trace-tls-mod.c
new file mode 100644
index 00000000000..7b855fd814c
--- /dev/null
+++ b/elf/tst-trace-tls-mod.c
@@ -0,0 +1,41 @@ 
+/* Shared object with relocations against undefined TLS symbols.
+   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/>.  */
+
+extern __thread int missing_tls_gd
+  __attribute__ ((tls_model ("global-dynamic")));
+extern __thread int missing_tls_ie
+  __attribute__ ((tls_model ("initial-exec")));
+extern int missing_data;
+
+int
+access_tls_gd (void)
+{
+  return missing_tls_gd;
+}
+
+int
+access_tls_ie (void)
+{
+  return missing_tls_ie;
+}
+
+int
+access_data (void)
+{
+  return missing_data;
+}
diff --git a/elf/tst-trace-tls.c b/elf/tst-trace-tls.c
new file mode 100644
index 00000000000..2d541b1a519
--- /dev/null
+++ b/elf/tst-trace-tls.c
@@ -0,0 +1,82 @@ 
+/* Check that trace mode handles unresolved TLS symbols (bug XXXXX).
+   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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/support.h>
+
+/* Check if unresolved TLS symbol in trace mode (LD_TRACE_LOADED_OBJECTS)
+   is correctly reported.  */
+
+static char *mod;
+
+static struct support_capture_subprocess
+run_ldso (const char *env1, const char *env2)
+{
+  char *const argv[] =
+    { (char *) support_objdir_elf_ldso, (char *) mod, NULL };
+  char *const envp[] =
+    { (char *) "LD_TRACE_LOADED_OBJECTS=1", (char *) env1, (char *) env2,
+      NULL };
+
+  struct support_capture_subprocess result
+    = support_capture_subprogram (argv[0], argv, envp);
+  support_capture_subprocess_check (&result, "tst-trace-tls", 0,
+				    sc_allow_stdout | sc_allow_stderr);
+  return result;
+}
+
+static void
+run_trace (const char *title, const char *env1, const char *env2,
+	   bool check_undefined)
+{
+  printf ("info: checking %s\n", title);
+
+  struct support_capture_subprocess result = run_ldso (env1, env2);
+
+  if (check_undefined)
+    {
+      TEST_VERIFY (strstr (result.err.buffer,
+			   "undefined symbol: missing_tls_gd") != NULL);
+      TEST_VERIFY (strstr (result.err.buffer,
+			   "undefined symbol: missing_tls_ie") != NULL);
+      TEST_VERIFY (strstr (result.err.buffer,
+			   "undefined symbol: missing_data") != NULL);
+    }
+
+  support_capture_subprocess_free (&result);
+}
+
+static int
+do_test (void)
+{
+  mod = xasprintf ("%s/elf/tst-trace-tls-mod.so", support_objdir_root);
+
+  run_trace ("ldd -d", "LD_WARN=yes", NULL, true);
+  run_trace ("ldd -r", "LD_WARN=yes", "LD_BIND_NOW=1", true);
+  run_trace ("ldd -u", "LD_DEBUG=unused", NULL, false);
+
+  free (mod);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/hppa/dl-machine.h b/sysdeps/hppa/dl-machine.h
index 9bdbd85b6b3..ba2e4123c6e 100644
--- a/sysdeps/hppa/dl-machine.h
+++ b/sysdeps/hppa/dl-machine.h
@@ -700,7 +700,9 @@  elf_machine_rela (struct link_map *map, struct r_scope_elem *scope[],
 
 #if !defined RTLD_BOOTSTRAP
     case R_PARISC_TLS_DTPMOD32:
-      value = sym_map->l_tls_modid;
+      /* The sym_map may be NULL in trace mode (LD_TRACE_LOADED_OBJECTS).  */
+      if (sym_map != NULL)
+	value = sym_map->l_tls_modid;
       break;
 
     case R_PARISC_TLS_DTPOFF32:
diff --git a/sysdeps/loongarch/dl-machine.h b/sysdeps/loongarch/dl-machine.h
index ae295264ae9..1fb2635f92e 100644
--- a/sysdeps/loongarch/dl-machine.h
+++ b/sysdeps/loongarch/dl-machine.h
@@ -197,16 +197,22 @@  elf_machine_rela (struct link_map *map, struct r_scope_elem *scope[],
 
 #ifndef RTLD_BOOTSTRAP
     case __WORDSIZE == 64 ? R_LARCH_TLS_DTPMOD64 : R_LARCH_TLS_DTPMOD32:
-      *addr_field = sym_map->l_tls_modid;
+      /* The sym_map may be NULL in trace mode (LD_TRACE_LOADED_OBJECTS).  */
+      if (sym_map != NULL)
+	*addr_field = sym_map->l_tls_modid;
       break;
 
     case __WORDSIZE == 64 ? R_LARCH_TLS_DTPREL64 : R_LARCH_TLS_DTPREL32:
-      *addr_field = TLS_DTPREL_VALUE (sym) + reloc->r_addend;
+      if (sym != NULL)
+	*addr_field = TLS_DTPREL_VALUE (sym) + reloc->r_addend;
       break;
 
     case __WORDSIZE == 64 ? R_LARCH_TLS_TPREL64 : R_LARCH_TLS_TPREL32:
-      CHECK_STATIC_TLS (map, sym_map, sym);
-      *addr_field = TLS_TPREL_VALUE (sym_map, sym) + reloc->r_addend;
+      if (sym != NULL)
+	{
+	  CHECK_STATIC_TLS (map, sym_map, sym);
+	  *addr_field = TLS_TPREL_VALUE (sym_map, sym) + reloc->r_addend;
+	}
       break;
 
     case __WORDSIZE == 64 ? R_LARCH_TLS_DESC64 : R_LARCH_TLS_DESC32: