Run C++ global/static destructors during DLL_PROCESS_DETACH while other other dllimport functions they may want to call are still viable. While windows imposes many restrictions on what may be done in such destructors (given that the run inside the loader lock), there are lots of legal and useful kernel32 functions like DestroyCriticalSection, DeleteAtom, TlsFree, etc that are both useful and legal. Currently this does not work for builtin modules because all the Win32 structures are discarded well before NtUnmapViewOfSection finally does the dlllose.
Even 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)
Therefore, winegcc now always uses the DllMainCRTStartup entry point unless you specify your own --entry=func. Previously it did this only for PE modules using msvcrt. Making this default consistent matches cl.exe, which also always defaults to _DllMainCRTStartup unless overridden by /entry:foo https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbo...
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
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)
https://docs.microsoft.com/en-us/cpp/build/run-time-library-behavior?view=ms...
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).
-- v3: winecrt0: run C++ object destructors during module unload.
From: Kevin Puetz PuetzKevinA@JohnDeere.com
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-symbo...
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-symbo...
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)
https://docs.microsoft.com/en-us/cpp/build/run-time-library-behavior?view=ms...
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). --- dlls/winecrt0/Makefile.in | 1 + dlls/winecrt0/crt_dllmain.c | 13 ++++++++---- dlls/winecrt0/exe_entry.c | 9 +++++++- dlls/winecrt0/exe_wentry.c | 9 +++++++- dlls/winecrt0/finalize.c | 41 +++++++++++++++++++++++++++++++++++++ tools/winegcc/winegcc.c | 4 +++- 6 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 dlls/winecrt0/finalize.c
diff --git a/dlls/winecrt0/Makefile.in b/dlls/winecrt0/Makefile.in index 6915e69eb4b..da30ec2ddab 100644 --- a/dlls/winecrt0/Makefile.in +++ b/dlls/winecrt0/Makefile.in @@ -15,6 +15,7 @@ C_SRCS = \ exe_main.c \ exe_wentry.c \ exe_wmain.c \ + finalize.c \ register.c \ setjmp.c \ stub.c \ diff --git a/dlls/winecrt0/crt_dllmain.c b/dlls/winecrt0/crt_dllmain.c index 181760c884a..e0862c5dab1 100644 --- a/dlls/winecrt0/crt_dllmain.c +++ b/dlls/winecrt0/crt_dllmain.c @@ -18,16 +18,21 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
-#ifdef __WINE_PE_BUILD - #include <stdarg.h> #include <stdio.h> #include "windef.h" #include "winbase.h"
+extern void __wine_finalize_dso(void); + BOOL WINAPI DllMainCRTStartup( HINSTANCE inst, DWORD reason, void *reserved ) { - return DllMain( inst, reason, reserved ); -} + BOOL result = DllMain( inst, reason, reserved );
+#ifndef __WINE_PE_BUILD + if(reason == DLL_PROCESS_DETACH) { + __wine_finalize_dso(); + } #endif + return result; +} diff --git a/dlls/winecrt0/exe_entry.c b/dlls/winecrt0/exe_entry.c index d4d1d7d6757..49ebab0ff23 100644 --- a/dlls/winecrt0/exe_entry.c +++ b/dlls/winecrt0/exe_entry.c @@ -28,6 +28,7 @@ #include "winternl.h"
extern int main( int argc, char *argv[] ); +extern void __wine_finalize_dso(void);
static char **build_argv( const char *src, int *ret_argc ) { @@ -97,5 +98,11 @@ DWORD WINAPI DECLSPEC_HIDDEN __wine_spec_exe_entry( PEB *peb ) int argc; char **argv = build_argv( GetCommandLineA(), &argc );
- ExitProcess( main( argc, argv )); + int ret = main( argc, argv ); + +#ifndef __WINE_PE_BUILD + __wine_finalize_dso(); +#endif + + ExitProcess( ret ); } diff --git a/dlls/winecrt0/exe_wentry.c b/dlls/winecrt0/exe_wentry.c index a4c1a0897fb..15062de79a2 100644 --- a/dlls/winecrt0/exe_wentry.c +++ b/dlls/winecrt0/exe_wentry.c @@ -28,6 +28,7 @@ #include "winternl.h"
extern int wmain( int argc, WCHAR *argv[] ); +extern void __wine_finalize_dso(void);
static WCHAR **build_argv( const WCHAR *src, int *ret_argc ) { @@ -97,5 +98,11 @@ DWORD WINAPI DECLSPEC_HIDDEN __wine_spec_exe_wentry( PEB *peb ) int argc; WCHAR **argv = build_argv( GetCommandLineW(), &argc );
- ExitProcess( wmain( argc, argv )); + int ret = wmain( argc, argv ); + +#ifndef __WINE_PE_BUILD + __wine_finalize_dso(); +#endif + + ExitProcess( ret ); } diff --git a/dlls/winecrt0/finalize.c b/dlls/winecrt0/finalize.c new file mode 100644 index 00000000000..a7e2e353645 --- /dev/null +++ b/dlls/winecrt0/finalize.c @@ -0,0 +1,41 @@ +/* + * Cleanup hook before module (dll/exe) unload + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#ifndef __WINE_PE_BUILD +void __wine_finalize_dso(void) +{ + /* 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. */ + + extern void __cxa_finalize (void *) __attribute__((weak)); + extern void *__dso_handle __attribute((visibility("hidden"),weak)); + if(__cxa_finalize && &__dso_handle) { + __cxa_finalize(&__dso_handle); + } +} +#endif diff --git a/tools/winegcc/winegcc.c b/tools/winegcc/winegcc.c index ab26adb07e8..639c2af5587 100644 --- a/tools/winegcc/winegcc.c +++ b/tools/winegcc/winegcc.c @@ -1266,6 +1266,8 @@ static void build(struct options* opts) entry_point = (is_pe && opts->target.cpu == CPU_i386) ? "DriverEntry@8" : "DriverEntry"; else if (opts->use_msvcrt && !opts->shared && !opts->win16_app) entry_point = opts->unicode_app ? "wmainCRTStartup" : "mainCRTStartup"; + else if (opts->shared && !opts->win16_app) + entry_point = opts->target.cpu == CPU_i386 ? "DllMainCRTStartup@12" : "DllMainCRTStartup"; } else entry_point = opts->entry_point;
@@ -1303,7 +1305,7 @@ static void build(struct options* opts) for ( j = 0; j < lib_dirs.count; j++ ) strarray_add(&link_args, strmake("-L%s", lib_dirs.str[j]));
- if (is_pe && opts->use_msvcrt && !entry_point && (opts->shared || opts->win16_app)) + if (is_pe && opts->use_msvcrt && !entry_point && opts->win16_app) entry_point = opts->target.cpu == CPU_i386 ? "DllMainCRTStartup@12" : "DllMainCRTStartup";
if (is_pe && entry_point)
Updated to make the glibc calls from a `#pragma makedep unix` file, and updated the description to cover that this is for .exe.so files too, not just DllMain. Will remove the Draft marking after I retest this with our code that showed the original issues.
I must say I barely understand anything here, although I did touch DLL load/unload code a bit in the past. You mention the goal is to make .dll.so match PE DLLs better. Since the goal is to get rid of .dll.so files and build things as separate .dll and .so modules (e.g. like already done in ntdll), does your patch matter in the long run?
I know wine itself has been heading toward more separation of the PE/posix syscall approach. But I thought the motive for that was mostly about WoW64 thunks (let a 32-bit Win32 process make syscalls that invoke 64-bit linux dependencies, so the distribution doesn't need much in the way of 32-bit linux dependencies). I get why this is good for the parts of wine that are mimicking microsoft ntdll//win32u boundaries, but if the plan is to eventually remove all support for https://wiki.winehq.org/Winelib, that's news to me, and rather unwelcome news at that.
It would not be easy to migrate our usage to PE; to start with, wine doesn't come with a C++ standard library the way it comes with a replacement msvcrt.dll, and our target is a Yocto system that doesn't currently have mingw or clang. Now, adding more toolchains is certainly possible in principle, and I suppose wine does come with a runtime for the few parts of msvcp* that are not inline/templates, so it might be possible to use that along with headers from https://github.com/microsoft/STL (which is even MIT licensed). But that's a fair amount of work, and the result would be a C++ runtime whose ABI doesn't match anything else on the linux system.
So that wouldn't really suit our purpose as well as today's winelib setup does - in a .dll.so, one can use wineg++, and glibc, and libstdc++, and ends up with an environment in which the same code can more-or-less freely use Win32 API while also linking to and even inheriting from classes in native linux libraries. You have to be a little careful about calling conventions, but it's still very low-friction to have what is substantially a native linux class that also happens to do things like expose like COM/oleaut interfaces. This is not wine's own use case, but it's been a good fit for us. Turning every point that crosses those boundaries into a syscall would require a ton of boilerplate.
But it's certainly true that the less wine uses "builtin" DLLs itself, the less relevant this patch is to wine itself. And it started out being not especially important since wine doesn't write its dlls in c++, either. This is not quite a c++-only thing - libc_nonshared.a uses it to implement a few things like atexit or at_quick_exit too, and wine seems to have a few users of that (they might all be msvcrt by now, though). But C++ destructors are the main case where idiomatic [RAII](https://en.cppreference.com/w/cpp/language/raii)code uses lots of user-defined code registered to run at module unload.
So this is definitely a patch we want; it lets c++ code that works in mingw or msvc also work in wineg++, by giving the library unload the same sequence of events it has in PE (atexit functions and destructors run during DLL_PROCESS_DETACH, and this is achieved using a DllMainCrtStartup). And it wasn't very invasive, and makes for one less thing that is subtly different between .dll.so and .dll files. So it seemed worth proposing.