summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>2019-06-12 10:37:11 -0300
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>2019-07-11 15:49:24 -0300
commitba11e52665a79c545a51a871397143b5f7827492 (patch)
tree3e607dbbf0bc7e994b019316a6e74114c4c105e6
parent3cd72edf63eedb3e95ae24c974e9b500dc5c9fa0 (diff)
downloadglibc-azanella/atexit-order.tar.gz
stdlib: Make atexit to not act as __cxa_atexitazanella/atexit-order
This is patch to addresses the issue brought in recent discussion regarding atexit and shared libraries [1] [2]. As indicated in the libc-alpha discussion, the issue is since atexit uses __cxa_atexit its interaction __cxa_finalize could lead to atexit handlers being executed in a different order than the expected one. The github project gives a small example that triggers it [3]. The changes I could come with changes slight the atexit semantic as described in my last email [4]. So basically the changes are: 1. Add the __atexit symbol which is linked as __cxa_finalize in static mode (so __dso_handle is correctly set). The __atexit symbol adds an ef_at exit_function entry on __exit_funcs, different than an ef_cxa one from __cxa_atexit. Old binaries would still call __cxa_atexit, so we do not actually need to add a compat symbol. 2. Make __cxa_finalize to handle ef_at as well, similar to ef_cxa. 3. Change how the internal exit handler are organized, so ef_at and ef_on handles (registered by atexit and on_exit) are executed before ef_cxa (registered by __cxa_atexit). Each entry set (struct exit_function_list) has on type associated (el_at or el_cxa) to represent the internal handle it contains. New insertions (done by the __atexit, __cxa_atexit, etc.) keep the node orders, with following constraints: 3.1. el_at nodes should be prior el_cxa. 3.2. el_at should contain only ef_at, ef_on, or ef_free elements. 3.3. el_cxa should contain only ef_cxa or ef_free elements. 3.4. new insertions on each node type should be be kept in lifo order. 3.5. the original first element should be last one (since it is static allocated and 'exit' will deallocated the nodes in order. */ So the execution on both __cxa_finalize, exit, or quick_exit will iterate over the list by executing first atexit/on_exit handlers and then __cxa_atexit ones. New handlers added by registered functions are handled as before, by using the ef_free entry and reseting the list iteration. [1] https://sourceware.org/ml/libc-alpha/2019-06/msg00229.html [2] https://sourceware.org/ml/libc-help/2019-06/msg00025.html [3] https://github.com/mulle-nat/ld-so-breakage [4] https://sourceware.org/ml/libc-alpha/2019-06/msg00231.html
-rw-r--r--dlfcn/tstatexit.c30
-rw-r--r--include/stdlib.h2
-rw-r--r--stdlib/Makefile2
-rw-r--r--stdlib/Versions3
-rw-r--r--stdlib/atexit.c5
-rw-r--r--stdlib/cxa_at_quick_exit.c6
-rw-r--r--stdlib/cxa_atexit.c114
-rw-r--r--stdlib/cxa_finalize.c131
-rw-r--r--stdlib/exit.c9
-rw-r--r--stdlib/exit.h33
-rw-r--r--stdlib/exitfn.c196
-rw-r--r--stdlib/old_atexit.c10
-rw-r--r--stdlib/on_exit.c24
-rw-r--r--sysdeps/unix/sysv/linux/i386/libc.abilist1
-rw-r--r--sysdeps/unix/sysv/linux/x86_64/64/libc.abilist1
15 files changed, 332 insertions, 235 deletions
diff --git a/dlfcn/tstatexit.c b/dlfcn/tstatexit.c
index c8cb272ce0..1f6037cb47 100644
--- a/dlfcn/tstatexit.c
+++ b/dlfcn/tstatexit.c
@@ -19,6 +19,8 @@
#include <stdio.h>
#include <stdlib.h>
+#include <support/xdlfcn.h>
+#include <support/check.h>
int
main (void)
@@ -28,35 +30,15 @@ main (void)
void (*fp) (void *);
int v = 0;
- h = dlopen (fname, RTLD_NOW);
- if (h == NULL)
- {
- printf ("cannot open \"%s\": %s\n", fname, dlerror ());
- exit (1);
- }
+ h = xdlopen (fname, RTLD_NOW);
- fp = dlsym (h, "foo");
- if (fp == NULL)
- {
- printf ("cannot find \"foo\": %s\n", dlerror ());
- exit (1);
- }
+ fp = xdlsym (h, "foo");
fp (&v);
- if (dlclose (h) != 0)
- {
- printf ("cannot close \"%s\": %s\n", fname, dlerror ());
- exit (1);
- }
+ xdlclose (h);
- if (v != 1)
- {
- puts ("module unload didn't change `v'");
- exit (1);
- }
-
- puts ("finishing now");
+ TEST_COMPARE (v, 1);
return 0;
}
diff --git a/include/stdlib.h b/include/stdlib.h
index 114e12d255..9489afd39f 100644
--- a/include/stdlib.h
+++ b/include/stdlib.h
@@ -103,6 +103,8 @@ extern int __on_exit (void (*__func) (int __status, void *__arg), void *__arg);
extern int __cxa_atexit (void (*func) (void *), void *arg, void *d);
libc_hidden_proto (__cxa_atexit);
+extern int __atexit (void (*func) (void), void *);
+
extern int __cxa_thread_atexit_impl (void (*func) (void *), void *arg,
void *d);
extern void __call_tls_dtors (void)
diff --git a/stdlib/Makefile b/stdlib/Makefile
index fe5b39a9b9..4a9d62fccc 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -36,7 +36,7 @@ routines := \
abort \
bsearch qsort msort \
getenv putenv setenv secure-getenv \
- exit on_exit atexit cxa_atexit cxa_finalize old_atexit \
+ exit on_exit atexit cxa_atexit cxa_finalize old_atexit exitfn \
quick_exit at_quick_exit cxa_at_quick_exit cxa_thread_atexit_impl \
abs labs llabs \
div ldiv lldiv \
diff --git a/stdlib/Versions b/stdlib/Versions
index 9e665d4c26..14cf15012b 100644
--- a/stdlib/Versions
+++ b/stdlib/Versions
@@ -136,6 +136,9 @@ libc {
strtof32; strtof64; strtof32x;
strtof32_l; strtof64_l; strtof32x_l;
}
+ GLIBC_2.29 {
+ __atexit;
+ }
GLIBC_PRIVATE {
# functions which have an additional interface since they are
# are cancelable.
diff --git a/stdlib/atexit.c b/stdlib/atexit.c
index 5671a43926..03b0b4b46d 100644
--- a/stdlib/atexit.c
+++ b/stdlib/atexit.c
@@ -32,9 +32,8 @@
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
-#include <stdlib.h>
+#include <exit.h>
#include <dso_handle.h>
-#include "exit.h"
/* Register FUNC to be executed by `exit'. */
int
@@ -43,5 +42,5 @@ attribute_hidden
#endif
atexit (void (*func) (void))
{
- return __cxa_atexit ((void (*) (void *)) func, NULL, __dso_handle);
+ return __atexit (func, __dso_handle);
}
diff --git a/stdlib/cxa_at_quick_exit.c b/stdlib/cxa_at_quick_exit.c
index ce3cfc5b51..32acbd5e35 100644
--- a/stdlib/cxa_at_quick_exit.c
+++ b/stdlib/cxa_at_quick_exit.c
@@ -16,8 +16,8 @@
<http://www.gnu.org/licenses/>. */
#include <stdlib.h>
-#include "exit.h"
-
+#include <exit.h>
+#include <assert.h>
static struct exit_function_list initial_quick;
struct exit_function_list *__quick_exit_funcs = &initial_quick;
@@ -26,5 +26,5 @@ struct exit_function_list *__quick_exit_funcs = &initial_quick;
int
__cxa_at_quick_exit (void (*func) (void *), void *d)
{
- return __internal_atexit (func, NULL, d, &__quick_exit_funcs);
+ return __new_exitfn (&__quick_exit_funcs, ef_cxa, func, NULL, d);
}
diff --git a/stdlib/cxa_atexit.c b/stdlib/cxa_atexit.c
index 5a39778959..01f5474b03 100644
--- a/stdlib/cxa_atexit.c
+++ b/stdlib/cxa_atexit.c
@@ -20,46 +20,11 @@
#include <stdint.h>
#include <libc-lock.h>
-#include "exit.h"
+#include <exit.h>
#include <sysdep.h>
-#undef __cxa_atexit
-
-/* See concurrency notes in stdlib/exit.h where this lock is declared. */
-__libc_lock_define_initialized (, __exit_funcs_lock)
-
-
-int
-attribute_hidden
-__internal_atexit (void (*func) (void *), void *arg, void *d,
- struct exit_function_list **listp)
-{
- struct exit_function *new;
-
- /* As a QoI issue we detect NULL early with an assertion instead
- of a SIGSEGV at program exit when the handler is run (bug 20544). */
- assert (func != NULL);
-
- __libc_lock_lock (__exit_funcs_lock);
- new = __new_exitfn (listp);
-
- if (new == NULL)
- {
- __libc_lock_unlock (__exit_funcs_lock);
- return -1;
- }
-
-#ifdef PTR_MANGLE
- PTR_MANGLE (func);
-#endif
- new->func.cxa.fn = (void (*) (void *, int)) func;
- new->func.cxa.arg = arg;
- new->func.cxa.dso_handle = d;
- new->flavor = ef_cxa;
- __libc_lock_unlock (__exit_funcs_lock);
- return 0;
-}
-
+static struct exit_function_list initial;
+struct exit_function_list *__exit_funcs = &initial;
/* Register a function to be called by exit or when a shared library
is unloaded. This function is only called from code generated by
@@ -67,77 +32,12 @@ __internal_atexit (void (*func) (void *), void *arg, void *d,
int
__cxa_atexit (void (*func) (void *), void *arg, void *d)
{
- return __internal_atexit (func, arg, d, &__exit_funcs);
+ return __new_exitfn (&__exit_funcs, ef_cxa, func, arg, d);
}
libc_hidden_def (__cxa_atexit)
-
-static struct exit_function_list initial;
-struct exit_function_list *__exit_funcs = &initial;
-uint64_t __new_exitfn_called;
-
-/* Must be called with __exit_funcs_lock held. */
-struct exit_function *
-__new_exitfn (struct exit_function_list **listp)
+int
+__atexit (void (*func) (void), void *d)
{
- struct exit_function_list *p = NULL;
- struct exit_function_list *l;
- struct exit_function *r = NULL;
- size_t i = 0;
-
- if (__exit_funcs_done)
- /* Exit code is finished processing all registered exit functions,
- therefore we fail this registration. */
- return NULL;
-
- for (l = *listp; l != NULL; p = l, l = l->next)
- {
- for (i = l->idx; i > 0; --i)
- if (l->fns[i - 1].flavor != ef_free)
- break;
-
- if (i > 0)
- break;
-
- /* This block is completely unused. */
- l->idx = 0;
- }
-
- if (l == NULL || i == sizeof (l->fns) / sizeof (l->fns[0]))
- {
- /* The last entry in a block is used. Use the first entry in
- the previous block if it exists. Otherwise create a new one. */
- if (p == NULL)
- {
- assert (l != NULL);
- p = (struct exit_function_list *)
- calloc (1, sizeof (struct exit_function_list));
- if (p != NULL)
- {
- p->next = *listp;
- *listp = p;
- }
- }
-
- if (p != NULL)
- {
- r = &p->fns[0];
- p->idx = 1;
- }
- }
- else
- {
- /* There is more room in the block. */
- r = &l->fns[i];
- l->idx = i + 1;
- }
-
- /* Mark entry as used, but we don't know the flavor now. */
- if (r != NULL)
- {
- r->flavor = ef_us;
- ++__new_exitfn_called;
- }
-
- return r;
+ return __new_exitfn (&__exit_funcs, ef_at, func, NULL, d);
}
diff --git a/stdlib/cxa_finalize.c b/stdlib/cxa_finalize.c
index e31b23467c..670d1a8e63 100644
--- a/stdlib/cxa_finalize.c
+++ b/stdlib/cxa_finalize.c
@@ -22,6 +22,77 @@
#include <sysdep.h>
#include <stdint.h>
+static bool
+handle_ef (struct exit_function *f)
+{
+ /* 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.
+
+ 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. */
+ const uint64_t check = __new_exitfn_called;
+
+ __libc_lock_unlock (__exit_funcs_lock);
+ switch (f->flavor)
+ {
+ void (*atfct) (void);
+ void (*cxafct) (void *arg, int status);
+
+ case ef_free:
+ case ef_on:
+ break;
+
+ case ef_at:
+ f->flavor = ef_free;
+ atfct = f->func.at.fn;
+#ifdef PTR_DEMANGLE
+ PTR_DEMANGLE (atfct);
+#endif
+ atfct ();
+ break;
+
+ case ef_cxa:
+ f->flavor = ef_free;
+ cxafct = f->func.cxa.fn;
+#ifdef PTR_DEMANGLE
+ PTR_DEMANGLE (cxafct);
+#endif
+ cxafct (f->func.cxa.arg, 0);
+ break;
+ }
+ __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))
+ return true;
+ return false;
+}
+
/* If D is non-NULL, call all functions registered with `__cxa_atexit'
with the same dso handle. Otherwise, if D is NULL, call all of the
registered handlers. */
@@ -35,59 +106,13 @@ __cxa_finalize (void *d)
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)
- 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.
-
- 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);
-#endif
- /* 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))
+ for (struct exit_function *f = &funcs->fns[funcs->idx - 1];
+ f >= &funcs->fns[0]; --f)
+ {
+ if ((d == NULL || d == f->dso_handle))
+ if (handle_ef (f))
goto restart;
- }
+ }
}
/* Also remove the quick_exit handlers, but do not call them. */
@@ -96,7 +121,7 @@ __cxa_finalize (void *d)
struct exit_function *f;
for (f = &funcs->fns[funcs->idx - 1]; f >= &funcs->fns[0]; --f)
- if (d == NULL || d == f->func.cxa.dso_handle)
+ if (d == NULL || d == f->dso_handle)
f->flavor = ef_free;
}
diff --git a/stdlib/exit.c b/stdlib/exit.c
index 49d12d9952..b194c158dd 100644
--- a/stdlib/exit.c
+++ b/stdlib/exit.c
@@ -25,10 +25,6 @@
#include "set-hooks.h"
DEFINE_HOOK (__libc_atexit, (void))
-/* Initialize the flag that indicates exit function processing
- is complete. See concurrency notes in stdlib/exit.h where
- __exit_funcs_lock is declared. */
-bool __exit_funcs_done = false;
/* Call all functions registered with `atexit' and `on_exit',
in the reverse of the order in which they were registered
@@ -81,9 +77,9 @@ __run_exit_handlers (int status, struct exit_function_list **listp,
void (*cxafct) (void *arg, int status);
case ef_free:
- case ef_us:
break;
case ef_on:
+ f->flavor = ef_free;
onfct = f->func.on.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (onfct);
@@ -91,7 +87,8 @@ __run_exit_handlers (int status, struct exit_function_list **listp,
onfct (status, f->func.on.arg);
break;
case ef_at:
- atfct = f->func.at;
+ f->flavor = ef_free;
+ atfct = f->func.at.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (atfct);
#endif
diff --git a/stdlib/exit.h b/stdlib/exit.h
index 6fab49c94f..aac3f4f292 100644
--- a/stdlib/exit.h
+++ b/stdlib/exit.h
@@ -20,12 +20,12 @@
#include <stdbool.h>
#include <stdint.h>
+#include <assert.h>
#include <libc-lock.h>
-enum
+enum ef_flavor
{
- ef_free, /* `ef_free' MUST be zero! */
- ef_us,
+ ef_free = 0, /* 'ef_free' MUST be zero! */
ef_on,
ef_at,
ef_cxa
@@ -35,10 +35,13 @@ struct exit_function
{
/* `flavour' should be of type of the `enum' above but since we need
this element in an atomic operation we have to use `long int'. */
- long int flavor;
+ enum ef_flavor flavor;
union
{
- void (*at) (void);
+ struct
+ {
+ void (*fn) (void);
+ } at;
struct
{
void (*fn) (int status, void *arg);
@@ -48,12 +51,21 @@ struct exit_function
{
void (*fn) (void *arg, int status);
void *arg;
- void *dso_handle;
} cxa;
} func;
+ void *dso_handle;
};
+
+enum el_type
+{
+ el_none = 0,
+ el_at,
+ el_cxa
+};
+
struct exit_function_list
{
+ enum el_type type;
struct exit_function_list *next;
size_t idx;
struct exit_function fns[32];
@@ -76,18 +88,15 @@ extern bool __exit_funcs_done attribute_hidden;
lock. */
__libc_lock_define (extern, __exit_funcs_lock);
-
-extern struct exit_function *__new_exitfn (struct exit_function_list **listp)
- attribute_hidden;
+extern int __new_exitfn (struct exit_function_list **listp,
+ enum ef_flavor flavor, void *func, void *arg,
+ void *dso_handle) attribute_hidden;
extern void __run_exit_handlers (int status,
struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
attribute_hidden __attribute__ ((__noreturn__));
-extern int __internal_atexit (void (*func) (void *), void *arg, void *d,
- struct exit_function_list **listp)
- attribute_hidden;
extern int __cxa_at_quick_exit (void (*func) (void *), void *d);
diff --git a/stdlib/exitfn.c b/stdlib/exitfn.c
new file mode 100644
index 0000000000..fec080be29
--- /dev/null
+++ b/stdlib/exitfn.c
@@ -0,0 +1,196 @@
+/* Internal function to manipulate exit handler lists.
+ Copyright (C) 2019 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 <assert.h>
+#include <exit.h>
+#include <array_length.h>
+
+/* Declared at stdlib/exit.h. */
+__libc_lock_define_initialized (, __exit_funcs_lock)
+/* Updated each time a new entry is added on any list. */
+uint64_t __new_exitfn_called;
+/* Set to indicate exit function processing is complete. */
+bool __exit_funcs_done = false;
+
+/* Allocate a new entry of flavor FLAVOR on list LISTP. The list must be kept
+ in order with following constraints:
+
+ 1. el_at nodes should be prior el_cxa.
+ 2. el_at should contain only ef_at, ef_on, or ef_free elements.
+ 3. el_cxa should contain only ef_cxa or ef_free elements.
+ 4. new insertions on each node type should be be kept in lifo order.
+ 5. the original first element should be last one (since it is static allocated
+ and 'exit' will deallocated the nodes in order. */
+static struct exit_function *
+__new_exitfn_entry (struct exit_function_list **listp, enum ef_flavor flavor)
+{
+ enum el_type type = flavor == ef_cxa ? el_cxa : el_at;
+
+ struct exit_function_list *pp = NULL;
+ struct exit_function_list *p = NULL;
+ struct exit_function_list *l;
+ struct exit_function_list *n;
+ struct exit_function *r = NULL;
+ size_t i = 0;
+
+ if (__exit_funcs_done)
+ /* Exit code is finished processing all registered exit functions,
+ therefore we fail this registration. */
+ return NULL;
+
+ /* We keep track of currentl 'l', previous 'p', and before previous 'pp'
+ nodes. */
+ for (l = *listp; l != NULL;
+ pp = (p != NULL && l != NULL) ? p : pp, p = l, l = l->next)
+ {
+ if (l->type != el_none && l->type != type)
+ continue;
+
+ for (i = l->idx; i > 0; --i)
+ if (l->fns[i - 1].flavor != ef_free)
+ break;
+
+ if (i > 0)
+ break;
+
+ /* This block is completely unused. */
+ l->idx = 0;
+ l->type = type;
+ }
+
+ if (l == NULL || i == array_length (l->fns))
+ {
+ /* The last entry in a block is used. Use the first entry in the
+ previous block if it exists. Otherwise create a new one. */
+ if (p == NULL || p->type != type)
+ {
+ n = calloc (1, sizeof (struct exit_function_list));
+ if (n == NULL)
+ return NULL;
+
+ /* The el_cxa nodes should be after el_at one in the list. */
+ if (type == el_cxa)
+ {
+ /* It is the first element in the list, set the sentinel
+ pointer. */
+ if (p == NULL)
+ {
+ n->next = *listp;
+ *listp = n;
+ p = n;
+ }
+ /* There is already an set of current type, add the new one
+ at the front. */
+ else if (l && l->type == type)
+ {
+ p->next = n;
+ n->next = l;
+ p = n;
+ }
+ /* There is already an set, but with different type, and it is
+ the sentinel set. We need to keep as the last one, since it
+ is static allocated and 'exit' deallocate all elements
+ except the last one. */
+ else
+ {
+ /* It copies the contents of the old element over the newly
+ allocated one, and uses it the new set. */
+ *n = *p;
+ /* Clear the initial element or its prior contents. */
+ memset (p, 0, sizeof (struct exit_function_list));
+
+ n->next = p;
+
+ /* Sets the sentinel points or update the list. */
+ if (pp != NULL)
+ pp->next = n;
+ else
+ *listp = n;
+ }
+ }
+ /* el_at blocks are always on before el_cxa, so just adds it on the
+ list. */
+ else
+ {
+ n->next = *listp;
+ *listp = n;
+ p = n;
+ }
+ }
+
+ r = &p->fns[0];
+ p->idx = 1;
+ p->type = type;
+ }
+ else
+ {
+ /* There is more room in the block. */
+ r = &l->fns[i];
+ l->idx = i + 1;
+ }
+
+ ++__new_exitfn_called;
+
+ return r;
+}
+
+int __new_exitfn (struct exit_function_list **listp, enum ef_flavor flavor,
+ void *func, void *arg, void *dso_handle)
+{
+ struct exit_function *new;
+
+ /* As a QoI issue we detect NULL early with an assertion instead of a
+ SIGSEGV at program exit when the handler is run (bug 20544). */
+ assert (func != NULL);
+
+ __libc_lock_lock (__exit_funcs_lock);
+
+ new = __new_exitfn_entry (listp, flavor);
+ if (new == NULL)
+ {
+ __libc_lock_unlock (__exit_funcs_lock);
+ return -1;
+ }
+
+#ifdef PTR_MANGLE
+ PTR_MANGLE (func);
+#endif
+
+ new->flavor = flavor;
+ switch (flavor)
+ {
+ case ef_on:
+ new->func.on.fn = func;
+ new->func.on.arg = arg;
+ break;
+ case ef_at:
+ new->func.at.fn = func;
+ break;
+ case ef_cxa:
+ new->func.cxa.fn = func;
+ new->func.cxa.arg = arg;
+ break;
+ default:
+ assert (!"invalid flavor");
+ }
+ new->dso_handle = dso_handle;
+
+ __libc_lock_unlock (__exit_funcs_lock);
+ return 0;
+}
diff --git a/stdlib/old_atexit.c b/stdlib/old_atexit.c
index 44bbd699bd..cb701143b0 100644
--- a/stdlib/old_atexit.c
+++ b/stdlib/old_atexit.c
@@ -1,8 +1,12 @@
+#include <exit.h>
+#include <dso_handle.h>
#include <shlib-compat.h>
#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_2_2)
-# define atexit attribute_compat_text_section __dyn_atexit
-# include "atexit.c"
-# undef atexit
+int attribute_compat_text_section
+__dyn_atexit (void (*func) (void))
+{
+ return __cxa_atexit ((void (*) (void *)) func, NULL, __dso_handle);
+}
compat_symbol (libc, __dyn_atexit, atexit, GLIBC_2_0);
#endif
diff --git a/stdlib/on_exit.c b/stdlib/on_exit.c
index 59e249f390..81f9b5ad4b 100644
--- a/stdlib/on_exit.c
+++ b/stdlib/on_exit.c
@@ -24,28 +24,6 @@
int
__on_exit (void (*func) (int status, void *arg), void *arg)
{
- struct exit_function *new;
-
- /* As a QoI issue we detect NULL early with an assertion instead
- of a SIGSEGV at program exit when the handler is run (bug 20544). */
- assert (func != NULL);
-
- __libc_lock_lock (__exit_funcs_lock);
- new = __new_exitfn (&__exit_funcs);
-
- if (new == NULL)
- {
- __libc_lock_unlock (__exit_funcs_lock);
- return -1;
- }
-
-#ifdef PTR_MANGLE
- PTR_MANGLE (func);
-#endif
- new->func.on.fn = func;
- new->func.on.arg = arg;
- new->flavor = ef_on;
- __libc_lock_unlock (__exit_funcs_lock);
- return 0;
+ return __new_exitfn (&__exit_funcs, ef_on, func, arg, NULL);
}
weak_alias (__on_exit, on_exit)
diff --git a/sysdeps/unix/sysv/linux/i386/libc.abilist b/sysdeps/unix/sysv/linux/i386/libc.abilist
index edeaf8e722..2aab737219 100644
--- a/sysdeps/unix/sysv/linux/i386/libc.abilist
+++ b/sysdeps/unix/sysv/linux/i386/libc.abilist
@@ -2045,6 +2045,7 @@ GLIBC_2.28 thrd_current F
GLIBC_2.28 thrd_equal F
GLIBC_2.28 thrd_sleep F
GLIBC_2.28 thrd_yield F
+GLIBC_2.29 __atexit F
GLIBC_2.29 getcpu F
GLIBC_2.29 posix_spawn_file_actions_addchdir_np F
GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
diff --git a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
index 59f85d9373..699fd105de 100644
--- a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
@@ -1895,6 +1895,7 @@ GLIBC_2.28 thrd_current F
GLIBC_2.28 thrd_equal F
GLIBC_2.28 thrd_sleep F
GLIBC_2.28 thrd_yield F
+GLIBC_2.29 __atexit F
GLIBC_2.29 getcpu F
GLIBC_2.29 posix_spawn_file_actions_addchdir_np F
GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F