[PATCH v6 0/2] MR9265: crt: Run MSVC constructors and 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. -- v6: crt: Run MSVC constructors and destructors. winecrtend: Add a staticlib for holding ctor/dtor section pointers. https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
From: Yuxuan Shui <yshui(a)codeweavers.com> These pointers are needed to run constructors/destructors from mainCRTStartup and DllMainCRTStartup, which are in libmsvcrt.a and libwinecrt0.a. And since libwinecrt0.a depends on crt, we can't put these into libwinecrt0.a. On the other hand some libraries don't link against libmsvcrt.a, so putting these pointers into libmsvcrt.a isn't a very good idea either. This commit adds a separate staticlib for this that both libmsvcrt.a and libwinecrt0.a can depend on. --- configure | 2 ++ configure.ac | 1 + dlls/winecrtend/Makefile.in | 3 +++ dlls/winecrtend/init.c | 28 ++++++++++++++++++++++++++++ tools/makedep.c | 2 ++ tools/winegcc/winegcc.c | 1 + 6 files changed, 37 insertions(+) create mode 100644 dlls/winecrtend/Makefile.in create mode 100644 dlls/winecrtend/init.c diff --git a/configure b/configure index 01538eefdc2..a859c58c903 100755 --- a/configure +++ b/configure @@ -1543,6 +1543,7 @@ enable_winebth_sys enable_winebus_sys enable_winecoreaudio_drv enable_winecrt0 +enable_winecrtend enable_wined3d enable_winedmo enable_winegstreamer @@ -23219,6 +23220,7 @@ wine_fn_config_makefile dlls/winebth.sys enable_winebth_sys wine_fn_config_makefile dlls/winebus.sys enable_winebus_sys wine_fn_config_makefile dlls/winecoreaudio.drv enable_winecoreaudio_drv wine_fn_config_makefile dlls/winecrt0 enable_winecrt0 +wine_fn_config_makefile dlls/winecrtend enable_winecrtend wine_fn_config_makefile dlls/wined3d enable_wined3d wine_fn_config_makefile dlls/winedmo enable_winedmo wine_fn_config_makefile dlls/winegstreamer enable_winegstreamer diff --git a/configure.ac b/configure.ac index 06a3a820e4c..d3d27cff36f 100644 --- a/configure.ac +++ b/configure.ac @@ -3359,6 +3359,7 @@ WINE_CONFIG_MAKEFILE(dlls/winebth.sys) WINE_CONFIG_MAKEFILE(dlls/winebus.sys) WINE_CONFIG_MAKEFILE(dlls/winecoreaudio.drv) WINE_CONFIG_MAKEFILE(dlls/winecrt0) +WINE_CONFIG_MAKEFILE(dlls/winecrtend) WINE_CONFIG_MAKEFILE(dlls/wined3d) WINE_CONFIG_MAKEFILE(dlls/winedmo) WINE_CONFIG_MAKEFILE(dlls/winegstreamer) diff --git a/dlls/winecrtend/Makefile.in b/dlls/winecrtend/Makefile.in new file mode 100644 index 00000000000..bf09dcca8cc --- /dev/null +++ b/dlls/winecrtend/Makefile.in @@ -0,0 +1,3 @@ +STATICLIB = libwinecrtend.a + +SOURCES = init.c diff --git a/dlls/winecrtend/init.c b/dlls/winecrtend/init.c new file mode 100644 index 00000000000..caf82cbbd1a --- /dev/null +++ b/dlls/winecrtend/init.c @@ -0,0 +1,28 @@ +#include <process.h> + +#if defined(_MSC_VER) +#define _CRTALLOC(x) __declspec(allocate(x)) +#elif defined(__GNUC__) +#define _CRTALLOC(x) __attribute__ ((section (x), used)) +#endif + +#if defined(_MSC_VER) +/* Instructions for the linker to merge .CRT$??? in objects files 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") +#endif + +_CRTALLOC(".CRT$XIA") _PIFV __xi_a[] = { NULL }; +_CRTALLOC(".CRT$XIZ") _PIFV __xi_z[] = { NULL }; +_CRTALLOC(".CRT$XCA") _PVFV __xc_a[] = { NULL }; +_CRTALLOC(".CRT$XCZ") _PVFV __xc_z[] = { NULL }; +_CRTALLOC(".CRT$XTA") _PVFV __xt_a[] = { NULL }; +_CRTALLOC(".CRT$XTZ") _PVFV __xt_z[] = { NULL }; + diff --git a/tools/makedep.c b/tools/makedep.c index 4e73894c933..02180dc58ef 100644 --- a/tools/makedep.c +++ b/tools/makedep.c @@ -2281,6 +2281,7 @@ static struct strarray get_default_imports( const struct makefile *make, struct STRARRAY_FOR_EACH( imp, &imports ) if (!strcmp( imp, "winecrt0" )) return ret; strarray_add( &ret, "winecrt0" ); if (compiler_rt) strarray_add( &ret, compiler_rt ); + strarray_add( &ret, "winecrtend" ); return ret; } @@ -2295,6 +2296,7 @@ static struct strarray get_default_imports( const struct makefile *make, struct strarray_add( &ret, "kernel32" ); strarray_add( &ret, "ntdll" ); + strarray_add( &ret, "winecrtend" ); return ret; } diff --git a/tools/winegcc/winegcc.c b/tools/winegcc/winegcc.c index 7458fb08773..d757f53a2f7 100644 --- a/tools/winegcc/winegcc.c +++ b/tools/winegcc/winegcc.c @@ -1333,6 +1333,7 @@ static void build(struct strarray input_files, const char *output) if (is_win16_app) add_library(lib_dirs, &files, "kernel"); add_library(lib_dirs, &files, "kernel32"); add_library(lib_dirs, &files, "ntdll"); + add_library(lib_dirs, &files, "winecrtend"); } /* set default entry point, if needed */ -- 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 | 51 +++++++++++++++++++++++++++++++++++++ dlls/msvcrt/crt_main.c | 2 ++ dlls/msvcrt/crt_wmain.c | 2 ++ dlls/ucrtbase/Makefile.in | 1 + dlls/winecrt0/crt_dllmain.c | 50 +++++++++++++++++++++++++++++++++++- 13 files changed, 113 insertions(+), 1 deletion(-) 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..5dc64c5882b --- /dev/null +++ b/dlls/msvcrt/crt_init.c @@ -0,0 +1,51 @@ +/* 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" + +extern _PIFV __xi_a[]; +extern _PIFV __xi_z[]; +extern _PVFV __xc_a[]; +extern _PVFV __xc_z[]; +extern _PVFV __xt_a[]; +extern _PVFV __xt_z[]; + +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 *); + +static __cdecl void do_global_dtors(void) +{ + _initterm(__xt_a, __xt_z); +} + +void do_global_ctors(void) +{ + if (INIT_initterm_e(__xi_a, __xi_z) != 0) return; + _initterm(__xc_a, __xc_z); + +#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 \ diff --git a/dlls/winecrt0/crt_dllmain.c b/dlls/winecrt0/crt_dllmain.c index 181760c884a..0b27189b98c 100644 --- a/dlls/winecrt0/crt_dllmain.c +++ b/dlls/winecrt0/crt_dllmain.c @@ -22,12 +22,60 @@ #include <stdarg.h> #include <stdio.h> +#include <process.h> #include "windef.h" #include "winbase.h" +extern _PIFV __xi_a[]; +extern _PIFV __xi_z[]; +extern _PVFV __xc_a[]; +extern _PVFV __xc_z[]; +extern _PVFV __xt_a[]; +extern _PVFV __xt_z[]; + +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 (initterm_e( __xi_a, __xi_z ) != 0) return; + initterm( __xc_a, __xc_z ); +} + + +static void do_global_dtors(void) +{ + initterm( __xt_a, __xt_z ); +} + 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
On Wed Dec 3 13:01:13 2025 +0000, Yuxuan Shui wrote:
changed this line in [version 6 of the diff](/wine/wine/-/merge_requests/9265/diffs?diff_id=229460&start_sha=610c8286caf12df8ad6207a8cbfdfd5b135899b5#0719c2159befb363c9a867ff09219d50ac8dc799_34_29) I went for an additional staticlib. If you think repeating libwinecrt0 will be better I can change it to that.
And I split out CTOR/DTOR_LIST stuff for later. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124545
On Wed Dec 3 13:02:04 2025 +0000, Yuxuan Shui wrote:
I went for an additional staticlib. If you think repeating libwinecrt0 will be better I can change it to that. And I split out CTOR/DTOR_LIST stuff for later. Could we just have it in ntdll's importlib instead of a new library?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124623
Jacek Caban (@jacek) commented about dlls/winecrtend/init.c:
+#if defined(_MSC_VER) +#define _CRTALLOC(x) __declspec(allocate(x)) +#elif defined(__GNUC__) +#define _CRTALLOC(x) __attribute__ ((section (x), used)) +#endif + +#if defined(_MSC_VER) +/* Instructions for the linker to merge .CRT$??? in objects files 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) R+W is the default, so I think we can skip this.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124626
Jacek Caban (@jacek) commented about dlls/winecrtend/init.c:
+#define _CRTALLOC(x) __declspec(allocate(x)) +#elif defined(__GNUC__) +#define _CRTALLOC(x) __attribute__ ((section (x), used)) +#endif + +#if defined(_MSC_VER) +/* Instructions for the linker to merge .CRT$??? in objects files 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") Unfortunately, binutils linker doesn't seem to understand such directives. We could probably just pass it with `-Wl,...` in winegcc only for MSVC target.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124627
On Wed Dec 3 23:33:08 2025 +0000, Jacek Caban wrote:
Unfortunately, binutils linker doesn't seem to understand such directives. We could probably just pass it with `-Wl,...` in winegcc only for MSVC target. errmm, why is this a problem? mingw ld has a linker script that merges the .CRT sections so this is not needed.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124639
On Wed Dec 3 23:33:08 2025 +0000, Jacek Caban wrote:
R+W is the default, so I think we can skip this. ok, i think these are actually wrong. should've been `long, read` instead.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124642
On Wed Dec 3 23:23:40 2025 +0000, Jacek Caban wrote:
Could we just have it in ntdll's importlib instead of a new library? Are there things that don't link with ntdll?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124643
On Thu Dec 4 02:39:42 2025 +0000, Yuxuan Shui wrote:
Are there things that don't link with ntdll? Also adding more symbols to ntdll that doesn't exist on native just feels a bit iffy to me.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124645
On Thu Dec 4 03:07:27 2025 +0000, Yuxuan Shui wrote:
Also adding more symbols to ntdll that doesn't exist on native just feels a bit iffy to me. Thinking more about it, we could just use winebuild, similar to `_load_config_used`. This would avoid entire problem and we'd not need section pragmas.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124681
On Thu Dec 4 02:34:21 2025 +0000, Yuxuan Shui wrote:
errmm, why is this a problem? mingw ld has a linker script that merges the .CRT sections so this is not needed. It is the same problem as with other `#ifdef`s. The way the comment pragma works is that it embeds the string into the object file for the linker to interpret. While that is fine for LLD, even in mingw mode, binutils understands only some of it. If you try to link this object file from a Clang Wine build using GCC, you will get a warning like:
Warning: corrupt .drectve at end of def file
In winegcc, we know whether the target is mingw or MSVC, and we can just pass it via the command line there. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124682
@jacek hope I understand you correctly, you suggestion is to: 1. instead of using `#progma section` and `__declspec(allocate(..))`, we generate these `__xi_a`, etc. symbols from winebuild, similar to `_load_config_used`. 2. in winegcc, depends on the target, add a `-Wl,/merge:.CRT=.rdata` to the compile flags. And after these we also don't need `winecrtend` anymore. Is that what you have in mind? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124700
On Thu Dec 4 13:37:06 2025 +0000, Yuxuan Shui wrote:
@jacek hope I understand you correctly, you suggestion is to: 1. instead of using `#progma section` and `__declspec(allocate(..))`, we generate these `__xi_a`, etc. symbols from winebuild, similar to `_load_config_used`. 2. in winegcc, depends on the target, add a `-Wl,/merge:.CRT=.rdata` to the compile flags. And after these we also don't need `winecrtend` anymore. Is that what you have in mind? Yes, that's what's I meant (I'd perhaps slightly prefer `-Wl,-merge:.CRT=.rdata` form).
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_124701
participants (3)
-
Jacek Caban (@jacek) -
Yuxuan Shui -
Yuxuan Shui (@yshui)