mirror of
https://github.com/golang/go
synced 2024-11-18 04:14:49 -07:00
runtime: fix defer matching of leaf functions on LR machines
Traceback matches the defer stack with the function call stack using the SP recorded in defer frames when the defer frame is created. However, on LR machines this is ambiguous: if function A pushes a defer and then calls function B, where B is a leaf function with a zero-sized frame, then both A and B have the same SP and will *both* match the defer on the defer stack. Since traceback unwinds through B first, it will incorrectly match up the defer with B's frame instead of A's frame. Where this goes particularly wrong is if function B causes a signal that turns into a panic (e.g., a nil pointer dereference). In order to handle the fact that we may not have a liveness map at the location that caused the signal and injected a sigpanic call, traceback has logic to unwind the panicking frame's continuation PC to the PC where the most recent defer was pushed (this is safe because the frame is dead other than any defers it pushed). However, if traceback mis-matches the defer stack, it winds up reporting the B's continuation PC is in A. If the runtime then uses this continuation PC to look up PCDATA in B, it will panic because the PC is out of range for B. This failure mode can be seen in sync/atomic/atomic_test.go:TestNilDeref. An example failure is: https://build.golang.org/log/8e07a762487839252af902355f6b1379dbd463c5 This CL fixes all of this by recognizing that a function that pushes a defer must also have a non-zero-sized frame and using this fact to refine the defer matching logic. Fixes the build for arm64, mips, mipsle, ppc64, ppc64le, and s390x. Fixes #25499. Change-Id: Iff7c01d08ad42f3de22b3a73658cc2f674900101 Reviewed-on: https://go-review.googlesource.com/114078 Run-TryBot: Austin Clements <austin@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
parent
f045ddc624
commit
e391faded7
@ -310,6 +310,39 @@ func testDeferPtrsPanic(c chan int, i int) {
|
||||
useStackAndCall(i, func() { panic(1) })
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func testDeferLeafSigpanic1() {
|
||||
// Cause a sigpanic to be injected in this frame.
|
||||
//
|
||||
// This function has to be declared before
|
||||
// TestDeferLeafSigpanic so the runtime will crash if we think
|
||||
// this function's continuation PC is in
|
||||
// TestDeferLeafSigpanic.
|
||||
*(*int)(nil) = 0
|
||||
}
|
||||
|
||||
// TestDeferLeafSigpanic tests defer matching around leaf functions
|
||||
// that sigpanic. This is tricky because on LR machines the outer
|
||||
// function and the inner function have the same SP, but it's critical
|
||||
// that we match up the defer correctly to get the right liveness map.
|
||||
// See issue #25499.
|
||||
func TestDeferLeafSigpanic(t *testing.T) {
|
||||
// Push a defer that will walk the stack.
|
||||
defer func() {
|
||||
if err := recover(); err == nil {
|
||||
t.Fatal("expected panic from nil pointer")
|
||||
}
|
||||
GC()
|
||||
}()
|
||||
// Call a leaf function. We must set up the exact call stack:
|
||||
//
|
||||
// defering function -> leaf function -> sigpanic
|
||||
//
|
||||
// On LR machines, the leaf function will have the same SP as
|
||||
// the SP pushed for the defer frame.
|
||||
testDeferLeafSigpanic1()
|
||||
}
|
||||
|
||||
// TestPanicUseStack checks that a chain of Panic structs on the stack are
|
||||
// updated correctly if the stack grows during the deferred execution that
|
||||
// happens as a result of the panic.
|
||||
|
@ -302,7 +302,14 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
|
||||
// returns; everything live at earlier deferprocs is still live at that one.
|
||||
frame.continpc = frame.pc
|
||||
if waspanic {
|
||||
if _defer != nil && _defer.sp == frame.sp {
|
||||
// We match up defers with frames using the SP.
|
||||
// However, if the function has an empty stack
|
||||
// frame, then it's possible (on LR machines)
|
||||
// for multiple call frames to have the same
|
||||
// SP. But, since a function with no frame
|
||||
// can't push a defer, the defer can't belong
|
||||
// to that frame.
|
||||
if _defer != nil && _defer.sp == frame.sp && frame.sp != frame.fp {
|
||||
frame.continpc = _defer.pc
|
||||
} else {
|
||||
frame.continpc = 0
|
||||
@ -310,7 +317,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
|
||||
}
|
||||
|
||||
// Unwind our local defer stack past this frame.
|
||||
for _defer != nil && (_defer.sp == frame.sp || _defer.sp == _NoArgs) {
|
||||
for _defer != nil && ((_defer.sp == frame.sp && frame.sp != frame.fp) || _defer.sp == _NoArgs) {
|
||||
_defer = _defer.link
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user