1 //
2 // onexit.cpp
3 //
4 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //
6 // The _onexit registry, which stores pointers to functions to be called when
7 // the program terminates or, when the CRT is statically linked into a DLL, when
8 // the DLL is unloaded.
9 //
10 // When the CRT is statically linked into an EXE or a DLL, this registry is used
11 // to hold the on-exit functions for the module (EXE or DLL) into which it is
12 // linked.
13 //
14 // When the dynamic CRT is used, this object is part of the AppCRT DLL and this
15 // registry stores the on-exit functions for the VCRuntime. If the EXE for the
16 // process uses this VCRuntime, then this registry also stores the on-exit
17 // functions for that EXE. If a DLL uses the dynamic CRT, then that DLL has its
18 // own registry, defined in the statically-linked VCStartup library.
19 //
20 #include <corecrt_internal.h>
21
22
23
24 // The global atexit and at_quick_exit registries
25 extern "C" { _onexit_table_t __acrt_atexit_table{}; }
26 extern "C" { _onexit_table_t __acrt_at_quick_exit_table{}; }
27
28
29
30 enum : size_t
31 {
32 initial_table_count = 32,
33 minimum_table_increment = 4,
34 maximum_table_increment = 512
35 };
36
37
38
39 // Registers a function to be executed on exit. This function modifies the global
40 // onexit table.
_crt_atexit(_PVFV const function)41 extern "C" int __cdecl _crt_atexit(_PVFV const function)
42 {
43 return _register_onexit_function(&__acrt_atexit_table, reinterpret_cast<_onexit_t>(function));
44 }
45
_crt_at_quick_exit(_PVFV const function)46 extern "C" int __cdecl _crt_at_quick_exit(_PVFV const function)
47 {
48 return _register_onexit_function(&__acrt_at_quick_exit_table, reinterpret_cast<_onexit_t>(function));
49 }
50
51
52
_initialize_onexit_table(_onexit_table_t * const table)53 extern "C" int __cdecl _initialize_onexit_table(_onexit_table_t* const table)
54 {
55 if (!table)
56 {
57 return -1;
58 }
59
60 // If the table has already been initialized, do not do anything. Note that
61 // this handles both the case where the table was value initialized and where
62 // the table was initialized with encoded null pointers.
63 if (table->_first != table->_end)
64 {
65 return 0;
66 }
67
68 _PVFV* const encoded_nullptr = __crt_fast_encode_pointer(nullptr);
69
70 table->_first = encoded_nullptr;
71 table->_last = encoded_nullptr;
72 table->_end = encoded_nullptr;
73
74 return 0;
75 }
76
77
78
79 // Appends the given 'function' to the given onexit 'table'. Returns 0 on
80 // success; returns -1 on failure. In general, failures are considered fatal
81 // in calling code.
_register_onexit_function(_onexit_table_t * const table,_onexit_t const function)82 extern "C" int __cdecl _register_onexit_function(_onexit_table_t* const table, _onexit_t const function)
83 {
84 return __acrt_lock_and_call(__acrt_select_exit_lock(), [&]
85 {
86 if (!table)
87 {
88 return -1;
89 }
90
91 _PVFV* first = __crt_fast_decode_pointer(table->_first);
92 _PVFV* last = __crt_fast_decode_pointer(table->_last);
93 _PVFV* end = __crt_fast_decode_pointer(table->_end);
94
95 // If there is no room for the new entry, reallocate a larger table:
96 if (last == end)
97 {
98 size_t const old_count = end - first;
99
100 size_t const increment = old_count > maximum_table_increment ? maximum_table_increment : old_count;
101
102 // First, try to double the capacity of the table:
103 size_t new_count = old_count + increment;
104 if (new_count == 0)
105 {
106 new_count = initial_table_count;
107 }
108
109 _PVFV* new_first = nullptr;
110 if (new_count >= old_count)
111 {
112 new_first = _recalloc_crt_t(_PVFV, first, new_count).detach();
113 }
114
115 // If that didn't work, try to allocate a smaller increment:
116 if (new_first == nullptr)
117 {
118 new_count = old_count + minimum_table_increment;
119 new_first = _recalloc_crt_t(_PVFV, first, new_count).detach();
120 }
121
122 if (new_first == nullptr)
123 {
124 return -1;
125 }
126
127 first = new_first;
128 last = new_first + old_count;
129 end = new_first + new_count;
130
131 // The "additional" storage obtained from recalloc is sero-initialized.
132 // The array holds encoded function pointers, so we need to fill the
133 // storage with encoded nullptrs:
134 _PVFV const encoded_nullptr = __crt_fast_encode_pointer(nullptr);
135 for (auto it = last; it != end; ++it)
136 {
137 *it = encoded_nullptr;
138 }
139 }
140
141 *last++ = reinterpret_cast<_PVFV>(__crt_fast_encode_pointer(function));
142
143 table->_first = __crt_fast_encode_pointer(first);
144 table->_last = __crt_fast_encode_pointer(last);
145 table->_end = __crt_fast_encode_pointer(end);
146
147 return 0;
148 });
149 }
150
151
152
153 // This function executes a table of _onexit()/atexit() functions. The
154 // terminators are executed in reverse order, to give the required LIFO
155 // execution order. If the table is uninitialized, this function has no
156 // effect. After executing the terminators, this function resets the table
157 // so that it is uninitialized. Returns 0 on success; -1 on failure.
_execute_onexit_table(_onexit_table_t * const table)158 extern "C" int __cdecl _execute_onexit_table(_onexit_table_t* const table)
159 {
160 return __acrt_lock_and_call(__acrt_select_exit_lock(), [&]
161 {
162 if (!table)
163 {
164 return -1;
165 }
166
167 _PVFV* first = __crt_fast_decode_pointer(table->_first);
168 _PVFV* last = __crt_fast_decode_pointer(table->_last);
169 if (!first || first == reinterpret_cast<_PVFV*>(-1))
170 {
171 return 0;
172 }
173
174 // This loop calls through caller-provided function pointers. We must
175 // save and reset the global state mode before calling them, to maintain
176 // proper mode nesting. (These calls to caller-provided function pointers
177 // are the only non-trivial calls, so we can do this once for the entire
178 // loop.)
179 {
180 __crt_state_management::scoped_global_state_reset saved_state;
181
182 _PVFV const encoded_nullptr = __crt_fast_encode_pointer(nullptr);
183
184 _PVFV* saved_first = first;
185 _PVFV* saved_last = last;
186 for (;;)
187 {
188 // Find the last valid function pointer to call:
189 while (--last >= first && *last == encoded_nullptr)
190 {
191 // Keep going backwards
192 }
193
194 if (last < first)
195 {
196 // There are no more valid entries in the list; we are done:
197 break;
198 }
199
200 // Store the function pointer and mark it as visited in the list:
201 _PVFV const function = __crt_fast_decode_pointer(*last);
202 *last = encoded_nullptr;
203
204 function();
205
206 _PVFV* const new_first = __crt_fast_decode_pointer(table->_first);
207 _PVFV* const new_last = __crt_fast_decode_pointer(table->_last);
208
209 // Reset iteration if either the begin or end pointer has changed:
210 if (new_first != saved_first || new_last != saved_last)
211 {
212 first = saved_first = new_first;
213 last = saved_last = new_last;
214 }
215 }
216 }
217
218 if (first != reinterpret_cast<_PVFV*>(-1))
219 {
220 _free_crt(first);
221 }
222
223 _PVFV* const encoded_nullptr = __crt_fast_encode_pointer(nullptr);
224
225 table->_first = encoded_nullptr;
226 table->_last = encoded_nullptr;
227 table->_end = encoded_nullptr;
228
229 return 0;
230 });
231 }
232