On Wed, 1 Jan 2025, Pali Rohár wrote:

To continue my investigation, I finally found the documentation from
where are those 0x0601 values coming from. In mingw32 project is this:

https://sourceforge.net/p/mingw/mingw-org-wsl/ci/5.1-trunk/tree/mingwrt/msvcrt-xref/msvcrt.def.in

* Note that mapping of symbols is directed by __MSVCRT_VERSION__;
* for the MSVCRT.DLL versions shipped with the various versions of
* MS-Windows, this must be a 32-bit unsigned long value with the
* high order 16-bits set to zero, and the low order 16-bits set
* to the appropriate platform specific hexadecimal value:--
*
*   NT4                 0x0400UL
*   Win98                       0x0410UL
*   Win98-SE                    0x0412UL
*   Win2K                       0x0500UL
*   WinXP                       0x0501UL
*   Vista                       0x0600UL
*   Win7                        0x0601UL

While these version numbers, essentially NT version numbers, seem to have been used here like this, I don't think this is how they were used for __MSVCRT_VERSION__ in headers though.

In mingw32 file include/msvcrtver.h is this content:

/* When it is intended to link an application with any one of the
* MSVC version specific MSVCRxx.DLL libraries, rather than with the
* OS default MSVCRT.DLL, the particular substitute MSVCRxx.DLL may
* be specified as any one of the following...
*/
#define __MSVCR60_DLL           0x0600
#define __MSVCR61_DLL           0x0601
#define __MSVCR70_DLL           0x0700
#define __MSVCR71_DLL           0x0701
#define __MSVCR80_DLL           0x0800
#define __MSVCR90_DLL           0x0900
#define __MSVCR100_DLL          0x1000
#define __MSVCR110_DLL          0x1100
#define __MSVCR120_DLL          0x1200

Note that while the msvcr70, 71, etc ones map to real msvcrXX.dlls that have been shipped, there's no msvcr60.dll or msvcr61.dll anywhere, AFAIK.


#ifndef __MSVCRT_VERSION__
/* This may be set, when the intent is to link with any of the above
* non-freely distributable MSVCRxx.DLL libraries, rather than with the
* pseudo-free MSVCRT.DLL provided as an OS component.  High byte is the
* major version number, low byte is the minor; however, users are advised
* to use custom GCC specs files to set this, while also substituting the
* appropriate library in place of MSVCRT.DLL, rather than to simply set
* it directly.
*
* It should be noted that __MSVCRT_VERSION__ is NOT a good indicator of
* evolving MSVCRT.DLL features; that is better accomplished by using the
* NTDDI_VERSION setting from the Windows API.  Thus, users of MSVCRT.DLL
* should NOT set __MSVCRT_VERSION__, leaving us to establish a default,
* equivalent to MSVCR60.DLL, which seems reasonably well aligned with
* the feature set of the earliest MSVCRT.DLL version we support.
*/
# define __MSVCRT_VERSION__  __MSVCR60_DLL
#endif


So it looks like that current mingw32 is setting 0x0600 as default value
for __MSVCRT_VERSION__.


Next, I looked into mingw-org-wsl git repo history and I found out that
in more released mingw32 versions following patterns in header files:

 /* These require msvcr70.dll or higher. */
 #if __MSVCRT_VERSION__ >= 0x0700
  _CRTIMP void * __cdecl __MINGW_NOTHROW _aligned_offset_malloc(size_t, size_t, 
size_t);
  _CRTIMP void * __cdecl __MINGW_NOTHROW _aligned_offset_realloc(void*, size_t, 
size_t, size_t);
  _CRTIMP void * __cdecl __MINGW_NOTHROW _aligned_offset_recalloc(void*, 
size_t, size_t, size_t, size_t);
 ...
 #endif

 /* This require msvcr70.dll or higher. */
 #if __MSVCRT_VERSION__ >= 0x0700
 _CRTIMP int __cdecl _set_SSE2_enable (int);
 #endif /* __MSVCRT_VERSION__ >= 0x0700 */

 #if __MSVCRT_VERSION__ >= 0x0800
 _CRTIMP int __cdecl __MINGW_NOTHROW    _utime32 (const char*, struct 
__utimbuf32*);
 _CRTIMP int __cdecl __MINGW_NOTHROW    _wutime32 (const wchar_t*, struct 
__utimbuf32*);
 _CRTIMP int __cdecl __MINGW_NOTHROW    _futime32 (int, struct __utimbuf32*);
 ...
 #endif

Also there are patterns:

 #if __MSVCRT_VERSION__ >= 0x0601
 _CRTIMP int __cdecl __MINGW_NOTHROW _wstat64 (const wchar_t*, struct 
__stat64*);
 #endif /* __MSVCRT_VERSION__ >= 0x0601 */


So it means that those "big" values 0x07000000UL were never used at all
in header file, and that values 0x0400UL-0x0601UL for system msvcrt.dll
are/were used.

Also in all older versions was __MSVCRT_VERSION__ by default set to the
0x0600 value.

With all information, it looks now clear why applications are checking
__MSVCRT_VERSION__ against 0x0601 value or are changing to higher value.
It means that they want Windows 7 APIs.

No, 0x0601 for __MSVCRT_VERSION__ doesn't mean Windows 7. I think you've made way too much conclusions based on the info in msvcrt.def.in, which does not seem to be using the same numbering scheme as the headers.


I believe the version numbering for __MSVCRT_VERSION__ in headers was meant to use both the msvcrXX.dll version numbers, just like we do, but also based on the actual embedded version number of the msvcrt.dll that shipped with the OS.

I don't have a copy of msvcrt.dll from win9x or older versions handy for looking at, but I have a set of the modern ones. Using "peres -v msvcrt.dll" (the "peres" tool from the "pev" or "readpe" package on Ubuntu), I'm getting the following version:

Win2k 6.1.8637.0
WinXP 7.0.2600.5512
Vista 7.0.6002.18551
Win7 7.0.7601.17744

This also lines up perfectly with some of the ifdefs you referenced - e.g. _aligned_malloc exists in msvcrt.dll since XP, and is guarded by __MSVCRT_VERSION__ >= 0x0700. And _wstat64 which is guarded by __MSVCRT_VERSION__ >= 0x0601 is available since Win2k.

So the mythical __MSVCR61_DLL means Win2k.

So, based on this investigation, I think that changing default value of
__MSVCRT_VERSION__ again should not be needed. I would suggest to just
adjust checks in mingw-w64 header files for:

* OS system msvcrt.dll version (any OS):
 - #if __MSVCRT_VERSION__ > 0x400 && __MSVCRT_VERSION__ < 0x700
 - This will match any mingw32 OS system msvcrt.dll version except the Windows 
NT4
 - It does not conflict with neither mingw-w64 msvcrt40 (== 0x400) nor with 
msvcr70 (== 0x700)

I don't think they used __MSVCRT_VERSION__ < 0x600 for anything, other than potentially msvcrXX.dll versions just like we do.

* msvcr100+:
 - (__MSVCRT_VERSION__ >= 0xA00 && __MSVCRT_VERSION__ < 0x1000) || 
(__MSVCRT_VERSION__ >= 0x1000)
 - First part is for mingw-w64, second for mingw32

* msvcr110+:
 - (__MSVCRT_VERSION__ >= 0xB00 && __MSVCRT_VERSION__ < 0x1000) || 
(__MSVCRT_VERSION__ >= 0x1100)
 - First part is for mingw-w64, second for mingw32

* msvcr120+:
 - (__MSVCRT_VERSION__ >= 0xC00 && __MSVCRT_VERSION__ < 0x1000) || 
(__MSVCRT_VERSION__ >= 0x1200)
 - First part is for mingw-w64, second for mingw32

I don't think we should do anything to complicate things for numbered msvcrXX.dll here - using anything other than msvcrt.dll and UCRT is exceedingly rare. (I only practically know of two cases; you, and VLC did need to target msvcr120*.dll for Windows Store distribution at some point, and that's no longer relevant for them.)

Would be this enough to finally fix those issues with __MSVCRT_VERSION__?

I don't think this would make any difference here at all.


I believe that the issues that can be caused by our changes fall into two classes: (Sorry, I didn't browse all the code search results to see what other concrete cases there were.)

- Projects that look at __MSVCRT_VERSION__ and conditionally compile things. This may lead to projects building with fewer features (by skipping things that would require a newer msvcrt.dll), or trying to use extra compat routines that wouldn't be necessary. Or there can be clashes, if they see our __MSVCRT_VERSION__ and conclude that function X isn't provided, and they try to provide their own replacement function X, and they can get duplicate conflicting definitions.

Or the ifdefs are only tested in one configuration and simply break in the other one, like I think was the case in the linked msys2 issue.

Addapting our header ifdefs to also recognize different values from mingw.org toolchains won't make any difference here at all.

Most of the cases I saw in the code search I browsed earlier seemed to be this case - e.g. trying to provide workarounds for 64 bit stat functions or similar. I haven't tried compiling them to see if our new definitions actually cause a problem or not.

- Projects that themselves try to change __MSVCRT_VERSION__ to expose more features, e.g. setting __MSVCRT_VERSION__ to 0x700 to get XP level features like _aligned_malloc available. This is mostly harmless for us, even if it in corner cases could cause problems (by exposing msvcr70.dll features when they actually meant msvcrt.dll/XP - but mingw.org headers also seem to generally consider the two equal).

One such case is familiar to me, https://github.com/FFmpeg/FFmpeg/blob/n7.1/configure#L6036-L6037. But that one is only done if the headers are mingw.org headers, not mingw-w64, so it doesn't make any difference for us. (It's used for getting specifically _aligned_malloc.)



So I think the main source of problems for user code is that we used to default to __MSVCRT_VERSION__ == 0x700 and now we default to something else.

We could consider defaulting to 0x0601 (or 0x06FF); this would pick the same branch as before, for all the cases of __MSVCRT_VERSION__ >= 0x0601. So it could reduce the amount of breakage that we cause.

For any code that checks __MSVCRT_VERSION__ >= 0x700 though, things would unfortunately change.

// Martin

_______________________________________________
Mingw-w64-public mailing list
Mingw-w64-public@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public

Reply via email to