548bcb
commit 83b5323261bb72313bffcf37476c1b8f0847c736
548bcb
Author: Szabolcs Nagy <szabolcs.nagy@arm.com>
548bcb
Date:   Wed Sep 15 15:16:19 2021 +0100
548bcb
548bcb
    elf: Avoid deadlock between pthread_create and ctors [BZ #28357]
548bcb
548bcb
    The fix for bug 19329 caused a regression such that pthread_create can
548bcb
    deadlock when concurrent ctors from dlopen are waiting for it to finish.
548bcb
    Use a new GL(dl_load_tls_lock) in pthread_create that is not taken
548bcb
    around ctors in dlopen.
548bcb
548bcb
    The new lock is also used in __tls_get_addr instead of GL(dl_load_lock).
548bcb
548bcb
    The new lock is held in _dl_open_worker and _dl_close_worker around
548bcb
    most of the logic before/after the init/fini routines.  When init/fini
548bcb
    routines are running then TLS is in a consistent, usable state.
548bcb
    In _dl_open_worker the new lock requires catching and reraising dlopen
548bcb
    failures that happen in the critical section.
548bcb
548bcb
    The new lock is reinitialized in a fork child, to keep the existing
548bcb
    behaviour and it is kept recursive in case malloc interposition or TLS
548bcb
    access from signal handlers can retake it.  It is not obvious if this
548bcb
    is necessary or helps, but avoids changing the preexisting behaviour.
548bcb
548bcb
    The new lock may be more appropriate for dl_iterate_phdr too than
548bcb
    GL(dl_load_write_lock), since TLS state of an incompletely loaded
548bcb
    module may be accessed.  If the new lock can replace the old one,
548bcb
    that can be a separate change.
548bcb
548bcb
    Fixes bug 28357.
548bcb
548bcb
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
548bcb
548bcb
Conflicts:
548bcb
	posix/fork.c
548bcb
	  (reworked due to file rename upstream and libpthread integration)
548bcb
	sysdeps/pthread/Makefile
548bcb
	  (htl testing support was missing downstream, reconstituted here;
548bcb
	  added $(libdl) required downstream)
548bcb
548bcb
diff --git a/elf/dl-close.c b/elf/dl-close.c
548bcb
index 18227fe992029364..7fe91bdd9aaf694e 100644
548bcb
--- a/elf/dl-close.c
548bcb
+++ b/elf/dl-close.c
548bcb
@@ -549,6 +549,9 @@ _dl_close_worker (struct link_map *map, bool force)
548bcb
   size_t tls_free_end;
548bcb
   tls_free_start = tls_free_end = NO_TLS_OFFSET;
548bcb
 
548bcb
+  /* Protects global and module specitic TLS state.  */
548bcb
+  __rtld_lock_lock_recursive (GL(dl_load_tls_lock));
548bcb
+
548bcb
   /* We modify the list of loaded objects.  */
548bcb
   __rtld_lock_lock_recursive (GL(dl_load_write_lock));
548bcb
 
548bcb
@@ -784,6 +787,9 @@ _dl_close_worker (struct link_map *map, bool force)
548bcb
 	GL(dl_tls_static_used) = tls_free_start;
548bcb
     }
548bcb
 
548bcb
+  /* TLS is cleaned up for the unloaded modules.  */
548bcb
+  __rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
548bcb
+
548bcb
 #ifdef SHARED
548bcb
   /* Auditing checkpoint: we have deleted all objects.  */
548bcb
   if (__glibc_unlikely (do_audit))
548bcb
diff --git a/elf/dl-open.c b/elf/dl-open.c
548bcb
index 54727402750f4c0c..736df62ce6e46d34 100644
548bcb
--- a/elf/dl-open.c
548bcb
+++ b/elf/dl-open.c
548bcb
@@ -65,6 +65,9 @@ struct dl_open_args
548bcb
      libc_map value in the namespace in case of a dlopen failure.  */
548bcb
   bool libc_already_loaded;
548bcb
 
548bcb
+  /* Set to true if the end of dl_open_worker_begin was reached.  */
548bcb
+  bool worker_continue;
548bcb
+
548bcb
   /* Original parameters to the program and the current environment.  */
548bcb
   int argc;
548bcb
   char **argv;
548bcb
@@ -481,7 +484,7 @@ call_dl_init (void *closure)
548bcb
 }
548bcb
 
548bcb
 static void
548bcb
-dl_open_worker (void *a)
548bcb
+dl_open_worker_begin (void *a)
548bcb
 {
548bcb
   struct dl_open_args *args = a;
548bcb
   const char *file = args->file;
548bcb
@@ -772,6 +775,36 @@ dl_open_worker (void *a)
548bcb
   DL_STATIC_INIT (new);
548bcb
 #endif
548bcb
 
548bcb
+  args->worker_continue = true;
548bcb
+}
548bcb
+
548bcb
+static void
548bcb
+dl_open_worker (void *a)
548bcb
+{
548bcb
+  struct dl_open_args *args = a;
548bcb
+
548bcb
+  args->worker_continue = false;
548bcb
+
548bcb
+  {
548bcb
+    /* Protects global and module specific TLS state.  */
548bcb
+    __rtld_lock_lock_recursive (GL(dl_load_tls_lock));
548bcb
+
548bcb
+    struct dl_exception ex;
548bcb
+    int err = _dl_catch_exception (&ex, dl_open_worker_begin, args);
548bcb
+
548bcb
+    __rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
548bcb
+
548bcb
+    if (__glibc_unlikely (ex.errstring != NULL))
548bcb
+      /* Reraise the error.  */
548bcb
+      _dl_signal_exception (err, &ex, NULL);
548bcb
+  }
548bcb
+
548bcb
+  if (!args->worker_continue)
548bcb
+    return;
548bcb
+
548bcb
+  int mode = args->mode;
548bcb
+  struct link_map *new = args->map;
548bcb
+
548bcb
   /* Run the initializer functions of new objects.  Temporarily
548bcb
      disable the exception handler, so that lazy binding failures are
548bcb
      fatal.  */
548bcb
diff --git a/elf/dl-support.c b/elf/dl-support.c
548bcb
index 34be8e5babfb6af3..3e5531138eaa18f8 100644
548bcb
--- a/elf/dl-support.c
548bcb
+++ b/elf/dl-support.c
548bcb
@@ -212,6 +212,13 @@ __rtld_lock_define_initialized_recursive (, _dl_load_lock)
548bcb
    list of loaded objects while an object is added to or removed from
548bcb
    that list.  */
548bcb
 __rtld_lock_define_initialized_recursive (, _dl_load_write_lock)
548bcb
+  /* This lock protects global and module specific TLS related data.
548bcb
+     E.g. it is held in dlopen and dlclose when GL(dl_tls_generation),
548bcb
+     GL(dl_tls_max_dtv_idx) or GL(dl_tls_dtv_slotinfo_list) are
548bcb
+     accessed and when TLS related relocations are processed for a
548bcb
+     module.  It was introduced to keep pthread_create accessing TLS
548bcb
+     state that is being set up.  */
548bcb
+__rtld_lock_define_initialized_recursive (, _dl_load_tls_lock)
548bcb
 
548bcb
 
548bcb
 #ifdef HAVE_AUX_VECTOR
548bcb
diff --git a/elf/dl-tls.c b/elf/dl-tls.c
548bcb
index 8c0f9e972d7a0eac..7865fc390c3f3f0a 100644
548bcb
--- a/elf/dl-tls.c
548bcb
+++ b/elf/dl-tls.c
548bcb
@@ -527,7 +527,7 @@ _dl_allocate_tls_init (void *result)
548bcb
   size_t maxgen = 0;
548bcb
 
548bcb
   /* Protects global dynamic TLS related state.  */
548bcb
-  __rtld_lock_lock_recursive (GL(dl_load_lock));
548bcb
+  __rtld_lock_lock_recursive (GL(dl_load_tls_lock));
548bcb
 
548bcb
   /* Check if the current dtv is big enough.   */
548bcb
   if (dtv[-1].counter < GL(dl_tls_max_dtv_idx))
548bcb
@@ -601,7 +601,7 @@ _dl_allocate_tls_init (void *result)
548bcb
       listp = listp->next;
548bcb
       assert (listp != NULL);
548bcb
     }
548bcb
-  __rtld_lock_unlock_recursive (GL(dl_load_lock));
548bcb
+  __rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
548bcb
 
548bcb
   /* The DTV version is up-to-date now.  */
548bcb
   dtv[0].counter = maxgen;
548bcb
@@ -740,7 +740,7 @@ _dl_update_slotinfo (unsigned long int req_modid)
548bcb
 
548bcb
 	 Here the dtv needs to be updated to new_gen generation count.
548bcb
 
548bcb
-	 This code may be called during TLS access when GL(dl_load_lock)
548bcb
+	 This code may be called during TLS access when GL(dl_load_tls_lock)
548bcb
 	 is not held.  In that case the user code has to synchronize with
548bcb
 	 dlopen and dlclose calls of relevant modules.  A module m is
548bcb
 	 relevant if the generation of m <= new_gen and dlclose of m is
548bcb
@@ -862,11 +862,11 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
548bcb
   if (__glibc_unlikely (the_map->l_tls_offset
548bcb
 			!= FORCED_DYNAMIC_TLS_OFFSET))
548bcb
     {
548bcb
-      __rtld_lock_lock_recursive (GL(dl_load_lock));
548bcb
+      __rtld_lock_lock_recursive (GL(dl_load_tls_lock));
548bcb
       if (__glibc_likely (the_map->l_tls_offset == NO_TLS_OFFSET))
548bcb
 	{
548bcb
 	  the_map->l_tls_offset = FORCED_DYNAMIC_TLS_OFFSET;
548bcb
-	  __rtld_lock_unlock_recursive (GL(dl_load_lock));
548bcb
+	  __rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
548bcb
 	}
548bcb
       else if (__glibc_likely (the_map->l_tls_offset
548bcb
 			       != FORCED_DYNAMIC_TLS_OFFSET))
548bcb
@@ -878,7 +878,7 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
548bcb
 #else
548bcb
 # error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
548bcb
 #endif
548bcb
-	  __rtld_lock_unlock_recursive (GL(dl_load_lock));
548bcb
+	  __rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
548bcb
 
548bcb
 	  dtv[GET_ADDR_MODULE].pointer.to_free = NULL;
548bcb
 	  dtv[GET_ADDR_MODULE].pointer.val = p;
548bcb
@@ -886,7 +886,7 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
548bcb
 	  return (char *) p + GET_ADDR_OFFSET;
548bcb
 	}
548bcb
       else
548bcb
-	__rtld_lock_unlock_recursive (GL(dl_load_lock));
548bcb
+	__rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
548bcb
     }
548bcb
   struct dtv_pointer result = allocate_and_init (the_map);
548bcb
   dtv[GET_ADDR_MODULE].pointer = result;
548bcb
@@ -957,7 +957,7 @@ _dl_tls_get_addr_soft (struct link_map *l)
548bcb
     return NULL;
548bcb
 
548bcb
   dtv_t *dtv = THREAD_DTV ();
548bcb
-  /* This may be called without holding the GL(dl_load_lock).  Reading
548bcb
+  /* This may be called without holding the GL(dl_load_tls_lock).  Reading
548bcb
      arbitrary gen value is fine since this is best effort code.  */
548bcb
   size_t gen = atomic_load_relaxed (&GL(dl_tls_generation));
548bcb
   if (__glibc_unlikely (dtv[0].counter != gen))
548bcb
diff --git a/elf/rtld.c b/elf/rtld.c
548bcb
index 118c454a2329573f..9e09896da078274d 100644
548bcb
--- a/elf/rtld.c
548bcb
+++ b/elf/rtld.c
548bcb
@@ -317,6 +317,7 @@ struct rtld_global _rtld_global =
548bcb
 #ifdef _LIBC_REENTRANT
548bcb
     ._dl_load_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
548bcb
     ._dl_load_write_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
548bcb
+    ._dl_load_tls_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
548bcb
 #endif
548bcb
     ._dl_nns = 1,
548bcb
     ._dl_ns =
548bcb
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
548bcb
index 0138353ccb41c5f1..7b0a667629ddc06a 100644
548bcb
--- a/sysdeps/generic/ldsodefs.h
548bcb
+++ b/sysdeps/generic/ldsodefs.h
548bcb
@@ -373,6 +373,13 @@ struct rtld_global
548bcb
      list of loaded objects while an object is added to or removed
548bcb
      from that list.  */
548bcb
   __rtld_lock_define_recursive (EXTERN, _dl_load_write_lock)
548bcb
+  /* This lock protects global and module specific TLS related data.
548bcb
+     E.g. it is held in dlopen and dlclose when GL(dl_tls_generation),
548bcb
+     GL(dl_tls_max_dtv_idx) or GL(dl_tls_dtv_slotinfo_list) are
548bcb
+     accessed and when TLS related relocations are processed for a
548bcb
+     module.  It was introduced to keep pthread_create accessing TLS
548bcb
+     state that is being set up.  */
548bcb
+  __rtld_lock_define_recursive (EXTERN, _dl_load_tls_lock)
548bcb
 
548bcb
   /* Incremented whenever something may have been added to dl_loaded.  */
548bcb
   EXTERN unsigned long long _dl_load_adds;
548bcb
@@ -1192,7 +1199,7 @@ extern int _dl_scope_free (void *) attribute_hidden;
548bcb
 
548bcb
 /* Add module to slot information data.  If DO_ADD is false, only the
548bcb
    required memory is allocated.  Must be called with GL
548bcb
-   (dl_load_lock) acquired.  If the function has already been called
548bcb
+   (dl_load_tls_lock) acquired.  If the function has already been called
548bcb
    for the link map L with !do_add, then this function will not raise
548bcb
    an exception, otherwise it is possible that it encounters a memory
548bcb
    allocation failure.  */
548bcb
diff --git a/sysdeps/nptl/fork.c b/sysdeps/nptl/fork.c
548bcb
index 37db30f3d1e846b6..b4d20fa652f4ba3b 100644
548bcb
--- a/sysdeps/nptl/fork.c
548bcb
+++ b/sysdeps/nptl/fork.c
548bcb
@@ -125,6 +125,9 @@ __libc_fork (void)
548bcb
       /* Reset the lock the dynamic loader uses to protect its data.  */
548bcb
       __rtld_lock_initialize (GL(dl_load_lock));
548bcb
 
548bcb
+      /* Reset the lock protecting dynamic TLS related data.  */
548bcb
+      __rtld_lock_initialize (GL(dl_load_tls_lock));
548bcb
+
548bcb
       /* Run the handlers registered for the child.  */
548bcb
       __run_fork_handlers (atfork_run_child, multiple_threads);
548bcb
     }
548bcb
diff --git a/sysdeps/pthread/Makefile b/sysdeps/pthread/Makefile
548bcb
index ea4f8894891b2636..98a92f8d6bb119ba 100644
548bcb
--- a/sysdeps/pthread/Makefile
548bcb
+++ b/sysdeps/pthread/Makefile
548bcb
@@ -25,3 +25,24 @@ $(objpfx)tst-timer: $(objpfx)librt.a $(static-thread-library)
548bcb
 endif
548bcb
 
548bcb
 endif
548bcb
+
548bcb
+ifneq (,$(filter $(subdir),htl nptl))
548bcb
+ifeq ($(build-shared),yes)
548bcb
+tests += tst-create1
548bcb
+endif
548bcb
+
548bcb
+tst-create1mod.so-no-z-defs = yes
548bcb
+
548bcb
+ifeq ($(build-shared),yes)
548bcb
+# Build all the modules even when not actually running test programs.
548bcb
+tests: $(test-modules)
548bcb
+endif
548bcb
+
548bcb
+modules-names += tst-create1mod
548bcb
+test-modules = $(addprefix $(objpfx),$(addsuffix .so,$(modules-names)))
548bcb
+
548bcb
+LDFLAGS-tst-create1 = -Wl,-export-dynamic
548bcb
+$(objpfx)tst-create1: $(libdl) $(shared-thread-library)
548bcb
+$(objpfx)tst-create1.out: $(objpfx)tst-create1mod.so
548bcb
+
548bcb
+endif
548bcb
diff --git a/sysdeps/pthread/tst-create1.c b/sysdeps/pthread/tst-create1.c
548bcb
new file mode 100644
548bcb
index 0000000000000000..932586c30990d1d4
548bcb
--- /dev/null
548bcb
+++ b/sysdeps/pthread/tst-create1.c
548bcb
@@ -0,0 +1,119 @@
548bcb
+/* Verify that pthread_create does not deadlock when ctors take locks.
548bcb
+   Copyright (C) 2021 Free Software Foundation, Inc.
548bcb
+   This file is part of the GNU C Library.
548bcb
+
548bcb
+   The GNU C Library is free software; you can redistribute it and/or
548bcb
+   modify it under the terms of the GNU Lesser General Public
548bcb
+   License as published by the Free Software Foundation; either
548bcb
+   version 2.1 of the License, or (at your option) any later version.
548bcb
+
548bcb
+   The GNU C Library is distributed in the hope that it will be useful,
548bcb
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
548bcb
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
548bcb
+   Lesser General Public License for more details.
548bcb
+
548bcb
+   You should have received a copy of the GNU Lesser General Public
548bcb
+   License along with the GNU C Library; if not, see
548bcb
+   <https://www.gnu.org/licenses/>.  */
548bcb
+
548bcb
+#include <stdio.h>
548bcb
+#include <support/xdlfcn.h>
548bcb
+#include <support/xthread.h>
548bcb
+
548bcb
+/*
548bcb
+Check if ctor and pthread_create deadlocks in
548bcb
+
548bcb
+thread 1: dlopen -> ctor -> lock(user_lock)
548bcb
+thread 2: lock(user_lock) -> pthread_create
548bcb
+
548bcb
+or in
548bcb
+
548bcb
+thread 1: dlclose -> dtor -> lock(user_lock)
548bcb
+thread 2: lock(user_lock) -> pthread_create
548bcb
+*/
548bcb
+
548bcb
+static pthread_barrier_t bar_ctor;
548bcb
+static pthread_barrier_t bar_dtor;
548bcb
+static pthread_mutex_t user_lock = PTHREAD_MUTEX_INITIALIZER;
548bcb
+
548bcb
+void
548bcb
+ctor (void)
548bcb
+{
548bcb
+  xpthread_barrier_wait (&bar_ctor);
548bcb
+  dprintf (1, "thread 1: in ctor: started.\n");
548bcb
+  xpthread_mutex_lock (&user_lock);
548bcb
+  dprintf (1, "thread 1: in ctor: locked user_lock.\n");
548bcb
+  xpthread_mutex_unlock (&user_lock);
548bcb
+  dprintf (1, "thread 1: in ctor: unlocked user_lock.\n");
548bcb
+  dprintf (1, "thread 1: in ctor: done.\n");
548bcb
+}
548bcb
+
548bcb
+void
548bcb
+dtor (void)
548bcb
+{
548bcb
+  xpthread_barrier_wait (&bar_dtor);
548bcb
+  dprintf (1, "thread 1: in dtor: started.\n");
548bcb
+  xpthread_mutex_lock (&user_lock);
548bcb
+  dprintf (1, "thread 1: in dtor: locked user_lock.\n");
548bcb
+  xpthread_mutex_unlock (&user_lock);
548bcb
+  dprintf (1, "thread 1: in dtor: unlocked user_lock.\n");
548bcb
+  dprintf (1, "thread 1: in dtor: done.\n");
548bcb
+}
548bcb
+
548bcb
+static void *
548bcb
+thread3 (void *a)
548bcb
+{
548bcb
+  dprintf (1, "thread 3: started.\n");
548bcb
+  dprintf (1, "thread 3: done.\n");
548bcb
+  return 0;
548bcb
+}
548bcb
+
548bcb
+static void *
548bcb
+thread2 (void *a)
548bcb
+{
548bcb
+  pthread_t t3;
548bcb
+  dprintf (1, "thread 2: started.\n");
548bcb
+
548bcb
+  xpthread_mutex_lock (&user_lock);
548bcb
+  dprintf (1, "thread 2: locked user_lock.\n");
548bcb
+  xpthread_barrier_wait (&bar_ctor);
548bcb
+  t3 = xpthread_create (0, thread3, 0);
548bcb
+  xpthread_mutex_unlock (&user_lock);
548bcb
+  dprintf (1, "thread 2: unlocked user_lock.\n");
548bcb
+  xpthread_join (t3);
548bcb
+
548bcb
+  xpthread_mutex_lock (&user_lock);
548bcb
+  dprintf (1, "thread 2: locked user_lock.\n");
548bcb
+  xpthread_barrier_wait (&bar_dtor);
548bcb
+  t3 = xpthread_create (0, thread3, 0);
548bcb
+  xpthread_mutex_unlock (&user_lock);
548bcb
+  dprintf (1, "thread 2: unlocked user_lock.\n");
548bcb
+  xpthread_join (t3);
548bcb
+
548bcb
+  dprintf (1, "thread 2: done.\n");
548bcb
+  return 0;
548bcb
+}
548bcb
+
548bcb
+static void
548bcb
+thread1 (void)
548bcb
+{
548bcb
+  dprintf (1, "thread 1: started.\n");
548bcb
+  xpthread_barrier_init (&bar_ctor, NULL, 2);
548bcb
+  xpthread_barrier_init (&bar_dtor, NULL, 2);
548bcb
+  pthread_t t2 = xpthread_create (0, thread2, 0);
548bcb
+  void *p = xdlopen ("tst-create1mod.so", RTLD_NOW | RTLD_GLOBAL);
548bcb
+  dprintf (1, "thread 1: dlopen done.\n");
548bcb
+  xdlclose (p);
548bcb
+  dprintf (1, "thread 1: dlclose done.\n");
548bcb
+  xpthread_join (t2);
548bcb
+  dprintf (1, "thread 1: done.\n");
548bcb
+}
548bcb
+
548bcb
+static int
548bcb
+do_test (void)
548bcb
+{
548bcb
+  thread1 ();
548bcb
+  return 0;
548bcb
+}
548bcb
+
548bcb
+#include <support/test-driver.c>
548bcb
diff --git a/sysdeps/pthread/tst-create1mod.c b/sysdeps/pthread/tst-create1mod.c
548bcb
new file mode 100644
548bcb
index 0000000000000000..62c9006961683177
548bcb
--- /dev/null
548bcb
+++ b/sysdeps/pthread/tst-create1mod.c
548bcb
@@ -0,0 +1,41 @@
548bcb
+/* Verify that pthread_create does not deadlock when ctors take locks.
548bcb
+   Copyright (C) 2021 Free Software Foundation, Inc.
548bcb
+   This file is part of the GNU C Library.
548bcb
+
548bcb
+   The GNU C Library is free software; you can redistribute it and/or
548bcb
+   modify it under the terms of the GNU Lesser General Public
548bcb
+   License as published by the Free Software Foundation; either
548bcb
+   version 2.1 of the License, or (at your option) any later version.
548bcb
+
548bcb
+   The GNU C Library is distributed in the hope that it will be useful,
548bcb
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
548bcb
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
548bcb
+   Lesser General Public License for more details.
548bcb
+
548bcb
+   You should have received a copy of the GNU Lesser General Public
548bcb
+   License along with the GNU C Library; if not, see
548bcb
+   <https://www.gnu.org/licenses/>.  */
548bcb
+
548bcb
+#include <stdio.h>
548bcb
+
548bcb
+/* Require TLS setup for the module.  */
548bcb
+__thread int tlsvar;
548bcb
+
548bcb
+void ctor (void);
548bcb
+void dtor (void);
548bcb
+
548bcb
+static void __attribute__ ((constructor))
548bcb
+do_init (void)
548bcb
+{
548bcb
+  dprintf (1, "constructor started: %d.\n", tlsvar++);
548bcb
+  ctor ();
548bcb
+  dprintf (1, "constructor done: %d.\n", tlsvar++);
548bcb
+}
548bcb
+
548bcb
+static void __attribute__ ((destructor))
548bcb
+do_end (void)
548bcb
+{
548bcb
+  dprintf (1, "destructor started: %d.\n", tlsvar++);
548bcb
+  dtor ();
548bcb
+  dprintf (1, "destructor done: %d.\n", tlsvar++);
548bcb
+}