aboutsummaryrefslogtreecommitdiff
path: root/stdlib/cxa_finalize.c
diff options
context:
space:
mode:
authorPaul Pluzhnikov <ppluzhnikov@google.com>2017-09-20 09:31:48 -0700
committerPaul Pluzhnikov <ppluzhnikov@google.com>2017-09-20 09:31:48 -0700
commit26e70aec7028feeb196744eb97cd2dff3670b7aa (patch)
tree0eb04d182ca05b4161b1cc0f6c59265da6ccb621 /stdlib/cxa_finalize.c
parent0525ce4850f2c22a235dcd3422bc92f40815f377 (diff)
downloadglibc-26e70aec7028feeb196744eb97cd2dff3670b7aa.tar
glibc-26e70aec7028feeb196744eb97cd2dff3670b7aa.tar.gz
glibc-26e70aec7028feeb196744eb97cd2dff3670b7aa.tar.bz2
glibc-26e70aec7028feeb196744eb97cd2dff3670b7aa.zip
Fix BZ 14333
Diffstat (limited to 'stdlib/cxa_finalize.c')
-rw-r--r--stdlib/cxa_finalize.c68
1 files changed, 48 insertions, 20 deletions
diff --git a/stdlib/cxa_finalize.c b/stdlib/cxa_finalize.c
index aa0a70cb58..23fc6dcafb 100644
--- a/stdlib/cxa_finalize.c
+++ b/stdlib/cxa_finalize.c
@@ -17,7 +17,6 @@
#include <assert.h>
#include <stdlib.h>
-#include <atomic.h>
#include "exit.h"
#include <fork.h>
#include <sysdep.h>
@@ -31,36 +30,64 @@ __cxa_finalize (void *d)
{
struct exit_function_list *funcs;
+ __libc_lock_lock (__exit_funcs_lock);
+
restart:
for (funcs = __exit_funcs; funcs; funcs = funcs->next)
{
struct exit_function *f;
for (f = &funcs->fns[funcs->idx - 1]; f >= &funcs->fns[0]; --f)
- {
- void (*cxafn) (void *arg, int status);
- void *cxaarg;
+ if ((d == NULL || d == f->func.cxa.dso_handle) && f->flavor == ef_cxa)
+ {
+ const uint64_t check = __new_exitfn_called;
+ void (*cxafn) (void *arg, int status) = f->func.cxa.fn;
+ void *cxaarg = f->func.cxa.arg;
+
+ /* We don't want to run this cleanup more than once. The Itanium
+ C++ ABI requires that multiple calls to __cxa_finalize not
+ result in calling termination functions more than once. One
+ potential scenario where that could happen is with a concurrent
+ dlclose and exit, where the running dlclose must at some point
+ release the list lock, an exiting thread may acquire it, and
+ without setting flavor to ef_free, might re-run this destructor
+ which could result in undefined behaviour. Therefore we must
+ set flavor to ef_free to avoid calling this destructor again.
+ Note that the concurrent exit must also take the dynamic loader
+ lock (for library finalizer processing) and therefore will
+ block while dlclose completes the processing of any in-progress
+ exit functions. Lastly, once we release the list lock for the
+ entry marked ef_free, we must not read from that entry again
+ since it may have been reused by the time we take the list lock
+ again. Lastly the detection of new registered exit functions is
+ based on a monotonically incrementing counter, and there is an
+ ABA if between the unlock to run the exit function and the
+ re-lock after completion the user registers 2^64 exit functions,
+ the implementation will not detect this and continue without
+ executing any more functions.
- if ((d == NULL || d == f->func.cxa.dso_handle)
- /* We don't want to run this cleanup more than once. */
- && (cxafn = f->func.cxa.fn,
- cxaarg = f->func.cxa.arg,
- ! catomic_compare_and_exchange_bool_acq (&f->flavor, ef_free,
- ef_cxa)))
- {
- uint64_t check = __new_exitfn_called;
+ One minor issue remains: A registered exit function that is in
+ progress by a call to dlclose() may not completely finish before
+ the next registered exit function is run. This may, according to
+ some readings of POSIX violate the requirement that functions
+ run in effective LIFO order. This should probably be fixed in a
+ future implementation to ensure the functions do not run in
+ parallel. */
+ f->flavor = ef_free;
#ifdef PTR_DEMANGLE
- PTR_DEMANGLE (cxafn);
+ PTR_DEMANGLE (cxafn);
#endif
- cxafn (cxaarg, 0);
+ /* Unlock the list while we call a foreign function. */
+ __libc_lock_unlock (__exit_funcs_lock);
+ cxafn (cxaarg, 0);
+ __libc_lock_lock (__exit_funcs_lock);
- /* It is possible that that last exit function registered
- more exit functions. Start the loop over. */
- if (__glibc_unlikely (check != __new_exitfn_called))
- goto restart;
- }
- }
+ /* It is possible that that last exit function registered
+ more exit functions. Start the loop over. */
+ if (__glibc_unlikely (check != __new_exitfn_called))
+ goto restart;
+ }
}
/* Also remove the quick_exit handlers, but do not call them. */
@@ -79,4 +106,5 @@ __cxa_finalize (void *d)
if (d != NULL)
UNREGISTER_ATFORK (d);
#endif
+ __libc_lock_unlock (__exit_funcs_lock);
}