#define _ALL_SOURCE #include "libc.h" #include #include #include /* Ensure that at least 32 atexit handlers can be registered without malloc */ #define COUNT 32 static struct fl { struct fl* next; void (*f[COUNT])(void*); void* a[COUNT]; } builtin, *head; static int slot; static mtx_t lock = MTX_INIT; // Phantom unlock to satisfy analysis when actually we leave it locked forever. __TA_RELEASE(&lock) __TA_NO_THREAD_SAFETY_ANALYSIS static void synchronize_exit(void) {} void __funcs_on_exit(void) { void (*func)(void*), *arg; mtx_lock(&lock); for (; head; head = head->next, slot = COUNT) { while (slot-- > 0) { func = head->f[slot]; arg = head->a[slot]; mtx_unlock(&lock); func(arg); mtx_lock(&lock); } } // Leaving this lock held effectively synchronizes the rest of exit after // we return to it. It's technically undefined behavior for the program // to enter exit twice no matter what, so worrying about it at all is just // trying to give the most useful possible result for a buggy program. Up // to this point, we gracefully handle multiple threads calling exit by // giving them a random interleaving of which thread runs the next atexit // hook. The rest of the teardown that exit does after this is presumed // to happen once in a single thread. So the most graceful way to // maintain orderly shutdown in a buggy program is to err on the side of // deadlock (if DSO destructors or stdio teardown try to synchronize with // another thread that's illegally trying to enter exit again). synchronize_exit(); } void __cxa_finalize(void* dso) {} int __cxa_atexit(void (*func)(void*), void* arg, void* dso) { mtx_lock(&lock); /* Defer initialization of head so it can be in BSS */ if (!head) head = &builtin; /* If the current function list is full, add a new one */ if (slot == COUNT) { struct fl* new_fl = calloc(sizeof(struct fl), 1); if (!new_fl) { mtx_unlock(&lock); return -1; } new_fl->next = head; head = new_fl; slot = 0; } /* Append function to the list. */ head->f[slot] = func; head->a[slot] = arg; slot++; mtx_unlock(&lock); return 0; } static void call(void* p) { ((void (*)(void))(uintptr_t)p)(); } // In an implementation where dlclose actually unloads a module and runs // its destructors, the third argument to __cxa_atexit must differ between // modules (that is, between the main executable and between each DSO) so // that dlclose can run the subset of destructors registered by that one // DSO's code. For C++ static destructors, the compiler generates the call: // __cxa_atexit(&destructor, &instance, &__dso_handle); // __dso_handle is defined with __attribute__((visibility("hidden"))) in // a special object crtbegin.o that is included implicitly in every link. // For the C atexit API to do the equivalent, atexit must be defined in // a small static library that is linked into things that dynamically link // in -lc; that's the only way for &__dso_handle to refer to the different // instance of that symbol in each module. // // Our dlclose doesn't actually do anything, so we never need to run a // subset of destructors before we run them all at actual process exit. // Hence, the third argument to __cxa_atexit is ignored and it doesn't // matter what we pass it; thus, we can include atexit in the -lc DSO // as we do here. int atexit(void (*func)(void)) { return __cxa_atexit(call, (void*)(uintptr_t)func, NULL); }