[PATCH v7 0/3] 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. -- v7: crt: Run MSVC constructors and destructors. winegcc: Merge .CRT sections for windows targets. winebuild: Generate start and end symbols for .CRT sections. https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
From: Yuxuan Shui <yshui@codeweavers.com> --- tools/winebuild/build.h | 1 + tools/winebuild/spec16.c | 1 + tools/winebuild/spec32.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/tools/winebuild/build.h b/tools/winebuild/build.h index 3acf987aab2..0cabc4648db 100644 --- a/tools/winebuild/build.h +++ b/tools/winebuild/build.h @@ -301,6 +301,7 @@ extern void output_imports( DLLSPEC *spec ); extern void output_import_lib( DLLSPEC *spec, struct strarray files ); extern void output_static_lib( const char *output_name, struct strarray files, int create ); extern void output_exports( DLLSPEC *spec ); +extern void output_crt_sections(void); extern int load_res32_file( const char *name, DLLSPEC *spec ); extern void output_resources( DLLSPEC *spec ); extern void output_bin_resources( DLLSPEC *spec, unsigned int start_rva ); diff --git a/tools/winebuild/spec16.c b/tools/winebuild/spec16.c index face1c551d9..d16e97eabe6 100644 --- a/tools/winebuild/spec16.c +++ b/tools/winebuild/spec16.c @@ -790,6 +790,7 @@ void output_spec16_file( DLLSPEC *spec16 ) output_stubs( spec16 ); output_exports( spec32 ); output_imports( spec16 ); + output_crt_sections(); if (!strcmp( spec16->dll_name, "kernel" )) output_asm_relays16(); if (needs_get_pc_thunk) output_get_pc_thunk(); if (spec16->main_module) diff --git a/tools/winebuild/spec32.c b/tools/winebuild/spec32.c index 7099e82e0f6..1530188d6a6 100644 --- a/tools/winebuild/spec32.c +++ b/tools/winebuild/spec32.c @@ -703,6 +703,33 @@ static void output_load_config(void) } +void output_crt_sections(void) +{ + /* Generate the start/end symbols for .CRT$X?? sections. The start symbol is put into + * .CRT$X?A, the end .CRT$X?Z. Since the linker sort .CRT$X?? sections by name, these symbols + * will end up at the right location.*/ + static const char sections[] = "ict"; + char symbol_name[7]; + int i; + for (i = 0; sections[i]; i++) + { + snprintf( symbol_name, ARRAY_SIZE(symbol_name), "__x%c_a", sections[i] ); + output( "\t.section .CRT$X%cA\n", toupper( sections[i] ) ); + output( "\t.globl %s\n", asm_name( symbol_name ) ); + output( "\t.balign %u\n", get_ptr_size() ); + output( "%s:\n", asm_name( symbol_name ) ); + output( "\t%s 0\n", get_asm_ptr_keyword() ); + + snprintf( symbol_name, ARRAY_SIZE(symbol_name), "__x%c_z", sections[i] ); + output( "\t.section .CRT$X%cZ\n", toupper( sections[i] ) ); + output( "\t.globl %s\n", asm_name( symbol_name ) ); + output( "\t.balign %u\n", get_ptr_size() ); + output( "%s:\n", asm_name( symbol_name ) ); + output( "\t%s 0\n", get_asm_ptr_keyword() ); + } +} + + /******************************************************************* * output_module * @@ -841,6 +868,7 @@ void output_spec32_file( DLLSPEC *spec ) output_imports( spec ); if (needs_get_pc_thunk) output_get_pc_thunk(); output_load_config(); + output_crt_sections(); output_resources( spec ); output_gnu_stack_note(); close_output_file(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
From: Yuxuan Shui <yshui@codeweavers.com> On mingw targets this is already done by ld's linker script. --- tools/winegcc/winegcc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/winegcc/winegcc.c b/tools/winegcc/winegcc.c index 7458fb08773..724bb9208d5 100644 --- a/tools/winegcc/winegcc.c +++ b/tools/winegcc/winegcc.c @@ -628,6 +628,9 @@ static struct strarray get_link_args( const char *output_name ) strarray_add( &link_args, strmake( "-Wl,-filealign:%s,-align:%s,-driver", file_align, section_align )); + /* Merge .CRT sections into .rdata */ + strarray_add( &link_args, "-Wl,-merge:.CRT=.rdata" ); + strarray_addall( &link_args, flags ); return link_args; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
From: Yuxuan Shui <yshui@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
I think this should do it? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_125306
On Tue Dec 9 20:04:18 2025 +0000, Yuxuan Shui wrote:
I think this should do it? And for `CTOR/DTOR_LIST` I think I can use the `-Wl,-alternatename` [flag](https://devblogs.microsoft.com/oldnewthing/20200731-00/?p=104024) on windows targets.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_125307
Jacek Caban (@jacek) commented about tools/winebuild/spec32.c:
}
+void output_crt_sections(void) +{ + /* Generate the start/end symbols for .CRT$X?? sections. The start symbol is put into + * .CRT$X?A, the end .CRT$X?Z. Since the linker sort .CRT$X?? sections by name, these symbols + * will end up at the right location.*/ + static const char sections[] = "ict"; + char symbol_name[7]; + int i; + for (i = 0; sections[i]; i++) + { + snprintf( symbol_name, ARRAY_SIZE(symbol_name), "__x%c_a", sections[i] ); `winebuild` usually uses `strmake` for that.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_125439
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"
+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 )
It would be nice to import it from msvcrt or ucrtbase when possible. Could we add this, along with the `initterm_e` fallback implementation, to the ntdll importlib? That way we would use the static version only when the function is not available otherwise. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_125440
Thanks, I think it looks better now. I left some comments, but generally the merge will have to wait until the code freeze is over. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_125441
On Wed Dec 10 23:40:04 2025 +0000, Jacek Caban wrote:
It would be nice to import it from msvcrt or ucrtbase when possible. Could we add this, along with the `initterm_e` fallback implementation, to the ntdll importlib? That way we would use the static version only when the function is not available otherwise. uhmm, i think i am not getting it.
if we add this to `libntdll.a`, won't that conflict with msvcrt if it also provides this symbol? i had to add these to winecrt0 because i don't know which version of msvcrt libwinecrt0 will be linked with. i don't know how to selectively use the version from msvcrt only if it's available. :thinking: -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_125506
On Fri Dec 12 01:58:57 2025 +0000, Yuxuan Shui wrote:
uhmm, i think i am not getting it. if we add this to `libntdll.a`, won't that conflict with msvcrt if it also provides this symbol? i had to add these to winecrt0 because i don't know which version of msvcrt libwinecrt0 will be linked with. i don't know how to selectively use the version from msvcrt only if it's available. :thinking: or should i use a weak symbol or something?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_125507
participants (3)
-
Jacek Caban (@jacek) -
Yuxuan Shui -
Yuxuan Shui (@yshui)