You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
8.4 KiB
224 lines
8.4 KiB
commit e400f3ccd36fe91d432cc7d45b4ccc799dece763 |
|
Author: Siddhesh Poyarekar <siddhesh@redhat.com> |
|
Date: Fri Jul 24 19:13:38 2015 +0530 |
|
|
|
Use IE model for static variables in libc.so, libpthread.so and rtld |
|
|
|
The recently introduced TLS variables in the thread-local destructor |
|
implementation (__cxa_thread_atexit_impl) used the default GD access |
|
model, resulting in a call to __tls_get_addr. This causes a deadlock |
|
with recent changes to the way TLS is initialized because DTV |
|
allocations are delayed and hence despite knowing the offset to the |
|
variable inside its TLS block, the thread has to take the global rtld |
|
lock to safely update the TLS offset. |
|
|
|
This causes deadlocks when a thread is instantiated and joined inside |
|
a destructor of a dlopen'd DSO. The correct long term fix is to |
|
somehow not take the lock, but that will need a lot deeper change set |
|
to alter the way in which the big rtld lock is used. |
|
|
|
Instead, this patch just eliminates the call to __tls_get_addr for the |
|
thread-local variables inside libc.so, libpthread.so and rtld by |
|
building all of their units with -mtls-model=initial-exec. |
|
|
|
There were concerns that the static storage for TLS is limited and |
|
hence we should not be using it. Additionally, dynamically loaded |
|
modules may result in libc.so looking for this static storage pretty |
|
late in static binaries. Both concerns are valid when using TLSDESC |
|
since that is where one may attempt to allocate a TLS block from |
|
static storage for even those variables that are not IE. They're not |
|
very strong arguments for the traditional TLS model though, since it |
|
assumes that the static storage would be used sparingly and definitely |
|
not by default. Hence, for now this would only theoretically affect |
|
ARM architectures. |
|
|
|
The impact is hence limited to statically linked binaries that dlopen |
|
modules that in turn load libc.so, all that on arm hardware. It seems |
|
like a small enough impact to justify fixing the larger problem that |
|
currently affects everything everywhere. |
|
|
|
This still does not solve the original problem completely. That is, |
|
it is still possible to deadlock on the big rtld lock with a small |
|
tweak to the test case attached to this patch. That problem is |
|
however not a regression in 2.22 and hence could be tackled as a |
|
separate project. The test case is picked up as is from Alex's patch. |
|
|
|
This change has been tested to verify that it does not cause any |
|
issues on x86_64. |
|
|
|
ChangeLog: |
|
|
|
[BZ #18457] |
|
* nptl/Makefile (tests): New test case tst-join7. |
|
(modules-names): New test case module tst-join7mod. |
|
* nptl/tst-join7.c: New file. |
|
* nptl/tst-join7mod.c: New file. |
|
* Makeconfig (tls-model): Pass -ftls-model=initial-exec for |
|
all translation units in libc.so, libpthread.so and rtld. |
|
|
|
diff --git a/nptl/Makefile b/nptl/Makefile |
|
index 140f063..aaca0a4 100644 |
|
--- a/nptl/Makefile |
|
+++ b/nptl/Makefile |
|
@@ -245,7 +245,7 @@ tests = tst-typesizes \ |
|
tst-basic7 \ |
|
tst-kill1 tst-kill2 tst-kill3 tst-kill4 tst-kill5 tst-kill6 \ |
|
tst-raise1 \ |
|
- tst-join1 tst-join2 tst-join3 tst-join4 tst-join5 tst-join6 \ |
|
+ tst-join1 tst-join2 tst-join3 tst-join4 tst-join5 tst-join6 tst-join7 \ |
|
tst-detach1 \ |
|
tst-eintr1 tst-eintr2 tst-eintr3 tst-eintr4 tst-eintr5 \ |
|
tst-tsd1 tst-tsd2 tst-tsd3 tst-tsd4 tst-tsd5 tst-tsd6 \ |
|
@@ -327,7 +327,8 @@ endif |
|
modules-names = tst-atfork2mod tst-tls3mod tst-tls4moda tst-tls4modb \ |
|
tst-tls5mod tst-tls5moda tst-tls5modb tst-tls5modc \ |
|
tst-tls5modd tst-tls5mode tst-tls5modf tst-stack4mod \ |
|
- tst-_res1mod1 tst-_res1mod2 tst-execstack-mod tst-fini1mod |
|
+ tst-_res1mod1 tst-_res1mod2 tst-execstack-mod tst-fini1mod \ |
|
+ tst-join7mod |
|
extra-test-objs += $(addsuffix .os,$(strip $(modules-names))) tst-cleanup4aux.o |
|
test-extras += $(modules-names) tst-cleanup4aux |
|
test-modules = $(addprefix $(objpfx),$(addsuffix .so,$(modules-names))) |
|
@@ -532,6 +533,11 @@ $(objpfx)tst-tls6.out: tst-tls6.sh $(objpfx)tst-tls5 \ |
|
$(rtld-installed-name) '$(test-wrapper-env)' |
|
endif |
|
|
|
+$(objpfx)tst-join7: $(libdl) $(shared-thread-library) |
|
+$(objpfx)tst-join7.out: $(objpfx)tst-join7mod.so |
|
+$(objpfx)tst-join7mod.so: $(shared-thread-library) |
|
+LDFLAGS-tst-join7mod.so = -Wl,-soname,tst-join7mod.so |
|
+ |
|
$(objpfx)tst-dlsym1: $(libdl) $(shared-thread-library) |
|
|
|
$(objpfx)tst-fini1: $(shared-thread-library) $(objpfx)tst-fini1mod.so |
|
diff --git a/nptl/tst-join7.c b/nptl/tst-join7.c |
|
new file mode 100644 |
|
index 0000000..439d0fc |
|
--- /dev/null |
|
+++ b/nptl/tst-join7.c |
|
@@ -0,0 +1,46 @@ |
|
+/* Verify that TLS access in separate thread in a dlopened library does not |
|
+ deadlock. |
|
+ Copyright (C) 2015 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 |
|
+ <http://www.gnu.org/licenses/>. */ |
|
+ |
|
+#include <dlfcn.h> |
|
+ |
|
+/* When one dynamically loads a module, which spawns a thread to perform some |
|
+ activities, it could be possible that TLS storage is accessed for the first |
|
+ time in that thread. This results in an allocation request within the |
|
+ thread, which could result in an attempt to take the rtld load_lock. This |
|
+ is a problem because it would then deadlock with the dlopen (which owns the |
|
+ lock), if the main thread is waiting for the spawned thread to exit. We can |
|
+ at least ensure that this problem does not occur due to accesses within |
|
+ libc.so, by marking TLS variables within libc.so as IE. The problem of an |
|
+ arbitrary variable being accessed and constructed within such a thread still |
|
+ exists but this test case does not verify that. */ |
|
+ |
|
+int |
|
+do_test (void) |
|
+{ |
|
+ void *f = dlopen ("tst-join7mod.so", RTLD_NOW | RTLD_GLOBAL); |
|
+ if (f) |
|
+ dlclose (f); |
|
+ else |
|
+ return 1; |
|
+ |
|
+ return 0; |
|
+} |
|
+ |
|
+#define TEST_FUNCTION do_test () |
|
+#include "../test-skeleton.c" |
|
diff --git a/nptl/tst-join7mod.c b/nptl/tst-join7mod.c |
|
new file mode 100644 |
|
index 0000000..92bb381 |
|
--- /dev/null |
|
+++ b/nptl/tst-join7mod.c |
|
@@ -0,0 +1,61 @@ |
|
+/* Verify that TLS access in separate thread in a dlopened library does not |
|
+ deadlock - the module. |
|
+ Copyright (C) 2015 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 |
|
+ <http://www.gnu.org/licenses/>. */ |
|
+ |
|
+#include <stdio.h> |
|
+#include <pthread.h> |
|
+#include <atomic.h> |
|
+ |
|
+static pthread_t th; |
|
+static int running = 1; |
|
+ |
|
+static void * |
|
+test_run (void *p) |
|
+{ |
|
+ while (atomic_load_relaxed (&running)) |
|
+ printf ("Test running\n"); |
|
+ printf ("Test finished\n"); |
|
+ return NULL; |
|
+} |
|
+ |
|
+static void __attribute__ ((constructor)) |
|
+do_init (void) |
|
+{ |
|
+ int ret = pthread_create (&th, NULL, test_run, NULL); |
|
+ |
|
+ if (ret != 0) |
|
+ { |
|
+ printf ("failed to create thread: %s (%d)\n", strerror (ret), ret); |
|
+ exit (1); |
|
+ } |
|
+} |
|
+ |
|
+static void __attribute__ ((destructor)) |
|
+do_end (void) |
|
+{ |
|
+ atomic_store_relaxed (&running, 0); |
|
+ int ret = pthread_join (th, NULL); |
|
+ |
|
+ if (ret != 0) |
|
+ { |
|
+ printf ("pthread_join: %s(%d)\n", strerror (ret), ret); |
|
+ exit (1); |
|
+ } |
|
+ |
|
+ printf ("Thread joined\n"); |
|
+} |
|
diff -pruN a/string/strerror_l.c b/string/strerror_l.c |
|
--- a/string/strerror_l.c |
|
+++ b/string/strerror_l.c |
|
@@ -23,7 +23,7 @@ |
|
#include <sys/param.h> |
|
|
|
|
|
-static __thread char *last_value; |
|
+static __thread char *last_value attribute_tls_model_ie; |
|
|
|
|
|
static const char *
|
|
|