Commit e391fade authored by Austin Clements's avatar Austin Clements

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: default avatarKeith Randall <khr@golang.org>
parent f045ddc6
...@@ -310,6 +310,39 @@ func testDeferPtrsPanic(c chan int, i int) { ...@@ -310,6 +310,39 @@ func testDeferPtrsPanic(c chan int, i int) {
useStackAndCall(i, func() { panic(1) }) 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 // TestPanicUseStack checks that a chain of Panic structs on the stack are
// updated correctly if the stack grows during the deferred execution that // updated correctly if the stack grows during the deferred execution that
// happens as a result of the panic. // 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 ...@@ -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. // returns; everything live at earlier deferprocs is still live at that one.
frame.continpc = frame.pc frame.continpc = frame.pc
if waspanic { 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 frame.continpc = _defer.pc
} else { } else {
frame.continpc = 0 frame.continpc = 0
...@@ -310,7 +317,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in ...@@ -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. // 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 _defer = _defer.link
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment