xref: /reactos/sdk/lib/ucrt/startup/onexit.cpp (revision ffd69754)
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