From aa63f26f368f0a100cb1ccdfa7e9310aa626afcd Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 7 Apr 2025 15:09:32 +0200 Subject: [PATCH] windows: use MSVCRT.DLL instead of UCRT on i386 This allows the binaries to run on Windows XP, without needing any extra DLLs. Tested in an x86 Windows XP SP3 virtual machine. --- GNUmakefile | 12 +++- builder/builtins.go | 10 +++ builder/mingw-w64.go | 115 +++++++++++++++++++++++--------- compileopts/config.go | 16 ++++- compileopts/target.go | 2 + compiler/symbol.go | 2 +- src/crypto/rand/rand_windows.go | 39 +++++------ src/runtime/runtime_windows.go | 75 ++++++++++++++++----- 8 files changed, 196 insertions(+), 75 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 58568d3ec..a685c0848 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -940,8 +940,9 @@ build/release: tinygo gen-device $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binary @mkdir -p build/release/tinygo/lib/clang/include @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/macos-minimal-sdk + @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/crt + @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/math @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common - @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @mkdir -p build/release/tinygo/lib/musl/arch @mkdir -p build/release/tinygo/lib/musl/crt @@ -997,10 +998,17 @@ endif @cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/process build/release/tinygo/lib/musl/src + @cp -rp lib/mingw-w64/mingw-w64-crt/crt/pseudo-reloc.c build/release/tinygo/lib/mingw-w64/mingw-w64-crt/crt @cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt + @cp -rp lib/mingw-w64/mingw-w64-crt/gdtoa build/release/tinygo/lib/mingw-w64/mingw-w64-crt + @cp -rp lib/mingw-w64/mingw-w64-crt/include build/release/tinygo/lib/mingw-w64/mingw-w64-crt @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common + @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/advapi32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common - @cp -rp lib/mingw-w64/mingw-w64-crt/stdio/ucrt_* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio + @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/msvcrt.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common + @cp -rp lib/mingw-w64/mingw-w64-crt/math/x86 build/release/tinygo/lib/mingw-w64/mingw-w64-crt/math + @cp -rp lib/mingw-w64/mingw-w64-crt/misc build/release/tinygo/lib/mingw-w64/mingw-w64-crt + @cp -rp lib/mingw-w64/mingw-w64-crt/stdio build/release/tinygo/lib/mingw-w64/mingw-w64-crt @cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers @cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @cp -rp lib/mingw-w64/mingw-w64-headers/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers diff --git a/builder/builtins.go b/builder/builtins.go index b493b6680..5e91c7891 100644 --- a/builder/builtins.go +++ b/builder/builtins.go @@ -3,6 +3,7 @@ package builder import ( "os" "path/filepath" + "strings" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" @@ -201,6 +202,11 @@ var avrBuiltins = []string{ "avr/udivmodqi4.S", } +// Builtins needed specifically for windows/386. +var windowsI386Builtins = []string{ + "i386/chkstk.S", // also _alloca +} + // libCompilerRT is a library with symbols required by programs compiled with // LLVM. These symbols are for operations that cannot be emitted with a single // instruction or a short sequence of instructions for that target. @@ -229,6 +235,10 @@ var libCompilerRT = Library{ builtins = append(builtins, avrBuiltins...) case "x86_64", "aarch64", "riscv64": // any 64-bit arch builtins = append(builtins, genericBuiltins128...) + case "i386": + if strings.Split(target, "-")[2] == "windows" { + builtins = append(builtins, windowsI386Builtins...) + } } return builtins, nil }, diff --git a/builder/mingw-w64.go b/builder/mingw-w64.go index 6ea3560f7..6bbced821 100644 --- a/builder/mingw-w64.go +++ b/builder/mingw-w64.go @@ -30,25 +30,62 @@ var libMinGW = Library{ sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") }, cflags: func(target, headerPath string) []string { mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") - return []string{ + flags := []string{ "-nostdlibinc", + "-isystem", mingwDir + "/mingw-w64-crt/include", "-isystem", mingwDir + "/mingw-w64-headers/crt", + "-isystem", mingwDir + "/mingw-w64-headers/include", "-I", mingwDir + "/mingw-w64-headers/defaults/include", "-I" + headerPath, } + if strings.Split(target, "-")[0] == "i386" { + flags = append(flags, + "-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002 + "-D_WIN32_WINNT=0x0501", // target Windows XP + "-D_CRTBLD", + "-Wno-pragma-pack", + ) + } + return flags }, librarySources: func(target string) ([]string, error) { // These files are needed so that printf and the like are supported. - sources := []string{ - "mingw-w64-crt/stdio/ucrt_fprintf.c", - "mingw-w64-crt/stdio/ucrt_fwprintf.c", - "mingw-w64-crt/stdio/ucrt_printf.c", - "mingw-w64-crt/stdio/ucrt_snprintf.c", - "mingw-w64-crt/stdio/ucrt_sprintf.c", - "mingw-w64-crt/stdio/ucrt_vfprintf.c", - "mingw-w64-crt/stdio/ucrt_vprintf.c", - "mingw-w64-crt/stdio/ucrt_vsnprintf.c", - "mingw-w64-crt/stdio/ucrt_vsprintf.c", + var sources []string + if strings.Split(target, "-")[0] == "i386" { + // Old 32-bit x86 systems use msvcrt.dll. + sources = []string{ + "mingw-w64-crt/crt/pseudo-reloc.c", + "mingw-w64-crt/gdtoa/dmisc.c", + "mingw-w64-crt/gdtoa/gdtoa.c", + "mingw-w64-crt/gdtoa/gmisc.c", + "mingw-w64-crt/gdtoa/misc.c", + "mingw-w64-crt/math/x86/exp2.S", + "mingw-w64-crt/math/x86/trunc.S", + "mingw-w64-crt/misc/___mb_cur_max_func.c", + "mingw-w64-crt/misc/lc_locale_func.c", + "mingw-w64-crt/misc/mbrtowc.c", + "mingw-w64-crt/misc/strnlen.c", + "mingw-w64-crt/misc/wcrtomb.c", + "mingw-w64-crt/misc/wcsnlen.c", + "mingw-w64-crt/stdio/acrt_iob_func.c", + "mingw-w64-crt/stdio/mingw_lock.c", + "mingw-w64-crt/stdio/mingw_pformat.c", + "mingw-w64-crt/stdio/mingw_vfprintf.c", + "mingw-w64-crt/stdio/mingw_vsnprintf.c", + } + } else { + // Anything somewhat modern (amd64, arm64) uses UCRT. + sources = []string{ + "mingw-w64-crt/stdio/ucrt_fprintf.c", + "mingw-w64-crt/stdio/ucrt_fwprintf.c", + "mingw-w64-crt/stdio/ucrt_printf.c", + "mingw-w64-crt/stdio/ucrt_snprintf.c", + "mingw-w64-crt/stdio/ucrt_sprintf.c", + "mingw-w64-crt/stdio/ucrt_vfprintf.c", + "mingw-w64-crt/stdio/ucrt_vprintf.c", + "mingw-w64-crt/stdio/ucrt_vsnprintf.c", + "mingw-w64-crt/stdio/ucrt_vsprintf.c", + } } return sources, nil }, @@ -63,27 +100,41 @@ var libMinGW = Library{ func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob { var jobs []*compileJob root := goenv.Get("TINYGOROOT") - // Normally all the api-ms-win-crt-*.def files are all compiled to a single - // .lib file. But to simplify things, we're going to leave them as separate - // files. - for _, name := range []string{ - "kernel32.def.in", - "api-ms-win-crt-conio-l1-1-0.def", - "api-ms-win-crt-convert-l1-1-0.def.in", - "api-ms-win-crt-environment-l1-1-0.def", - "api-ms-win-crt-filesystem-l1-1-0.def", - "api-ms-win-crt-heap-l1-1-0.def", - "api-ms-win-crt-locale-l1-1-0.def", - "api-ms-win-crt-math-l1-1-0.def.in", - "api-ms-win-crt-multibyte-l1-1-0.def", - "api-ms-win-crt-private-l1-1-0.def.in", - "api-ms-win-crt-process-l1-1-0.def", - "api-ms-win-crt-runtime-l1-1-0.def.in", - "api-ms-win-crt-stdio-l1-1-0.def", - "api-ms-win-crt-string-l1-1-0.def", - "api-ms-win-crt-time-l1-1-0.def", - "api-ms-win-crt-utility-l1-1-0.def", - } { + var libs []string + if goarch == "386" { + libs = []string{ + // x86 uses msvcrt.dll instead of UCRT for compatibility with old + // Windows versions. + "advapi32.def.in", + "kernel32.def.in", + "msvcrt.def.in", + } + } else { + // Use the modernized UCRT on new systems. + // Normally all the api-ms-win-crt-*.def files are all compiled to a + // single .lib file. But to simplify things, we're going to leave them + // as separate files. + libs = []string{ + "advapi32.def.in", + "kernel32.def.in", + "api-ms-win-crt-conio-l1-1-0.def", + "api-ms-win-crt-convert-l1-1-0.def.in", + "api-ms-win-crt-environment-l1-1-0.def", + "api-ms-win-crt-filesystem-l1-1-0.def", + "api-ms-win-crt-heap-l1-1-0.def", + "api-ms-win-crt-locale-l1-1-0.def", + "api-ms-win-crt-math-l1-1-0.def.in", + "api-ms-win-crt-multibyte-l1-1-0.def", + "api-ms-win-crt-private-l1-1-0.def.in", + "api-ms-win-crt-process-l1-1-0.def", + "api-ms-win-crt-runtime-l1-1-0.def.in", + "api-ms-win-crt-stdio-l1-1-0.def", + "api-ms-win-crt-string-l1-1-0.def", + "api-ms-win-crt-time-l1-1-0.def", + "api-ms-win-crt-utility-l1-1-0.def", + } + } + for _, name := range libs { outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib") inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name) job := &compileJob{ diff --git a/compileopts/config.go b/compileopts/config.go index 61efa439f..d05111f2b 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -398,15 +398,25 @@ func (c *Config) LibcCFlags() []string { case "mingw-w64": root := goenv.Get("TINYGOROOT") path := c.LibraryPath("mingw-w64") - return []string{ + cflags := []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"), - "-D_UCRT", - "-D_WIN32_WINNT=0x0a00", // target Windows 10 } + if c.GOARCH() == "386" { + cflags = append(cflags, + "-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002 + "-D_WIN32_WINNT=0x0501", // target Windows XP + ) + } else { + cflags = append(cflags, + "-D_UCRT", + "-D_WIN32_WINNT=0x0a00", // target Windows 10 + ) + } + return cflags case "": // No libc specified, nothing to add. return nil diff --git a/compileopts/target.go b/compileopts/target.go index c3d57a583..7fb3906d3 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -437,6 +437,8 @@ func defaultTarget(options *Options) (*TargetSpec, error) { case "386": spec.LDFlags = append(spec.LDFlags, "-m", "i386pe", + "--major-os-version", "4", + "--major-subsystem-version", "4", ) // __udivdi3 is not present in ucrt it seems. spec.RTLib = "compiler-rt" diff --git a/compiler/symbol.go b/compiler/symbol.go index 944f36f0f..c56e0792f 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -208,7 +208,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) // > circumstances, and should not be exposed to source languages. llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn) } - case "GetModuleHandleExA", "GetProcAddress", "GetSystemInfo", "GetSystemTimeAsFileTime", "LoadLibraryExW", "QueryUnbiasedInterruptTime", "SetEnvironmentVariableA", "Sleep", "VirtualAlloc": + case "GetModuleHandleExA", "GetProcAddress", "GetSystemInfo", "GetSystemTimeAsFileTime", "LoadLibraryExW", "QueryPerformanceCounter", "QueryPerformanceFrequency", "QueryUnbiasedInterruptTime", "SetEnvironmentVariableA", "Sleep", "SystemFunction036", "VirtualAlloc": // On Windows we need to use a special calling convention for some // external calls. if c.GOOS == "windows" && c.GOARCH == "386" { diff --git a/src/crypto/rand/rand_windows.go b/src/crypto/rand/rand_windows.go index 16266198f..d8c9173c1 100644 --- a/src/crypto/rand/rand_windows.go +++ b/src/crypto/rand/rand_windows.go @@ -1,6 +1,9 @@ package rand -import "errors" +import ( + "errors" + "unsafe" +) func init() { Reader = &reader{} @@ -16,28 +19,22 @@ func (r *reader) Read(b []byte) (n int, err error) { return } - var randomByte uint32 - for i := range b { - // Call rand_s every four bytes because it's a C int (always 32-bit in - // Windows). - if i%4 == 0 { - errCode := libc_rand_s(&randomByte) - if errCode != 0 { - // According to the documentation, it can return an error. - return n, errRandom - } - } else { - randomByte >>= 8 - } - b[i] = byte(randomByte) + // Use the old RtlGenRandom, introduced in Windows XP. + // Even though the documentation says it is deprecated, it is widely used + // and probably won't go away anytime soon. + // See for example: https://github.com/golang/go/issues/33542 + // For Windows 7 and newer, we might switch to ProcessPrng in the future + // (which is a documented function and might be a tiny bit faster). + ok := libc_RtlGenRandom(unsafe.Pointer(&b[0]), len(b)) + if !ok { + return 0, errRandom } - return len(b), nil } -// Cryptographically secure random number generator. -// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/rand-s?view=msvc-170 -// errno_t rand_s(unsigned int* randomValue); +// This function is part of advapi32.dll, and is called SystemFunction036 for +// some reason. It's available on Windows XP and newer. +// See: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom // -//export rand_s -func libc_rand_s(randomValue *uint32) int32 +//export SystemFunction036 +func libc_RtlGenRandom(buf unsafe.Pointer, len int) bool diff --git a/src/runtime/runtime_windows.go b/src/runtime/runtime_windows.go index 17ee2ce72..c4df8dec3 100644 --- a/src/runtime/runtime_windows.go +++ b/src/runtime/runtime_windows.go @@ -42,10 +42,24 @@ func __p___argc() *int32 //export __p___argv func __p___argv() **unsafe.Pointer +type startupInfo struct { + newMode int32 +} + +//export __getmainargs +func __getmainargs(argc *int32, argv, env **unsafe.Pointer, doWildcard int, startInfo *startupInfo) int32 + +var performanceFrequency int64 + //export mainCRTStartup func mainCRTStartup() int { preinit() + // Obtain the (constant) performance frequency when needed. + if GOARCH == "386" { + _QueryPerformanceFrequency(&performanceFrequency) + } + // Obtain the initial stack pointer right before calling the run() function. // The run function has been moved to a separate (non-inlined) function so // that the correct stack pointer is read. @@ -78,9 +92,19 @@ var args []string func os_runtime_args() []string { if args == nil { // Obtain argc/argv from the environment. - _configure_narrow_argv(2) - argc := *__p___argc() - argv := *__p___argv() + var argc int32 + var argv *unsafe.Pointer + if GOARCH == "386" { + // MSVCRT.DLL + var env *unsafe.Pointer + startInfo := startupInfo{newMode: 0} + __getmainargs(&argc, &argv, &env, 1, &startInfo) + } else { + // UCRT + _configure_narrow_argv(2) + argc = *__p___argc() + argv = *__p___argv() + } // Make args slice big enough so that it can store all command line // arguments. @@ -146,10 +170,30 @@ func sleepTicks(d timeUnit) { } } +//export QueryPerformanceFrequency +func _QueryPerformanceFrequency(*int64) bool + +//export QueryPerformanceCounter +func _QueryPerformanceCounter(*int64) bool + func ticks() timeUnit { - var unbiasedTime uint64 - _QueryUnbiasedInterruptTime(&unbiasedTime) - return timeUnit(unbiasedTime) + if GOARCH == "386" { + // Unfortunately QueryUnbiasedInterruptTime is only available starting + // with Windows 7. + + // Obtain counter (that runs at a fixed frequency). + var counter int64 + _QueryPerformanceCounter(&counter) + // Convert this counter to ticks of 100ns (just like + // QueryUnbiasedInterruptTime). + // (We could also change the definition of ticks on GOOS=386 but that + // seems messy). + return timeUnit((counter * 10000000) / performanceFrequency) + } else { + var unbiasedTime uint64 + _QueryUnbiasedInterruptTime(&unbiasedTime) + return timeUnit(unbiasedTime) + } } //go:linkname now time.now @@ -237,17 +281,16 @@ func procUnpin() { } func hardwareRand() (n uint64, ok bool) { - var n1, n2 uint32 - errCode1 := libc_rand_s(&n1) - errCode2 := libc_rand_s(&n2) - n = uint64(n1)<<32 | uint64(n2) - ok = errCode1 == 0 && errCode2 == 0 + // Use the old RtlGenRandom, introduced in Windows XP. + // See the rationale in src/crypto/rand/rand_windows.go for why we use this + // one. + ok = _RtlGenRandom(unsafe.Pointer(&n), 8) return } -// Cryptographically secure random number generator. -// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/rand-s?view=msvc-170 -// errno_t rand_s(unsigned int* randomValue); +// This function is part of advapi32.dll, and is called SystemFunction036 for +// some reason. It's available on Windows XP and newer. +// See: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom // -//export rand_s -func libc_rand_s(randomValue *uint32) int32 +//export SystemFunction036 +func _RtlGenRandom(buf unsafe.Pointer, len int) bool