Files
glibc/elf/tst-ifunc-tls-init-gd-ld.c
Adhemerval Zanella f9a11a380d elf: Set up TLS slotinfo for dlopen'd modules before relocation (BZ 34170)
An IFUNC resolver in a DSO that is being loaded by dlopen is allowed
to read its own TLS storage during the resolver call.  After
af34b1376a ("elf: Initialize static TLS before relocation processing
(BZ 34164)") that works for the initial-exec model on every supported
architecture.

However, it does not work for the dynamic-TLS path (on both -mtls-dialect mode,
if the ABI supports both).  Both lookup paths index the calling thread's
DTV by the new module's l_tls_modid and, on miss, walk
GL(dl_tls_dtv_slotinfo_list) to discover the module and lazily allocate
its TLS block.  The just-loaded DSO is however not yet in that list when
its resolver fires, so the lookup faults inside dlopen.  This is the
direct dlopen analog of BZ 34164.

The solution is to reorder dl_open_worker_begin so the slotinfo install
happens before the relocation pass.  The new order is:

  1. resize_scopes, resize_tls_slotinfo, add_to_global_resize
     (unchanged, still recoverable).
  2. update_tls_slotinfo: register the new modules in slotinfo, bump
     dl_tls_generation, initialise their static TLS images.
  3. Relocate the new objects.  IFUNC resolvers can now read their
     own DSO's __thread storage via any TLS model.
  4. Demarcation point.
  5. update_scopes, _dl_find_object_update.

Checked on aarch64-linux-gnu and x86_64-linux-gnu.

Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
2026-06-05 16:20:20 -03:00

90 lines
2.8 KiB
C

/* Check if dynamic-TLS variables (global-dynamic, local-dynamic) are
correctly initialised in IFUNC resolvers reached via dlopen.
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 test dlopens three modules covering the dynamic-TLS access variants:
gd-lib file-local 'sentinel' + tls_model("global-dynamic")
ld-lib file-local 'sentinel' + tls_model("local-dynamic")
gd-global-lib external 'sentinel' + tls_model("global-dynamic")
In each case the IFUNC resolver's read of 'sentinel' must traverse the
dynamic-TLS resolution path at the moment the resolver fires during
dlopen-time relocation.
Each variant additionally calls the resolved IFUNC from a thread spawned
after dlopen, to verify per-thread DTV propagation for the newly-loaded
module. */
#include <support/check.h>
#include <support/xdlfcn.h>
#include <support/xthread.h>
#define SENTINEL 0x5A5A1234
struct ifunc_handles
{
int (*get_last_seen_sentinel) (void);
int (**fptr) (void);
int (*ifunc_tls) (void);
};
static void *
ifunc_caller (void *arg)
{
struct ifunc_handles *h = arg;
TEST_COMPARE ((*h->fptr) (), SENTINEL);
TEST_COMPARE (h->ifunc_tls (), SENTINEL);
return NULL;
}
static void
test_lib (const char *soname)
{
void *handle = xdlopen (soname, RTLD_LAZY | RTLD_LOCAL);
struct ifunc_handles h;
h.get_last_seen_sentinel = xdlsym (handle, "get_last_seen_sentinel");
h.fptr = xdlsym (handle, "fptr");
h.ifunc_tls = xdlsym (handle, "ifunc_tls");
TEST_COMPARE (h.get_last_seen_sentinel (), SENTINEL);
TEST_VERIFY (*h.fptr != NULL);
TEST_COMPARE ((*h.fptr) (), SENTINEL);
TEST_COMPARE (h.ifunc_tls (), SENTINEL);
/* From a thread spawned *after* the dlopen, which exercises DTV propagation
for the new module into a fresh TCB. */
pthread_t consumer = xpthread_create (NULL, ifunc_caller, &h);
xpthread_join (consumer);
xdlclose (handle);
}
static int
do_test (void)
{
test_lib ("tst-ifunc-tls-init-gd-lib.so");
test_lib ("tst-ifunc-tls-init-ld-lib.so");
test_lib ("tst-ifunc-tls-init-gd-global-lib.so");
return 0;
}
#include <support/test-driver.c>