Blame stdlib/cxa_finalize.c

Packit 6c4009
/* Copyright (C) 1999-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <assert.h>
Packit 6c4009
#include <stdlib.h>
Packit 6c4009
#include "exit.h"
Packit 6c4009
#include <fork.h>
Packit 6c4009
#include <sysdep.h>
Packit 6c4009
#include <stdint.h>
Packit 6c4009
Packit 6c4009
/* If D is non-NULL, call all functions registered with `__cxa_atexit'
Packit 6c4009
   with the same dso handle.  Otherwise, if D is NULL, call all of the
Packit 6c4009
   registered handlers.  */
Packit 6c4009
void
Packit 6c4009
__cxa_finalize (void *d)
Packit 6c4009
{
Packit 6c4009
  struct exit_function_list *funcs;
Packit 6c4009
Packit 6c4009
  __libc_lock_lock (__exit_funcs_lock);
Packit 6c4009
Packit 6c4009
 restart:
Packit 6c4009
  for (funcs = __exit_funcs; funcs; funcs = funcs->next)
Packit 6c4009
    {
Packit 6c4009
      struct exit_function *f;
Packit 6c4009
Packit 6c4009
      for (f = &funcs->fns[funcs->idx - 1]; f >= &funcs->fns[0]; --f)
Packit 6c4009
	if ((d == NULL || d == f->func.cxa.dso_handle) && f->flavor == ef_cxa)
Packit 6c4009
	  {
Packit 6c4009
	    const uint64_t check = __new_exitfn_called;
Packit 6c4009
	    void (*cxafn) (void *arg, int status) = f->func.cxa.fn;
Packit 6c4009
	    void *cxaarg = f->func.cxa.arg;
Packit 6c4009
Packit 6c4009
	    /* We don't want to run this cleanup more than once.  The Itanium
Packit 6c4009
	       C++ ABI requires that multiple calls to __cxa_finalize not
Packit 6c4009
	       result in calling termination functions more than once.  One
Packit 6c4009
	       potential scenario where that could happen is with a concurrent
Packit 6c4009
	       dlclose and exit, where the running dlclose must at some point
Packit 6c4009
	       release the list lock, an exiting thread may acquire it, and
Packit 6c4009
	       without setting flavor to ef_free, might re-run this destructor
Packit 6c4009
	       which could result in undefined behaviour.  Therefore we must
Packit 6c4009
	       set flavor to ef_free to avoid calling this destructor again.
Packit 6c4009
	       Note that the concurrent exit must also take the dynamic loader
Packit 6c4009
	       lock (for library finalizer processing) and therefore will
Packit 6c4009
	       block while dlclose completes the processing of any in-progress
Packit 6c4009
	       exit functions. Lastly, once we release the list lock for the
Packit 6c4009
	       entry marked ef_free, we must not read from that entry again
Packit 6c4009
	       since it may have been reused by the time we take the list lock
Packit 6c4009
	       again.  Lastly the detection of new registered exit functions is
Packit 6c4009
	       based on a monotonically incrementing counter, and there is an
Packit 6c4009
	       ABA if between the unlock to run the exit function and the
Packit 6c4009
	       re-lock after completion the user registers 2^64 exit functions,
Packit 6c4009
	       the implementation will not detect this and continue without
Packit 6c4009
	       executing any more functions.
Packit 6c4009
Packit 6c4009
	       One minor issue remains: A registered exit function that is in
Packit 6c4009
	       progress by a call to dlclose() may not completely finish before
Packit 6c4009
	       the next registered exit function is run. This may, according to
Packit 6c4009
	       some readings of POSIX violate the requirement that functions
Packit 6c4009
	       run in effective LIFO order.  This should probably be fixed in a
Packit 6c4009
	       future implementation to ensure the functions do not run in
Packit 6c4009
	       parallel.  */
Packit 6c4009
	    f->flavor = ef_free;
Packit 6c4009
Packit 6c4009
#ifdef PTR_DEMANGLE
Packit 6c4009
	    PTR_DEMANGLE (cxafn);
Packit 6c4009
#endif
Packit 6c4009
	    /* Unlock the list while we call a foreign function.  */
Packit 6c4009
	    __libc_lock_unlock (__exit_funcs_lock);
Packit 6c4009
	    cxafn (cxaarg, 0);
Packit 6c4009
	    __libc_lock_lock (__exit_funcs_lock);
Packit 6c4009
Packit 6c4009
	    /* It is possible that that last exit function registered
Packit 6c4009
	       more exit functions.  Start the loop over.  */
Packit 6c4009
	    if (__glibc_unlikely (check != __new_exitfn_called))
Packit 6c4009
	      goto restart;
Packit 6c4009
	  }
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Also remove the quick_exit handlers, but do not call them.  */
Packit 6c4009
  for (funcs = __quick_exit_funcs; funcs; funcs = funcs->next)
Packit 6c4009
    {
Packit 6c4009
      struct exit_function *f;
Packit 6c4009
Packit 6c4009
      for (f = &funcs->fns[funcs->idx - 1]; f >= &funcs->fns[0]; --f)
Packit 6c4009
	if (d == NULL || d == f->func.cxa.dso_handle)
Packit 6c4009
	  f->flavor = ef_free;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Remove the registered fork handlers.  We do not have to
Packit 6c4009
     unregister anything if the program is going to terminate anyway.  */
Packit 6c4009
#ifdef UNREGISTER_ATFORK
Packit 6c4009
  if (d != NULL)
Packit 6c4009
    UNREGISTER_ATFORK (d);
Packit 6c4009
#endif
Packit 6c4009
  __libc_lock_unlock (__exit_funcs_lock);
Packit 6c4009
}