[PATCH v5 0/2] MR9265: crt: Run MingW and MSVC constructors/destructors
There are several motivations to enable ctor/dtor support: 1. ASan. This is not just about my wine-asan work, this is important for @bernhardu's PE asan work as well. The compiler generates ctors that poisons memory around global variables so misuses of them can be caught. This is the same both of our implementations of ASan. 2. Building third-party codebases against wine's CRT. This is not necessary for mingw-gcc or llvm-mingw, but the MSVC frontend of clang is another option for cross-compiling to Windows. However right now that requires Windows SDK. Bringing wine CRT to the point of being functional enough will give us another option. Could be useful for things like Proton. 3. (Controversial) Many dlls perform init/deinit tasks in `DllMain`. Some of them might benefit from a conversion to ctor/dtor. Since nothing in wine uses ctor/dtor right now, this change shouldn't have any impact by itself. -- v5: msvcrt: Run ctors and dtors from mainCRTStartup. winecrt0: Run ctors and dtors from DllMainCRTStartup. https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
From: Yuxuan Shui <yshui(a)codeweavers.com> --- dlls/winecrt0/crt_dllmain.c | 79 ++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/dlls/winecrt0/crt_dllmain.c b/dlls/winecrt0/crt_dllmain.c index 181760c884a..7cdd24c4516 100644 --- a/dlls/winecrt0/crt_dllmain.c +++ b/dlls/winecrt0/crt_dllmain.c @@ -22,12 +22,89 @@ #include <stdarg.h> #include <stdio.h> +#include <process.h> #include "windef.h" #include "winbase.h" +#if defined(__GNUC__) +/* Reference: mingw-w64-crt: crt/gccmain.c */ +typedef void (*fnptr)(void); +extern fnptr __CTOR_LIST__[]; +extern fnptr __DTOR_LIST__[]; +#elif defined(_MSC_VER) +/* Sections named .CRT$??? in objects files are automatically merged into .CRT in the + * final binary during linking, sorted by the section names alphabetically. The names + * don't have significance to the linker but conventions do apply. */ +#pragma section(".CRT$XIA", read, write) +#pragma section(".CRT$XIZ", read, write) +#pragma section(".CRT$XCA", read, write) +#pragma section(".CRT$XCZ", read, write) +#pragma section(".CRT$XTA", read, write) +#pragma section(".CRT$XTZ", read, write) +#pragma comment(linker, "/merge:.CRT=.rdata") +__declspec(allocate(".CRT$XIA")) _PIFV __xi_a[] = { NULL }; +__declspec(allocate(".CRT$XIZ")) _PIFV __xi_z[] = { NULL }; +__declspec(allocate(".CRT$XCA")) _PVFV __xc_a[] = { NULL }; +__declspec(allocate(".CRT$XCZ")) _PVFV __xc_z[] = { NULL }; +__declspec(allocate(".CRT$XTA")) _PVFV __xt_a[] = { NULL }; +__declspec(allocate(".CRT$XTZ")) _PVFV __xt_z[] = { NULL }; +#endif + +static inline void initterm( _PVFV *start,_PVFV *end ) +{ + _PVFV* current = start; + + while (current<end) + { + if (*current) + (**current)(); + current++; + } +} + +static inline int initterm_e( _PIFV *table, _PIFV *end ) +{ + int res = 0; + + while (!res && table < end) { + if (*table) + res = (**table)(); + table++; + } + return res; +} + +static void do_global_ctors(void) +{ +#if defined(__GNUC__) + ULONG_PTR nfns = (ULONG_PTR)__CTOR_LIST__[0], i; + if (nfns == (ULONG_PTR)-1) + for (nfns = 0; __CTOR_LIST__[nfns + 1]; nfns++); + for (i = nfns; i >= 1; i--) __CTOR_LIST__[i](); +#elif defined(_MSC_VER) + if (initterm_e( __xi_a, __xi_z ) != 0) return; + initterm( __xc_a, __xc_z ); +#endif +} + + +static void do_global_dtors(void) +{ +#if defined(__GNUC__) + SIZE_T i; + for (i = 1; __DTOR_LIST__[i]; i++) __DTOR_LIST__[i](); +#elif defined(_MSC_VER) + initterm( __xt_a, __xt_z ); +#endif +} + BOOL WINAPI DllMainCRTStartup( HINSTANCE inst, DWORD reason, void *reserved ) { - return DllMain( inst, reason, reserved ); + BOOL ret; + if (reason == DLL_PROCESS_ATTACH) do_global_ctors(); + ret = DllMain( inst, reason, reserved ); + if (reason == DLL_PROCESS_DETACH) do_global_dtors(); + return ret; } #endif -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
From: Yuxuan Shui <yshui(a)codeweavers.com> --- dlls/msvcr100/Makefile.in | 1 + dlls/msvcr110/Makefile.in | 1 + dlls/msvcr120/Makefile.in | 1 + dlls/msvcr70/Makefile.in | 1 + dlls/msvcr71/Makefile.in | 1 + dlls/msvcr80/Makefile.in | 1 + dlls/msvcr90/Makefile.in | 1 + dlls/msvcrt/Makefile.in | 1 + dlls/msvcrt/crt_init.c | 72 +++++++++++++++++++++++++++++++++++++++ dlls/msvcrt/crt_main.c | 2 ++ dlls/msvcrt/crt_wmain.c | 2 ++ dlls/ucrtbase/Makefile.in | 1 + 12 files changed, 85 insertions(+) create mode 100644 dlls/msvcrt/crt_init.c diff --git a/dlls/msvcr100/Makefile.in b/dlls/msvcr100/Makefile.in index a0a3f64d6bf..34182e69f19 100644 --- a/dlls/msvcr100/Makefile.in +++ b/dlls/msvcr100/Makefile.in @@ -10,6 +10,7 @@ SOURCES = \ console.c \ cpp.c \ crt_gccmain.c \ + crt_init.c \ crt_main.c \ crt_winmain.c \ crt_wmain.c \ diff --git a/dlls/msvcr110/Makefile.in b/dlls/msvcr110/Makefile.in index e6798427795..fdbcba054d4 100644 --- a/dlls/msvcr110/Makefile.in +++ b/dlls/msvcr110/Makefile.in @@ -10,6 +10,7 @@ SOURCES = \ console.c \ cpp.c \ crt_gccmain.c \ + crt_init.c \ crt_main.c \ crt_winmain.c \ crt_wmain.c \ diff --git a/dlls/msvcr120/Makefile.in b/dlls/msvcr120/Makefile.in index b233753fa2e..3465d77a31e 100644 --- a/dlls/msvcr120/Makefile.in +++ b/dlls/msvcr120/Makefile.in @@ -10,6 +10,7 @@ SOURCES = \ console.c \ cpp.c \ crt_gccmain.c \ + crt_init.c \ crt_main.c \ crt_winmain.c \ crt_wmain.c \ diff --git a/dlls/msvcr70/Makefile.in b/dlls/msvcr70/Makefile.in index 9413738ae1f..be93d88764b 100644 --- a/dlls/msvcr70/Makefile.in +++ b/dlls/msvcr70/Makefile.in @@ -9,6 +9,7 @@ SOURCES = \ console.c \ cpp.c \ crt_gccmain.c \ + crt_init.c \ crt_main.c \ crt_winmain.c \ crt_wmain.c \ diff --git a/dlls/msvcr71/Makefile.in b/dlls/msvcr71/Makefile.in index 6e0b855cac3..c59ce97e8e4 100644 --- a/dlls/msvcr71/Makefile.in +++ b/dlls/msvcr71/Makefile.in @@ -9,6 +9,7 @@ SOURCES = \ console.c \ cpp.c \ crt_gccmain.c \ + crt_init.c \ crt_main.c \ crt_winmain.c \ crt_wmain.c \ diff --git a/dlls/msvcr80/Makefile.in b/dlls/msvcr80/Makefile.in index 2a84383f79d..78accf161e9 100644 --- a/dlls/msvcr80/Makefile.in +++ b/dlls/msvcr80/Makefile.in @@ -9,6 +9,7 @@ SOURCES = \ console.c \ cpp.c \ crt_gccmain.c \ + crt_init.c \ crt_main.c \ crt_winmain.c \ crt_wmain.c \ diff --git a/dlls/msvcr90/Makefile.in b/dlls/msvcr90/Makefile.in index d476d2e3080..3dfc080e554 100644 --- a/dlls/msvcr90/Makefile.in +++ b/dlls/msvcr90/Makefile.in @@ -9,6 +9,7 @@ SOURCES = \ console.c \ cpp.c \ crt_gccmain.c \ + crt_init.c \ crt_main.c \ crt_winmain.c \ crt_wmain.c \ diff --git a/dlls/msvcrt/Makefile.in b/dlls/msvcrt/Makefile.in index aa068b62e51..a4023568d71 100644 --- a/dlls/msvcrt/Makefile.in +++ b/dlls/msvcrt/Makefile.in @@ -9,6 +9,7 @@ SOURCES = \ console.c \ cpp.c \ crt_gccmain.c \ + crt_init.c \ crt_main.c \ crt_winmain.c \ crt_wmain.c \ diff --git a/dlls/msvcrt/crt_init.c b/dlls/msvcrt/crt_init.c new file mode 100644 index 00000000000..44af867ebea --- /dev/null +++ b/dlls/msvcrt/crt_init.c @@ -0,0 +1,72 @@ +/* Utility functions for calling MSVC/MingW ctors & dtors from mainCRTStartup. */ + +#if 0 +#pragma makedep implib +#endif + +#include <process.h> +#include <stdlib.h> +#include "minwindef.h" + +#if defined(__GNUC__) +/* Reference: mingw-w64-crt: crt/gccmain.c */ +typedef void (*fnptr)(void); +extern fnptr __CTOR_LIST__[]; +extern fnptr __DTOR_LIST__[]; +#elif defined(_MSC_VER) +extern _PIFV __xi_a[]; +extern _PIFV __xi_z[]; +extern _PVFV __xc_a[]; +extern _PVFV __xc_z[]; +extern _PVFV __xt_a[]; +extern _PVFV __xt_z[]; +#endif + +#if defined(_MSC_VER) +static inline int INIT_initterm_e(_PIFV *table, _PIFV *end) +{ +#if _MSVCR_VER<80 + int res = 0; + + while (!res && table < end) { + if (*table) + res = (**table)(); + table++; + } + return res; +#else + return _initterm_e(table, end); +#endif +} + +void CDECL _initterm(_PVFV *,_PVFV *); +#endif + +static __cdecl void do_global_dtors(void) +{ +#if defined(__GNUC__) + SIZE_T i; + for (i = 1; __DTOR_LIST__[i]; i++) __DTOR_LIST__[i](); +#elif defined(_MSC_VER) + _initterm(__xt_a, __xt_z); +#endif +} + +void do_global_ctors(void) +{ +#if defined(__GNUC__) + ULONG_PTR nfns = (ULONG_PTR)__CTOR_LIST__[0], i; + if (nfns == (ULONG_PTR)-1) + for (nfns = 0; __CTOR_LIST__[nfns + 1]; nfns++); + for (i = nfns; i >= 1; i--) __CTOR_LIST__[i](); +#elif defined(_MSC_VER) + if (INIT_initterm_e(__xi_a, __xi_z) != 0) return; + _initterm(__xc_a, __xc_z); +#endif + +#ifdef _UCRT + _crt_atexit(do_global_dtors); +#else + _onexit((_onexit_t)do_global_dtors); +#endif +} diff --git a/dlls/msvcrt/crt_main.c b/dlls/msvcrt/crt_main.c index eb785e30bb0..5eb9e5032a0 100644 --- a/dlls/msvcrt/crt_main.c +++ b/dlls/msvcrt/crt_main.c @@ -31,6 +31,7 @@ #include "winternl.h" int __cdecl main(int argc, char **argv, char **env); +void do_global_ctors( void ); static const IMAGE_NT_HEADERS *get_nt_header( void ) { @@ -54,6 +55,7 @@ int __cdecl mainCRTStartup(void) __getmainargs(&argc, &argv, &env, 0, &new_mode); #endif _set_app_type(get_nt_header()->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI ? _crt_gui_app : _crt_console_app); + do_global_ctors(); ret = main(argc, argv, env); diff --git a/dlls/msvcrt/crt_wmain.c b/dlls/msvcrt/crt_wmain.c index 79755d1a6e9..71cc0e48ec7 100644 --- a/dlls/msvcrt/crt_wmain.c +++ b/dlls/msvcrt/crt_wmain.c @@ -31,6 +31,7 @@ #include "winternl.h" int __cdecl wmain(int argc, WCHAR **argv, WCHAR **env); +void do_global_ctors(void); static const IMAGE_NT_HEADERS *get_nt_header( void ) { @@ -54,6 +55,7 @@ int __cdecl wmainCRTStartup(void) __wgetmainargs(&argc, &argv, &env, 0, &new_mode); #endif _set_app_type(get_nt_header()->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI ? _crt_gui_app : _crt_console_app); + do_global_ctors(); ret = wmain(argc, argv, env); diff --git a/dlls/ucrtbase/Makefile.in b/dlls/ucrtbase/Makefile.in index a3bb54750af..1bd1073dcf1 100644 --- a/dlls/ucrtbase/Makefile.in +++ b/dlls/ucrtbase/Makefile.in @@ -9,6 +9,7 @@ SOURCES = \ console.c \ cpp.c \ crt_gccmain.c \ + crt_init.c \ crt_main.c \ crt_winmain.c \ crt_wmain.c \ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
On Fri Oct 24 22:26:02 2025 +0000, Yuxuan Shui wrote:
thanks, i'll look into how XI? is used and add it as well. XL? is used for thread locals IIUC so we can leave that aside for now since wine don't use those. XT? is for destructors. Thanks for taking care about this use case. I rebased on top of the latest version of your patches, dropped my modification, and it works like charm.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_119726
Jacek Caban (@jacek) commented about dlls/winecrt0/crt_dllmain.c:
#include <stdarg.h> #include <stdio.h> +#include <process.h> #include "windef.h" #include "winbase.h"
+#if defined(__GNUC__) +/* Reference: mingw-w64-crt: crt/gccmain.c */ +typedef void (*fnptr)(void); +extern fnptr __CTOR_LIST__[]; +extern fnptr __DTOR_LIST__[]; +#elif defined(_MSC_VER)
Those static libs are shipped in Wine and we don't really know what environment they are used with, so we aim to make those things work with mingw or clang msvc regardless of the compiler used to build Wine itself. In this case, even mingw-w64 supports both `.CRT` section and GCC-style constructor lists. Do you need GCC-style constructors for something? Maybe we could just make `.CRT` section work for everyone? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_119823
even mingw-w64 supports both `.CRT` section and GCC-style constructor lists. Do you need GCC-style constructors for something?
mingw-gcc generates GCC-style constructors for ASan, do you mean there is a way maybe a compile flag to make mingw-gcc use .CRT instead? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_119834
On Tue Oct 28 07:03:26 2025 +0000, Yuxuan Shui wrote:
even mingw-w64 supports both `.CRT` section and GCC-style constructor lists. Do you need GCC-style constructors for something? mingw-gcc generates GCC-style constructors for ASan, do you mean there is a way maybe a compile flag to make mingw-gcc use .CRT instead? Sorry for the delay. My main concern is that those decisions are made at Wine build time, yet we don't know how the resulting static libraries will be used. For all we know, a Wine built with GCC might be used with `winegcc` for Clang/MSVC-style builds and things would mostly work. There are subtle differences that won't, and as we extend the CRT it will become harder to maintain conditionals like these, so it'd be nice to avoid `#ifdef`s here where possible.
In this particular case, the `#ifdef` also doesn’t match how the mingw CRT behaves. The mingw CRT supports `.CRT` sections, so from the CRT point of view both GCC-style and MSVC-style constructors work. This becomes even more important for TLS constructors, which I believe only work with `.CRT` sections. Always supporting `.CRT` should be straightforward, but doing the same for `__CTOR_LIST__` is trickier because it’s undefined in MSVC mode. We could maybe provide a fallback using weak symbols, I'm not sure without trying. My question was mostly in hope that we can ignore `__CTOR_LIST__` for the first iteration and just get the `.CRT` support right. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124025
On Fri Nov 28 16:47:57 2025 +0000, Jacek Caban wrote:
Sorry for the delay. My main concern is that those decisions are made at Wine build time, yet we don't know how the resulting static libraries will be used. For all we know, a Wine built with GCC might be used with `winegcc` for Clang/MSVC-style builds and things would mostly work. There are subtle differences that won't, and as we extend the CRT it will become harder to maintain conditionals like these, so it'd be nice to avoid `#ifdef`s here where possible. In this particular case, the `#ifdef` also doesn’t match how the mingw CRT behaves. The mingw CRT supports `.CRT` sections, so from the CRT point of view both GCC-style and MSVC-style constructors work. This becomes even more important for TLS constructors, which I believe only work with `.CRT` sections. Always supporting `.CRT` should be straightforward, but doing the same for `__CTOR_LIST__` is trickier because it’s undefined in MSVC mode. We could maybe provide a fallback using weak symbols, I'm not sure without trying. My question was mostly in hope that we can ignore `__CTOR_LIST__` for the first iteration and just get the `.CRT` support right. Alright I will do some experiments with weak symbols on mingw, if that doesn't work out I'll drop gcc ctor/dtor list support for now.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124044
On Fri Nov 28 20:22:18 2025 +0000, Yuxuan Shui wrote:
Alright I will do some experiments with weak symbols on mingw, if that doesn't work out I'll drop gcc ctor/dtor list support for now. There is also a link order issue if we want to support `.CRT` on mingw. `lld` doesn't care about object file orders so it works, but GNU ld does. And `libmsvcrt.a` can't use `__xc_a` etc. from `libwinecrt0.a`. Moving definitions into `crt_init.c` doesn't work because `msvcr*.dll` naturally aren't linked with `libmsvcrt.a`. So I think I need to add a new staticlib specifically for these definitions.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124519
On Tue Dec 2 23:34:59 2025 +0000, Yuxuan Shui wrote:
There is also a link order issue if we want to support `.CRT` on mingw. `lld` doesn't care about object file orders so it works, but GNU ld does care. Which means `libmsvcrt.a` can't use `__xc_a` etc. from `libwinecrt0.a`. Moving definitions into `dlls/msvcrt/crt_init.c` doesn't work either because `msvcr*.dll` naturally aren't linked with `libmsvcrt.a` (in addition to a few other ones). So I think I need to add a new staticlib specifically for these definitions, and link that staticlib into everything. Yeah, it is one of those mingw annoyances. For compiler driver default libs, mingw works around it by repeating default imports. In my case, GCC uses "-lmingw32 -lgcc -lmoldname -lmingwex -lmsvcrt -lkernel32 -lpthread -ladvapi32 -lshell32 -luser32 -lkernel32 -lmingw32 -lgcc -lmoldname -lmingwex -lmsvcrt -lkernel32". Note how mingw32, mingwex, and msvcrt (and a few others) are repeated. That is exactly to allow calls between mingwex and msvcrt.
In Wine, we do not use compiler default libs and specify them ourselves. We have been fine without such tricks so far, but as we extend our CRT, I expect it to become increasingly problematic, so maybe we should just do the same with winecrt0. FWIW, in the broader context of how linkers handle these things, MSVC has yet another behavior, somewhere between binutils and LLD. There are attempts to make LLD more compatible, but it is tricky: https://github.com/llvm/llvm-project/pull/85290. Overall, it is probably a good idea to avoid relying on specific linker behavior in these areas. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124540
participants (4)
-
Bernhard Übelacker -
Jacek Caban (@jacek) -
Yuxuan Shui -
Yuxuan Shui (@yshui)