Skip to content

Commit 306f8f1

Browse files
aclementsrsc
authored andcommitted
runtime: unwind stack barriers when writing above the current frame
Stack barriers assume that writes through pointers to frames above the current frame will get write barriers, and hence these frames do not need to be re-scanned to pick up these changes. For normal writes, this is true. However, there are places in the runtime that use typedmemmove to potentially write through pointers to higher frames (such as mapassign1). Currently, typedmemmove does not execute write barriers if the destination is on the stack. If there's a stack barrier between the current frame and the frame being modified with typedmemmove, and the stack barrier is not otherwise hit, it's possible that the garbage collector will never see the updated pointer and incorrectly reclaim the object. Fix this by making heapBitsBulkBarrier (which lies behind typedmemmove and its variants) detect when the destination is in the stack and unwind stack barriers up to the point, forcing mark termination to later rescan the effected frame and collect these pointers. Fixes #11084. Might be related to #10240, #10541, #10941, #11023, #11027 and possibly others. Change-Id: I323d6cd0f1d29fa01f8fc946f4b90e04ef210efd Reviewed-on: https://go-review.googlesource.com/10791 Reviewed-by: Russ Cox <[email protected]>
1 parent 1303957 commit 306f8f1

File tree

3 files changed

+43
-6
lines changed

3 files changed

+43
-6
lines changed

src/runtime/mbitmap.go

+28-1
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,34 @@ func heapBitsBulkBarrier(p, size uintptr) {
367367
if (p|size)&(ptrSize-1) != 0 {
368368
throw("heapBitsBulkBarrier: unaligned arguments")
369369
}
370-
if !writeBarrierEnabled || !inheap(p) {
370+
if !writeBarrierEnabled {
371+
return
372+
}
373+
if !inheap(p) {
374+
// If p is on the stack and in a higher frame than the
375+
// caller, we either need to execute write barriers on
376+
// it (which is what happens for normal stack writes
377+
// through pointers to higher frames), or we need to
378+
// force the mark termination stack scan to scan the
379+
// frame containing p.
380+
//
381+
// Executing write barriers on p is complicated in the
382+
// general case because we either need to unwind the
383+
// stack to get the stack map, or we need the type's
384+
// bitmap, which may be a GC program.
385+
//
386+
// Hence, we opt for forcing the re-scan to scan the
387+
// frame containing p, which we can do by simply
388+
// unwinding the stack barriers between the current SP
389+
// and p's frame.
390+
gp := getg().m.curg
391+
if gp.stack.lo <= p && p < gp.stack.hi {
392+
// Run on the system stack to give it more
393+
// stack space.
394+
systemstack(func() {
395+
gcUnwindBarriers(gp, p)
396+
})
397+
}
371398
return
372399
}
373400

src/runtime/mgcmark.go

+14-4
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,11 @@ func gcRemoveStackBarriers(gp *g) {
539539

540540
// gcRemoveStackBarrier removes a single stack barrier. It is the
541541
// inverse operation of gcInstallStackBarrier.
542+
//
543+
// This is nosplit to ensure gp's stack does not move.
544+
//
542545
//go:nowritebarrier
546+
//go:nosplit
543547
func gcRemoveStackBarrier(gp *g, stkbar stkbar) {
544548
if debugStackBarrier {
545549
print("remove stack barrier at ", hex(stkbar.savedLRPtr), " with ", hex(stkbar.savedLRVal), ", goid=", gp.goid, "\n")
@@ -568,15 +572,21 @@ func gcPrintStkbars(stkbar []stkbar) {
568572
print("]")
569573
}
570574

571-
// gcSkipBarriers marks all stack barriers up to sp as hit. This is
572-
// used during stack unwinding for panic/recover. This must run on the
573-
// system stack to ensure gp's stack does not get copied.
574-
func gcSkipBarriers(gp *g, sp uintptr) {
575+
// gcUnwindBarriers marks all stack barriers up the frame containing
576+
// sp as hit and removes them. This is used during stack unwinding for
577+
// panic/recover and by heapBitsBulkBarrier to force stack re-scanning
578+
// when its destination is on the stack.
579+
//
580+
// This is nosplit to ensure gp's stack does not move.
581+
//
582+
//go:nosplit
583+
func gcUnwindBarriers(gp *g, sp uintptr) {
575584
// On LR machines, if there is a stack barrier on the return
576585
// from the frame containing sp, this will mark it as hit even
577586
// though it isn't, but it's okay to be conservative.
578587
before := gp.stkbarPos
579588
for int(gp.stkbarPos) < len(gp.stkbar) && gp.stkbar[gp.stkbarPos].savedLRPtr < sp {
589+
gcRemoveStackBarrier(gp, gp.stkbar[gp.stkbarPos])
580590
gp.stkbarPos++
581591
}
582592
if debugStackBarrier && gp.stkbarPos != before {

src/runtime/panic1.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func recovery(gp *g) {
2929
// Make the deferproc for this d return again,
3030
// this time returning 1. The calling function will
3131
// jump to the standard return epilogue.
32-
gcSkipBarriers(gp, sp)
32+
gcUnwindBarriers(gp, sp)
3333
gp.sched.sp = sp
3434
gp.sched.pc = pc
3535
gp.sched.lr = 0

0 commit comments

Comments
 (0)