--- /dev/null
+// 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.
+
+package main
+
+// This program failed when run under the C/C++ ThreadSanitizer. The
+// TSAN library was not keeping track of whether signals should be
+// delivered on the alternate signal stack.
+
+/*
+#cgo CFLAGS: -g -fsanitize=thread
+#cgo LDFLAGS: -g -fsanitize=thread
+
+#include <stdlib.h>
+#include <sys/time.h>
+
+void spin() {
+ size_t n;
+ struct timeval tvstart, tvnow;
+ int diff;
+
+ gettimeofday(&tvstart, NULL);
+ for (n = 0; n < 1<<20; n++) {
+ free(malloc(n));
+ gettimeofday(&tvnow, NULL);
+ diff = (tvnow.tv_sec - tvstart.tv_sec) * 1000 * 1000 + (tvnow.tv_usec - tvstart.tv_usec);
+
+ // Profile frequency is 100Hz so we should definitely
+ // get a signal in 50 milliseconds.
+ if (diff > 50 * 1000) {
+ break;
+ }
+ }
+}
+*/
+import "C"
+
+import (
+ "io/ioutil"
+ "runtime/pprof"
+ "time"
+)
+
+func goSpin() {
+ start := time.Now()
+ for n := 0; n < 1<<20; n++ {
+ _ = make([]byte, n)
+ if time.Since(start) > 50*time.Millisecond {
+ break
+ }
+ }
+}
+
+func main() {
+ pprof.StartCPUProfile(ioutil.Discard)
+ go C.spin()
+ goSpin()
+ pprof.StopCPUProfile()
+}
}
// If some non-Go code called sigaltstack, adjust.
+ setStack := false
+ var gsignalStack gsignalStack
sp := uintptr(unsafe.Pointer(&sig))
if sp < g.m.gsignal.stack.lo || sp >= g.m.gsignal.stack.hi {
- var st stackt
- sigaltstack(nil, &st)
- if st.ss_flags&_SS_DISABLE != 0 {
- setg(nil)
- needm(0)
- noSignalStack(sig)
- dropm()
- }
- stsp := uintptr(unsafe.Pointer(st.ss_sp))
- if sp < stsp || sp >= stsp+st.ss_size {
- setg(nil)
- needm(0)
- sigNotOnStack(sig)
- dropm()
+ if sp >= g.m.g0.stack.lo && sp < g.m.g0.stack.hi {
+ // The signal was delivered on the g0 stack.
+ // This can happen when linked with C code
+ // using the thread sanitizer, which collects
+ // signals then delivers them itself by calling
+ // the signal handler directly when C code,
+ // including C code called via cgo, calls a
+ // TSAN-intercepted function such as malloc.
+ st := stackt{ss_size: g.m.g0.stack.hi - g.m.g0.stack.lo}
+ setSignalstackSP(&st, g.m.g0.stack.lo)
+ setGsignalStack(&st, &gsignalStack)
+ g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
+ setStack = true
+ } else {
+ var st stackt
+ sigaltstack(nil, &st)
+ if st.ss_flags&_SS_DISABLE != 0 {
+ setg(nil)
+ needm(0)
+ noSignalStack(sig)
+ dropm()
+ }
+ stsp := uintptr(unsafe.Pointer(st.ss_sp))
+ if sp < stsp || sp >= stsp+st.ss_size {
+ setg(nil)
+ needm(0)
+ sigNotOnStack(sig)
+ dropm()
+ }
+ setGsignalStack(&st, &gsignalStack)
+ g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
+ setStack = true
}
- setGsignalStack(&st)
- g.m.gsignal.stktopsp = getcallersp(unsafe.Pointer(&sig))
}
setg(g.m.gsignal)
c.fixsigcode(sig)
sighandler(sig, info, ctx, g)
setg(g)
+ if setStack {
+ restoreGsignalStack(&gsignalStack)
+ }
}
// sigpanic turns a synchronous signal into a run-time panic.
signalstack(&_g_.m.gsignal.stack)
_g_.m.newSigstack = true
} else {
- setGsignalStack(&st)
+ setGsignalStack(&st, nil)
_g_.m.newSigstack = false
}
}
}
}
+// gsignalStack saves the fields of the gsignal stack changed by
+// setGsignalStack.
+type gsignalStack struct {
+ stack stack
+ stackguard0 uintptr
+ stackguard1 uintptr
+ stackAlloc uintptr
+ stktopsp uintptr
+}
+
// setGsignalStack sets the gsignal stack of the current m to an
// alternate signal stack returned from the sigaltstack system call.
+// It saves the old values in *old for use by restoreGsignalStack.
// This is used when handling a signal if non-Go code has set the
// alternate signal stack.
//go:nosplit
//go:nowritebarrierrec
-func setGsignalStack(st *stackt) {
+func setGsignalStack(st *stackt, old *gsignalStack) {
g := getg()
+ if old != nil {
+ old.stack = g.m.gsignal.stack
+ old.stackguard0 = g.m.gsignal.stackguard0
+ old.stackguard1 = g.m.gsignal.stackguard1
+ old.stackAlloc = g.m.gsignal.stackAlloc
+ old.stktopsp = g.m.gsignal.stktopsp
+ }
stsp := uintptr(unsafe.Pointer(st.ss_sp))
g.m.gsignal.stack.lo = stsp
g.m.gsignal.stack.hi = stsp + st.ss_size
g.m.gsignal.stackAlloc = st.ss_size
}
+// restoreGsignalStack restores the gsignal stack to the value it had
+// before entering the signal handler.
+//go:nosplit
+//go:nowritebarrierrec
+func restoreGsignalStack(st *gsignalStack) {
+ gp := getg().m.gsignal
+ gp.stack = st.stack
+ gp.stackguard0 = st.stackguard0
+ gp.stackguard1 = st.stackguard1
+ gp.stackAlloc = st.stackAlloc
+ gp.stktopsp = st.stktopsp
+}
+
// signalstack sets the current thread's alternate signal stack to s.
//go:nosplit
func signalstack(s *stack) {