[libc] Add __cxa_atexit support to the atexit function.

Reviewed By: abrachet

Differential Revision: https://reviews.llvm.org/D131219
This commit is contained in:
Siva Chandra Reddy 2022-08-02 07:14:12 +00:00
parent daa44a2309
commit c6dcc359ac
9 changed files with 110 additions and 25 deletions

View File

@ -422,8 +422,15 @@ function(add_integration_test test_name)
get_fq_deps_list(fq_deps_list ${INTEGRATION_TEST_DEPENDS})
# All integration tests setup TLS area and the main thread's self object.
# So, we need to link in the threads implementation.
list(APPEND fq_deps_list libc.src.__support.threads.thread)
# So, we need to link in the threads implementation. Likewise, the startup
# code also has to run init_array callbacks which potentially register
# their own atexit callbacks. So, link in exit and atexit also with all
# integration tests.
list(
APPEND fq_deps_list
libc.src.__support.threads.thread
libc.src.stdlib.atexit
libc.src.stdlib.exit)
list(REMOVE_DUPLICATES fq_deps_list)
# TODO: Instead of gathering internal object files from entrypoints,
# collect the object files with public names of entrypoints.

View File

@ -7,6 +7,8 @@ add_loader_object(
libc.include.sys_mman
libc.include.sys_syscall
libc.src.__support.OSUtil.osutil
libc.src.stdlib.exit
libc.src.stdlib.atexit
libc.src.string.memory_utils.memcpy_implementation
COMPILE_OPTIONS
-fno-omit-frame-pointer

View File

@ -9,6 +9,8 @@
#include "config/linux/app.h"
#include "src/__support/OSUtil/syscall.h"
#include "src/__support/threads/thread.h"
#include "src/stdlib/atexit.h"
#include "src/stdlib/exit.h"
#include "src/string/memory_utils/memcpy_implementations.h"
#include <arm_acle.h>
@ -116,8 +118,8 @@ static void call_init_array_callbacks(int argc, char **argv, char **env) {
static void call_fini_array_callbacks() {
size_t fini_array_size = __fini_array_end - __fini_array_start;
for (size_t i = 0; i < fini_array_size; ++i)
reinterpret_cast<FiniCallback *>(__fini_array_start[i])();
for (size_t i = fini_array_size; i > 0; --i)
reinterpret_cast<FiniCallback *>(__fini_array_start[i - 1])();
}
} // namespace __llvm_libc
@ -185,6 +187,12 @@ __attribute__((noinline)) static void do_start() {
__llvm_libc::self.attrib = &__llvm_libc::main_thread_attrib;
// We want the fini array callbacks to be run after other atexit
// callbacks are run. So, we register them before running the init
// array callbacks as they can potentially register their own atexit
// callbacks.
__llvm_libc::atexit(&__llvm_libc::call_fini_array_callbacks);
__llvm_libc::call_init_array_callbacks(
app.args->argc, reinterpret_cast<char **>(app.args->argv),
reinterpret_cast<char **>(env_ptr));
@ -192,10 +200,11 @@ __attribute__((noinline)) static void do_start() {
int retval = main(app.args->argc, reinterpret_cast<char **>(app.args->argv),
reinterpret_cast<char **>(env_ptr));
__llvm_libc::call_fini_array_callbacks();
// TODO: TLS cleanup should be done after all other atexit callbacks
// are run. So, register a cleanup callback for it with atexit before
// everything else.
__llvm_libc::cleanup_tls(tls.addr, tls.size);
__llvm_libc::syscall(SYS_exit, retval);
__llvm_libc::exit(retval);
}
extern "C" void _start() {

View File

@ -8,6 +8,8 @@ add_loader_object(
libc.include.sys_syscall
libc.src.__support.threads.thread
libc.src.__support.OSUtil.osutil
libc.src.stdlib.exit
libc.src.stdlib.atexit
libc.src.string.memory_utils.memcpy_implementation
COMPILE_OPTIONS
-fno-omit-frame-pointer

View File

@ -9,6 +9,8 @@
#include "config/linux/app.h"
#include "src/__support/OSUtil/syscall.h"
#include "src/__support/threads/thread.h"
#include "src/stdlib/atexit.h"
#include "src/stdlib/exit.h"
#include "src/string/memory_utils/memcpy_implementations.h"
#include <asm/prctl.h>
@ -115,8 +117,8 @@ static void call_init_array_callbacks(int argc, char **argv, char **env) {
static void call_fini_array_callbacks() {
size_t fini_array_size = __fini_array_end - __fini_array_start;
for (size_t i = 0; i < fini_array_size; ++i)
reinterpret_cast<FiniCallback *>(__fini_array_start[i])();
for (size_t i = fini_array_size; i > 0; --i)
reinterpret_cast<FiniCallback *>(__fini_array_start[i - 1])();
}
} // namespace __llvm_libc
@ -204,6 +206,12 @@ extern "C" void _start() {
__llvm_libc::self.attrib = &__llvm_libc::main_thread_attrib;
// We want the fini array callbacks to be run after other atexit
// callbacks are run. So, we register them before running the init
// array callbacks as they can potentially register their own atexit
// callbacks.
__llvm_libc::atexit(&__llvm_libc::call_fini_array_callbacks);
__llvm_libc::call_init_array_callbacks(
app.args->argc, reinterpret_cast<char **>(app.args->argv),
reinterpret_cast<char **>(env_ptr));
@ -211,8 +219,9 @@ extern "C" void _start() {
int retval = main(app.args->argc, reinterpret_cast<char **>(app.args->argv),
reinterpret_cast<char **>(env_ptr));
__llvm_libc::call_fini_array_callbacks();
// TODO: TLS cleanup should be done after all other atexit callbacks
// are run. So, register a cleanup callback for it with atexit before
// everything else.
__llvm_libc::cleanup_tls(tls.addr, tls.size);
__llvm_libc::syscall(SYS_exit, retval);
__llvm_libc::exit(retval);
}

View File

@ -286,6 +286,7 @@ add_entrypoint_object(
CXX_STANDARD
20 # For constinit of the atexit callback list.
DEPENDS
libc.src.__support.fixedvector
libc.src.__support.CPP.blockstore
libc.src.__support.threads.mutex
)

View File

@ -9,6 +9,7 @@
#include "src/stdlib/atexit.h"
#include "src/__support/CPP/blockstore.h"
#include "src/__support/common.h"
#include "src/__support/fixedvector.h"
#include "src/__support/threads/mutex.h"
namespace __llvm_libc {
@ -17,10 +18,36 @@ namespace {
Mutex handler_list_mtx(false, false, false);
using AtExitCallback = void(void);
using ExitCallbackList = cpp::ReverseOrderBlockStore<AtExitCallback *, 32>;
using AtExitCallback = void(void *);
using StdCAtExitCallback = void(void);
struct AtExitUnit {
AtExitCallback *callback = nullptr;
void *payload = nullptr;
constexpr AtExitUnit() = default;
constexpr AtExitUnit(AtExitCallback *c, void *p) : callback(c), payload(p) {}
};
#ifdef LLVM_LIBC_PUBLIC_PACKAGING
using ExitCallbackList = cpp::ReverseOrderBlockStore<AtExitUnit, 32>;
#else
// BlockStore uses dynamic memory allocation. To avoid dynamic memory
// allocation in tests, we use a fixed size callback list when built for
// tests.
// If we use BlockStore, then we will have to pull in malloc etc into
// the tests. While this is not bad, the problem we have currently is
// that LLVM libc' allocator is SCUDO. So, we will end up pulling SCUDO's
// deps also (some of which are not yet available in LLVM libc) into the
// integration tests.
using ExitCallbackList = FixedVector<AtExitUnit, CALLBACK_LIST_SIZE_FOR_TESTS>;
#endif // LLVM_LIBC_PUBLIC_PACKAGING
constinit ExitCallbackList exit_callbacks;
void stdc_at_exit_func(void *payload) {
reinterpret_cast<StdCAtExitCallback *>(payload)();
}
} // namespace
namespace internal {
@ -28,10 +55,10 @@ namespace internal {
void call_exit_callbacks() {
handler_list_mtx.lock();
while (!exit_callbacks.empty()) {
auto *callback = exit_callbacks.back();
auto unit = exit_callbacks.back();
exit_callbacks.pop_back();
handler_list_mtx.unlock();
callback();
unit.callback(unit.payload);
handler_list_mtx.lock();
}
ExitCallbackList::destroy(&exit_callbacks);
@ -39,11 +66,25 @@ void call_exit_callbacks() {
} // namespace internal
LLVM_LIBC_FUNCTION(int, atexit, (AtExitCallback * callback)) {
static int add_atexit_unit(const AtExitUnit &unit) {
// TODO: Use the return value of push_back and bubble it to the public
// function as error return value. Note that a BlockStore push_back can
// fail because of allocation failure. Likewise, a FixedVector push_back
// can fail when it is full.
handler_list_mtx.lock();
exit_callbacks.push_back(callback);
exit_callbacks.push_back(unit);
handler_list_mtx.unlock();
return 0;
}
// TODO: Handle the last dso handle argument.
extern "C" int __cxa_atexit(AtExitCallback *callback, void *payload, void *) {
return add_atexit_unit({callback, payload});
}
LLVM_LIBC_FUNCTION(int, atexit, (StdCAtExitCallback * callback)) {
return add_atexit_unit(
{&stdc_at_exit_func, reinterpret_cast<void *>(callback)});
}
} // namespace __llvm_libc

View File

@ -9,8 +9,12 @@
#ifndef LLVM_LIBC_SRC_STDLIB_ATEXIT_H
#define LLVM_LIBC_SRC_STDLIB_ATEXIT_H
#include <stddef.h> // For size_t
namespace __llvm_libc {
constexpr size_t CALLBACK_LIST_SIZE_FOR_TESTS = 1024;
int atexit(void (*function)());
} // namespace __llvm_libc

View File

@ -8,6 +8,10 @@
#include "utils/IntegrationTest/test.h"
#include <stddef.h>
int global_destroyed = false;
class A {
private:
int val[1024];
@ -19,10 +23,7 @@ public:
val[i] = a;
}
// TODO: When we have implementation for __cxa_atexit, an explicit definition
// of the destructor should be provided to test that path of registering the
// destructor callback for a global.
~A() = default;
~A() { global_destroyed = true; }
int get(int i) const { return val[i]; }
};
@ -33,14 +34,23 @@ int INITVAL_INITIALIZER = 0x600D;
A global(GLOBAL_INDEX, INITVAL_INITIALIZER);
int initval = 0;
int preinitval = 0;
__attribute__((constructor)) void set_initval() {
initval = INITVAL_INITIALIZER;
}
__attribute__((destructor)) void reset_initval() { initval = 0; }
__attribute__((destructor(1))) void reset_initval() {
ASSERT_TRUE(global_destroyed);
ASSERT_EQ(preinitval, 0);
initval = 0;
}
int preinitval = 0;
void set_preinitval() { preinitval = INITVAL_INITIALIZER; }
__attribute__((destructor)) void reset_preinitval() { preinitval = 0; }
__attribute__((destructor(2))) void reset_preinitval() {
ASSERT_TRUE(global_destroyed);
ASSERT_EQ(initval, INITVAL_INITIALIZER);
preinitval = 0;
}
using PreInitFunc = void();
__attribute__((section(".preinit_array"))) PreInitFunc *preinit_func_ptr =