Skip to content

Commit 9c9d74a

Browse files
committed
runtime: prevent sigprof during all stack barrier ops
A sigprof during stack barrier insertion or removal can crash if it detects an inconsistency between the stkbar array and the stack itself. Currently we protect against this when scanning another G's stack using stackLock, but we don't protect against it when unwinding stack barriers for a recover or a memmove to the stack. This commit cleans up and improves the stack locking code. It abstracts out the lock and unlock operations. It uses the lock consistently everywhere we perform stack operations, and pushes the lock/unlock down closer to where the stack barrier operations happen to make it more obvious what it's protecting. Finally, it modifies sigprof so that instead of spinning until it acquires the lock, it simply doesn't perform a traceback if it can't acquire it. This is necessary to prevent self-deadlock. Updates #11863, which introduced stackLock to fix some of these issues, but didn't go far enough. Updates #12528. Change-Id: I9d1fa88ae3744d31ba91500c96c6988ce1a3a349 Reviewed-on: https://go-review.googlesource.com/17036 Reviewed-by: Russ Cox <[email protected]> Run-TryBot: Austin Clements <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 3a2fc06 commit 9c9d74a

File tree

3 files changed

+47
-12
lines changed

3 files changed

+47
-12
lines changed

src/runtime/mgcmark.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,8 @@ func scanstack(gp *g) {
620620
throw("g already has stack barriers")
621621
}
622622

623+
gcLockStackBarriers(gp)
624+
623625
case _GCmarktermination:
624626
if int(gp.stkbarPos) == len(gp.stkbar) {
625627
// gp hit all of the stack barriers (or there
@@ -675,6 +677,9 @@ func scanstack(gp *g) {
675677
if gcphase == _GCmarktermination {
676678
gcw.dispose()
677679
}
680+
if gcphase == _GCmark {
681+
gcUnlockStackBarriers(gp)
682+
}
678683
gp.gcscanvalid = true
679684
}
680685

src/runtime/mstkbar.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
package runtime
107107

108108
import (
109+
"runtime/internal/atomic"
109110
"runtime/internal/sys"
110111
"unsafe"
111112
)
@@ -199,6 +200,8 @@ func gcRemoveStackBarriers(gp *g) {
199200
print("hit ", gp.stkbarPos, " stack barriers, goid=", gp.goid, "\n")
200201
}
201202

203+
gcLockStackBarriers(gp)
204+
202205
// Remove stack barriers that we didn't hit.
203206
for _, stkbar := range gp.stkbar[gp.stkbarPos:] {
204207
gcRemoveStackBarrier(gp, stkbar)
@@ -208,6 +211,8 @@ func gcRemoveStackBarriers(gp *g) {
208211
// adjust them.
209212
gp.stkbarPos = 0
210213
gp.stkbar = gp.stkbar[:0]
214+
215+
gcUnlockStackBarriers(gp)
211216
}
212217

213218
// gcRemoveStackBarrier removes a single stack barrier. It is the
@@ -254,6 +259,7 @@ func gcPrintStkbars(stkbar []stkbar) {
254259
//
255260
//go:nosplit
256261
func gcUnwindBarriers(gp *g, sp uintptr) {
262+
gcLockStackBarriers(gp)
257263
// On LR machines, if there is a stack barrier on the return
258264
// from the frame containing sp, this will mark it as hit even
259265
// though it isn't, but it's okay to be conservative.
@@ -262,6 +268,7 @@ func gcUnwindBarriers(gp *g, sp uintptr) {
262268
gcRemoveStackBarrier(gp, gp.stkbar[gp.stkbarPos])
263269
gp.stkbarPos++
264270
}
271+
gcUnlockStackBarriers(gp)
265272
if debugStackBarrier && gp.stkbarPos != before {
266273
print("skip barriers below ", hex(sp), " in goid=", gp.goid, ": ")
267274
gcPrintStkbars(gp.stkbar[before:gp.stkbarPos])
@@ -284,3 +291,25 @@ func setNextBarrierPC(pc uintptr) {
284291
gp := getg()
285292
gp.stkbar[gp.stkbarPos].savedLRVal = pc
286293
}
294+
295+
// gcLockStackBarriers synchronizes with tracebacks of gp's stack
296+
// during sigprof for installation or removal of stack barriers. It
297+
// blocks until any current sigprof is done tracebacking gp's stack
298+
// and then disallows profiling tracebacks of gp's stack.
299+
//
300+
// This is necessary because a sigprof during barrier installation or
301+
// removal could observe inconsistencies between the stkbar array and
302+
// the stack itself and crash.
303+
func gcLockStackBarriers(gp *g) {
304+
for !atomic.Cas(&gp.stackLock, 0, 1) {
305+
osyield()
306+
}
307+
}
308+
309+
func gcTryLockStackBarriers(gp *g) bool {
310+
return atomic.Cas(&gp.stackLock, 0, 1)
311+
}
312+
313+
func gcUnlockStackBarriers(gp *g) {
314+
atomic.Store(&gp.stackLock, 0)
315+
}

src/runtime/proc.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -735,13 +735,7 @@ func scang(gp *g) {
735735
// the goroutine until we're done.
736736
if castogscanstatus(gp, s, s|_Gscan) {
737737
if !gp.gcscandone {
738-
// Coordinate with traceback
739-
// in sigprof.
740-
for !atomic.Cas(&gp.stackLock, 0, 1) {
741-
osyield()
742-
}
743738
scanstack(gp)
744-
atomic.Store(&gp.stackLock, 0)
745739
gp.gcscandone = true
746740
}
747741
restartg(gp)
@@ -2846,11 +2840,6 @@ func sigprof(pc, sp, lr uintptr, gp *g, mp *m) {
28462840
// Profiling runs concurrently with GC, so it must not allocate.
28472841
mp.mallocing++
28482842

2849-
// Coordinate with stack barrier insertion in scanstack.
2850-
for !atomic.Cas(&gp.stackLock, 0, 1) {
2851-
osyield()
2852-
}
2853-
28542843
// Define that a "user g" is a user-created goroutine, and a "system g"
28552844
// is one that is m->g0 or m->gsignal.
28562845
//
@@ -2917,8 +2906,18 @@ func sigprof(pc, sp, lr uintptr, gp *g, mp *m) {
29172906
// transition. We simply require that g and SP match and that the PC is not
29182907
// in gogo.
29192908
traceback := true
2909+
haveStackLock := false
29202910
if gp == nil || sp < gp.stack.lo || gp.stack.hi < sp || setsSP(pc) {
29212911
traceback = false
2912+
} else if gp.m.curg != nil {
2913+
if gcTryLockStackBarriers(gp.m.curg) {
2914+
haveStackLock = true
2915+
} else {
2916+
// Stack barriers are being inserted or
2917+
// removed, so we can't get a consistent
2918+
// traceback right now.
2919+
traceback = false
2920+
}
29222921
}
29232922
var stk [maxCPUProfStack]uintptr
29242923
n := 0
@@ -2961,7 +2960,9 @@ func sigprof(pc, sp, lr uintptr, gp *g, mp *m) {
29612960
}
29622961
}
29632962
}
2964-
atomic.Store(&gp.stackLock, 0)
2963+
if haveStackLock {
2964+
gcUnlockStackBarriers(gp.m.curg)
2965+
}
29652966

29662967
if prof.hz != 0 {
29672968
// Simple cas-lock to coordinate with setcpuprofilerate.

0 commit comments

Comments
 (0)