Skip to content

Commit 4c5130a

Browse files
aclementsgopherbot
authored andcommitted
runtime: ignore SPWrite on innermost traceback frame
Prior to CL 458218, gentraceback ignored the SPWrite function flag on the innermost frame when doing a precise traceback on the assumption that precise tracebacks could only be started from the morestack prologue, and that meant that the innermost function could not have modified SP yet. CL 458218 rearranged this logic a bit and unintentionally lost this particular case. As a result, if traceback starts in an assembly function that modifies SP (either as a result of stack growth or stack scanning during a GC preemption), traceback stop at the SPWrite function and then crash with "traceback did not unwind completely". Fix this by restoring the earlier special case for when the innermost frame is SPWrite. This is a fairly minimal change that should be easy to backport. I think a more robust change would be to encode this per-PC in the spdelta table, so it would be clear that we're unwinding from the morestack prologue and wouldn't rely on a complicated and potentially fragile set of conditions. Fixes #62326. Change-Id: I34f38157631890d33a79d0bd32e32c0fcc2574e4 Reviewed-on: https://go-review.googlesource.com/c/go/+/525835 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Austin Clements <[email protected]> Reviewed-by: Michael Pratt <[email protected]>
1 parent 08e35cc commit 4c5130a

File tree

6 files changed

+73
-18
lines changed

6 files changed

+73
-18
lines changed

src/runtime/import_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//
1111
// There are a few limitations on runtime package tests that this bridges:
1212
//
13-
// 1. Tests use the signature "XTest<name>(t T)". Since runtime can't import
13+
// 1. Tests use the signature "XTest<name>(t TestingT)". Since runtime can't import
1414
// testing, test functions can't use testing.T, so instead we have the T
1515
// interface, which *testing.T satisfies. And we start names with "XTest"
1616
// because otherwise go test will complain about Test functions with the wrong
@@ -39,3 +39,7 @@ func init() {
3939
func TestInlineUnwinder(t *testing.T) {
4040
runtime.XTestInlineUnwinder(t)
4141
}
42+
43+
func TestSPWrite(t *testing.T) {
44+
runtime.XTestSPWrite(t)
45+
}

src/runtime/test_amd64.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package runtime
6+
7+
func testSPWrite()

src/runtime/test_amd64.s

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Create a large frame to force stack growth. See #62326.
2+
TEXT ·testSPWrite(SB),0,$16384-0
3+
// Write to SP
4+
MOVQ SP, AX
5+
ANDQ $~0xf, SP
6+
MOVQ AX, SP
7+
RET

src/runtime/test_stubs.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build !amd64
6+
7+
package runtime
8+
9+
func testSPWrite() {}

src/runtime/traceback.go

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -333,30 +333,40 @@ func (u *unwinder) resolveInternal(innermost, isSyscall bool) {
333333
if flag&abi.FuncFlagTopFrame != 0 {
334334
// This function marks the top of the stack. Stop the traceback.
335335
frame.lr = 0
336-
} else if flag&abi.FuncFlagSPWrite != 0 {
336+
} else if flag&abi.FuncFlagSPWrite != 0 && (!innermost || u.flags&(unwindPrintErrors|unwindSilentErrors) != 0) {
337337
// The function we are in does a write to SP that we don't know
338338
// how to encode in the spdelta table. Examples include context
339339
// switch routines like runtime.gogo but also any code that switches
340340
// to the g0 stack to run host C code.
341-
if u.flags&(unwindPrintErrors|unwindSilentErrors) != 0 {
342-
// We can't reliably unwind the SP (we might
343-
// not even be on the stack we think we are),
344-
// so stop the traceback here.
345-
frame.lr = 0
346-
} else {
347-
// For a GC stack traversal, we should only see
348-
// an SPWRITE function when it has voluntarily preempted itself on entry
349-
// during the stack growth check. In that case, the function has
350-
// not yet had a chance to do any writes to SP and is safe to unwind.
351-
// isAsyncSafePoint does not allow assembly functions to be async preempted,
352-
// and preemptPark double-checks that SPWRITE functions are not async preempted.
353-
// So for GC stack traversal, we can safely ignore SPWRITE for the innermost frame,
354-
// but farther up the stack we'd better not find any.
355-
if !innermost {
356-
println("traceback: unexpected SPWRITE function", funcname(f))
341+
// We can't reliably unwind the SP (we might not even be on
342+
// the stack we think we are), so stop the traceback here.
343+
//
344+
// The one exception (encoded in the complex condition above) is that
345+
// we assume if we're doing a precise traceback, and this is the
346+
// innermost frame, that the SPWRITE function voluntarily preempted itself on entry
347+
// during the stack growth check. In that case, the function has
348+
// not yet had a chance to do any writes to SP and is safe to unwind.
349+
// isAsyncSafePoint does not allow assembly functions to be async preempted,
350+
// and preemptPark double-checks that SPWRITE functions are not async preempted.
351+
// So for GC stack traversal, we can safely ignore SPWRITE for the innermost frame,
352+
// but farther up the stack we'd better not find any.
353+
// This is somewhat imprecise because we're just guessing that we're in the stack
354+
// growth check. It would be better if SPWRITE were encoded in the spdelta
355+
// table so we would know for sure that we were still in safe code.
356+
//
357+
// uSE uPE inn | action
358+
// T _ _ | frame.lr = 0
359+
// F T F | frame.lr = 0; print
360+
// F T T | frame.lr = 0
361+
// F F F | print; panic
362+
// F F T | ignore SPWrite
363+
if u.flags&unwindSilentErrors == 0 && !innermost {
364+
println("traceback: unexpected SPWRITE function", funcname(f))
365+
if u.flags&unwindPrintErrors == 0 {
357366
throw("traceback")
358367
}
359368
}
369+
frame.lr = 0
360370
} else {
361371
var lrPtr uintptr
362372
if usesLR {

src/runtime/tracebackx_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package runtime
6+
7+
func XTestSPWrite(t TestingT) {
8+
// Test that we can traceback from the stack check prologue of a function
9+
// that writes to SP. See #62326.
10+
11+
// Start a goroutine to minimize the initial stack and ensure we grow the stack.
12+
done := make(chan bool)
13+
go func() {
14+
testSPWrite() // Defined in assembly
15+
done <- true
16+
}()
17+
<-done
18+
}

0 commit comments

Comments
 (0)