Skip to content

Commit 85d4d46

Browse files
committed
runtime: store syscall parameters in m not on stack
Stack can move during callback, so libcall struct cannot be stored on stack. asmstdcall updates return values and errno in libcall struct parameter, but these could be at different location when callback returns. Store these in m, so they are not affected by GC. Fixes #10406 Change-Id: Id01c9d2b4b44530494e6d9e9e1c875261ce477cd Reviewed-on: https://go-review.googlesource.com/10370 Reviewed-by: Russ Cox <[email protected]>
1 parent 3b7841b commit 85d4d46

File tree

4 files changed

+111
-21
lines changed

4 files changed

+111
-21
lines changed

src/runtime/cgocall.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ func cgocallbackg() {
163163
exit(2)
164164
}
165165

166+
// Save current syscall parameters, so m.syscall can be
167+
// used again if callback decide to make syscall.
168+
syscall := gp.m.syscall
169+
166170
// entersyscall saves the caller's SP to allow the GC to trace the Go
167171
// stack. However, since we're returning to an earlier stack frame and
168172
// need to pair with the entersyscall() call made by cgocall, we must
@@ -173,6 +177,8 @@ func cgocallbackg() {
173177
cgocallbackg1()
174178
// going back to cgo call
175179
reentersyscall(savedpc, uintptr(savedsp))
180+
181+
gp.m.syscall = syscall
176182
}
177183

178184
func cgocallbackg1() {

src/runtime/runtime2.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ type m struct {
332332
libcallpc uintptr // for cpu profiler
333333
libcallsp uintptr
334334
libcallg guintptr
335+
syscall libcall // stores syscall parameters on windows
335336
//#endif
336337
//#ifdef GOOS_solaris
337338
perrno *int32 // pointer to tls errno

src/runtime/syscall_windows.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ func compileCallback(fn eface, cleanstack bool) (code uintptr) {
9191
//go:linkname syscall_loadlibrary syscall.loadlibrary
9292
//go:nosplit
9393
func syscall_loadlibrary(filename *uint16) (handle, err uintptr) {
94-
var c libcall
94+
c := &getg().m.syscall
9595
c.fn = getLoadLibrary()
9696
c.n = 1
97-
c.args = uintptr(unsafe.Pointer(&filename))
98-
cgocall(asmstdcallAddr, unsafe.Pointer(&c))
97+
c.args = uintptr(noescape(unsafe.Pointer(&filename)))
98+
cgocall(asmstdcallAddr, unsafe.Pointer(c))
9999
handle = c.r1
100100
if handle == 0 {
101101
err = c.err
@@ -106,11 +106,11 @@ func syscall_loadlibrary(filename *uint16) (handle, err uintptr) {
106106
//go:linkname syscall_getprocaddress syscall.getprocaddress
107107
//go:nosplit
108108
func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uintptr) {
109-
var c libcall
109+
c := &getg().m.syscall
110110
c.fn = getGetProcAddress()
111111
c.n = 2
112-
c.args = uintptr(unsafe.Pointer(&handle))
113-
cgocall(asmstdcallAddr, unsafe.Pointer(&c))
112+
c.args = uintptr(noescape(unsafe.Pointer(&handle)))
113+
cgocall(asmstdcallAddr, unsafe.Pointer(c))
114114
outhandle = c.r1
115115
if outhandle == 0 {
116116
err = c.err
@@ -121,54 +121,54 @@ func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uint
121121
//go:linkname syscall_Syscall syscall.Syscall
122122
//go:nosplit
123123
func syscall_Syscall(fn, nargs, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
124-
var c libcall
124+
c := &getg().m.syscall
125125
c.fn = fn
126126
c.n = nargs
127-
c.args = uintptr(unsafe.Pointer(&a1))
128-
cgocall(asmstdcallAddr, unsafe.Pointer(&c))
127+
c.args = uintptr(noescape(unsafe.Pointer(&a1)))
128+
cgocall(asmstdcallAddr, unsafe.Pointer(c))
129129
return c.r1, c.r2, c.err
130130
}
131131

132132
//go:linkname syscall_Syscall6 syscall.Syscall6
133133
//go:nosplit
134134
func syscall_Syscall6(fn, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) {
135-
var c libcall
135+
c := &getg().m.syscall
136136
c.fn = fn
137137
c.n = nargs
138-
c.args = uintptr(unsafe.Pointer(&a1))
139-
cgocall(asmstdcallAddr, unsafe.Pointer(&c))
138+
c.args = uintptr(noescape(unsafe.Pointer(&a1)))
139+
cgocall(asmstdcallAddr, unsafe.Pointer(c))
140140
return c.r1, c.r2, c.err
141141
}
142142

143143
//go:linkname syscall_Syscall9 syscall.Syscall9
144144
//go:nosplit
145145
func syscall_Syscall9(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) {
146-
var c libcall
146+
c := &getg().m.syscall
147147
c.fn = fn
148148
c.n = nargs
149-
c.args = uintptr(unsafe.Pointer(&a1))
150-
cgocall(asmstdcallAddr, unsafe.Pointer(&c))
149+
c.args = uintptr(noescape(unsafe.Pointer(&a1)))
150+
cgocall(asmstdcallAddr, unsafe.Pointer(c))
151151
return c.r1, c.r2, c.err
152152
}
153153

154154
//go:linkname syscall_Syscall12 syscall.Syscall12
155155
//go:nosplit
156156
func syscall_Syscall12(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 uintptr) (r1, r2, err uintptr) {
157-
var c libcall
157+
c := &getg().m.syscall
158158
c.fn = fn
159159
c.n = nargs
160-
c.args = uintptr(unsafe.Pointer(&a1))
161-
cgocall(asmstdcallAddr, unsafe.Pointer(&c))
160+
c.args = uintptr(noescape(unsafe.Pointer(&a1)))
161+
cgocall(asmstdcallAddr, unsafe.Pointer(c))
162162
return c.r1, c.r2, c.err
163163
}
164164

165165
//go:linkname syscall_Syscall15 syscall.Syscall15
166166
//go:nosplit
167167
func syscall_Syscall15(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
168-
var c libcall
168+
c := &getg().m.syscall
169169
c.fn = fn
170170
c.n = nargs
171-
c.args = uintptr(unsafe.Pointer(&a1))
172-
cgocall(asmstdcallAddr, unsafe.Pointer(&c))
171+
c.args = uintptr(noescape(unsafe.Pointer(&a1)))
172+
cgocall(asmstdcallAddr, unsafe.Pointer(c))
173173
return c.r1, c.r2, c.err
174174
}

src/runtime/syscall_windows_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,3 +554,86 @@ func TestWERDialogue(t *testing.T) {
554554
// Child process should not open WER dialogue, but return immediately instead.
555555
cmd.CombinedOutput()
556556
}
557+
558+
var used byte
559+
560+
func use(buf []byte) {
561+
for _, c := range buf {
562+
used += c
563+
}
564+
}
565+
566+
func forceStackCopy() (r int) {
567+
var f func(int) int
568+
f = func(i int) int {
569+
var buf [256]byte
570+
use(buf[:])
571+
if i == 0 {
572+
return 0
573+
}
574+
return i + f(i-1)
575+
}
576+
r = f(128)
577+
return
578+
}
579+
580+
func TestReturnAfterStackGrowInCallback(t *testing.T) {
581+
582+
const src = `
583+
#include <stdint.h>
584+
#include <windows.h>
585+
586+
typedef uintptr_t __stdcall (*callback)(uintptr_t);
587+
588+
uintptr_t cfunc(callback f, uintptr_t n) {
589+
uintptr_t r;
590+
r = f(n);
591+
SetLastError(333);
592+
return r;
593+
}
594+
`
595+
tmpdir, err := ioutil.TempDir("", "TestReturnAfterStackGrowInCallback")
596+
if err != nil {
597+
t.Fatal("TempDir failed: ", err)
598+
}
599+
defer os.RemoveAll(tmpdir)
600+
601+
srcname := "mydll.c"
602+
err = ioutil.WriteFile(filepath.Join(tmpdir, srcname), []byte(src), 0)
603+
if err != nil {
604+
t.Fatal(err)
605+
}
606+
outname := "mydll.dll"
607+
cmd := exec.Command("gcc", "-shared", "-s", "-Werror", "-o", outname, srcname)
608+
cmd.Dir = tmpdir
609+
out, err := cmd.CombinedOutput()
610+
if err != nil {
611+
t.Fatalf("failed to build dll: %v - %v", err, string(out))
612+
}
613+
dllpath := filepath.Join(tmpdir, outname)
614+
615+
dll := syscall.MustLoadDLL(dllpath)
616+
defer dll.Release()
617+
618+
proc := dll.MustFindProc("cfunc")
619+
620+
cb := syscall.NewCallback(func(n uintptr) uintptr {
621+
forceStackCopy()
622+
return n
623+
})
624+
625+
// Use a new goroutine so that we get a small stack.
626+
type result struct {
627+
r uintptr
628+
err syscall.Errno
629+
}
630+
c := make(chan result)
631+
go func() {
632+
r, _, err := proc.Call(cb, 100)
633+
c <- result{r, err.(syscall.Errno)}
634+
}()
635+
want := result{r: 100, err: 333}
636+
if got := <-c; got != want {
637+
t.Errorf("got %d want %d", got, want)
638+
}
639+
}

0 commit comments

Comments
 (0)