Path: blob/main/lib/libc/tests/stdlib/cxa_atexit_test.c
39530 views
/*-1* Copyright (c) 2025 Kyle Evans <[email protected]>2*3* SPDX-License-Identifier: BSD-2-Clause4*/56#include <sys/wait.h>78#include <dlfcn.h>9#include <stdbool.h>10#include <stdio.h>11#include <stdlib.h>1213#include <atf-c.h>1415#define ARBITRARY_EXIT_CODE 421617static char *18get_shlib(const char *srcdir)19{20char *shlib;2122shlib = NULL;23if (asprintf(&shlib, "%s/libatexit.so", srcdir) < 0)24atf_tc_fail("failed to construct path to libatexit.so");25return (shlib);26}2728static void29run_test(const atf_tc_t *tc, bool with_fatal_atexit, bool with_exit)30{31pid_t p;32void (*set_fatal_atexit)(bool);33void (*set_exit_code)(int);34void *hdl;35char *shlib;3637shlib = get_shlib(atf_tc_get_config_var(tc, "srcdir"));3839hdl = dlopen(shlib, RTLD_LAZY);40ATF_REQUIRE_MSG(hdl != NULL, "dlopen: %s", dlerror());4142free(shlib);4344if (with_fatal_atexit) {45set_fatal_atexit = dlsym(hdl, "set_fatal_atexit");46ATF_REQUIRE_MSG(set_fatal_atexit != NULL,47"set_fatal_atexit: %s", dlerror());48}49if (with_exit) {50set_exit_code = dlsym(hdl, "set_exit_code");51ATF_REQUIRE_MSG(set_exit_code != NULL, "set_exit_code: %s",52dlerror());53}5455p = atf_utils_fork();56if (p == 0) {57/*58* Don't let the child clobber the results file; stderr/stdout59* have been replaced by atf_utils_fork() to capture it. We're60* intentionally using exit() instead of _exit() here to run61* __cxa_finalize at exit, otherwise we'd just leave it be.62*/63closefrom(3);6465if (with_fatal_atexit)66set_fatal_atexit(true);67if (with_exit)68set_exit_code(ARBITRARY_EXIT_CODE);6970dlclose(hdl);7172/*73* If the dtor was supposed to exit (most cases), then we should74* not have made it to this point. If it's not supposed to75* exit, then we just exit with success here because we might76* be expecting either a clean exit or a signal on our way out77* as the final __cxa_finalize tries to run a callback in the78* unloaded DSO.79*/80if (with_exit)81exit(1);82exit(0);83}8485dlclose(hdl);86atf_utils_wait(p, with_exit ? ARBITRARY_EXIT_CODE : 0, "", "");87}8889ATF_TC_WITHOUT_HEAD(simple_cxa_atexit);90ATF_TC_BODY(simple_cxa_atexit, tc)91{92/*93* This test exits in a global object's dtor so that we check for our94* dtor being run at dlclose() time. If it isn't, then the forked child95* will have a chance to exit(1) after dlclose() to raise a failure.96*/97run_test(tc, false, true);98}99100ATF_TC_WITHOUT_HEAD(late_cxa_atexit);101ATF_TC_BODY(late_cxa_atexit, tc)102{103/*104* This test creates another global object during a __cxa_atexit handler105* invocation. It's been observed in the wild that we weren't executing106* it, then the DSO gets torn down and it was executed at application107* exit time instead. In the best case scenario we would crash if108* something else hadn't been mapped there.109*/110run_test(tc, true, false);111}112113ATF_TC_WITHOUT_HEAD(late_cxa_atexit_ran);114ATF_TC_BODY(late_cxa_atexit_ran, tc)115{116/*117* This is a slight variation of the previous test where we trigger an118* exit() in our late-registered __cxa_atexit handler so that we can119* ensure it was ran *before* dlclose() finished and not through some120* weird chain of events afterwards.121*/122run_test(tc, true, true);123}124125ATF_TP_ADD_TCS(tp)126{127ATF_TP_ADD_TC(tp, simple_cxa_atexit);128ATF_TP_ADD_TC(tp, late_cxa_atexit);129ATF_TP_ADD_TC(tp, late_cxa_atexit_ran);130return (atf_no_error());131}132133134