]>
Commit | Line | Data |
---|---|---|
747b9f55 | 1 | /* __cxa_atexit backwards-compatibility support for Darwin. |
99dee823 | 2 | Copyright (C) 2006-2021 Free Software Foundation, Inc. |
747b9f55 GK |
3 | |
4 | This file is part of GCC. | |
5 | ||
6 | GCC is free software; you can redistribute it and/or modify it under | |
7 | the terms of the GNU General Public License as published by the Free | |
748086b7 | 8 | Software Foundation; either version 3, or (at your option) any later |
747b9f55 GK |
9 | version. |
10 | ||
747b9f55 GK |
11 | GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
12 | WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
13 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
14 | for more details. | |
15 | ||
748086b7 JJ |
16 | Under Section 7 of GPL version 3, you are granted additional |
17 | permissions described in the GCC Runtime Library Exception, version | |
18 | 3.1, as published by the Free Software Foundation. | |
19 | ||
20 | You should have received a copy of the GNU General Public License and | |
21 | a copy of the GCC Runtime Library Exception along with this program; | |
22 | see the files COPYING3 and COPYING.RUNTIME respectively. If not, see | |
23 | <http://www.gnu.org/licenses/>. */ | |
747b9f55 | 24 | |
3b414146 MS |
25 | /* Don't do anything if we are compiling for a kext multilib. */ |
26 | #ifdef __PIC__ | |
27 | ||
747b9f55 GK |
28 | #include "tconfig.h" |
29 | #include "tsystem.h" | |
30 | ||
31 | #include <dlfcn.h> | |
32 | #include <stdbool.h> | |
33 | #include <stdlib.h> | |
895de5a8 | 34 | #include <string.h> |
747b9f55 GK |
35 | |
36 | /* This file works around two different problems. | |
37 | ||
38 | The first problem is that there is no __cxa_atexit on Mac OS versions | |
895de5a8 GK |
39 | before 10.4. It fixes this by providing a complete atexit and |
40 | __cxa_atexit emulation called from the regular atexit. | |
747b9f55 | 41 | |
895de5a8 GK |
42 | The second problem is that on all shipping versions of Mac OS, |
43 | __cxa_finalize and exit() don't work right: they don't run routines | |
44 | that were registered while other atexit routines are running. This | |
45 | is worked around by wrapping each atexit/__cxa_atexit routine with | |
46 | our own routine which ensures that any __cxa_atexit calls while it | |
47 | is running are honoured. | |
747b9f55 | 48 | |
895de5a8 GK |
49 | There are still problems which this does not solve. Before 10.4, |
50 | shared objects linked with previous compilers won't have their | |
51 | atexit calls properly interleaved with code compiled with newer | |
52 | compilers. Also, atexit routines registered from shared objects | |
53 | linked with previous compilers won't get the bug fix. */ | |
54 | ||
55 | typedef int (*cxa_atexit_p)(void (*func) (void*), void* arg, const void* dso); | |
56 | typedef void (*cxa_finalize_p)(const void *dso); | |
57 | typedef int (*atexit_p)(void (*func)(void)); | |
58 | ||
59 | /* These are from "keymgr.h". */ | |
60 | extern void *_keymgr_get_and_lock_processwide_ptr (unsigned key); | |
61 | extern int _keymgr_get_and_lock_processwide_ptr_2 (unsigned, void **); | |
62 | extern int _keymgr_set_and_unlock_processwide_ptr (unsigned key, void *ptr); | |
63 | ||
64 | extern void *__keymgr_global[]; | |
65 | typedef struct _Sinfo_Node { | |
66 | unsigned int size ; /*size of this node*/ | |
67 | unsigned short major_version ; /*API major version.*/ | |
68 | unsigned short minor_version ; /*API minor version.*/ | |
69 | } _Tinfo_Node ; | |
747b9f55 GK |
70 | |
71 | #ifdef __ppc__ | |
895de5a8 GK |
72 | #define CHECK_KEYMGR_ERROR(e) \ |
73 | (((_Tinfo_Node *)__keymgr_global[2])->major_version >= 4 ? (e) : 0) | |
747b9f55 | 74 | #else |
895de5a8 | 75 | #define CHECK_KEYMGR_ERROR(e) (e) |
747b9f55 GK |
76 | #endif |
77 | ||
895de5a8 GK |
78 | /* Our globals are stored under this keymgr index. */ |
79 | #define KEYMGR_ATEXIT_LIST 14 | |
747b9f55 | 80 | |
895de5a8 GK |
81 | /* The different kinds of callback routines. */ |
82 | typedef void (*atexit_callback)(void); | |
83 | typedef void (*cxa_atexit_callback)(void *); | |
747b9f55 | 84 | |
895de5a8 GK |
85 | /* This structure holds a routine to call. There may be extra fields |
86 | at the end of the structure that this code doesn't know about. */ | |
87 | struct one_atexit_routine | |
88 | { | |
89 | union { | |
90 | atexit_callback ac; | |
91 | cxa_atexit_callback cac; | |
92 | } callback; | |
93 | /* has_arg is 0/2/4 if 'ac' is live, 1/3/5 if 'cac' is live. | |
94 | Higher numbers indicate a later version of the structure that this | |
95 | code doesn't understand and will ignore. */ | |
96 | int has_arg; | |
97 | void * arg; | |
98 | }; | |
747b9f55 | 99 | |
895de5a8 GK |
100 | struct atexit_routine_list |
101 | { | |
102 | struct atexit_routine_list * next; | |
103 | struct one_atexit_routine r; | |
104 | }; | |
105 | ||
106 | /* The various possibilities for status of atexit(). */ | |
107 | enum atexit_status { | |
108 | atexit_status_unknown = 0, | |
109 | atexit_status_missing = 1, | |
110 | atexit_status_broken = 2, | |
111 | atexit_status_working = 16 | |
112 | }; | |
113 | ||
114 | struct keymgr_atexit_list | |
115 | { | |
116 | /* Version of this list. This code knows only about version 0. | |
117 | If the version is higher than 0, this code may add new atexit routines | |
118 | but should not attempt to run the list. */ | |
119 | short version; | |
120 | /* 1 if an atexit routine is currently being run by this code, 0 | |
121 | otherwise. */ | |
122 | char running_routines; | |
123 | /* Holds a value from 'enum atexit_status'. */ | |
124 | unsigned char atexit_status; | |
125 | /* The list of atexit and cxa_atexit routines registered. If | |
126 | atexit_status_missing it contains all routines registered while | |
127 | linked with this code. If atexit_status_broken it contains all | |
128 | routines registered during cxa_finalize while linked with this | |
129 | code. */ | |
130 | struct atexit_routine_list *l; | |
131 | /* &__cxa_atexit; set if atexit_status >= atexit_status_broken. */ | |
132 | cxa_atexit_p cxa_atexit_f; | |
133 | /* &__cxa_finalize; set if atexit_status >= atexit_status_broken. */ | |
134 | cxa_finalize_p cxa_finalize_f; | |
135 | /* &atexit; set if atexit_status >= atexit_status_working | |
136 | or atexit_status == atexit_status_missing. */ | |
137 | atexit_p atexit_f; | |
138 | }; | |
139 | ||
140 | /* Return 0 if __cxa_atexit has the bug it has in Mac OS 10.4: it | |
141 | fails to call routines registered while an atexit routine is | |
142 | running. Return 1 if it works properly, and -1 if an error occurred. */ | |
143 | ||
144 | struct atexit_data | |
145 | { | |
146 | int result; | |
147 | cxa_atexit_p cxa_atexit; | |
148 | }; | |
149 | ||
150 | static void cxa_atexit_check_2 (void *arg) | |
151 | { | |
152 | ((struct atexit_data *)arg)->result = 1; | |
747b9f55 GK |
153 | } |
154 | ||
895de5a8 GK |
155 | static void cxa_atexit_check_1 (void *arg) |
156 | { | |
157 | struct atexit_data * aed = arg; | |
158 | if (aed->cxa_atexit (cxa_atexit_check_2, arg, arg) != 0) | |
159 | aed->result = -1; | |
160 | } | |
747b9f55 GK |
161 | |
162 | static int | |
895de5a8 | 163 | check_cxa_atexit (cxa_atexit_p cxa_atexit, cxa_finalize_p cxa_finalize) |
747b9f55 | 164 | { |
895de5a8 GK |
165 | struct atexit_data aed = { 0, cxa_atexit }; |
166 | ||
167 | /* We re-use &aed as the 'dso' parameter, since it's a unique address. */ | |
168 | if (cxa_atexit (cxa_atexit_check_1, &aed, &aed) != 0) | |
169 | return -1; | |
170 | cxa_finalize (&aed); | |
171 | if (aed.result == 0) | |
747b9f55 | 172 | { |
895de5a8 GK |
173 | /* Call __cxa_finalize again to make sure that cxa_atexit_check_2 |
174 | is removed from the list before AED goes out of scope. */ | |
175 | cxa_finalize (&aed); | |
176 | aed.result = 0; | |
747b9f55 | 177 | } |
895de5a8 GK |
178 | return aed.result; |
179 | } | |
180 | ||
181 | #ifdef __ppc__ | |
182 | /* This comes from Csu. It works only before 10.4. The prototype has | |
183 | been altered a bit to avoid casting. */ | |
184 | extern int _dyld_func_lookup(const char *dyld_func_name, | |
185 | void *address) __attribute__((visibility("hidden"))); | |
186 | ||
187 | static void our_atexit (void); | |
188 | ||
189 | /* We're running on 10.3.9. Find the address of the system atexit() | |
190 | function. So easy to say, so hard to do. */ | |
191 | static atexit_p | |
192 | find_atexit_10_3 (void) | |
193 | { | |
194 | unsigned int (*dyld_image_count_fn)(void); | |
195 | const char *(*dyld_get_image_name_fn)(unsigned int image_index); | |
196 | const void *(*dyld_get_image_header_fn)(unsigned int image_index); | |
197 | const void *(*NSLookupSymbolInImage_fn)(const void *image, | |
198 | const char *symbolName, | |
199 | unsigned int options); | |
200 | void *(*NSAddressOfSymbol_fn)(const void *symbol); | |
201 | unsigned i, count; | |
202 | ||
203 | /* Find some dyld functions. */ | |
204 | _dyld_func_lookup("__dyld_image_count", &dyld_image_count_fn); | |
205 | _dyld_func_lookup("__dyld_get_image_name", &dyld_get_image_name_fn); | |
206 | _dyld_func_lookup("__dyld_get_image_header", &dyld_get_image_header_fn); | |
207 | _dyld_func_lookup("__dyld_NSLookupSymbolInImage", &NSLookupSymbolInImage_fn); | |
208 | _dyld_func_lookup("__dyld_NSAddressOfSymbol", &NSAddressOfSymbol_fn); | |
209 | ||
210 | /* If any of these don't exist, that's an error. */ | |
211 | if (! dyld_image_count_fn || ! dyld_get_image_name_fn | |
212 | || ! dyld_get_image_header_fn || ! NSLookupSymbolInImage_fn | |
213 | || ! NSAddressOfSymbol_fn) | |
214 | return NULL; | |
215 | ||
216 | count = dyld_image_count_fn (); | |
217 | for (i = 0; i < count; i++) | |
747b9f55 | 218 | { |
895de5a8 GK |
219 | const char * path = dyld_get_image_name_fn (i); |
220 | const void * image; | |
221 | const void * symbol; | |
222 | ||
223 | if (strcmp (path, "/usr/lib/libSystem.B.dylib") != 0) | |
224 | continue; | |
225 | image = dyld_get_image_header_fn (i); | |
226 | if (! image) | |
227 | return NULL; | |
228 | /* '4' is NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR. */ | |
229 | symbol = NSLookupSymbolInImage_fn (image, "_atexit", 4); | |
230 | if (! symbol) | |
231 | return NULL; | |
232 | return NSAddressOfSymbol_fn (symbol); | |
747b9f55 | 233 | } |
895de5a8 | 234 | return NULL; |
747b9f55 | 235 | } |
895de5a8 | 236 | #endif |
747b9f55 | 237 | |
895de5a8 GK |
238 | /* Create (if necessary), find, lock, fill in, and return our globals. |
239 | Return NULL on error, in which case the globals will not be locked. | |
240 | The caller should call keymgr_set_and_unlock. */ | |
241 | static struct keymgr_atexit_list * | |
242 | get_globals (void) | |
747b9f55 | 243 | { |
895de5a8 GK |
244 | struct keymgr_atexit_list * r; |
245 | ||
246 | #ifdef __ppc__ | |
247 | /* 10.3.9 doesn't have _keymgr_get_and_lock_processwide_ptr_2 so the | |
248 | PPC side can't use it. On 10.4 this just means the error gets | |
249 | reported a little later when | |
250 | _keymgr_set_and_unlock_processwide_ptr finds that the key was | |
251 | never locked. */ | |
252 | r = _keymgr_get_and_lock_processwide_ptr (KEYMGR_ATEXIT_LIST); | |
253 | #else | |
254 | void * rr; | |
255 | if (_keymgr_get_and_lock_processwide_ptr_2 (KEYMGR_ATEXIT_LIST, &rr)) | |
256 | return NULL; | |
257 | r = rr; | |
258 | #endif | |
259 | ||
260 | if (r == NULL) | |
261 | { | |
262 | r = calloc (sizeof (struct keymgr_atexit_list), 1); | |
263 | if (! r) | |
264 | return NULL; | |
265 | } | |
266 | ||
267 | if (r->atexit_status == atexit_status_unknown) | |
268 | { | |
269 | void *handle; | |
270 | ||
271 | handle = dlopen ("/usr/lib/libSystem.B.dylib", RTLD_NOLOAD); | |
272 | if (!handle) | |
273 | { | |
274 | #ifdef __ppc__ | |
275 | r->atexit_status = atexit_status_missing; | |
276 | r->atexit_f = find_atexit_10_3 (); | |
277 | if (! r->atexit_f) | |
278 | goto error; | |
279 | if (r->atexit_f (our_atexit)) | |
280 | goto error; | |
281 | #else | |
282 | goto error; | |
283 | #endif | |
284 | } | |
285 | else | |
286 | { | |
287 | int chk_result; | |
288 | ||
289 | r->cxa_atexit_f = (cxa_atexit_p)dlsym (handle, "__cxa_atexit"); | |
290 | r->cxa_finalize_f = (cxa_finalize_p)dlsym (handle, "__cxa_finalize"); | |
291 | if (! r->cxa_atexit_f || ! r->cxa_finalize_f) | |
292 | goto error; | |
747b9f55 | 293 | |
895de5a8 GK |
294 | chk_result = check_cxa_atexit (r->cxa_atexit_f, r->cxa_finalize_f); |
295 | if (chk_result == -1) | |
296 | goto error; | |
297 | else if (chk_result == 0) | |
298 | r->atexit_status = atexit_status_broken; | |
299 | else | |
300 | { | |
301 | r->atexit_f = (atexit_p)dlsym (handle, "atexit"); | |
302 | if (! r->atexit_f) | |
303 | goto error; | |
304 | r->atexit_status = atexit_status_working; | |
305 | } | |
306 | } | |
307 | } | |
747b9f55 | 308 | |
895de5a8 GK |
309 | return r; |
310 | ||
311 | error: | |
312 | _keymgr_set_and_unlock_processwide_ptr (KEYMGR_ATEXIT_LIST, r); | |
313 | return NULL; | |
314 | } | |
747b9f55 | 315 | |
895de5a8 GK |
316 | /* Add TO_ADD to ATEXIT_LIST. ATEXIT_LIST may be NULL but is |
317 | always the result of calling _keymgr_get_and_lock_processwide_ptr and | |
318 | so KEYMGR_ATEXIT_LIST is known to be locked; this routine is responsible | |
319 | for unlocking it. */ | |
747b9f55 GK |
320 | |
321 | static int | |
895de5a8 GK |
322 | add_routine (struct keymgr_atexit_list * g, |
323 | const struct one_atexit_routine * to_add) | |
747b9f55 | 324 | { |
895de5a8 GK |
325 | struct atexit_routine_list * s |
326 | = malloc (sizeof (struct atexit_routine_list)); | |
327 | int result; | |
328 | ||
747b9f55 | 329 | if (!s) |
895de5a8 GK |
330 | { |
331 | _keymgr_set_and_unlock_processwide_ptr (KEYMGR_ATEXIT_LIST, g); | |
332 | return -1; | |
333 | } | |
334 | s->r = *to_add; | |
335 | s->next = g->l; | |
336 | g->l = s; | |
337 | result = _keymgr_set_and_unlock_processwide_ptr (KEYMGR_ATEXIT_LIST, g); | |
338 | return CHECK_KEYMGR_ERROR (result) == 0 ? 0 : -1; | |
747b9f55 GK |
339 | } |
340 | ||
895de5a8 GK |
341 | /* This runs the routines in G->L up to STOP. */ |
342 | static struct keymgr_atexit_list * | |
343 | run_routines (struct keymgr_atexit_list *g, | |
344 | struct atexit_routine_list *stop) | |
345 | { | |
346 | for (;;) | |
347 | { | |
348 | struct atexit_routine_list * cur = g->l; | |
349 | if (! cur || cur == stop) | |
350 | break; | |
351 | g->l = cur->next; | |
352 | _keymgr_set_and_unlock_processwide_ptr (KEYMGR_ATEXIT_LIST, g); | |
353 | ||
354 | switch (cur->r.has_arg) { | |
355 | case 0: case 2: case 4: | |
356 | cur->r.callback.ac (); | |
357 | break; | |
358 | case 1: case 3: case 5: | |
359 | cur->r.callback.cac (cur->r.arg); | |
360 | break; | |
361 | default: | |
362 | /* Don't understand, so don't call it. */ | |
363 | break; | |
364 | } | |
365 | free (cur); | |
366 | ||
367 | g = _keymgr_get_and_lock_processwide_ptr (KEYMGR_ATEXIT_LIST); | |
368 | if (! g) | |
369 | break; | |
370 | } | |
371 | return g; | |
372 | } | |
373 | ||
374 | /* Call the routine described by ROUTINE_PARAM and then call any | |
375 | routines added to KEYMGR_ATEXIT_LIST while that routine was | |
376 | running, all with in_cxa_finalize set. */ | |
747b9f55 | 377 | |
747b9f55 | 378 | static void |
895de5a8 | 379 | cxa_atexit_wrapper (void* routine_param) |
747b9f55 | 380 | { |
895de5a8 GK |
381 | struct one_atexit_routine * routine = routine_param; |
382 | struct keymgr_atexit_list *g; | |
383 | struct atexit_routine_list * base = NULL; | |
384 | char prev_running = 0; | |
385 | ||
386 | g = _keymgr_get_and_lock_processwide_ptr (KEYMGR_ATEXIT_LIST); | |
387 | if (g) | |
747b9f55 | 388 | { |
895de5a8 GK |
389 | prev_running = g->running_routines; |
390 | g->running_routines = 1; | |
391 | base = g->l; | |
392 | _keymgr_set_and_unlock_processwide_ptr (KEYMGR_ATEXIT_LIST, g); | |
393 | } | |
394 | ||
395 | if (routine->has_arg) | |
396 | routine->callback.cac (routine->arg); | |
397 | else | |
398 | routine->callback.ac (); | |
399 | ||
400 | if (g) | |
401 | g = _keymgr_get_and_lock_processwide_ptr (KEYMGR_ATEXIT_LIST); | |
402 | if (g) | |
403 | g = run_routines (g, base); | |
404 | if (g) | |
405 | { | |
406 | g->running_routines = prev_running; | |
407 | _keymgr_set_and_unlock_processwide_ptr (KEYMGR_ATEXIT_LIST, g); | |
408 | } | |
409 | } | |
410 | ||
411 | #ifdef __ppc__ | |
412 | /* This code is used while running on 10.3.9, when __cxa_atexit doesn't | |
413 | exist in the system library. 10.3.9 only supported regular PowerPC, | |
414 | so this code isn't necessary on x86 or ppc64. */ | |
415 | ||
416 | /* This routine is called from the system atexit(); it runs everything | |
417 | registered on the KEYMGR_ATEXIT_LIST. */ | |
418 | ||
419 | static void | |
420 | our_atexit (void) | |
421 | { | |
422 | struct keymgr_atexit_list *g; | |
423 | char prev_running; | |
424 | ||
425 | g = _keymgr_get_and_lock_processwide_ptr (KEYMGR_ATEXIT_LIST); | |
426 | if (! g || g->version != 0 || g->atexit_status != atexit_status_missing) | |
427 | return; | |
428 | ||
429 | prev_running = g->running_routines; | |
430 | g->running_routines = 1; | |
431 | g = run_routines (g, NULL); | |
432 | if (! g) | |
433 | return; | |
434 | g->running_routines = prev_running; | |
435 | _keymgr_set_and_unlock_processwide_ptr (KEYMGR_ATEXIT_LIST, g); | |
436 | } | |
437 | #endif | |
438 | ||
439 | /* This is our wrapper around atexit and __cxa_atexit. It will return | |
440 | nonzero if an error occurs, and otherwise: | |
441 | - if in_cxa_finalize is set, or running on 10.3.9, add R to | |
442 | KEYMGR_ATEXIT_LIST; or | |
443 | - call the system __cxa_atexit to add cxa_atexit_wrapper with an argument | |
444 | that indicates how cxa_atexit_wrapper should call R. */ | |
445 | ||
446 | static int | |
447 | atexit_common (const struct one_atexit_routine *r, const void *dso) | |
448 | { | |
449 | struct keymgr_atexit_list *g = get_globals (); | |
450 | ||
451 | if (! g) | |
452 | return -1; | |
453 | ||
454 | if (g->running_routines || g->atexit_status == atexit_status_missing) | |
455 | return add_routine (g, r); | |
456 | ||
457 | if (g->atexit_status >= atexit_status_working) | |
458 | { | |
459 | int result; | |
460 | if (r->has_arg) | |
747b9f55 | 461 | { |
895de5a8 GK |
462 | cxa_atexit_p cxa_atexit = g->cxa_atexit_f; |
463 | result = _keymgr_set_and_unlock_processwide_ptr (KEYMGR_ATEXIT_LIST, | |
464 | g); | |
465 | if (CHECK_KEYMGR_ERROR (result)) | |
466 | return -1; | |
467 | return cxa_atexit (r->callback.cac, r->arg, dso); | |
747b9f55 | 468 | } |
895de5a8 GK |
469 | else |
470 | { | |
471 | atexit_p atexit_f = g->atexit_f; | |
472 | result = _keymgr_set_and_unlock_processwide_ptr (KEYMGR_ATEXIT_LIST, | |
473 | g); | |
474 | if (CHECK_KEYMGR_ERROR (result)) | |
475 | return -1; | |
476 | return atexit_f (r->callback.ac); | |
477 | } | |
478 | } | |
479 | else | |
480 | { | |
481 | cxa_atexit_p cxa_atexit = g->cxa_atexit_f; | |
482 | struct one_atexit_routine *alloced; | |
483 | int result; | |
484 | ||
485 | result = _keymgr_set_and_unlock_processwide_ptr (KEYMGR_ATEXIT_LIST, g); | |
486 | if (CHECK_KEYMGR_ERROR (result)) | |
487 | return -1; | |
488 | ||
489 | alloced = malloc (sizeof (struct one_atexit_routine)); | |
490 | if (! alloced) | |
491 | return -1; | |
492 | *alloced = *r; | |
493 | return cxa_atexit (cxa_atexit_wrapper, alloced, dso); | |
747b9f55 GK |
494 | } |
495 | } | |
747b9f55 | 496 | |
895de5a8 GK |
497 | /* These are the actual replacement routines; they just funnel into |
498 | atexit_common. */ | |
499 | ||
500 | int __cxa_atexit (cxa_atexit_callback func, void* arg, | |
501 | const void* dso) __attribute__((visibility("hidden"))); | |
502 | ||
747b9f55 | 503 | int |
895de5a8 | 504 | __cxa_atexit (cxa_atexit_callback func, void* arg, const void* dso) |
747b9f55 | 505 | { |
895de5a8 GK |
506 | struct one_atexit_routine r; |
507 | r.callback.cac = func; | |
508 | r.has_arg = 1; | |
509 | r.arg = arg; | |
510 | return atexit_common (&r, dso); | |
511 | } | |
512 | ||
513 | int atexit (atexit_callback func) __attribute__((visibility("hidden"))); | |
514 | ||
b3c2c547 GK |
515 | /* Use __dso_handle to allow even bundles that call atexit() to be unloaded |
516 | on 10.4. */ | |
517 | extern void __dso_handle; | |
518 | ||
895de5a8 GK |
519 | int |
520 | atexit (atexit_callback func) | |
521 | { | |
522 | struct one_atexit_routine r; | |
523 | r.callback.ac = func; | |
524 | r.has_arg = 0; | |
b3c2c547 | 525 | return atexit_common (&r, &__dso_handle); |
747b9f55 | 526 | } |
3b414146 MS |
527 | |
528 | #endif /* __PIC__ */ |