Draft: winecrt0: run C++ object destructors during module unload.
Execute any user-defined cleanups (e.g. C++ destructors) registered via the Itanium C++ ABI early enough that they can still safely call dllimport functions from other Win32 modules this module depended on.
Win32 imposes many restrictions on what may be done in such destructors (given that the run inside the loader lock), but there are lots of kernel32 functions like DestroyCriticalSection, DeleteAtom, TlsFree, etc that are both useful and legal. Previously this did not work for builtin modules because all the Win32 structures are discarded well before NtUnmapViewOfSection finally does the dlclose that ran C++ finalizers.
For a winelib .dll.so module, it would be preferable for destructors to execute during process_detach (before wine tears down the MODREF and detaches dependant dlls), rather than be left until after the last NtUnmapViewOfSection (when we finally reach dlclose).
In a PE module (whether build with mingw-gcc or MSVC), c++ destructors are run by a DllMainCRTStartup provided in the compiler runtime. This is the default entry point unless overridden with your own --entry=func. winegcc now does this for ELF, matching what it already did for PE builds, and MSVC's defaults to _DllMainCRTStartup unless overridden by /entry:foo https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol?view=msvc-170
the ELF version of libwinecrt0.a now provides this, calling __cxa_finalize to trigger destructors queued by the underlying g++ at the same point in time. winegcc now defaults to this unless overridden with your own --entry=func, matching what it already did for PE builds, and consistent with MSVC (which also defaults to _DllMainCRTStartup unless overridden by /entry:foo) https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol?view=msvc-170
The ELF version of winecrt0.a now provides a DllMainCRTStartup which, per the Itanium ABI that is in practice what is used by gcc and clang, performs this this destruction by calling __cxa_finalize(&__dso_handle). This libc function is required to be idempotent, so it's OK that dlclose still calls it again later (there will just be no further work to do).
Multiple calls to __cxa_finalize shall not result in calling termination function entries multiple times; the implementation may either remove entries or mark them finished.
https://itanium-cxx-abi.github.io/cxx-abi/abi.html#dso-dtor-runtime-api
Wine builds executables as .exe.so files to better emulate the windows HMODULE concept, so their their C++ destructors are also queued up via __cxa_atexit in that .so, just like DLLs. So while there is no DLL_PROCESS_DETACH, __cxa_finalize should run after main returns, but before Windows ExitProcess() tears down the Win32 PEB.
This has two main effects; it moves ELF destructors earlier (before imports are unmapped), and it moves them inside the Nt loader lock. Being earlier was the intended goal, and moving them inside the lock seems fine. Any Win32 API calls in destructors are just being subjected to the same lock hierarchy rules as usual on windows (MSVC also runs destructors from DllMainCrtStartup)
And any purely-ELF destructors that happen to also run earlier should never call functions exported from wine (and thus don't care about ntdll's locks).