mirror of
https://github.com/opnsense/src.git
synced 2026-06-06 23:32:52 -04:00
libc: tests: add some tests for __cxa_atexit handling
This adds a basic test that __cxa_atexit works, and also adds some tests for __cxa_atexit handlers registered in the middle of __cxa_finalize. PR: 285870
This commit is contained in:
parent
23427c8e1f
commit
ee9ce1078c
4 changed files with 212 additions and 0 deletions
|
|
@ -1,6 +1,7 @@
|
|||
.include <src.opts.mk>
|
||||
|
||||
ATF_TESTS_C+= clearenv_test
|
||||
ATF_TESTS_C+= cxa_atexit_test
|
||||
ATF_TESTS_C+= dynthr_test
|
||||
ATF_TESTS_C+= heapsort_test
|
||||
ATF_TESTS_C+= libc_exit_test
|
||||
|
|
@ -79,5 +80,6 @@ LIBADD.libc_exit_test+= pthread
|
|||
LIBADD.strtod_test+= m
|
||||
|
||||
SUBDIR+= dynthr_mod
|
||||
SUBDIR+= libatexit
|
||||
|
||||
.include <bsd.test.mk>
|
||||
|
|
|
|||
132
lib/libc/tests/stdlib/cxa_atexit_test.c
Normal file
132
lib/libc/tests/stdlib/cxa_atexit_test.c
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/*-
|
||||
* Copyright (c) 2025 Kyle Evans <kevans@FreeBSD.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <atf-c.h>
|
||||
|
||||
#define ARBITRARY_EXIT_CODE 42
|
||||
|
||||
static char *
|
||||
get_shlib(const char *srcdir)
|
||||
{
|
||||
char *shlib;
|
||||
|
||||
shlib = NULL;
|
||||
if (asprintf(&shlib, "%s/libatexit.so", srcdir) < 0)
|
||||
atf_tc_fail("failed to construct path to libatexit.so");
|
||||
return (shlib);
|
||||
}
|
||||
|
||||
static void
|
||||
run_test(const atf_tc_t *tc, bool with_fatal_atexit, bool with_exit)
|
||||
{
|
||||
pid_t p;
|
||||
void (*set_fatal_atexit)(bool);
|
||||
void (*set_exit_code)(int);
|
||||
void *hdl;
|
||||
char *shlib;
|
||||
|
||||
shlib = get_shlib(atf_tc_get_config_var(tc, "srcdir"));
|
||||
|
||||
hdl = dlopen(shlib, RTLD_LAZY);
|
||||
ATF_REQUIRE_MSG(hdl != NULL, "dlopen: %s", dlerror());
|
||||
|
||||
free(shlib);
|
||||
|
||||
if (with_fatal_atexit) {
|
||||
set_fatal_atexit = dlsym(hdl, "set_fatal_atexit");
|
||||
ATF_REQUIRE_MSG(set_fatal_atexit != NULL,
|
||||
"set_fatal_atexit: %s", dlerror());
|
||||
}
|
||||
if (with_exit) {
|
||||
set_exit_code = dlsym(hdl, "set_exit_code");
|
||||
ATF_REQUIRE_MSG(set_exit_code != NULL, "set_exit_code: %s",
|
||||
dlerror());
|
||||
}
|
||||
|
||||
p = atf_utils_fork();
|
||||
if (p == 0) {
|
||||
/*
|
||||
* Don't let the child clobber the results file; stderr/stdout
|
||||
* have been replaced by atf_utils_fork() to capture it. We're
|
||||
* intentionally using exit() instead of _exit() here to run
|
||||
* __cxa_finalize at exit, otherwise we'd just leave it be.
|
||||
*/
|
||||
closefrom(3);
|
||||
|
||||
if (with_fatal_atexit)
|
||||
set_fatal_atexit(true);
|
||||
if (with_exit)
|
||||
set_exit_code(ARBITRARY_EXIT_CODE);
|
||||
|
||||
dlclose(hdl);
|
||||
|
||||
/*
|
||||
* If the dtor was supposed to exit (most cases), then we should
|
||||
* not have made it to this point. If it's not supposed to
|
||||
* exit, then we just exit with success here because we might
|
||||
* be expecting either a clean exit or a signal on our way out
|
||||
* as the final __cxa_finalize tries to run a callback in the
|
||||
* unloaded DSO.
|
||||
*/
|
||||
if (with_exit)
|
||||
exit(1);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
dlclose(hdl);
|
||||
atf_utils_wait(p, with_exit ? ARBITRARY_EXIT_CODE : 0, "", "");
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(simple_cxa_atexit);
|
||||
ATF_TC_BODY(simple_cxa_atexit, tc)
|
||||
{
|
||||
/*
|
||||
* This test exits in a global object's dtor so that we check for our
|
||||
* dtor being run at dlclose() time. If it isn't, then the forked child
|
||||
* will have a chance to exit(1) after dlclose() to raise a failure.
|
||||
*/
|
||||
run_test(tc, false, true);
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(late_cxa_atexit);
|
||||
ATF_TC_BODY(late_cxa_atexit, tc)
|
||||
{
|
||||
/*
|
||||
* This test creates another global object during a __cxa_atexit handler
|
||||
* invocation. It's been observed in the wild that we weren't executing
|
||||
* it, then the DSO gets torn down and it was executed at application
|
||||
* exit time instead. In the best case scenario we would crash if
|
||||
* something else hadn't been mapped there.
|
||||
*/
|
||||
run_test(tc, true, false);
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(late_cxa_atexit_ran);
|
||||
ATF_TC_BODY(late_cxa_atexit_ran, tc)
|
||||
{
|
||||
/*
|
||||
* This is a slight variation of the previous test where we trigger an
|
||||
* exit() in our late-registered __cxa_atexit handler so that we can
|
||||
* ensure it was ran *before* dlclose() finished and not through some
|
||||
* weird chain of events afterwards.
|
||||
*/
|
||||
run_test(tc, true, true);
|
||||
}
|
||||
|
||||
ATF_TP_ADD_TCS(tp)
|
||||
{
|
||||
ATF_TP_ADD_TC(tp, simple_cxa_atexit);
|
||||
ATF_TP_ADD_TC(tp, late_cxa_atexit);
|
||||
ATF_TP_ADD_TC(tp, late_cxa_atexit_ran);
|
||||
return (atf_no_error());
|
||||
}
|
||||
11
lib/libc/tests/stdlib/libatexit/Makefile
Normal file
11
lib/libc/tests/stdlib/libatexit/Makefile
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
SHLIB_CXX= libatexit
|
||||
SHLIB_NAME= libatexit.so
|
||||
SHLIB_MAJOR= 1
|
||||
SHLIBDIR= ${TESTSDIR}
|
||||
PACKAGE= tests
|
||||
SRCS= libatexit.cc
|
||||
|
||||
TESTSDIR:= ${TESTSBASE}/${RELDIR:C/libc\/tests/libc/:H}
|
||||
|
||||
|
||||
.include <bsd.lib.mk>
|
||||
67
lib/libc/tests/stdlib/libatexit/libatexit.cc
Normal file
67
lib/libc/tests/stdlib/libatexit/libatexit.cc
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Kyle Evans <kevans@FreeBSD.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
static int exit_code = -1;
|
||||
static bool fatal_atexit;
|
||||
|
||||
extern "C" {
|
||||
void set_fatal_atexit(bool);
|
||||
void set_exit_code(int);
|
||||
}
|
||||
|
||||
void
|
||||
set_fatal_atexit(bool fexit)
|
||||
{
|
||||
fatal_atexit = fexit;
|
||||
}
|
||||
|
||||
void
|
||||
set_exit_code(int code)
|
||||
{
|
||||
exit_code = code;
|
||||
}
|
||||
|
||||
struct other_object {
|
||||
~other_object() {
|
||||
|
||||
/*
|
||||
* In previous versions of our __cxa_atexit handling, we would
|
||||
* never actually execute this handler because it's added during
|
||||
* ~object() below; __cxa_finalize would never revisit it. We
|
||||
* will allow the caller to configure us to exit with a certain
|
||||
* exit code so that it can run us twice: once to ensure we
|
||||
* don't crash at the end, and again to make sure the handler
|
||||
* actually ran.
|
||||
*/
|
||||
if (exit_code != -1)
|
||||
_exit(exit_code);
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
create_staticobj()
|
||||
{
|
||||
static other_object obj;
|
||||
}
|
||||
|
||||
struct object {
|
||||
~object() {
|
||||
/*
|
||||
* If we're doing the fatal_atexit behavior (i.e., create an
|
||||
* object that will add its own dtor for __cxa_finalize), then
|
||||
* we don't exit here.
|
||||
*/
|
||||
if (fatal_atexit)
|
||||
create_staticobj();
|
||||
else if (exit_code != -1)
|
||||
_exit(exit_code);
|
||||
}
|
||||
};
|
||||
|
||||
static object obj;
|
||||
Loading…
Reference in a new issue