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)