From e95d56f7495f2751d7b3f8e3b537c0b4a5f4a49e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 22 Nov 2024 15:17:37 +0100 Subject: [PATCH 1/2] windows: add windows/386 support --- .github/workflows/build-macos.yml | 2 +- .github/workflows/windows.yml | 2 +- builder/builder_test.go | 1 + builder/mingw-w64.go | 3 ++ compileopts/target.go | 18 ++++++--- compiler/calls.go | 10 ++++- compiler/llvm.go | 15 ++++++++ compiler/symbol.go | 6 +++ compiler/syscall.go | 25 ++++++++++++- main_test.go | 60 ++++++++++++++++++------------ src/internal/task/task_stack_386.S | 14 +++++++ src/runtime/asm_386.S | 14 +++++++ 12 files changed, 136 insertions(+), 34 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 25b5971783..fe1961543c 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -101,7 +101,7 @@ jobs: - name: make gen-device run: make -j3 gen-device - name: Test TinyGo - run: make test GOTESTFLAGS="-short" + run: make test GOTESTFLAGS="-only-current-os" - name: Build TinyGo release tarball run: make release -j3 - name: Test stdlib packages diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 42365f59b7..535e45ccc1 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -114,7 +114,7 @@ jobs: run: make -j3 gen-device - name: Test TinyGo shell: bash - run: make test GOTESTFLAGS="-short" + run: make test GOTESTFLAGS="-only-current-os" - name: Build TinyGo release tarball shell: bash run: make build/release -j4 diff --git a/builder/builder_test.go b/builder/builder_test.go index ccccef30ba..6b84b10070 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -67,6 +67,7 @@ func TestClangAttributes(t *testing.T) { {GOOS: "linux", GOARCH: "mipsle", GOMIPS: "softfloat"}, {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "arm64"}, + {GOOS: "windows", GOARCH: "386"}, {GOOS: "windows", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "arm64"}, } { diff --git a/builder/mingw-w64.go b/builder/mingw-w64.go index 32cf58f531..6ea3560f73 100644 --- a/builder/mingw-w64.go +++ b/builder/mingw-w64.go @@ -93,6 +93,9 @@ func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob { defpath := inpath var archDef, emulation string switch goarch { + case "386": + archDef = "-DDEF_I386" + emulation = "i386pe" case "amd64": archDef = "-DDEF_X64" emulation = "i386pep" diff --git a/compileopts/target.go b/compileopts/target.go index baf6c1214a..41740d9570 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -430,14 +430,20 @@ func defaultTarget(options *Options) (*TargetSpec, error) { spec.GC = "boehm" spec.Linker = "ld.lld" spec.Libc = "mingw-w64" - // Note: using a medium code model, low image base and no ASLR - // because Go doesn't really need those features. ASLR patches - // around issues for unsafe languages like C/C++ that are not - // normally present in Go (without explicitly opting in). - // For more discussion: - // https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1 switch options.GOARCH { + case "386": + spec.LDFlags = append(spec.LDFlags, + "-m", "i386pe", + ) + // __udivdi3 is not present in ucrt it seems. + spec.RTLib = "compiler-rt" case "amd64": + // Note: using a medium code model, low image base and no ASLR + // because Go doesn't really need those features. ASLR patches + // around issues for unsafe languages like C/C++ that are not + // normally present in Go (without explicitly opting in). + // For more discussion: + // https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1 spec.LDFlags = append(spec.LDFlags, "-m", "i386pep", "--image-base", "0x400000", diff --git a/compiler/calls.go b/compiler/calls.go index 6400e634bd..a44ac38a87 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -76,7 +76,15 @@ func (b *builder) createCall(fnType llvm.Type, fn llvm.Value, args []llvm.Value, fragments := b.expandFormalParam(arg) expanded = append(expanded, fragments...) } - return b.CreateCall(fnType, fn, expanded, name) + call := b.CreateCall(fnType, fn, expanded, name) + if !fn.IsAFunction().IsNil() { + if cc := fn.FunctionCallConv(); cc != llvm.CCallConv { + // Set a different calling convention if needed. + // This is needed for GetModuleHandleExA on Windows, for example. + call.SetInstructionCallConv(cc) + } + } + return call } // createInvoke is like createCall but continues execution at the landing pad if diff --git a/compiler/llvm.go b/compiler/llvm.go index 139c5a1cd8..de387b39c0 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -452,6 +452,21 @@ func (b *builder) readStackPointer() llvm.Value { return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "") } +// writeStackPointer emits a LLVM intrinsic call that updates the current stack +// pointer. +func (b *builder) writeStackPointer(sp llvm.Value) { + name := "llvm.stackrestore.p0" + if llvmutil.Version() < 18 { + name = "llvm.stackrestore" // backwards compatibility with LLVM 17 and below + } + stackrestore := b.mod.NamedFunction(name) + if stackrestore.IsNil() { + fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false) + stackrestore = llvm.AddFunction(b.mod, name, fnType) + } + b.CreateCall(stackrestore.GlobalValueType(), stackrestore, []llvm.Value{sp}, "") +} + // createZExtOrTrunc lets the input value fit in the output type bits, by zero // extending or truncating the integer. func (b *builder) createZExtOrTrunc(value llvm.Value, t llvm.Type) llvm.Value { diff --git a/compiler/symbol.go b/compiler/symbol.go index 1226683d57..9b3265602f 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -208,6 +208,12 @@ 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": + // On Windows we need to use a special calling convention for some + // external calls. + if c.GOOS == "windows" && c.GOARCH == "386" { + llvmFn.SetFunctionCallConv(llvm.X86StdcallCallConv) + } } // External/exported functions may not retain pointer values. diff --git a/compiler/syscall.go b/compiler/syscall.go index aa40ad1a55..7fd6e354c6 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -268,6 +268,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { // The signature looks like this: // func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) + isI386 := strings.HasPrefix(b.Triple, "i386-") + // Prepare input values. var paramTypes []llvm.Type var params []llvm.Value @@ -285,11 +287,17 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { if setLastError.IsNil() { llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false) setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType) + if isI386 { + setLastError.SetFunctionCallConv(llvm.X86StdcallCallConv) + } } getLastError := b.mod.NamedFunction("GetLastError") if getLastError.IsNil() { llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false) getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType) + if isI386 { + getLastError.SetFunctionCallConv(llvm.X86StdcallCallConv) + } } // Now do the actual call. Pseudocode: @@ -300,9 +308,24 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { // Note that SetLastError/GetLastError could be replaced with direct // access to the thread control block, which is probably smaller and // faster. The Go runtime does this in assembly. - b.CreateCall(setLastError.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "") + // On windows/386, we also need to save/restore the stack pointer. I'm + // not entirely sure why this is needed, but without it these calls + // change the stack pointer leading to a crash soon after. + setLastErrorCall := b.CreateCall(setLastError.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "") + var sp llvm.Value + if isI386 { + setLastErrorCall.SetInstructionCallConv(llvm.X86StdcallCallConv) + sp = b.readStackPointer() + } syscallResult := b.CreateCall(llvmType, fnPtr, params, "") + if isI386 { + syscallResult.SetInstructionCallConv(llvm.X86StdcallCallConv) + b.writeStackPointer(sp) + } errResult := b.CreateCall(getLastError.GlobalValueType(), getLastError, nil, "err") + if isI386 { + errResult.SetInstructionCallConv(llvm.X86StdcallCallConv) + } if b.uintptrType != b.ctx.Int32Type() { errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr") } diff --git a/main_test.go b/main_test.go index f193f46799..d950340b24 100644 --- a/main_test.go +++ b/main_test.go @@ -35,6 +35,8 @@ const TESTDATA = "testdata" var testTarget = flag.String("target", "", "override test target") +var testOnlyCurrentOS = flag.Bool("only-current-os", false, "") + var supportedLinuxArches = map[string]string{ "AMD64Linux": "linux/amd64", "X86Linux": "linux/386", @@ -158,20 +160,35 @@ func TestBuild(t *testing.T) { return } - t.Run("EmulatedCortexM3", func(t *testing.T) { - t.Parallel() - runPlatTests(optionsFromTarget("cortex-m-qemu", sema), tests, t) - }) + if !*testOnlyCurrentOS { + t.Run("EmulatedCortexM3", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("cortex-m-qemu", sema), tests, t) + }) - t.Run("EmulatedRISCV", func(t *testing.T) { - t.Parallel() - runPlatTests(optionsFromTarget("riscv-qemu", sema), tests, t) - }) + t.Run("EmulatedRISCV", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("riscv-qemu", sema), tests, t) + }) - t.Run("AVR", func(t *testing.T) { - t.Parallel() - runPlatTests(optionsFromTarget("simavr", sema), tests, t) - }) + t.Run("AVR", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("simavr", sema), tests, t) + }) + + t.Run("WebAssembly", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("wasm", sema), tests, t) + }) + t.Run("WASI", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("wasip1", sema), tests, t) + }) + t.Run("WASIp2", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("wasip2", sema), tests, t) + }) + } if runtime.GOOS == "linux" { for name, osArch := range supportedLinuxArches { @@ -191,18 +208,13 @@ func TestBuild(t *testing.T) { options := optionsFromOSARCH("linux/mipsle/softfloat", sema) runTest("cgo/", options, t, nil, nil) }) - t.Run("WebAssembly", func(t *testing.T) { - t.Parallel() - runPlatTests(optionsFromTarget("wasm", sema), tests, t) - }) - t.Run("WASI", func(t *testing.T) { - t.Parallel() - runPlatTests(optionsFromTarget("wasip1", sema), tests, t) - }) - t.Run("WASIp2", func(t *testing.T) { - t.Parallel() - runPlatTests(optionsFromTarget("wasip2", sema), tests, t) - }) + } else if runtime.GOOS == "windows" { + if runtime.GOARCH != "386" { + t.Run("Windows386", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromOSARCH("windows/386", sema), tests, t) + }) + } } } diff --git a/src/internal/task/task_stack_386.S b/src/internal/task/task_stack_386.S index c82213e98f..1a52921540 100644 --- a/src/internal/task/task_stack_386.S +++ b/src/internal/task/task_stack_386.S @@ -1,7 +1,12 @@ +#ifdef _WIN32 +.global _tinygo_startTask +_tinygo_startTask: +#else // Linux etc .section .text.tinygo_startTask .global tinygo_startTask .type tinygo_startTask, %function tinygo_startTask: +#endif .cfi_startproc // Small assembly stub for starting a goroutine. This is already run on the // new stack, with the callee-saved registers already loaded. @@ -24,12 +29,21 @@ tinygo_startTask: addl $4, %esp // After return, exit this goroutine. This is a tail call. + #ifdef _WIN32 + jmp _tinygo_pause + #else jmp tinygo_pause + #endif .cfi_endproc +#ifdef _WIN32 +.global _tinygo_swapTask +_tinygo_swapTask: +#else .global tinygo_swapTask .type tinygo_swapTask, %function tinygo_swapTask: +#endif // This function gets the following parameters: movl 4(%esp), %eax // newStack uintptr movl 8(%esp), %ecx // oldStack *uintptr diff --git a/src/runtime/asm_386.S b/src/runtime/asm_386.S index faaa7c3a3b..f463ffa0a0 100644 --- a/src/runtime/asm_386.S +++ b/src/runtime/asm_386.S @@ -1,7 +1,12 @@ +#ifdef _WIN32 +.global _tinygo_scanCurrentStack +_tinygo_scanCurrentStack: +#else .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack .type tinygo_scanCurrentStack, %function tinygo_scanCurrentStack: +#endif // Sources: // * https://stackoverflow.com/questions/18024672/what-registers-are-preserved-through-a-linux-x86-64-function-call // * https://godbolt.org/z/q7e8dn @@ -15,7 +20,11 @@ tinygo_scanCurrentStack: // Scan the stack. subl $8, %esp // adjust the stack before the call to maintain 16-byte alignment pushl %esp + #ifdef _WIN32 + calll _tinygo_scanstack + #else calll tinygo_scanstack + #endif // Restore the stack pointer. Registers do not need to be restored as they // were only pushed to be discoverable by the GC. @@ -23,9 +32,14 @@ tinygo_scanCurrentStack: retl +#ifdef _WIN32 +.global _tinygo_longjmp +_tinygo_longjmp: +#else .section .text.tinygo_longjmp .global tinygo_longjmp tinygo_longjmp: +#endif // Note: the code we jump to assumes eax is set to a non-zero value if we // jump from here. movl 4(%esp), %eax From bd7594a0ffdddeb41f868996e746ccb3f47468ca Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 7 Apr 2025 15:09:32 +0200 Subject: [PATCH 2/2] WIP try to use MSVCRT.DLL instead of UCRT Very simple "hello world" style programs work. --- builder/builtins.go | 5 ++ builder/mingw-w64.go | 106 ++++++++++++++++++++++---------- compileopts/config.go | 16 ++++- src/crypto/rand/rand_windows.go | 1 + src/runtime/runtime_windows.go | 23 +++++-- 5 files changed, 112 insertions(+), 39 deletions(-) diff --git a/builder/builtins.go b/builder/builtins.go index b493b6680a..67cbd9c338 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" @@ -229,6 +230,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, "i386/chkstk.S") + } } return builtins, nil }, diff --git a/builder/mingw-w64.go b/builder/mingw-w64.go index 6ea3560f73..a4c91e4795 100644 --- a/builder/mingw-w64.go +++ b/builder/mingw-w64.go @@ -32,23 +32,54 @@ var libMinGW = Library{ mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") return []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, + "-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002 + "-D_WIN32_WINNT=0x0501", // target Windows XP + "-D__LIBMSVCRT_OS__", + "-D_CRTBLD", + "-Wno-pragma-pack", } }, 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/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/secapi/rand_s.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 +94,40 @@ 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{ + "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 3d8a73627c..d368503eed 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -391,15 +391,25 @@ func (c *Config) LibcCFlags() []string { case "mingw-w64": root := goenv.Get("TINYGOROOT") path, _ := c.LibcPath("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/src/crypto/rand/rand_windows.go b/src/crypto/rand/rand_windows.go index 16266198fe..1426db1f42 100644 --- a/src/crypto/rand/rand_windows.go +++ b/src/crypto/rand/rand_windows.go @@ -21,6 +21,7 @@ func (r *reader) Read(b []byte) (n int, err error) { // Call rand_s every four bytes because it's a C int (always 32-bit in // Windows). if i%4 == 0 { + // TODO: use RtlGenRandom on GOARCH=386. errCode := libc_rand_s(&randomByte) if errCode != 0 { // According to the documentation, it can return an error. diff --git a/src/runtime/runtime_windows.go b/src/runtime/runtime_windows.go index 88857fc3a5..b2fbdf3398 100644 --- a/src/runtime/runtime_windows.go +++ b/src/runtime/runtime_windows.go @@ -239,11 +239,17 @@ 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 + if GOARCH == "386" { + // Use the old RtlGenRandom, introduced in Windows XP. + ok = RtlGenRandom(unsafe.Pointer(&n), 8) + } else { + // Use the newer secure rand_s function that's part of UCRT. + 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 + } return } @@ -253,3 +259,10 @@ func hardwareRand() (n uint64, ok bool) { // //export rand_s func libc_rand_s(randomValue *uint32) int32 + +// 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 SystemFunction036 +func RtlGenRandom(buf unsafe.Pointer, len int) bool