Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/lib/libc/tests/stdlib/cxa_atexit_test.c
39530 views
1
/*-
2
* Copyright (c) 2025 Kyle Evans <[email protected]>
3
*
4
* SPDX-License-Identifier: BSD-2-Clause
5
*/
6
7
#include <sys/wait.h>
8
9
#include <dlfcn.h>
10
#include <stdbool.h>
11
#include <stdio.h>
12
#include <stdlib.h>
13
14
#include <atf-c.h>
15
16
#define ARBITRARY_EXIT_CODE 42
17
18
static char *
19
get_shlib(const char *srcdir)
20
{
21
char *shlib;
22
23
shlib = NULL;
24
if (asprintf(&shlib, "%s/libatexit.so", srcdir) < 0)
25
atf_tc_fail("failed to construct path to libatexit.so");
26
return (shlib);
27
}
28
29
static void
30
run_test(const atf_tc_t *tc, bool with_fatal_atexit, bool with_exit)
31
{
32
pid_t p;
33
void (*set_fatal_atexit)(bool);
34
void (*set_exit_code)(int);
35
void *hdl;
36
char *shlib;
37
38
shlib = get_shlib(atf_tc_get_config_var(tc, "srcdir"));
39
40
hdl = dlopen(shlib, RTLD_LAZY);
41
ATF_REQUIRE_MSG(hdl != NULL, "dlopen: %s", dlerror());
42
43
free(shlib);
44
45
if (with_fatal_atexit) {
46
set_fatal_atexit = dlsym(hdl, "set_fatal_atexit");
47
ATF_REQUIRE_MSG(set_fatal_atexit != NULL,
48
"set_fatal_atexit: %s", dlerror());
49
}
50
if (with_exit) {
51
set_exit_code = dlsym(hdl, "set_exit_code");
52
ATF_REQUIRE_MSG(set_exit_code != NULL, "set_exit_code: %s",
53
dlerror());
54
}
55
56
p = atf_utils_fork();
57
if (p == 0) {
58
/*
59
* Don't let the child clobber the results file; stderr/stdout
60
* have been replaced by atf_utils_fork() to capture it. We're
61
* intentionally using exit() instead of _exit() here to run
62
* __cxa_finalize at exit, otherwise we'd just leave it be.
63
*/
64
closefrom(3);
65
66
if (with_fatal_atexit)
67
set_fatal_atexit(true);
68
if (with_exit)
69
set_exit_code(ARBITRARY_EXIT_CODE);
70
71
dlclose(hdl);
72
73
/*
74
* If the dtor was supposed to exit (most cases), then we should
75
* not have made it to this point. If it's not supposed to
76
* exit, then we just exit with success here because we might
77
* be expecting either a clean exit or a signal on our way out
78
* as the final __cxa_finalize tries to run a callback in the
79
* unloaded DSO.
80
*/
81
if (with_exit)
82
exit(1);
83
exit(0);
84
}
85
86
dlclose(hdl);
87
atf_utils_wait(p, with_exit ? ARBITRARY_EXIT_CODE : 0, "", "");
88
}
89
90
ATF_TC_WITHOUT_HEAD(simple_cxa_atexit);
91
ATF_TC_BODY(simple_cxa_atexit, tc)
92
{
93
/*
94
* This test exits in a global object's dtor so that we check for our
95
* dtor being run at dlclose() time. If it isn't, then the forked child
96
* will have a chance to exit(1) after dlclose() to raise a failure.
97
*/
98
run_test(tc, false, true);
99
}
100
101
ATF_TC_WITHOUT_HEAD(late_cxa_atexit);
102
ATF_TC_BODY(late_cxa_atexit, tc)
103
{
104
/*
105
* This test creates another global object during a __cxa_atexit handler
106
* invocation. It's been observed in the wild that we weren't executing
107
* it, then the DSO gets torn down and it was executed at application
108
* exit time instead. In the best case scenario we would crash if
109
* something else hadn't been mapped there.
110
*/
111
run_test(tc, true, false);
112
}
113
114
ATF_TC_WITHOUT_HEAD(late_cxa_atexit_ran);
115
ATF_TC_BODY(late_cxa_atexit_ran, tc)
116
{
117
/*
118
* This is a slight variation of the previous test where we trigger an
119
* exit() in our late-registered __cxa_atexit handler so that we can
120
* ensure it was ran *before* dlclose() finished and not through some
121
* weird chain of events afterwards.
122
*/
123
run_test(tc, true, true);
124
}
125
126
ATF_TP_ADD_TCS(tp)
127
{
128
ATF_TP_ADD_TC(tp, simple_cxa_atexit);
129
ATF_TP_ADD_TC(tp, late_cxa_atexit);
130
ATF_TP_ADD_TC(tp, late_cxa_atexit_ran);
131
return (atf_no_error());
132
}
133
134