diff options
-rw-r--r-- | ChangeLog | 9 | ||||
-rw-r--r-- | stdlib/Makefile | 10 | ||||
-rw-r--r-- | stdlib/exit.c | 6 | ||||
-rw-r--r-- | stdlib/test-dlclose-exit-race-helper.c | 79 | ||||
-rw-r--r-- | stdlib/test-dlclose-exit-race.c | 80 |
5 files changed, 180 insertions, 4 deletions
diff --git a/ChangeLog b/ChangeLog index 399f30d956..f7c8b910a9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2017-09-21 Paul Pluzhnikov <ppluzhnikov@google.com> + Carlos O'Donell <carlos@redhat.com> + + [BZ #22180] + * stdlib/Makefile (tests): Add test-dlclose-exit-race. + * stdlib/test-dlclose-exit-race.c: New file. + * stdlib/test-dlclose-exit-race-helper.c: New file. + * stdlib/exit.c (__run_exit_handlers): Mark slot as free. + 2017-09-21 Joseph Myers <joseph@codesourcery.com> * crypt/Banner: Remove file. diff --git a/stdlib/Makefile b/stdlib/Makefile index 2fb08342e0..0a51b7bc90 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -83,7 +83,7 @@ tests := tst-strtol tst-strtod testmb testrand testsort testdiv \ tst-getrandom tst-atexit tst-at_quick_exit \ tst-cxa_atexit tst-on_exit test-atexit-race \ test-at_quick_exit-race test-cxa_atexit-race \ - test-on_exit-race + test-on_exit-race test-dlclose-exit-race tests-internal := tst-strtod1i tst-strtod3 tst-strtod4 tst-strtod5i \ tst-tls-atexit tst-tls-atexit-nodelete @@ -98,6 +98,10 @@ LDLIBS-test-at_quick_exit-race = $(shared-thread-library) LDLIBS-test-cxa_atexit-race = $(shared-thread-library) LDLIBS-test-on_exit-race = $(shared-thread-library) +LDLIBS-test-dlclose-exit-race = $(shared-thread-library) $(libdl) +LDFLAGS-test-dlclose-exit-race = $(LDFLAGS-rdynamic) +LDLIBS-test-dlclose-exit-race-helper.so = $(libsupport) $(shared-thread-library) + ifeq ($(have-cxx-thread_local),yes) CFLAGS-tst-quick_exit.o = -std=c++11 LDLIBS-tst-quick_exit = -lstdc++ @@ -108,7 +112,7 @@ else tests-unsupported += tst-quick_exit tst-thread-quick_exit endif -modules-names = tst-tls-atexit-lib +modules-names = tst-tls-atexit-lib test-dlclose-exit-race-helper extra-test-objs += $(addsuffix .os, $(modules-names)) ifeq ($(build-shared),yes) @@ -177,6 +181,7 @@ $(objpfx)tst-strtod-nan-locale.out: $(gen-locales) $(objpfx)tst-strfmon_l.out: $(gen-locales) $(objpfx)tst-strfrom.out: $(gen-locales) $(objpfx)tst-strfrom-locale.out: $(gen-locales) +$(objpfx)test-dlclose-exit-race.out: $(objpfx)test-dlclose-exit-race-helper.so endif # Testdir has to be named stdlib and needs to be writable @@ -215,6 +220,7 @@ $(objpfx)tst-strtod6: $(libm) $(objpfx)tst-strtod-nan-locale: $(libm) tst-tls-atexit-lib.so-no-z-defs = yes +test-dlclose-exit-race-helper.so-no-z-defs = yes $(objpfx)tst-tls-atexit: $(shared-thread-library) $(libdl) $(objpfx)tst-tls-atexit.out: $(objpfx)tst-tls-atexit-lib.so diff --git a/stdlib/exit.c b/stdlib/exit.c index b74f1825f0..fec91aa175 100644 --- a/stdlib/exit.c +++ b/stdlib/exit.c @@ -69,8 +69,7 @@ __run_exit_handlers (int status, struct exit_function_list **listp, while (cur->idx > 0) { - const struct exit_function *const f = - &cur->fns[--cur->idx]; + struct exit_function *const f = &cur->fns[--cur->idx]; const uint64_t new_exitfn_called = __new_exitfn_called; /* Unlock the list while we call a foreign function. */ @@ -99,6 +98,9 @@ __run_exit_handlers (int status, struct exit_function_list **listp, atfct (); break; case ef_cxa: + /* To avoid dlclose/exit race calling cxafct twice (BZ 22180), + we must mark this function as ef_free. */ + f->flavor = ef_free; cxafct = f->func.cxa.fn; #ifdef PTR_DEMANGLE PTR_DEMANGLE (cxafct); diff --git a/stdlib/test-dlclose-exit-race-helper.c b/stdlib/test-dlclose-exit-race-helper.c new file mode 100644 index 0000000000..fa765899bc --- /dev/null +++ b/stdlib/test-dlclose-exit-race-helper.c @@ -0,0 +1,79 @@ +/* Helper for exit/dlclose race test (Bug 22180). + Copyright (C) 2017 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 <stdbool.h> +#include <stdlib.h> +#include <semaphore.h> +#include <unistd.h> +#include <support/check.h> +#include <support/xthread.h> + +/* Semaphore defined in executable to ensure we have a happens-before + between the first function starting and exit being called. */ +extern sem_t order1; + +/* Semaphore defined in executable to ensure we have a happens-before + between the second function starting and the first function returning. */ +extern sem_t order2; + +/* glibc function for registering DSO-specific exit functions. */ +extern int __cxa_atexit (void (*func) (void *), void *arg, void *dso_handle); + +/* Hidden compiler handle to this shared object. */ +extern void *__dso_handle __attribute__ ((__weak__)); + +static void +first (void *start) +{ + /* Let the exiting thread run. */ + sem_post (&order1); + + /* Wait for exiting thread to finish. */ + sem_wait (&order2); + + printf ("first\n"); +} + +static void +second (void *start) +{ + /* We may be called from different threads. + This lock protects called. */ + static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; + static bool called = false; + + xpthread_mutex_lock (&mtx); + if (called) + FAIL_EXIT1 ("second called twice!"); + + called = true; + xpthread_mutex_unlock (&mtx); + + printf ("second\n"); +} + + +__attribute__ ((constructor)) static void +constructor (void) +{ + sem_init (&order1, 0, 0); + sem_init (&order2, 0, 0); + __cxa_atexit (second, NULL, __dso_handle); + __cxa_atexit (first, NULL, __dso_handle); +} diff --git a/stdlib/test-dlclose-exit-race.c b/stdlib/test-dlclose-exit-race.c new file mode 100644 index 0000000000..9ec294e168 --- /dev/null +++ b/stdlib/test-dlclose-exit-race.c @@ -0,0 +1,80 @@ +/* Test for exit/dlclose race (Bug 22180). + Copyright (C) 2017 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/>. */ + +/* This file must be run from within a directory called "stdlib". */ + +/* This test verifies that when dlopen in one thread races against exit + in another thread, we don't call registered destructor twice. + + Expected result: + second + first + ... clean termination +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <semaphore.h> +#include <support/check.h> +#include <support/xdlfcn.h> +#include <support/xthread.h> + +/* Semaphore to ensure we have a happens-before between the first function + starting and exit being called. */ +sem_t order1; + +/* Semaphore to ensure we have a happens-before between the second function + starting and the first function returning. */ +sem_t order2; + +void * +exit_thread (void *arg) +{ + /* Wait for the dlclose to start... */ + sem_wait (&order1); + /* Then try to run the exit sequence which should call all + __cxa_atexit registered functions and in parallel with + the executing dlclose(). */ + exit (0); +} + + +void +last (void) +{ + /* Let dlclose thread proceed. */ + sem_post (&order2); +} + +int +main (void) +{ + void *dso; + pthread_t thread; + + atexit (last); + + dso = xdlopen ("$ORIGIN/test-dlclose-exit-race-helper.so", + RTLD_NOW|RTLD_GLOBAL); + thread = xpthread_create (NULL, exit_thread, NULL); + + xdlclose (dso); + xpthread_join (thread); + + FAIL_EXIT1 ("Did not terminate via exit(0) in exit_thread() as expected."); +} |