]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: don't call cgocallback from signal handler
authorIan Lance Taylor <iant@golang.org>
Mon, 3 Oct 2016 23:58:34 +0000 (16:58 -0700)
committerIan Lance Taylor <iant@golang.org>
Wed, 5 Oct 2016 13:21:49 +0000 (13:21 +0000)
Calling cgocallback from a signal handler can fail when using the race
detector. Calling cgocallback will lead to a call to newextram which
will call oneNewExtraM which will call racegostart. The racegostart
function will set up some race detector data structures, and doing that
will sometimes call the C memory allocator. If we are running the signal
handler from a signal that interrupted the C memory allocator, we will
crash or hang.

Instead, change the signal handler code to call needm and dropm. The
needm function will grab allocated m and g structures and initialize the
g to use the current stack--the signal stack. That is all we need to
safely call code that allocates memory and checks whether it needs to
split the stack. This may temporarily leave us with no m available to
run a cgo callback, but that is OK in this case since the code we call
will quickly either crash or call dropm to return the m.

Implementing this required changing some of the setSignalstackSP
functions to avoid a write barrier. These functions never need a write
barrier but in some cases generated one anyhow because on some systems
the ss_sp field is a pointer.

Change-Id: I3893f47c3a66278f85eab7f94c1ab11d4f3be133
Reviewed-on: https://go-review.googlesource.com/30218
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
src/runtime/crash_cgo_test.go
src/runtime/os3_solaris.go
src/runtime/os_darwin.go
src/runtime/os_linux.go
src/runtime/signal_unix.go
src/runtime/testdata/testprogcgo/racesig.go [new file with mode: 0644]

index 1e509c113a608ff50949c3a20f1cb35986711f8b..2e2f95e5f0ded0bc821c6bd03b2e0a7e51c69dc0 100644 (file)
@@ -319,3 +319,32 @@ func TestRaceProf(t *testing.T) {
                t.Errorf("expected %q got %s", want, got)
        }
 }
+
+func TestRaceSignal(t *testing.T) {
+       if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
+               t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH)
+       }
+
+       testenv.MustHaveGoRun(t)
+
+       // This test requires building various packages with -race, so
+       // it's somewhat slow.
+       if testing.Short() {
+               t.Skip("skipping test in -short mode")
+       }
+
+       exe, err := buildTestProg(t, "testprogcgo", "-race")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       got, err := testEnv(exec.Command(exe, "CgoRaceSignal")).CombinedOutput()
+       if err != nil {
+               t.Logf("%s\n", got)
+               t.Fatal(err)
+       }
+       want := "OK\n"
+       if string(got) != want {
+               t.Errorf("expected %q got %s", want, got)
+       }
+}
index ad66797b71a652865cd310e177417a336cc8c084..d6def7ba432d1f0f6105711c875f7018c1a92e7a 100644 (file)
@@ -287,7 +287,7 @@ func getsig(i uint32) uintptr {
 // setSignaltstackSP sets the ss_sp field of a stackt.
 //go:nosplit
 func setSignalstackSP(s *stackt, sp uintptr) {
-       s.ss_sp = (*byte)(unsafe.Pointer(sp))
+       *(*uintptr)(unsafe.Pointer(&s.ss_sp)) = sp
 }
 
 //go:nosplit
index 03badb18e10362fa9598d734e402982d8a3e5d29..fa5aca2f99763e09f05dbc76da8812f17088e4b4 100644 (file)
@@ -536,7 +536,7 @@ func getsig(i uint32) uintptr {
 // setSignaltstackSP sets the ss_sp field of a stackt.
 //go:nosplit
 func setSignalstackSP(s *stackt, sp uintptr) {
-       s.ss_sp = (*byte)(unsafe.Pointer(sp))
+       *(*uintptr)(unsafe.Pointer(&s.ss_sp)) = sp
 }
 
 //go:nosplit
index ad9c1894dc1d3c378dfce530a7294536c69bef53..1adabe1a42b3e2b6135045322c4e9a3caeefad2d 100644 (file)
@@ -384,7 +384,7 @@ func getsig(i uint32) uintptr {
 // setSignaltstackSP sets the ss_sp field of a stackt.
 //go:nosplit
 func setSignalstackSP(s *stackt, sp uintptr) {
-       s.ss_sp = (*byte)(unsafe.Pointer(sp))
+       *(*uintptr)(unsafe.Pointer(&s.ss_sp)) = sp
 }
 
 func (c *sigctxt) fixsigcode(sig uint32) {
index 47ac8e94e17f359b897318b55061c0819a70cd60..f0c27cedbfff39b8577aeb6607d21c91a2503181 100644 (file)
@@ -221,12 +221,16 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
                sigaltstack(nil, &st)
                if st.ss_flags&_SS_DISABLE != 0 {
                        setg(nil)
-                       cgocallback(unsafe.Pointer(funcPC(noSignalStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig), 0)
+                       needm(0)
+                       noSignalStack(sig)
+                       dropm()
                }
                stsp := uintptr(unsafe.Pointer(st.ss_sp))
                if sp < stsp || sp >= stsp+st.ss_size {
                        setg(nil)
-                       cgocallback(unsafe.Pointer(funcPC(sigNotOnStack)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig), 0)
+                       needm(0)
+                       sigNotOnStack(sig)
+                       dropm()
                }
                setGsignalStack(&st)
                g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
@@ -422,7 +426,7 @@ func ensureSigM() {
 
 // This is called when we receive a signal when there is no signal stack.
 // This can only happen if non-Go code calls sigaltstack to disable the
-// signal stack. This is called via cgocallback to establish a stack.
+// signal stack.
 func noSignalStack(sig uint32) {
        println("signal", sig, "received on thread with no signal stack")
        throw("non-Go code disabled sigaltstack")
@@ -441,15 +445,13 @@ func sigNotOnStack(sig uint32) {
 //go:norace
 //go:nowritebarrierrec
 func badsignal(sig uintptr, c *sigctxt) {
-       cgocallback(unsafe.Pointer(funcPC(badsignalgo)), noescape(unsafe.Pointer(&sig)), unsafe.Sizeof(sig)+unsafe.Sizeof(c), 0)
-}
-
-func badsignalgo(sig uintptr, c *sigctxt) {
+       needm(0)
        if !sigsend(uint32(sig)) {
                // A foreign thread received the signal sig, and the
                // Go code does not want to handle it.
                raisebadsignal(uint32(sig), c)
        }
+       dropm()
 }
 
 //go:noescape
diff --git a/src/runtime/testdata/testprogcgo/racesig.go b/src/runtime/testdata/testprogcgo/racesig.go
new file mode 100644 (file)
index 0000000..e126b8b
--- /dev/null
@@ -0,0 +1,102 @@
+// Copyright 2016 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 linux,amd64
+
+package main
+
+// Test that an external C thread that is calling malloc can be hit
+// with SIGCHLD signals. This used to fail when built with the race
+// detector, because in that case the signal handler would indirectly
+// call the C malloc function.
+
+/*
+#include <errno.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <sched.h>
+#include <unistd.h>
+
+#define ALLOCERS 100
+#define SIGNALERS 10
+
+static void* signalThread(void* p) {
+       pthread_t* pt = (pthread_t*)(p);
+       int i, j;
+
+       for (i = 0; i < 100; i++) {
+               for (j = 0; j < ALLOCERS; j++) {
+                       if (pthread_kill(pt[j], SIGCHLD) < 0) {
+                               return;
+                       }
+               }
+               usleep(1);
+       }
+       return NULL;
+}
+
+#define CALLS 100
+
+static void* mallocThread(void* p) {
+       int i;
+       void *a[CALLS];
+
+       for (i = 0; i < ALLOCERS; i++) {
+               sched_yield();
+       }
+       for (i = 0; i < CALLS; i++) {
+               a[i] = malloc(i);
+       }
+       for (i = 0; i < CALLS; i++) {
+               free(a[i]);
+       }
+       return NULL;
+}
+
+void runRaceSignalThread() {
+       int i;
+       pthread_t m[ALLOCERS];
+       pthread_t s[SIGNALERS];
+
+       for (i = 0; i < ALLOCERS; i++) {
+               pthread_create(&m[i], NULL, mallocThread, NULL);
+       }
+       for (i = 0; i < SIGNALERS; i++) {
+               pthread_create(&s[i], NULL, signalThread, &m[0]);
+       }
+       for (i = 0; i < SIGNALERS; i++) {
+               pthread_join(s[i], NULL);
+       }
+       for (i = 0; i < ALLOCERS; i++) {
+               pthread_join(m[i], NULL);
+       }
+}
+*/
+import "C"
+
+import (
+       "fmt"
+       "os"
+       "time"
+)
+
+func init() {
+       register("CgoRaceSignal", CgoRaceSignal)
+}
+
+func CgoRaceSignal() {
+       // The failure symptom is that the program hangs because of a
+       // deadlock in malloc, so set an alarm.
+       go func() {
+               time.Sleep(5 * time.Second)
+               fmt.Println("Hung for 5 seconds")
+               os.Exit(1)
+       }()
+
+       C.runRaceSignalThread()
+       fmt.Println("OK")
+}