5 ms·
I believe this was changed everywhere because glibc maintainers decided that it made more sense to run at library unload time, and then everyone else had to cha
by gok 7y ago
I believe this was changed everywhere because glibc maintainers decided that it made more sense to run at library unload time, and then everyone else had to change their implementation to match "what Linux does."
That being said, none of these behaviors really make any sense. If the registered function isn't mapped into the process address space any more, what is supposed to happen?
- AnimalMuppet 7y agoSeems to me that what's "supposed to happen" is "make it so the situation can't happen". That is, either make it so that you cannot unload the library if some function is registered with atexit, or else make it so that unloading the library unregisters the atexit function. But of course, that can't happen, because you can have N function calls between the registered atexit function and the call to the library, and deciding if any calls to the library are anywhere in the graph and will be called is probably equivalent to the halting problem. I agree that none of the possibilities are what we actually want.
- gok 7y agoBonus: what happens when the function you register is JIT'd, so there's no unregistration code path when it's unloaded?
- kazinator 7y ago> because you can have N function calls between the registered atexit function and the call to the library, I don't think that's a problem. A simple sweep through the atexit list to see if any of the pointers are into this library, to block the unload. If some atexit-registered function not in that unloaded library has a secretly stashed pointer to a function in that library, and calls it during atexit, that's a programmer problem.
- NOTTHE1 7y agoReally? That's your story and your sticking to that ?
- kazinator 7y agoIf the registered function isn't mapped, then you get undefined behavior. GCC has local functions which build trampolines on the stack. If I register a trampoline with atexit() what should happen if the process exits after that stack frame is gone? Gee, let's be idiots and go on a crusade against this Important Problem: of course, GCC must be patched to generate code to look for atexit-registered trampolines every time a stack frame is popped and run them right there and then. The spec for atexit is very clear that it's process-termination: https://pubs.opengroup.org/onlinepubs/9699919799/functions/atexit.html https://pubs.opengroup.org/onlinepubs/9699919799/functions/a... The spec for dlclose doesn't say that it must forcefully unmap everything: https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlclose.html https://pubs.opengroup.org/onlinepubs/9699919799/functions/d... The basic description is "The dlclose() function shall inform the system that the symbol table handle specified by handle is no longer needed by the application." The "symbol table is not needed" doesn't mean "the chunks of memory referenced by function pointers, and any data they need is no longer needed". Those are not "symbol table" material. Also: "Although a dlclose() operation is not required to remove any functions or data objects from the address space, neither is an implementation prohibited from doing so."
- comex 7y ago> Although a dlclose() operation is not required to remove any functions or data objects from the address space, neither is an implementation prohibited from doing so. As far as I can tell, “neither is an implementation prohibited from doing so” does not have an exception for atexit. In other words, a POSIX compliant system is free to implement atexit in a straightforward way without any magic to check which library the function pointers come from. On such a system, if atexit is called with a function from a dlopen’d shared library, and that library is subsequently dlclose’d, the C library will cheerfully call the now-invalid function pointer at program exit, resulting in undefined behavior. As such, using atexit from libraries intended to be dlclose’d is fundamentally non-portable. Even if some system supported the “atexit keeps a library alive” behavior you’re asking for, any program relying on it would have to be very careful to ensure it only gets run on that system: unlike other non-portable features that can be tested for at compile-time or runtime, or at least fail cleanly if they’re not supported, this one would give you no warning it’s unsupported other than a segfault, if you’re lucky. And the system had better clearly document that it is guaranteeing that behavior forever, as opposed to it being an implementation detail that could change at any time. IMO, it’s a much better idea to just avoid that pattern altogether.