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