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.
This commit is contained in:
Adhemerval Zanella
2026-07-19 10:32:11 -03:00
parent 6b29e74c73
commit 9039a9cb0f
12 changed files with 591 additions and 34 deletions
+38 -34
View File
@@ -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.
__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.
_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.
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));
/* 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;
/* See if we already encountered the DSO. */
if (__glibc_unlikely (dso_symbol_cache != dso_symbol))
{
ElfW(Addr) caller = (ElfW(Addr)) dso_symbol;
/* 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);
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;
new->map = map;
return 0;
}
+25
View File
@@ -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
+55
View File
@@ -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>
+57
View File
@@ -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 ();
}
+92
View File
@@ -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>
+27
View File
@@ -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
+53
View File
@@ -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);
}
+90
View File
@@ -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>
+34
View File
@@ -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
+35
View File
@@ -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 ();
}
}
+35
View File
@@ -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 ();
}
}
+50
View File
@@ -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>