1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
/*
* Copyright (c) 2019-2021, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Checked.h>
#include <AK/Debug.h>
#include <AK/Format.h>
#include <LibC/bits/pthread_integration.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/internals.h>
#include <sys/mman.h>
#include <unistd.h>
extern "C" {
struct AtExitEntry {
AtExitFunction method { nullptr };
void* parameter { nullptr };
void* dso_handle { nullptr };
bool has_been_called { false };
};
// We'll re-allocate the region if it ends up being too small at runtime
static size_t atexit_entry_region_size = 2 * PAGE_SIZE;
static AtExitEntry* atexit_entries;
static size_t atexit_entry_count = 0;
static pthread_mutex_t atexit_mutex = __PTHREAD_MUTEX_INITIALIZER;
static void lock_atexit_handlers()
{
if (mprotect(atexit_entries, atexit_entry_region_size, PROT_READ) < 0) {
perror("lock_atexit_handlers");
_exit(1);
}
}
static void unlock_atexit_handlers()
{
if (mprotect(atexit_entries, atexit_entry_region_size, PROT_READ | PROT_WRITE) < 0) {
perror("unlock_atexit_handlers");
_exit(1);
}
}
int __cxa_atexit(AtExitFunction exit_function, void* parameter, void* dso_handle)
{
__pthread_mutex_lock(&atexit_mutex);
// allocate initial atexit region
if (!atexit_entries) {
atexit_entries = (AtExitEntry*)mmap(nullptr, atexit_entry_region_size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (atexit_entries == MAP_FAILED) {
__pthread_mutex_unlock(&atexit_mutex);
perror("__cxa_atexit mmap");
_exit(1);
}
}
// reallocate atexit region, increasing size by PAGE_SIZE
if ((atexit_entry_count) >= (atexit_entry_region_size / sizeof(AtExitEntry))) {
if (Checked<size_t>::addition_would_overflow(atexit_entry_region_size, PAGE_SIZE)) {
__pthread_mutex_unlock(&atexit_mutex);
return -1;
}
dbgln_if(GLOBAL_DTORS_DEBUG, "__cxa_atexit: Growing exit handler region from {} to {}", atexit_entry_region_size, atexit_entry_region_size + PAGE_SIZE);
size_t new_atexit_region_size = atexit_entry_region_size + PAGE_SIZE;
auto* new_atexit_entries = (AtExitEntry*)mmap(nullptr, new_atexit_region_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (new_atexit_entries == MAP_FAILED) {
__pthread_mutex_unlock(&atexit_mutex);
perror("__cxa_atexit mmap (new size)");
return -1;
}
memcpy(new_atexit_entries, atexit_entries, atexit_entry_region_size);
if (munmap(atexit_entries, atexit_entry_region_size) < 0) {
perror("__cxa_atexit munmap old region");
// leak the old region on failure
}
atexit_entries = new_atexit_entries;
atexit_entry_region_size = new_atexit_region_size;
}
unlock_atexit_handlers();
atexit_entries[atexit_entry_count++] = { exit_function, parameter, dso_handle, false };
lock_atexit_handlers();
__pthread_mutex_unlock(&atexit_mutex);
return 0;
}
void __cxa_finalize(void* dso_handle)
{
// From the itanium abi, https://itanium-cxx-abi.github.io/cxx-abi/abi.html#dso-dtor-runtime-api
//
// When __cxa_finalize(d) is called, it should walk the termination function list, calling each in turn
// if d matches __dso_handle for the termination function entry. If d == NULL, it should call all of them.
// Multiple calls to __cxa_finalize shall not result in calling termination function entries multiple times;
// the implementation may either remove entries or mark them finished.
__pthread_mutex_lock(&atexit_mutex);
ssize_t entry_index = atexit_entry_count;
dbgln_if(GLOBAL_DTORS_DEBUG, "__cxa_finalize: {} entries in the finalizer list", entry_index);
while (--entry_index >= 0) {
auto& exit_entry = atexit_entries[entry_index];
bool needs_calling = !exit_entry.has_been_called && (!dso_handle || dso_handle == exit_entry.dso_handle);
if (needs_calling) {
dbgln_if(GLOBAL_DTORS_DEBUG, "__cxa_finalize: calling entry[{}] {:p}({:p}) dso: {:p}", entry_index, exit_entry.method, exit_entry.parameter, exit_entry.dso_handle);
unlock_atexit_handlers();
exit_entry.has_been_called = true;
lock_atexit_handlers();
__pthread_mutex_unlock(&atexit_mutex);
exit_entry.method(exit_entry.parameter);
__pthread_mutex_lock(&atexit_mutex);
}
}
__pthread_mutex_unlock(&atexit_mutex);
}
__attribute__((noreturn)) void __cxa_pure_virtual()
{
VERIFY_NOT_REACHED();
}
} // extern "C"
|