mirror of
git://sourceware.org/git/glibc.git
synced 2026-09-08 23:58:31 +08:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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 ();
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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 ();
|
||||
}
|
||||
}
|
||||
@@ -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 ();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user