65faa39
commit 024a7640ab9ecea80e527f4e4d7f7a1868e952c5
65faa39
Author: Szabolcs Nagy <szabolcs.nagy@arm.com>
65faa39
Date:   Wed Sep 15 15:16:19 2021 +0100
65faa39
65faa39
    elf: Avoid deadlock between pthread_create and ctors [BZ #28357]
65faa39
    
65faa39
    The fix for bug 19329 caused a regression such that pthread_create can
65faa39
    deadlock when concurrent ctors from dlopen are waiting for it to finish.
65faa39
    Use a new GL(dl_load_tls_lock) in pthread_create that is not taken
65faa39
    around ctors in dlopen.
65faa39
    
65faa39
    The new lock is also used in __tls_get_addr instead of GL(dl_load_lock).
65faa39
    
65faa39
    The new lock is held in _dl_open_worker and _dl_close_worker around
65faa39
    most of the logic before/after the init/fini routines.  When init/fini
65faa39
    routines are running then TLS is in a consistent, usable state.
65faa39
    In _dl_open_worker the new lock requires catching and reraising dlopen
65faa39
    failures that happen in the critical section.
65faa39
    
65faa39
    The new lock is reinitialized in a fork child, to keep the existing
65faa39
    behaviour and it is kept recursive in case malloc interposition or TLS
65faa39
    access from signal handlers can retake it.  It is not obvious if this
65faa39
    is necessary or helps, but avoids changing the preexisting behaviour.
65faa39
    
65faa39
    The new lock may be more appropriate for dl_iterate_phdr too than
65faa39
    GL(dl_load_write_lock), since TLS state of an incompletely loaded
65faa39
    module may be accessed.  If the new lock can replace the old one,
65faa39
    that can be a separate change.
65faa39
    
65faa39
    Fixes bug 28357.
65faa39
    
65faa39
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
65faa39
    (cherry picked from commit 83b5323261bb72313bffcf37476c1b8f0847c736)
65faa39
65faa39
diff --git a/elf/dl-close.c b/elf/dl-close.c
65faa39
index f39001cab981b723..cd7b9c9fe83a1a44 100644
65faa39
--- a/elf/dl-close.c
65faa39
+++ b/elf/dl-close.c
65faa39
@@ -549,6 +549,9 @@ _dl_close_worker (struct link_map *map, bool force)
65faa39
   size_t tls_free_end;
65faa39
   tls_free_start = tls_free_end = NO_TLS_OFFSET;
65faa39
 
65faa39
+  /* Protects global and module specitic TLS state.  */
65faa39
+  __rtld_lock_lock_recursive (GL(dl_load_tls_lock));
65faa39
+
65faa39
   /* We modify the list of loaded objects.  */
65faa39
   __rtld_lock_lock_recursive (GL(dl_load_write_lock));
65faa39
 
65faa39
@@ -784,6 +787,9 @@ _dl_close_worker (struct link_map *map, bool force)
65faa39
 	GL(dl_tls_static_used) = tls_free_start;
65faa39
     }
65faa39
 
65faa39
+  /* TLS is cleaned up for the unloaded modules.  */
65faa39
+  __rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
65faa39
+
65faa39
 #ifdef SHARED
65faa39
   /* Auditing checkpoint: we have deleted all objects.  */
65faa39
   if (__glibc_unlikely (do_audit))
65faa39
diff --git a/elf/dl-open.c b/elf/dl-open.c
65faa39
index 41c7250bf630f978..bc68e2c376debd71 100644
65faa39
--- a/elf/dl-open.c
65faa39
+++ b/elf/dl-open.c
65faa39
@@ -66,6 +66,9 @@ struct dl_open_args
65faa39
      libc_map value in the namespace in case of a dlopen failure.  */
65faa39
   bool libc_already_loaded;
65faa39
 
65faa39
+  /* Set to true if the end of dl_open_worker_begin was reached.  */
65faa39
+  bool worker_continue;
65faa39
+
65faa39
   /* Original parameters to the program and the current environment.  */
65faa39
   int argc;
65faa39
   char **argv;
65faa39
@@ -482,7 +485,7 @@ call_dl_init (void *closure)
65faa39
 }
65faa39
 
65faa39
 static void
65faa39
-dl_open_worker (void *a)
65faa39
+dl_open_worker_begin (void *a)
65faa39
 {
65faa39
   struct dl_open_args *args = a;
65faa39
   const char *file = args->file;
65faa39
@@ -774,6 +777,36 @@ dl_open_worker (void *a)
65faa39
       _dl_call_libc_early_init (libc_map, false);
65faa39
     }
65faa39
 
65faa39
+  args->worker_continue = true;
65faa39
+}
65faa39
+
65faa39
+static void
65faa39
+dl_open_worker (void *a)
65faa39
+{
65faa39
+  struct dl_open_args *args = a;
65faa39
+
65faa39
+  args->worker_continue = false;
65faa39
+
65faa39
+  {
65faa39
+    /* Protects global and module specific TLS state.  */
65faa39
+    __rtld_lock_lock_recursive (GL(dl_load_tls_lock));
65faa39
+
65faa39
+    struct dl_exception ex;
65faa39
+    int err = _dl_catch_exception (&ex, dl_open_worker_begin, args);
65faa39
+
65faa39
+    __rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
65faa39
+
65faa39
+    if (__glibc_unlikely (ex.errstring != NULL))
65faa39
+      /* Reraise the error.  */
65faa39
+      _dl_signal_exception (err, &ex, NULL);
65faa39
+  }
65faa39
+
65faa39
+  if (!args->worker_continue)
65faa39
+    return;
65faa39
+
65faa39
+  int mode = args->mode;
65faa39
+  struct link_map *new = args->map;
65faa39
+
65faa39
   /* Run the initializer functions of new objects.  Temporarily
65faa39
      disable the exception handler, so that lazy binding failures are
65faa39
      fatal.  */
65faa39
diff --git a/elf/dl-support.c b/elf/dl-support.c
65faa39
index 01557181753fb38d..d8c06ba7eb4c76ea 100644
65faa39
--- a/elf/dl-support.c
65faa39
+++ b/elf/dl-support.c
65faa39
@@ -229,6 +229,13 @@ __rtld_lock_define_initialized_recursive (, _dl_load_lock)
65faa39
    list of loaded objects while an object is added to or removed from
65faa39
    that list.  */
65faa39
 __rtld_lock_define_initialized_recursive (, _dl_load_write_lock)
65faa39
+  /* This lock protects global and module specific TLS related data.
65faa39
+     E.g. it is held in dlopen and dlclose when GL(dl_tls_generation),
65faa39
+     GL(dl_tls_max_dtv_idx) or GL(dl_tls_dtv_slotinfo_list) are
65faa39
+     accessed and when TLS related relocations are processed for a
65faa39
+     module.  It was introduced to keep pthread_create accessing TLS
65faa39
+     state that is being set up.  */
65faa39
+__rtld_lock_define_initialized_recursive (, _dl_load_tls_lock)
65faa39
 
65faa39
 
65faa39
 #ifdef HAVE_AUX_VECTOR
65faa39
diff --git a/elf/dl-tls.c b/elf/dl-tls.c
65faa39
index 423e380f7ca654fe..40263cf586e74c64 100644
65faa39
--- a/elf/dl-tls.c
65faa39
+++ b/elf/dl-tls.c
65faa39
@@ -532,7 +532,7 @@ _dl_allocate_tls_init (void *result)
65faa39
   size_t maxgen = 0;
65faa39
 
65faa39
   /* Protects global dynamic TLS related state.  */
65faa39
-  __rtld_lock_lock_recursive (GL(dl_load_lock));
65faa39
+  __rtld_lock_lock_recursive (GL(dl_load_tls_lock));
65faa39
 
65faa39
   /* Check if the current dtv is big enough.   */
65faa39
   if (dtv[-1].counter < GL(dl_tls_max_dtv_idx))
65faa39
@@ -606,7 +606,7 @@ _dl_allocate_tls_init (void *result)
65faa39
       listp = listp->next;
65faa39
       assert (listp != NULL);
65faa39
     }
65faa39
-  __rtld_lock_unlock_recursive (GL(dl_load_lock));
65faa39
+  __rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
65faa39
 
65faa39
   /* The DTV version is up-to-date now.  */
65faa39
   dtv[0].counter = maxgen;
65faa39
@@ -745,7 +745,7 @@ _dl_update_slotinfo (unsigned long int req_modid)
65faa39
 
65faa39
 	 Here the dtv needs to be updated to new_gen generation count.
65faa39
 
65faa39
-	 This code may be called during TLS access when GL(dl_load_lock)
65faa39
+	 This code may be called during TLS access when GL(dl_load_tls_lock)
65faa39
 	 is not held.  In that case the user code has to synchronize with
65faa39
 	 dlopen and dlclose calls of relevant modules.  A module m is
65faa39
 	 relevant if the generation of m <= new_gen and dlclose of m is
65faa39
@@ -867,11 +867,11 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
65faa39
   if (__glibc_unlikely (the_map->l_tls_offset
65faa39
 			!= FORCED_DYNAMIC_TLS_OFFSET))
65faa39
     {
65faa39
-      __rtld_lock_lock_recursive (GL(dl_load_lock));
65faa39
+      __rtld_lock_lock_recursive (GL(dl_load_tls_lock));
65faa39
       if (__glibc_likely (the_map->l_tls_offset == NO_TLS_OFFSET))
65faa39
 	{
65faa39
 	  the_map->l_tls_offset = FORCED_DYNAMIC_TLS_OFFSET;
65faa39
-	  __rtld_lock_unlock_recursive (GL(dl_load_lock));
65faa39
+	  __rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
65faa39
 	}
65faa39
       else if (__glibc_likely (the_map->l_tls_offset
65faa39
 			       != FORCED_DYNAMIC_TLS_OFFSET))
65faa39
@@ -883,7 +883,7 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
65faa39
 #else
65faa39
 # error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
65faa39
 #endif
65faa39
-	  __rtld_lock_unlock_recursive (GL(dl_load_lock));
65faa39
+	  __rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
65faa39
 
65faa39
 	  dtv[GET_ADDR_MODULE].pointer.to_free = NULL;
65faa39
 	  dtv[GET_ADDR_MODULE].pointer.val = p;
65faa39
@@ -891,7 +891,7 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
65faa39
 	  return (char *) p + GET_ADDR_OFFSET;
65faa39
 	}
65faa39
       else
65faa39
-	__rtld_lock_unlock_recursive (GL(dl_load_lock));
65faa39
+	__rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
65faa39
     }
65faa39
   struct dtv_pointer result = allocate_and_init (the_map);
65faa39
   dtv[GET_ADDR_MODULE].pointer = result;
65faa39
@@ -962,7 +962,7 @@ _dl_tls_get_addr_soft (struct link_map *l)
65faa39
     return NULL;
65faa39
 
65faa39
   dtv_t *dtv = THREAD_DTV ();
65faa39
-  /* This may be called without holding the GL(dl_load_lock).  Reading
65faa39
+  /* This may be called without holding the GL(dl_load_tls_lock).  Reading
65faa39
      arbitrary gen value is fine since this is best effort code.  */
65faa39
   size_t gen = atomic_load_relaxed (&GL(dl_tls_generation));
65faa39
   if (__glibc_unlikely (dtv[0].counter != gen))
65faa39
diff --git a/elf/rtld.c b/elf/rtld.c
65faa39
index d733359eaf808b8a..08cf50145a1c01ce 100644
65faa39
--- a/elf/rtld.c
65faa39
+++ b/elf/rtld.c
65faa39
@@ -322,6 +322,7 @@ struct rtld_global _rtld_global =
65faa39
 #ifdef _LIBC_REENTRANT
65faa39
     ._dl_load_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
65faa39
     ._dl_load_write_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
65faa39
+    ._dl_load_tls_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
65faa39
 #endif
65faa39
     ._dl_nns = 1,
65faa39
     ._dl_ns =
65faa39
diff --git a/posix/fork.c b/posix/fork.c
65faa39
index c471f7b15fe4290d..021691b9b7441f15 100644
65faa39
--- a/posix/fork.c
65faa39
+++ b/posix/fork.c
65faa39
@@ -99,6 +99,9 @@ __libc_fork (void)
65faa39
       /* Reset the lock the dynamic loader uses to protect its data.  */
65faa39
       __rtld_lock_initialize (GL(dl_load_lock));
65faa39
 
65faa39
+      /* Reset the lock protecting dynamic TLS related data.  */
65faa39
+      __rtld_lock_initialize (GL(dl_load_tls_lock));
65faa39
+
65faa39
       reclaim_stacks ();
65faa39
 
65faa39
       /* Run the handlers registered for the child.  */
65faa39
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
65faa39
index 9c15259236adab43..1ceb9c3212c148ba 100644
65faa39
--- a/sysdeps/generic/ldsodefs.h
65faa39
+++ b/sysdeps/generic/ldsodefs.h
65faa39
@@ -372,6 +372,13 @@ struct rtld_global
65faa39
      list of loaded objects while an object is added to or removed
65faa39
      from that list.  */
65faa39
   __rtld_lock_define_recursive (EXTERN, _dl_load_write_lock)
65faa39
+  /* This lock protects global and module specific TLS related data.
65faa39
+     E.g. it is held in dlopen and dlclose when GL(dl_tls_generation),
65faa39
+     GL(dl_tls_max_dtv_idx) or GL(dl_tls_dtv_slotinfo_list) are
65faa39
+     accessed and when TLS related relocations are processed for a
65faa39
+     module.  It was introduced to keep pthread_create accessing TLS
65faa39
+     state that is being set up.  */
65faa39
+  __rtld_lock_define_recursive (EXTERN, _dl_load_tls_lock)
65faa39
 
65faa39
   /* Incremented whenever something may have been added to dl_loaded.  */
65faa39
   EXTERN unsigned long long _dl_load_adds;
65faa39
@@ -1261,7 +1268,7 @@ extern int _dl_scope_free (void *) attribute_hidden;
65faa39
 
65faa39
 /* Add module to slot information data.  If DO_ADD is false, only the
65faa39
    required memory is allocated.  Must be called with GL
65faa39
-   (dl_load_lock) acquired.  If the function has already been called
65faa39
+   (dl_load_tls_lock) acquired.  If the function has already been called
65faa39
    for the link map L with !do_add, then this function will not raise
65faa39
    an exception, otherwise it is possible that it encounters a memory
65faa39
    allocation failure.  */
65faa39
diff --git a/sysdeps/pthread/Makefile b/sysdeps/pthread/Makefile
65faa39
index 0af9c59b425aefb1..df8943f4860a39d8 100644
65faa39
--- a/sysdeps/pthread/Makefile
65faa39
+++ b/sysdeps/pthread/Makefile
65faa39
@@ -152,15 +152,17 @@ tests += tst-cancelx2 tst-cancelx3 tst-cancelx6 tst-cancelx8 tst-cancelx9 \
65faa39
 	 tst-cleanupx0 tst-cleanupx1 tst-cleanupx2 tst-cleanupx3
65faa39
 
65faa39
 ifeq ($(build-shared),yes)
65faa39
-tests += tst-atfork2 tst-pt-tls4 tst-_res1 tst-fini1
65faa39
+tests += tst-atfork2 tst-pt-tls4 tst-_res1 tst-fini1 tst-create1
65faa39
 tests-nolibpthread += tst-fini1
65faa39
 endif
65faa39
 
65faa39
 modules-names += tst-atfork2mod tst-tls4moda tst-tls4modb \
65faa39
-		 tst-_res1mod1 tst-_res1mod2 tst-fini1mod
65faa39
+		 tst-_res1mod1 tst-_res1mod2 tst-fini1mod \
65faa39
+		 tst-create1mod
65faa39
 test-modules = $(addprefix $(objpfx),$(addsuffix .so,$(modules-names)))
65faa39
 
65faa39
 tst-atfork2mod.so-no-z-defs = yes
65faa39
+tst-create1mod.so-no-z-defs = yes
65faa39
 
65faa39
 ifeq ($(build-shared),yes)
65faa39
 # Build all the modules even when not actually running test programs.
65faa39
@@ -279,4 +281,8 @@ LDFLAGS-tst-join7mod.so = -Wl,-soname,tst-join7mod.so
65faa39
 
65faa39
 CFLAGS-tst-unwind-thread.c += -funwind-tables
65faa39
 
65faa39
+LDFLAGS-tst-create1 = -Wl,-export-dynamic
65faa39
+$(objpfx)tst-create1: $(shared-thread-library)
65faa39
+$(objpfx)tst-create1.out: $(objpfx)tst-create1mod.so
65faa39
+
65faa39
 endif
65faa39
diff --git a/sysdeps/pthread/tst-create1.c b/sysdeps/pthread/tst-create1.c
65faa39
new file mode 100644
65faa39
index 0000000000000000..932586c30990d1d4
65faa39
--- /dev/null
65faa39
+++ b/sysdeps/pthread/tst-create1.c
65faa39
@@ -0,0 +1,119 @@
65faa39
+/* Verify that pthread_create does not deadlock when ctors take locks.
65faa39
+   Copyright (C) 2021 Free Software Foundation, Inc.
65faa39
+   This file is part of the GNU C Library.
65faa39
+
65faa39
+   The GNU C Library is free software; you can redistribute it and/or
65faa39
+   modify it under the terms of the GNU Lesser General Public
65faa39
+   License as published by the Free Software Foundation; either
65faa39
+   version 2.1 of the License, or (at your option) any later version.
65faa39
+
65faa39
+   The GNU C Library is distributed in the hope that it will be useful,
65faa39
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
65faa39
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
65faa39
+   Lesser General Public License for more details.
65faa39
+
65faa39
+   You should have received a copy of the GNU Lesser General Public
65faa39
+   License along with the GNU C Library; if not, see
65faa39
+   <https://www.gnu.org/licenses/>.  */
65faa39
+
65faa39
+#include <stdio.h>
65faa39
+#include <support/xdlfcn.h>
65faa39
+#include <support/xthread.h>
65faa39
+
65faa39
+/*
65faa39
+Check if ctor and pthread_create deadlocks in
65faa39
+
65faa39
+thread 1: dlopen -> ctor -> lock(user_lock)
65faa39
+thread 2: lock(user_lock) -> pthread_create
65faa39
+
65faa39
+or in
65faa39
+
65faa39
+thread 1: dlclose -> dtor -> lock(user_lock)
65faa39
+thread 2: lock(user_lock) -> pthread_create
65faa39
+*/
65faa39
+
65faa39
+static pthread_barrier_t bar_ctor;
65faa39
+static pthread_barrier_t bar_dtor;
65faa39
+static pthread_mutex_t user_lock = PTHREAD_MUTEX_INITIALIZER;
65faa39
+
65faa39
+void
65faa39
+ctor (void)
65faa39
+{
65faa39
+  xpthread_barrier_wait (&bar_ctor);
65faa39
+  dprintf (1, "thread 1: in ctor: started.\n");
65faa39
+  xpthread_mutex_lock (&user_lock);
65faa39
+  dprintf (1, "thread 1: in ctor: locked user_lock.\n");
65faa39
+  xpthread_mutex_unlock (&user_lock);
65faa39
+  dprintf (1, "thread 1: in ctor: unlocked user_lock.\n");
65faa39
+  dprintf (1, "thread 1: in ctor: done.\n");
65faa39
+}
65faa39
+
65faa39
+void
65faa39
+dtor (void)
65faa39
+{
65faa39
+  xpthread_barrier_wait (&bar_dtor);
65faa39
+  dprintf (1, "thread 1: in dtor: started.\n");
65faa39
+  xpthread_mutex_lock (&user_lock);
65faa39
+  dprintf (1, "thread 1: in dtor: locked user_lock.\n");
65faa39
+  xpthread_mutex_unlock (&user_lock);
65faa39
+  dprintf (1, "thread 1: in dtor: unlocked user_lock.\n");
65faa39
+  dprintf (1, "thread 1: in dtor: done.\n");
65faa39
+}
65faa39
+
65faa39
+static void *
65faa39
+thread3 (void *a)
65faa39
+{
65faa39
+  dprintf (1, "thread 3: started.\n");
65faa39
+  dprintf (1, "thread 3: done.\n");
65faa39
+  return 0;
65faa39
+}
65faa39
+
65faa39
+static void *
65faa39
+thread2 (void *a)
65faa39
+{
65faa39
+  pthread_t t3;
65faa39
+  dprintf (1, "thread 2: started.\n");
65faa39
+
65faa39
+  xpthread_mutex_lock (&user_lock);
65faa39
+  dprintf (1, "thread 2: locked user_lock.\n");
65faa39
+  xpthread_barrier_wait (&bar_ctor);
65faa39
+  t3 = xpthread_create (0, thread3, 0);
65faa39
+  xpthread_mutex_unlock (&user_lock);
65faa39
+  dprintf (1, "thread 2: unlocked user_lock.\n");
65faa39
+  xpthread_join (t3);
65faa39
+
65faa39
+  xpthread_mutex_lock (&user_lock);
65faa39
+  dprintf (1, "thread 2: locked user_lock.\n");
65faa39
+  xpthread_barrier_wait (&bar_dtor);
65faa39
+  t3 = xpthread_create (0, thread3, 0);
65faa39
+  xpthread_mutex_unlock (&user_lock);
65faa39
+  dprintf (1, "thread 2: unlocked user_lock.\n");
65faa39
+  xpthread_join (t3);
65faa39
+
65faa39
+  dprintf (1, "thread 2: done.\n");
65faa39
+  return 0;
65faa39
+}
65faa39
+
65faa39
+static void
65faa39
+thread1 (void)
65faa39
+{
65faa39
+  dprintf (1, "thread 1: started.\n");
65faa39
+  xpthread_barrier_init (&bar_ctor, NULL, 2);
65faa39
+  xpthread_barrier_init (&bar_dtor, NULL, 2);
65faa39
+  pthread_t t2 = xpthread_create (0, thread2, 0);
65faa39
+  void *p = xdlopen ("tst-create1mod.so", RTLD_NOW | RTLD_GLOBAL);
65faa39
+  dprintf (1, "thread 1: dlopen done.\n");
65faa39
+  xdlclose (p);
65faa39
+  dprintf (1, "thread 1: dlclose done.\n");
65faa39
+  xpthread_join (t2);
65faa39
+  dprintf (1, "thread 1: done.\n");
65faa39
+}
65faa39
+
65faa39
+static int
65faa39
+do_test (void)
65faa39
+{
65faa39
+  thread1 ();
65faa39
+  return 0;
65faa39
+}
65faa39
+
65faa39
+#include <support/test-driver.c>
65faa39
diff --git a/sysdeps/pthread/tst-create1mod.c b/sysdeps/pthread/tst-create1mod.c
65faa39
new file mode 100644
65faa39
index 0000000000000000..62c9006961683177
65faa39
--- /dev/null
65faa39
+++ b/sysdeps/pthread/tst-create1mod.c
65faa39
@@ -0,0 +1,41 @@
65faa39
+/* Verify that pthread_create does not deadlock when ctors take locks.
65faa39
+   Copyright (C) 2021 Free Software Foundation, Inc.
65faa39
+   This file is part of the GNU C Library.
65faa39
+
65faa39
+   The GNU C Library is free software; you can redistribute it and/or
65faa39
+   modify it under the terms of the GNU Lesser General Public
65faa39
+   License as published by the Free Software Foundation; either
65faa39
+   version 2.1 of the License, or (at your option) any later version.
65faa39
+
65faa39
+   The GNU C Library is distributed in the hope that it will be useful,
65faa39
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
65faa39
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
65faa39
+   Lesser General Public License for more details.
65faa39
+
65faa39
+   You should have received a copy of the GNU Lesser General Public
65faa39
+   License along with the GNU C Library; if not, see
65faa39
+   <https://www.gnu.org/licenses/>.  */
65faa39
+
65faa39
+#include <stdio.h>
65faa39
+
65faa39
+/* Require TLS setup for the module.  */
65faa39
+__thread int tlsvar;
65faa39
+
65faa39
+void ctor (void);
65faa39
+void dtor (void);
65faa39
+
65faa39
+static void __attribute__ ((constructor))
65faa39
+do_init (void)
65faa39
+{
65faa39
+  dprintf (1, "constructor started: %d.\n", tlsvar++);
65faa39
+  ctor ();
65faa39
+  dprintf (1, "constructor done: %d.\n", tlsvar++);
65faa39
+}
65faa39
+
65faa39
+static void __attribute__ ((destructor))
65faa39
+do_end (void)
65faa39
+{
65faa39
+  dprintf (1, "destructor started: %d.\n", tlsvar++);
65faa39
+  dtor ();
65faa39
+  dprintf (1, "destructor done: %d.\n", tlsvar++);
65faa39
+}