The
af34b1376a37fa27e1de9d869ed9493fc569bfa6 (BZ 34164) changed the
TLS setup from:
relocation loop (applies relocations to .tdata in DSO memory)
_dl_allocate_tls_init copies relocated .tdata -> main thread TLS
to a new order:
_dl_allocate_tls_init copies unrelocated .tdata -> main thread TLS
relocation loop (relocates .tdata in DSO memory, but the TLS block
has stale copies)
This broke file-scope thread-local initialised with the address of a
function (for instance the cache structs in libmpfr).
Fix it by splitting ELF_DYNAMIC_RELOCATE inside
_dl_relocate_object_no_relro into the non-IRELATIVE and IRELATIVE
sub-passes (similar as done on static-pie startup by
b75ad99d45b)
and call _dl_init_static_tls between them. By the time the IFUNC
pass fires, .tdata is fully relocated.
Checked on x86_64-linux-gnu and aarch64-linux-gnu.
Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
tst-ifunc-resolver-protector \
tst-ifunc-tls-init \
tst-ifunc-tls-write \
+ tst-tls-tdata-reloc \
# tests
# Note: sysdeps/x86_64/ifuncmain8.c uses ifuncmain8.
tests-internal += \
tst-ifunc-tls-init-lib1 \
tst-ifunc-tls-init-lib2 \
tst-ifunc-tls-write-lib \
+ tst-tls-tdata-reloc-lib \
# modules-names
ifeq (no,$(with-lld))
modules-names += ifuncmod5
$(objpfx)tst-ifunc-tls-init.out: \
$(objpfx)tst-ifunc-tls-init-lib2.so
$(objpfx)tst-ifunc-tls-write: $(objpfx)tst-ifunc-tls-write-lib.so
+$(objpfx)tst-tls-tdata-reloc: $(objpfx)tst-tls-tdata-reloc-lib.so
$(objpfx)tst-unique1.out: $(objpfx)tst-unique1mod1.so \
$(objpfx)tst-unique1mod2.so
}
{
- /* Do the actual relocation of the object's GOT and other data. */
+ /* Do the actual relocation of the object's GOT and other data.
- ELF_DYNAMIC_RELOCATE (l, scope, lazy, consider_profiling, skip_ifunc);
+ Process the non-IRELATIVE pass first so .tdata is fully relocated
+ (including R_*_RELATIVE / R_*_64 fixups for TLS initialisers, e.g. a
+ file-scope thread-local initialised with the address of a function),
+ then refresh the static TLS slot before the IRELATIVE pass runs the
+ IFUNC resolvers. Without this, a resolver would see the unrelocated
+ initialiser bytes that were placed into the slot by the early
+ _dl_allocate_tls_init. */
+ ELF_DYNAMIC_RELOCATE_NOIFUNC (l, scope, lazy, consider_profiling);
+
+#ifdef SHARED
+ /* Re-initialise the static TLS slot with the .tdata so the IRELATIVE
+ pass observes a fully-relocated initialiser image. Skipped for objects
+ without static TLS or before the main thread TCB has been set up. */
+ if (l->l_tls_blocksize != 0
+ && __rtld_tls_init_tp_called
+ && l->l_tls_offset != NO_TLS_OFFSET
+ && l->l_tls_offset != FORCED_DYNAMIC_TLS_OFFSET)
+ _dl_init_static_tls (l);
+#endif
+
+ ELF_DYNAMIC_RELOCATE_IFUNC (l, scope, lazy, skip_ifunc);
if ((consider_profiling || consider_symbind)
&& l->l_info[DT_PLTRELSZ] != NULL)
--- /dev/null
+/* Shared library for tst-tls-tdata-reloc.
+ 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/>. */
+
+#define EXPECTED 0x12345678
+
+static int
+callee (void)
+{
+ return EXPECTED;
+}
+
+typedef struct
+{
+ int (*func) (void);
+} cache_t;
+
+/* General-dynamic TLS: the initialiser of '.func' produces an
+ R_*_RELATIVE on .tdata. */
+__thread cache_t cache = { .func = callee };
+
+int
+call_through_tls (void)
+{
+ if (cache.func == 0)
+ return -1;
+ return cache.func ();
+}
--- /dev/null
+/* Check that .tdata relocations are applied to the static TLS slot.
+ 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 <support/check.h>
+
+#define EXPECTED 0x12345678
+
+extern int call_through_tls (void);
+
+static int
+do_test (void)
+{
+ TEST_COMPARE (call_through_tls (), EXPECTED);
+ return 0;
+}
+
+#include <support/test-driver.c>