// and system calls even in "pure" Go code are actually C
// calls that may need more stack than we think.
//
- // The default stack reserve size affects only the main
+ // The default stack reserve size directly affects only the main
// thread, ctrlhandler thread, and profileloop thread. For
// these, it must be greater than the stack size assumed by
// externalthreadhandler.
//
- // For other threads we specify stack size in runtime explicitly.
- // For these, the reserve must match STACKSIZE in
- // runtime/cgo/gcc_windows_{386,amd64}.c and osStackSize in
- // runtime/os_windows.go.
+ // For other threads, the runtime explicitly asks the kernel
+ // to use the default stack size so that all stacks are
+ // consistent.
+ //
+ // At thread start, in minit, the runtime queries the OS for
+ // the actual stack bounds so that the stack size doesn't need
+ // to be hard-coded into the runtime.
oh64.SizeOfStackReserve = 0x00200000
if !iscgo {
oh64.SizeOfStackCommit = 0x00001000
runtime/asm_386.s: [386] uint32tofloat64: function uint32tofloat64 missing Go declaration
runtime/asm_386.s: [386] float64touint32: function float64touint32 missing Go declaration
-
-runtime/asm_386.s: [386] stackcheck: function stackcheck missing Go declaration
runtime/asm_amd64.s: [amd64] addmoduledata: function addmoduledata missing Go declaration
runtime/duff_amd64.s: [amd64] duffzero: function duffzero missing Go declaration
runtime/duff_amd64.s: [amd64] duffcopy: function duffcopy missing Go declaration
-runtime/asm_amd64.s: [amd64] stackcheck: function stackcheck missing Go declaration
runtime/asm_amd64p32.s: [amd64p32] rt0_go: unknown variable argv
runtime/asm_amd64p32.s: [amd64p32] asmcgocall: RET without writing to 4-byte ret+8(FP)
-
-runtime/asm_amd64p32.s: [amd64p32] stackcheck: function stackcheck missing Go declaration
static void threadentry(void*);
-/* 1MB is default stack size for 32-bit Windows.
- Allocation granularity on Windows is typically 64 KB.
- This constant must match SizeOfStackReserve in ../cmd/link/internal/ld/pe.go. */
-#define STACKSIZE (1*1024*1024)
-
void
x_cgo_init(G *g)
{
- int tmp;
- g->stacklo = (uintptr)&tmp - STACKSIZE + 8*1024;
}
ts = *(ThreadStart*)v;
free(v);
- ts.g->stackhi = (uintptr)&ts;
- ts.g->stacklo = (uintptr)&ts - STACKSIZE + 8*1024;
+ // minit queries stack bounds from the OS.
/*
* Set specific keys in thread local storage.
static void threadentry(void*);
-/* 2MB is default stack size for 64-bit Windows.
- Allocation granularity on Windows is typically 64 KB.
- This constant must match SizeOfStackReserve in ../cmd/link/internal/ld/pe.go. */
-#define STACKSIZE (2*1024*1024)
-
void
x_cgo_init(G *g)
{
- int tmp;
- g->stacklo = (uintptr)&tmp - STACKSIZE + 8*1024;
}
ts = *(ThreadStart*)v;
free(v);
- ts.g->stackhi = (uintptr)&ts;
- ts.g->stacklo = (uintptr)&ts - STACKSIZE + 8*1024;
+ // minit queries stack bounds from the OS.
/*
* Set specific keys in thread local storage.
t.Fatalf("failure incorrectly contains %q. output:\n%s\n", nowant, got)
}
}
+
+// Test that C code called via cgo can use large Windows thread stacks
+// and call back in to Go without crashing. See issue #20975.
+//
+// See also TestBigStackCallbackSyscall.
+func TestBigStackCallbackCgo(t *testing.T) {
+ if runtime.GOOS != "windows" {
+ t.Skip("skipping windows specific test")
+ }
+ t.Parallel()
+ got := runTestProg(t, "testprogcgo", "BigStack")
+ want := "OK\n"
+ if got != want {
+ t.Errorf("expected %q got %v", want, got)
+ }
+}
type M128a C.M128A
type Context C.CONTEXT
type Overlapped C.OVERLAPPED
+type MemoryBasicInformation C.MEMORY_BASIC_INFORMATION
anon0 [8]byte
hevent *byte
}
+
+type memoryBasicInformation struct {
+ baseAddress uintptr
+ allocationBase uintptr
+ allocationProtect uint32
+ regionSize uintptr
+ state uint32
+ protect uint32
+ type_ uint32
+}
anon0 [8]byte
hevent *byte
}
+
+type memoryBasicInformation struct {
+ baseAddress uintptr
+ allocationBase uintptr
+ allocationProtect uint32
+ regionSize uintptr
+ state uint32
+ protect uint32
+ type_ uint32
+}
//go:cgo_import_dynamic runtime._SwitchToThread SwitchToThread%0 "kernel32.dll"
//go:cgo_import_dynamic runtime._VirtualAlloc VirtualAlloc%4 "kernel32.dll"
//go:cgo_import_dynamic runtime._VirtualFree VirtualFree%3 "kernel32.dll"
+//go:cgo_import_dynamic runtime._VirtualQuery VirtualQuery%3 "kernel32.dll"
//go:cgo_import_dynamic runtime._WSAGetOverlappedResult WSAGetOverlappedResult%5 "ws2_32.dll"
//go:cgo_import_dynamic runtime._WaitForSingleObject WaitForSingleObject%2 "kernel32.dll"
//go:cgo_import_dynamic runtime._WriteConsoleW WriteConsoleW%5 "kernel32.dll"
_SwitchToThread,
_VirtualAlloc,
_VirtualFree,
+ _VirtualQuery,
_WSAGetOverlappedResult,
_WaitForSingleObject,
_WriteConsoleW,
}
}
-// osStackSize must match SizeOfStackReserve in ../cmd/link/internal/ld/pe.go.
-var osStackSize uintptr = 0x00200000*_64bit + 0x00100000*(1-_64bit)
-
func osinit() {
asmstdcallAddr = unsafe.Pointer(funcPC(asmstdcall))
usleep2Addr = unsafe.Pointer(funcPC(usleep2))
// equivalent threads that all do a mix of GUI, IO, computations, etc.
// In such context dynamic priority boosting does nothing but harm, so we turn it off.
stdcall2(_SetProcessPriorityBoost, currentProcess, 1)
-
- // Fix the entry thread's stack bounds, since runtime entry
- // assumed we were on a tiny stack. If this is a cgo binary,
- // x_cgo_init already fixed these.
- if !iscgo {
- // Leave 8K of slop for calling C functions that don't
- // have stack checks. We shouldn't be anywhere near
- // this bound anyway.
- g0.stack.lo = g0.stack.hi - osStackSize + 8*1024
- g0.stackguard0 = g0.stack.lo + _StackGuard
- g0.stackguard1 = g0.stackguard0
- }
}
func nanotime() int64
//go:nowritebarrierrec
//go:nosplit
func newosproc(mp *m) {
- const _STACK_SIZE_PARAM_IS_A_RESERVATION = 0x00010000
- thandle := stdcall6(_CreateThread, 0, osStackSize,
+ // We pass 0 for the stack size to use the default for this binary.
+ thandle := stdcall6(_CreateThread, 0, 0,
funcPC(tstart_stdcall), uintptr(unsafe.Pointer(mp)),
- _STACK_SIZE_PARAM_IS_A_RESERVATION, 0)
+ 0, 0)
if thandle == 0 {
if atomic.Load(&exiting) != 0 {
var thandle uintptr
stdcall7(_DuplicateHandle, currentProcess, currentThread, currentProcess, uintptr(unsafe.Pointer(&thandle)), 0, 0, _DUPLICATE_SAME_ACCESS)
atomic.Storeuintptr(&getg().m.thread, thandle)
+
+ // Query the true stack base from the OS. Currently we're
+ // running on a small assumed stack.
+ var mbi memoryBasicInformation
+ res := stdcall3(_VirtualQuery, uintptr(unsafe.Pointer(&mbi)), uintptr(unsafe.Pointer(&mbi)), unsafe.Sizeof(mbi))
+ if res == 0 {
+ print("runtime: VirtualQuery failed; errno=", getlasterror(), "\n")
+ throw("VirtualQuery for stack base failed")
+ }
+ // Add 8K of slop for calling C functions that don't have
+ // stack checks. We shouldn't be anywhere near this bound
+ // anyway.
+ base := mbi.allocationBase + 8*1024
+ // Sanity check the stack bounds.
+ g0 := getg()
+ if base > g0.stack.hi || g0.stack.hi-base > 64<<20 {
+ print("runtime: g0 stack [", hex(base), ",", hex(g0.stack.hi), ")\n")
+ throw("bad g0 stack")
+ }
+ g0.stack.lo = base
+ g0.stackguard0 = g0.stack.lo + _StackGuard
+ g0.stackguard1 = g0.stackguard0
+ // Sanity check the SP.
+ stackcheck()
}
// Called from dropm to undo the effect of an minit.
if osStack {
// Initialize stack bounds from system stack.
// Cgo may have left stack size in stack.hi.
+ // minit may update the stack bounds.
size := _g_.stack.hi
if size == 0 {
size = 8192 * sys.StackGuardMultiplier
--- /dev/null
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build amd64 amd64p32 386
+
+package runtime
+
+// stackcheck checks that SP is in range [g->stack.lo, g->stack.hi).
+func stackcheck()
// Layout new m scheduler stack on os stack.
MOVL SP, AX
MOVL AX, (g_stack+stack_hi)(DX)
- SUBL runtime·osStackSize(SB), AX // stack size
- ADDL $(8*1024), AX // slop for calling C
+ SUBL $(64*1024), AX // initial stack size (adjusted later)
MOVL AX, (g_stack+stack_lo)(DX)
ADDL $const__StackGuard, AX
MOVL AX, g_stackguard0(DX)
// Layout new m scheduler stack on os stack.
MOVQ SP, AX
MOVQ AX, (g_stack+stack_hi)(DX)
- SUBQ runtime·osStackSize(SB), AX // stack size
- ADDQ $(8*1024), AX // slop for calling C
+ SUBQ $(64*1024), AX // inital stack size (adjusted later)
MOVQ AX, (g_stack+stack_lo)(DX)
ADDQ $const__StackGuard, AX
MOVQ AX, g_stackguard0(DX)
}
}
+// Test that C code called via a DLL can use large Windows thread
+// stacks and call back in to Go without crashing. See issue #20975.
+//
+// See also TestBigStackCallbackCgo.
+func TestBigStackCallbackSyscall(t *testing.T) {
+ if _, err := exec.LookPath("gcc"); err != nil {
+ t.Skip("skipping test: gcc is missing")
+ }
+
+ srcname, err := filepath.Abs("testdata/testprogcgo/bigstack_windows.c")
+ if err != nil {
+ t.Fatal("Abs failed: ", err)
+ }
+
+ tmpdir, err := ioutil.TempDir("", "TestBigStackCallback")
+ if err != nil {
+ t.Fatal("TempDir failed: ", err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ outname := "mydll.dll"
+ cmd := exec.Command("gcc", "-shared", "-s", "-Werror", "-o", outname, srcname)
+ cmd.Dir = tmpdir
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("failed to build dll: %v - %v", err, string(out))
+ }
+ dllpath := filepath.Join(tmpdir, outname)
+
+ dll := syscall.MustLoadDLL(dllpath)
+ defer dll.Release()
+
+ var ok bool
+ proc := dll.MustFindProc("bigStack")
+ cb := syscall.NewCallback(func() uintptr {
+ // Do something interesting to force stack checks.
+ forceStackCopy()
+ ok = true
+ return 0
+ })
+ proc.Call(cb)
+ if !ok {
+ t.Fatalf("callback not called")
+ }
+}
+
// wantLoadLibraryEx reports whether we expect LoadLibraryEx to work for tests.
func wantLoadLibraryEx() bool {
return testenv.Builder() == "windows-amd64-gce" || testenv.Builder() == "windows-386-gce"
--- /dev/null
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This test source is used by both TestBigStackCallbackCgo (linked
+// directly into the Go binary) and TestBigStackCallbackSyscall
+// (compiled into a DLL).
+
+#include <windows.h>
+#include <stdio.h>
+
+#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
+#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000
+#endif
+
+typedef void callback(char*);
+
+// Allocate a stack that's much larger than the default.
+static const int STACK_SIZE = 16<<20;
+
+static callback *bigStackCallback;
+
+static void useStack(int bytes) {
+ // Windows doesn't like huge frames, so we grow the stack 64k at a time.
+ char x[64<<10];
+ if (bytes < sizeof x) {
+ bigStackCallback(x);
+ } else {
+ useStack(bytes - sizeof x);
+ }
+}
+
+static DWORD WINAPI threadEntry(LPVOID lpParam) {
+ useStack(STACK_SIZE - (128<<10));
+ return 0;
+}
+
+void bigStack(callback *cb) {
+ bigStackCallback = cb;
+ HANDLE hThread = CreateThread(NULL, STACK_SIZE, threadEntry, NULL, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
+ if (hThread == NULL) {
+ fprintf(stderr, "CreateThread failed\n");
+ exit(1);
+ }
+ WaitForSingleObject(hThread, INFINITE);
+}
--- /dev/null
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+/*
+typedef void callback(char*);
+extern void goBigStack1(char*);
+extern void bigStack(callback*);
+*/
+import "C"
+
+func init() {
+ register("BigStack", BigStack)
+}
+
+func BigStack() {
+ // Create a large thread stack and call back into Go to test
+ // if Go correctly determines the stack bounds.
+ C.bigStack((*C.callback)(C.goBigStack1))
+}
+
+//export goBigStack1
+func goBigStack1(x *C.char) {
+ println("OK")
+}