Skip to content

Commit

Permalink
[release-branch.go1.3] runtime: keep g->syscallsp consistent after cg…
Browse files Browse the repository at this point in the history
…o->Go callbacks

This is a manual backport of CL 131910043
to the Go 1.3 release branch.

We believe this CL can cause arbitrary corruption
in programs that call into C from Go and then
call back into Go from C.

This change will be released in Go 1.3.2.

LGTM=iant
R=iant, hector
CC=adg, dvyukov, golang-codereviews, r
https://golang.org/cl/142690043
  • Loading branch information
rsc authored and adg committed Sep 25, 2014
1 parent a3bfff1 commit 7935b51
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 6 deletions.
1 change: 1 addition & 0 deletions misc/cgo/test/cgo_test.go
Expand Up @@ -53,5 +53,6 @@ func Test5986(t *testing.T) { test5986(t) }
func Test7665(t *testing.T) { test7665(t) }
func TestNaming(t *testing.T) { testNaming(t) }
func Test7560(t *testing.T) { test7560(t) }
func Test7978(t *testing.T) { test7978(t) }

func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }
103 changes: 103 additions & 0 deletions misc/cgo/test/issue7978.go
@@ -0,0 +1,103 @@
// Copyright 2014 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.

// Issue 7978. Stack tracing didn't work during cgo code after calling a Go
// callback. Make sure GC works and the stack trace is correct.

package cgotest

/*
#include <stdint.h>
void issue7978cb(void);
// use ugly atomic variable sync since that doesn't require calling back into
// Go code or OS dependencies
static void issue7978c(uint32_t *sync) {
while(__sync_fetch_and_add(sync, 0) != 0)
;
__sync_fetch_and_add(sync, 1);
while(__sync_fetch_and_add(sync, 0) != 2)
;
issue7978cb();
__sync_fetch_and_add(sync, 1);
while(__sync_fetch_and_add(sync, 0) != 6)
;
}
*/
import "C"

import (
"os"
"runtime"
"strings"
"sync/atomic"
"testing"
)

var issue7978sync uint32

func issue7978check(t *testing.T, wantFunc string, badFunc string, depth int) {
runtime.GC()
buf := make([]byte, 65536)
trace := string(buf[:runtime.Stack(buf, true)])
for _, goroutine := range strings.Split(trace, "\n\n") {
if strings.Contains(goroutine, "test.issue7978go") {
trace := strings.Split(goroutine, "\n")
// look for the expected function in the stack
for i := 0; i < depth; i++ {
if badFunc != "" && strings.Contains(trace[1+2*i], badFunc) {
t.Errorf("bad stack: found %s in the stack:\n%s", badFunc, goroutine)
return
}
if strings.Contains(trace[1+2*i], wantFunc) {
return
}
}
t.Errorf("bad stack: didn't find %s in the stack:\n%s", wantFunc, goroutine)
return
}
}
t.Errorf("bad stack: goroutine not found. Full stack dump:\n%s", trace)
}

func issue7978wait(store uint32, wait uint32) {
if store != 0 {
atomic.StoreUint32(&issue7978sync, store)
}
for atomic.LoadUint32(&issue7978sync) != wait {
runtime.Gosched()
}
}

//export issue7978cb
func issue7978cb() {
issue7978wait(3, 4)
}

func issue7978go() {
C.issue7978c((*C.uint32_t)(&issue7978sync))
issue7978wait(7, 8)
}

func test7978(t *testing.T) {
if os.Getenv("GOTRACEBACK") != "2" {
t.Fatal("GOTRACEBACK must be 2")
}
issue7978sync = 0
go issue7978go()
// test in c code, before callback
issue7978wait(0, 1)
issue7978check(t, "runtime.cgocall(", "", 1)
// test in go code, during callback
issue7978wait(2, 3)
issue7978check(t, "test.issue7978cb(", "test.issue7978go", 4)
// test in c code, after callback
issue7978wait(4, 5)
issue7978check(t, "runtime.cgocall(", "runtime.cgocallback", 1)
// test in go code, after return from cgo
issue7978wait(6, 7)
issue7978check(t, "test.issue7978go(", "", 4)
atomic.StoreUint32(&issue7978sync, 8)
}
6 changes: 5 additions & 1 deletion src/pkg/runtime/cgocall.c
Expand Up @@ -234,14 +234,18 @@ void runtime·cgocallbackg1(void);
void
runtime·cgocallbackg(void)
{
uintptr pc, sp;

if(g != m->curg) {
runtime·prints("runtime: bad g in cgocallback");
runtime·exit(2);
}

pc = g->syscallpc;
sp = g->syscallsp;
runtime·exitsyscall(); // coming out of cgo call
runtime·cgocallbackg1();
runtime·entersyscall(); // going back to cgo call
runtime·reentersyscall((void*)pc, sp); // going back to cgo call
}

void
Expand Down
20 changes: 15 additions & 5 deletions src/pkg/runtime/proc.c
Expand Up @@ -1499,14 +1499,14 @@ save(void *pc, uintptr sp)
// entersyscall is going to return immediately after.
#pragma textflag NOSPLIT
void
·entersyscall(int32 dummy)
runtime·reentersyscall(void *pc, uintptr sp)
{
// Disable preemption because during this function g is in Gsyscall status,
// but can have inconsistent g->sched, do not let GC observe it.
m->locks++;

// Leave SP around for GC and traceback.
save(runtime·getcallerpc(&dummy), runtime·getcallersp(&dummy));
save(pc, sp);
g->syscallsp = g->sched.sp;
g->syscallpc = g->sched.pc;
g->syscallstack = g->stackbase;
Expand All @@ -1525,7 +1525,7 @@ void
runtime·notewakeup(&runtime·sched.sysmonnote);
}
runtime·unlock(&runtime·sched);
save(runtime·getcallerpc(&dummy), runtime·getcallersp(&dummy));
save(pc, sp);
}

m->mcache = nil;
Expand All @@ -1538,7 +1538,7 @@ void
runtime·notewakeup(&runtime·sched.stopnote);
}
runtime·unlock(&runtime·sched);
save(runtime·getcallerpc(&dummy), runtime·getcallersp(&dummy));
save(pc, sp);
}

// Goroutines must not split stacks in Gsyscall status (it would corrupt g->sched).
Expand All @@ -1548,6 +1548,13 @@ void
m->locks--;
}

#pragma textflag NOSPLIT
void
·entersyscall(int32 dummy)
{
runtime·reentersyscall(runtime·getcallerpc(&dummy), runtime·getcallersp(&dummy));
}

// The same as runtime·entersyscall(), but with a hint that the syscall is blocking.
#pragma textflag NOSPLIT
void
Expand Down Expand Up @@ -1588,10 +1595,13 @@ void
// from the low-level system calls used by the runtime.
#pragma textflag NOSPLIT
void
runtime·exitsyscall(void)
·exitsyscall(int32 dummy)
{
m->locks++; // see comment in entersyscall

if(runtime·getcallersp(&dummy) > g->syscallsp)
runtime·throw("exitsyscall: syscall frame is no longer valid");

if(g->isbackground) // do not consider blocked scavenger for deadlock detection
incidlelocked(-1);

Expand Down
1 change: 1 addition & 0 deletions src/pkg/runtime/runtime.h
Expand Up @@ -921,6 +921,7 @@ M* runtime·newm(void);
void runtime·goexit(void);
void runtime·asmcgocall(void (*fn)(void*), void*);
void runtime·entersyscall(void);
void runtime·reentersyscall(void*, uintptr);
void runtime·entersyscallblock(void);
void runtime·exitsyscall(void);
G* runtime·newproc1(FuncVal*, byte*, int32, int32, void*);
Expand Down
2 changes: 2 additions & 0 deletions src/run.bash
Expand Up @@ -112,6 +112,8 @@ go run $GOROOT/test/run.go - . || exit 1

[ "$CGO_ENABLED" != 1 ] ||
(xcd ../misc/cgo/test
# cgo tests inspect the traceback for runtime functions
export GOTRACEBACK=2
go test -ldflags '-linkmode=auto' || exit 1
# linkmode=internal fails on dragonfly since errno is a TLS relocation.
[ "$GOHOSTOS" == dragonfly ] || go test -ldflags '-linkmode=internal' || exit 1
Expand Down
7 changes: 7 additions & 0 deletions src/run.bat
Expand Up @@ -90,11 +90,18 @@ go run "%GOROOT%\test\run.go" - ..\misc\cgo\stdio
if errorlevel 1 goto fail
echo.

:: cgo tests inspect the traceback for runtime functions
set OLDGOTRACEBACK=%GOTRACEBACK%
set GOTRACEBACK=2

echo # ..\misc\cgo\test
go test ..\misc\cgo\test
if errorlevel 1 goto fail
echo.

set GOTRACEBACK=%OLDGOTRACEBACK%
set OLDGOTRACEBACK=

echo # ..\misc\cgo\testso
cd ..\misc\cgo\testso
set FAIL=0
Expand Down

0 comments on commit 7935b51

Please sign in to comment.