[glibc/azanella/bz15686] stdlib: Make __cxa_thread_atexit_impl lock-free (BZ 15686)
Adhemerval Zanella via Glibc-cvs <[email protected]> Sun, 19 Jul 2026 13:34:02 +0000 (GMT)
| Newsgroups | gmane.comp.lib.glibc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=9039a9cb0fb9cdd4c17d6ee2687389ee2e5e63eb commit 9039a9cb0fb9cdd4c17d6ee2687389ee2e5e63eb Author: Adhemerval Zanella <[email protected]> Date: Sun Jul 19 09:59:53 2026 -0300 stdlib: Make __cxa_thread_atexit_impl lock-free (BZ 15686) __cxa_thread_atexit_impl takes dl_load_lock to protect the _dl_find_dso_for_object lookup and the l_tls_dtor_count increment against a racing dlclose. This deadlocks when the function is reached from a thread spawned by an ELF constructor, because dlopen runs constructors with dl_load_lock held. The constructor blocks in pthread_join on the new thread, while the new thread blocks on dl_load_lock at its first thread_local access. Instead of releasing dl_load_lock around constructor execution (which requires per-map serialization of the constructor calls and re-auditing all the state accessed by dl_open_worker after the initializers run), remove the lock acquisition from __cxa_thread_atexit_impl: * The link map lookup now uses _dl_find_object, which is lock-free and async-signal-safe. The newly loaded object is inserted into the _dl_find_object data structures by dl_open_worker before its constructors run, so lookups from constructor-spawned threads succeed. * The lock is not required for correctness: DSO_SYMBOL is the address of the caller's __dso_handle, so the calling thread is executing code of the object being looked up. A dlclose that unloaded the object concurrently would unmap the running code itself, which is undefined regardless of the lock; a conforming program has to ensure the object stays loaded across the call, and the synchronization it uses for that also orders the l_tls_dtor_count increment before any subsequent _dl_close_worker check. The per-thread dso_symbol_cache/lm_cache is removed: the cache key was never updated, so every call already performed the O(n) _dl_find_dso_for_object walk over the loaded objects while holding dl_load_lock. The replacement is a lock-free O(log n) lookup, so this is also a performance improvement for TLS destructor registration. Checked on aarch64-linux-gnu, x86_64-linux-gnu, i686-linux-gnu, and arm-linux-gnueabihf. Diff: --- stdlib/cxa_thread_atexit_impl.c | 78 +++++++++++++++++--------------- sysdeps/pthread/Makefile | 25 +++++++++++ sysdeps/pthread/tst-create2.c | 55 +++++++++++++++++++++++ sysdeps/pthread/tst-create2mod.c | 57 +++++++++++++++++++++++ sysdeps/pthread/tst-create3.c | 92 ++++++++++++++++++++++++++++++++++++++ sysdeps/pthread/tst-create3.h | 27 +++++++++++ sysdeps/pthread/tst-create3mod.c | 53 ++++++++++++++++++++++ sysdeps/pthread/tst-create4.c | 90 +++++++++++++++++++++++++++++++++++++ sysdeps/pthread/tst-create4.h | 34 ++++++++++++++ sysdeps/pthread/tst-create4mod-a.c | 35 +++++++++++++++ sysdeps/pthread/tst-create4mod-b.c | 35 +++++++++++++++ sysdeps/pthread/tst-create5.c | 50 +++++++++++++++++++++ 12 files changed, 594 insertions(+), 37 deletions(-) diff --git a/stdlib/cxa_thread_atexit_impl.c b/stdlib/cxa_thread_atexit_impl.c index c4382bc9ef..3d143795dc 100644 --- a/stdlib/cxa_thread_atexit_impl.c +++ b/stdlib/cxa_thread_atexit_impl.c @@ -25,15 +25,25 @@ combinations of all three functions are the link map list, a link map for a DSO and the link map member l_tls_dtor_count. - __cxa_thread_atexit_impl acquires the dl_load_lock before accessing any - shared state and hence multiple of its instances can safely execute - concurrently. - - _dl_close_worker acquires the dl_load_lock before accessing any shared state - as well and hence can concurrently execute multiple of its own instances as - well as those of __cxa_thread_atexit_impl safely. Not all accesses to - l_tls_dtor_count are protected by the dl_load_lock, so we need to - synchronize using atomics. + __cxa_thread_atexit_impl does not take dl_load_lock (taking the lock + deadlocks if this function is reached from a thread spawned by an ELF + constructor running under dl_load_lock inside dlopen). It locates the + caller's link map with _dl_find_object, which is async-signal-safe and + lock-free, and then increments l_tls_dtor_count atomically. + + Not taking the lock is safe because DSO_SYMBOL is the address of the + caller's __dso_handle, so the calling thread is executing code of the very + object whose link map is being looked up. A concurrent dlclose that + unloads the object while this function runs would unmap the caller's code + as well, which is undefined regardless of this function. A conforming + program must ensure (via its own synchronization, e.g. joining the thread + before dlclose) that the object stays loaded across this call, and that + same synchronization publishes the l_tls_dtor_count increment to any + subsequent _dl_close_worker. + + _dl_close_worker acquires the dl_load_lock before accessing any shared + state. Not all accesses to l_tls_dtor_count are protected by the + dl_load_lock, so we need to synchronize using atomics. __call_tls_dtors accesses the l_tls_dtor_count without taking the lock; it decrements the value by one. It does not need the big lock because it does @@ -63,7 +73,9 @@ Concurrent executions of __call_tls_dtors should only ensure that the value is accessed atomically; no reordering constraints need to be considered. - Likewise for the increment of l_tls_dtor_count in __cxa_thread_atexit_impl. + The same holds for the increment in __cxa_thread_atexit_impl, whose + ordering against _dl_close_worker is provided by the caller as described + above. There is still a possibility on concurrent execution of _dl_close_worker and __call_tls_dtors where _dl_close_worker reads the value of l_tls_dtor_count @@ -72,6 +84,7 @@ is not very different from a case where __call_tls_dtors is called after _dl_close_worker on the DSO and hence is an accepted execution. */ +#include <dlfcn.h> #include <stdio.h> #include <stdlib.h> #include <ldsodefs.h> @@ -88,8 +101,6 @@ struct dtor_list }; static __thread struct dtor_list *tls_dtor_list; -static __thread void *dso_symbol_cache; -static __thread struct link_map *lm_cache; /* Register a destructor for TLS variables declared with the 'thread_local' keyword. This function is only called from code generated by the C++ @@ -112,31 +123,24 @@ __cxa_thread_atexit_impl (dtor_func func, void *obj, void *dso_symbol) new->next = tls_dtor_list; tls_dtor_list = new; - /* We have to acquire the big lock to prevent a racing dlclose from pulling - our DSO from underneath us while we're setting up our destructor. */ - __rtld_lock_lock_recursive (GL(dl_load_lock)); - - /* See if we already encountered the DSO. */ - if (__glibc_unlikely (dso_symbol_cache != dso_symbol)) - { - ElfW(Addr) caller = (ElfW(Addr)) dso_symbol; - - struct link_map *l = _dl_find_dso_for_object (caller); - - /* If the address is not recognized the call comes from the main - program (we hope). */ - lm_cache = l ? l : GL(dl_ns)[LM_ID_BASE]._ns_loaded; - } - - /* This increment may only be concurrently observed either by the decrement - in __call_tls_dtors since the other l_tls_dtor_count access in - _dl_close_worker is protected by the dl_load_lock. The execution in - __call_tls_dtors does not really depend on this value beyond the fact that - it should be atomic, so Relaxed MO should be sufficient. */ - atomic_fetch_add_relaxed (&lm_cache->l_tls_dtor_count, 1); - __rtld_lock_unlock_recursive (GL(dl_load_lock)); - - new->map = lm_cache; + /* Locate the link map of the caller without taking dl_load_lock. */ + struct link_map *map; + struct dl_find_object dfo; + if (GLRO (dl_find_object) (dso_symbol, &dfo) == 0 + && dfo.dlfo_link_map != NULL) + map = dfo.dlfo_link_map; + else + /* If the address is not recognized the call comes assume from the main + program. */ + map = GL(dl_ns)[LM_ID_BASE]._ns_loaded; + + /* This increment is only concurrently observed by the decrement in + __call_tls_dtors and by the load in _dl_close_worker; for the latter, + the caller's own synchronization with dlclose provides the required + ordering (see CONCURRENCY NOTES), so Relaxed MO is sufficient. */ + atomic_fetch_add_relaxed (&map->l_tls_dtor_count, 1); + + new->map = map; return 0; } diff --git a/sysdeps/pthread/Makefile b/sysdeps/pthread/Makefile index d0f3cd59ac..9eebbb9ab3 100644 --- a/sysdeps/pthread/Makefile +++ b/sysdeps/pthread/Makefile @@ -349,6 +349,10 @@ tests += \ tst-atfork3 \ tst-atfork4 \ tst-create1 \ + tst-create2 \ + tst-create3 \ + tst-create4 \ + tst-create5 \ tst-fini1 \ tst-pt-tls4 \ # tests @@ -365,6 +369,10 @@ modules-names += \ tst-atfork3mod \ tst-atfork4mod \ tst-create1mod \ + tst-create2mod \ + tst-create3mod \ + tst-create4mod-a \ + tst-create4mod-b \ tst-fini1mod \ tst-stack2-mod \ tst-tls4moda \ @@ -377,6 +385,9 @@ tst-atfork2mod.so-no-z-defs = yes tst-atfork3mod.so-no-z-defs = yes tst-atfork4mod.so-no-z-defs = yes tst-create1mod.so-no-z-defs = yes +tst-create2mod.so-no-z-defs = yes +tst-create4mod-a.so-no-z-defs = yes +tst-create4mod-b.so-no-z-defs = yes ifeq ($(build-shared),yes) # Build all the modules even when not actually running test programs. @@ -540,6 +551,20 @@ LDFLAGS-tst-create1 = -Wl,-export-dynamic $(objpfx)tst-create1: $(shared-thread-library) $(objpfx)tst-create1.out: $(objpfx)tst-create1mod.so +$(objpfx)tst-create2: $(shared-thread-library) +$(objpfx)tst-create2mod.so: $(shared-thread-library) +$(objpfx)tst-create2.out: $(objpfx)tst-create2mod.so + +$(objpfx)tst-create3: $(shared-thread-library) +$(objpfx)tst-create3.out: $(objpfx)tst-create3mod.so + +LDFLAGS-tst-create4 = -Wl,-export-dynamic +$(objpfx)tst-create4: $(shared-thread-library) +$(objpfx)tst-create4.out: \ + $(objpfx)tst-create4mod-a.so $(objpfx)tst-create4mod-b.so + +$(objpfx)tst-create5: $(shared-thread-library) + $(objpfx)tst-stack2.out: $(objpfx)tst-stack2-mod.so $(objpfx)tst-stack2-mod.so: $(shared-thread-library) LDFLAGS-tst-stack2-mod.so = -Wl,-z,execstack diff --git a/sysdeps/pthread/tst-create2.c b/sysdeps/pthread/tst-create2.c new file mode 100644 index 0000000000..961ee5ceea --- /dev/null +++ b/sysdeps/pthread/tst-create2.c @@ -0,0 +1,55 @@ +/* Verify that a thread spawned by a dlopen constructor can register a + TLS destructor without deadlocking (BZ 15686). + 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 1: dlopen -> ctor -> pthread_create (worker) -> pthread_join + thread 2 (worker): __cxa_thread_atexit_impl -> lock (dl_load_lock) + + dl_load_lock is held by thread 1 across the constructor execution, so if + __cxa_thread_atexit_impl acquires it the worker thread blocks forever and + pthread_join in the constructor never returns. */ + +#include <stdio.h> +#include <support/check.h> +#include <support/xdlfcn.h> + +static int +do_test (void) +{ + printf ("main: dlopen tst-create2mod.so\n"); + void *h = xdlopen ("tst-create2mod.so", RTLD_NOW); + printf ("main: dlopen done\n"); + + /* The worker thread exited before the constructor's pthread_join + returned, so its TLS destructor has already run. */ + int *dtor_done = xdlsym (h, "tst_create2mod_dtor_done"); + TEST_COMPARE (*dtor_done, 1); + + xdlclose (h); + printf ("main: dlclose done\n"); + + /* The destructor already ran, so no reference is left on the + module's l_tls_dtor_count and dlclose must have unloaded it. */ + TEST_VERIFY (dlopen ("tst-create2mod.so", RTLD_NOW | RTLD_NOLOAD) + == NULL); + + return 0; +} + +#include <support/test-driver.c> diff --git a/sysdeps/pthread/tst-create2mod.c b/sysdeps/pthread/tst-create2mod.c new file mode 100644 index 0000000000..9351c160b2 --- /dev/null +++ b/sysdeps/pthread/tst-create2mod.c @@ -0,0 +1,57 @@ +/* Verify that a thread spawned by a dlopen constructor can register a + TLS destructor without deadlocking (BZ 15686). + 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 <pthread.h> +#include <stdlib.h> +#include <dso_handle.h> + +extern int __cxa_thread_atexit_impl (void (*) (void *), void *, void *); + +int tst_create2mod_dtor_done; + +static void +dtor (void *obj) +{ + *(int *) obj = 1; +} + +/* The module TLS access mirrors the real-world trigger (a C++ thread_local + or Rust thread_local! first access), exercising __tls_get_addr from the + spawned thread as well. */ +static __thread int tls_obj; + +static void * +worker (void *arg) +{ + tls_obj = 1; + if (__cxa_thread_atexit_impl (dtor, &tst_create2mod_dtor_done, + __dso_handle) != 0) + abort (); + return &tls_obj; +} + +static void __attribute__ ((constructor)) +do_init (void) +{ + pthread_t t; + if (pthread_create (&t, NULL, worker, NULL) != 0) + abort (); + if (pthread_join (t, NULL) != 0) + abort (); +} diff --git a/sysdeps/pthread/tst-create3.c b/sysdeps/pthread/tst-create3.c new file mode 100644 index 0000000000..2122a473b0 --- /dev/null +++ b/sysdeps/pthread/tst-create3.c @@ -0,0 +1,92 @@ +/* Verify concurrent dlopen of the same DSO runs the constructor once + and does not return before the constructor has finished. + 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/>. */ + + +/* Two invariants are checked for concurrent dlopen of the same DSO: + + 1. The DSO's constructor runs exactly once, even if N threads race + into _dl_open. + + 2. No dlopen caller returns before the constructor has finished. + A caller that beats the constructor would observe globals in + their BSS-zeroed state. */ + +#include <pthread.h> +#include <stdatomic.h> +#include <stdint.h> +#include <support/check.h> +#include <support/xdlfcn.h> +#include <support/xthread.h> + +#include "tst-create3.h" + +#define NTHREADS 8 + +static pthread_barrier_t start_barrier; +static void *handles[NTHREADS]; + +static void * +worker (void *arg) +{ + int idx = (int) (intptr_t) arg; + + /* Release all workers at once. */ + xpthread_barrier_wait (&start_barrier); + + void *h = xdlopen ("tst-create3mod.so", RTLD_NOW); + + /* The "done" flag is the constructor's final write. If dlopen + returned before the constructor finished, this load will observe + 0 instead of TST_CREATE3_MAGIC_DONE. */ + _Atomic unsigned int *done = xdlsym (h, "tst_create3mod_done"); + unsigned int done_val = atomic_load_explicit (done, memory_order_acquire); + if (done_val != TST_CREATE3_MAGIC_DONE) + FAIL ("thread %d returned from dlopen before the constructor finished" + " (tst_create3mod_done=0x%x)", idx, done_val); + + /* Keep the handle open; main dlcloses after the count check. */ + handles[idx] = h; + return NULL; +} + +static int +do_test (void) +{ + pthread_t threads[NTHREADS]; + + xpthread_barrier_init (&start_barrier, NULL, NTHREADS); + + for (int i = 0; i < NTHREADS; ++i) + threads[i] = xpthread_create (0, worker, (void *) (intptr_t) i); + + for (int i = 0; i < NTHREADS; ++i) + xpthread_join (threads[i]); + + /* The DSO is still loaded (all handles open), so the counter + reflects every constructor execution during the race above. */ + _Atomic int *count = xdlsym (handles[0], "tst_create3mod_ctor_count"); + TEST_COMPARE (atomic_load_explicit (count, memory_order_acquire), 1); + + for (int i = 0; i < NTHREADS; ++i) + xdlclose (handles[i]); + + return 0; +} + +#include <support/test-driver.c> diff --git a/sysdeps/pthread/tst-create3.h b/sysdeps/pthread/tst-create3.h new file mode 100644 index 0000000000..9a448ba2bf --- /dev/null +++ b/sysdeps/pthread/tst-create3.h @@ -0,0 +1,27 @@ +/* Shared definitions for tst-create3 and tst-create3mod. + 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/>. */ + +#ifndef _TST_CREATE3_H +#define _TST_CREATE3_H + +/* Magic value published by the module constructor as its final action. + Any dlopen caller that observes tst_create3mod_done with a different + value immediately after dlopen returned has beaten the constructor. */ +#define TST_CREATE3_MAGIC_DONE 0xCAFEBABEu + +#endif diff --git a/sysdeps/pthread/tst-create3mod.c b/sysdeps/pthread/tst-create3mod.c new file mode 100644 index 0000000000..b9390ae043 --- /dev/null +++ b/sysdeps/pthread/tst-create3mod.c @@ -0,0 +1,53 @@ +/* DSO for tst-create3: concurrent dlopen constructor-once test. + 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 <stdatomic.h> +#include <time.h> + +#include "tst-create3.h" + +/* How long (nanoseconds) the constructor sleeps to simulate slow + initialization, so that concurrent dlopen callers are very likely + to reach _dl_open while the constructor is still running. */ +#define TST_CREATE3_CTOR_SLEEP_NS 200000000 /* 200 ms */ + +/* Counter incremented by the constructor. Must be exactly 1 after + concurrent dlopen. */ +_Atomic int tst_create3mod_ctor_count = 0; + +_Atomic unsigned int tst_create3mod_done = 0; + +static void __attribute__ ((constructor)) +do_init (void) +{ + atomic_fetch_add_explicit (&tst_create3mod_ctor_count, 1, + memory_order_relaxed); + + /* Slow the constructor down to widen the window in which a buggy + implementation would let a concurrent caller return from dlopen + before initialization finished. */ + struct timespec ts = + { + .tv_sec = TST_CREATE3_CTOR_SLEEP_NS / 1000000000L, + .tv_nsec = TST_CREATE3_CTOR_SLEEP_NS % 1000000000L + }; + nanosleep (&ts, NULL); + + atomic_store_explicit (&tst_create3mod_done, TST_CREATE3_MAGIC_DONE, + memory_order_release); +} diff --git a/sysdeps/pthread/tst-create4.c b/sysdeps/pthread/tst-create4.c new file mode 100644 index 0000000000..ce611dc2ea --- /dev/null +++ b/sysdeps/pthread/tst-create4.c @@ -0,0 +1,90 @@ +/* Verify that constructors of independent DSOs loaded from different + threads do not interleave. + 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/>. */ + + +/* Two worker threads concurrently dlopen two independent DSOs (no DT_NEEDED + between them). Each DSO's constructor appends a character to a shared + buffer in the main executable several times. The test then asserts that + the two constructors did not interleave: the buffer must contain either + "AAAAABBBBB" or "BBBBBAAAAA". + + This is a property of the current implementation, not of POSIX or the ELF + specification: dl_load_lock is held across the entire _dl_open call, + including constructor execution, so dlopen calls from different threads + are fully serialized. */ + +#include <pthread.h> +#include <stdatomic.h> +#include <stdio.h> +#include <string.h> +#include <support/check.h> +#include <support/xdlfcn.h> +#include <support/xthread.h> + +#include "tst-create4.h" + +_Atomic int tst_create4_seq_idx = 0; +char tst_create4_seq_buf[TST_CREATE4_NSTEPS * 2 + 1]; + +static pthread_barrier_t start_barrier; + +static void * +worker_a (void *unused) +{ + xpthread_barrier_wait (&start_barrier); + void *h = xdlopen ("tst-create4mod-a.so", RTLD_NOW); + xdlclose (h); + return NULL; +} + +static void * +worker_b (void *unused) +{ + xpthread_barrier_wait (&start_barrier); + void *h = xdlopen ("tst-create4mod-b.so", RTLD_NOW); + xdlclose (h); + return NULL; +} + +static int +do_test (void) +{ + xpthread_barrier_init (&start_barrier, NULL, 2); + + pthread_t ta = xpthread_create (0, worker_a, NULL); + pthread_t tb = xpthread_create (0, worker_b, NULL); + xpthread_join (ta); + xpthread_join (tb); + + int len = atomic_load_explicit (&tst_create4_seq_idx, + memory_order_acquire); + TEST_COMPARE (len, TST_CREATE4_NSTEPS * 2); + tst_create4_seq_buf[len] = '\0'; + + printf ("info: observed sequence: %s\n", tst_create4_seq_buf); + + if (strcmp (tst_create4_seq_buf, "AAAAABBBBB") != 0 + && strcmp (tst_create4_seq_buf, "BBBBBAAAAA") != 0) + FAIL ("constructors of independent DSOs interleaved: \"%s\"", + tst_create4_seq_buf); + + return 0; +} + +#include <support/test-driver.c> diff --git a/sysdeps/pthread/tst-create4.h b/sysdeps/pthread/tst-create4.h new file mode 100644 index 0000000000..5e13875e6f --- /dev/null +++ b/sysdeps/pthread/tst-create4.h @@ -0,0 +1,34 @@ +/* Shared definitions for tst-create4 and its modules. + 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/>. */ + +#ifndef _TST_CREATE4_H +#define _TST_CREATE4_H + +#include <stdatomic.h> + +/* Number of characters each module constructor appends to the shared + buffer. */ +#define TST_CREATE4_NSTEPS 5 + +/* Shared buffer filled in by the two DSO constructors. Defined in the + main executable and exported to the modules via the dynamic symbol + table (-Wl,-export-dynamic). */ +extern _Atomic int tst_create4_seq_idx; +extern char tst_create4_seq_buf[TST_CREATE4_NSTEPS * 2 + 1]; + +#endif diff --git a/sysdeps/pthread/tst-create4mod-a.c b/sysdeps/pthread/tst-create4mod-a.c new file mode 100644 index 0000000000..947680d743 --- /dev/null +++ b/sysdeps/pthread/tst-create4mod-a.c @@ -0,0 +1,35 @@ +/* DSO A for tst-create4: writes 'A' into the shared buffer several + times, yielding between writes to maximize visible interleaving. + 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 <stdatomic.h> +#include <sched.h> + +#include "tst-create4.h" + +static void __attribute__ ((constructor)) +init_a (void) +{ + for (int i = 0; i < TST_CREATE4_NSTEPS; ++i) + { + int idx = atomic_fetch_add_explicit (&tst_create4_seq_idx, 1, + memory_order_relaxed); + tst_create4_seq_buf[idx] = 'A'; + sched_yield (); + } +} diff --git a/sysdeps/pthread/tst-create4mod-b.c b/sysdeps/pthread/tst-create4mod-b.c new file mode 100644 index 0000000000..4ca4c19755 --- /dev/null +++ b/sysdeps/pthread/tst-create4mod-b.c @@ -0,0 +1,35 @@ +/* DSO B for tst-create4: writes 'B' into the shared buffer several + times, yielding between writes to maximize visible interleaving. + 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 <stdatomic.h> +#include <sched.h> + +#include "tst-create4.h" + +static void __attribute__ ((constructor)) +init_b (void) +{ + for (int i = 0; i < TST_CREATE4_NSTEPS; ++i) + { + int idx = atomic_fetch_add_explicit (&tst_create4_seq_idx, 1, + memory_order_relaxed); + tst_create4_seq_buf[idx] = 'B'; + sched_yield (); + } +} diff --git a/sysdeps/pthread/tst-create5.c b/sysdeps/pthread/tst-create5.c new file mode 100644 index 0000000000..85945d6653 --- /dev/null +++ b/sysdeps/pthread/tst-create5.c @@ -0,0 +1,50 @@ +/* Verify that dlopen (NULL) from a worker thread does not deadlock. + 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/>. */ + + +/* An implementation that tracks per-object initialization state must + mark the main executable as fully initialized, otherwise a later + multi-threaded dlopen (NULL) /__RTLD_OPENEXEC call takes the + already-loaded early-return path in _dl_open and can wait forever for a + "pending" constructor. */ + +#include <stdio.h> +#include <support/xdlfcn.h> +#include <support/xthread.h> + +static void * +worker (void *arg) +{ + printf ("worker: dlopen(NULL)\n"); + void *h = xdlopen (NULL, RTLD_NOW); + printf ("worker: dlopen(NULL) done\n"); + xdlclose (h); + return NULL; +} + +static int +do_test (void) +{ + pthread_t t = xpthread_create (0, worker, NULL); + xpthread_join (t); + + printf ("main: worker finished\n"); + return 0; +} + +#include <support/test-driver.c>