mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-23 13:30:06 +00:00
c6af8a9a3c
As for exit, also allows concurrent quick_exit to avoid race conditions when it is called concurrently. Since it uses the same internal function as exit, the __exit_lock lock is moved to __run_exit_handlers. It also solved a potential concurrent when calling exit and quick_exit concurrently. The test case 'expected' is expanded to a value larger than the minimum required by C/POSIX (32 entries) so at_quick_exit() will require libc to allocate a new block. This makes the test mre likely to trigger concurrent issues (through free() at __run_exit_handlers) if quick_exit() interacts with the at_quick_exit list concurrently. This is also the latest interpretation of the Austin Ticket [1]. Checked on x86_64-linux-gnu. [1] https://austingroupbugs.net/view.php?id=1845 Reviewed-by: Carlos O'Donell <carlos@redhat.com>
151 lines
4.5 KiB
C
151 lines
4.5 KiB
C
/* Copyright (C) 1991-2024 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
|
|
<https://www.gnu.org/licenses/>. */
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <pointer_guard.h>
|
|
#include <libc-lock.h>
|
|
#include <set-freeres.h>
|
|
#include "exit.h"
|
|
|
|
/* 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;
|
|
|
|
/* The lock handles concurrent exit() and quick_exit(), even though the
|
|
C/POSIX standard states that calling exit() more than once is UB. The
|
|
recursive lock allows atexit() handlers or destructors to call exit()
|
|
itself. In this case, the handler list execution will resume at the
|
|
point of the current handler. */
|
|
__libc_lock_define_initialized_recursive (static, __exit_lock)
|
|
|
|
/* Call all functions registered with `atexit' and `on_exit',
|
|
in the reverse of the order in which they were registered
|
|
perform stdio cleanup, and terminate program execution with STATUS. */
|
|
void
|
|
attribute_hidden
|
|
__run_exit_handlers (int status, struct exit_function_list **listp,
|
|
bool run_list_atexit, bool run_dtors)
|
|
{
|
|
/* The exit should never return, so there is no need to unlock it. */
|
|
__libc_lock_lock_recursive (__exit_lock);
|
|
|
|
/* First, call the TLS destructors. */
|
|
if (run_dtors)
|
|
call_function_static_weak (__call_tls_dtors);
|
|
|
|
__libc_lock_lock (__exit_funcs_lock);
|
|
|
|
/* We do it this way to handle recursive calls to exit () made by
|
|
the functions registered with `atexit' and `on_exit'. We call
|
|
everyone on the list and use the status value in the last
|
|
exit (). */
|
|
while (true)
|
|
{
|
|
struct exit_function_list *cur;
|
|
|
|
restart:
|
|
cur = *listp;
|
|
|
|
if (cur == NULL)
|
|
{
|
|
/* Exit processing complete. We will not allow any more
|
|
atexit/on_exit registrations. */
|
|
__exit_funcs_done = true;
|
|
break;
|
|
}
|
|
|
|
while (cur->idx > 0)
|
|
{
|
|
struct exit_function *const f = &cur->fns[--cur->idx];
|
|
const uint64_t new_exitfn_called = __new_exitfn_called;
|
|
|
|
switch (f->flavor)
|
|
{
|
|
void (*atfct) (void);
|
|
void (*onfct) (int status, void *arg);
|
|
void (*cxafct) (void *arg, int status);
|
|
void *arg;
|
|
|
|
case ef_free:
|
|
case ef_us:
|
|
break;
|
|
case ef_on:
|
|
onfct = f->func.on.fn;
|
|
arg = f->func.on.arg;
|
|
PTR_DEMANGLE (onfct);
|
|
|
|
/* Unlock the list while we call a foreign function. */
|
|
__libc_lock_unlock (__exit_funcs_lock);
|
|
onfct (status, arg);
|
|
__libc_lock_lock (__exit_funcs_lock);
|
|
break;
|
|
case ef_at:
|
|
atfct = f->func.at;
|
|
PTR_DEMANGLE (atfct);
|
|
|
|
/* Unlock the list while we call a foreign function. */
|
|
__libc_lock_unlock (__exit_funcs_lock);
|
|
atfct ();
|
|
__libc_lock_lock (__exit_funcs_lock);
|
|
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;
|
|
arg = f->func.cxa.arg;
|
|
PTR_DEMANGLE (cxafct);
|
|
|
|
/* Unlock the list while we call a foreign function. */
|
|
__libc_lock_unlock (__exit_funcs_lock);
|
|
cxafct (arg, status);
|
|
__libc_lock_lock (__exit_funcs_lock);
|
|
break;
|
|
}
|
|
|
|
if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
|
|
/* The last exit function, or another thread, has registered
|
|
more exit functions. Start the loop over. */
|
|
goto restart;
|
|
}
|
|
|
|
*listp = cur->next;
|
|
if (*listp != NULL)
|
|
/* Don't free the last element in the chain, this is the statically
|
|
allocate element. */
|
|
free (cur);
|
|
}
|
|
|
|
__libc_lock_unlock (__exit_funcs_lock);
|
|
|
|
if (run_list_atexit)
|
|
call_function_static_weak (_IO_cleanup);
|
|
|
|
_exit (status);
|
|
}
|
|
|
|
|
|
void
|
|
exit (int status)
|
|
{
|
|
__run_exit_handlers (status, &__exit_funcs, true, true);
|
|
}
|
|
libc_hidden_def (exit)
|