aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/traceback.go
diff options
context:
space:
mode:
authorAustin Clements <austin@google.com>2018-05-22 13:49:50 -0400
committerAustin Clements <austin@google.com>2018-05-22 19:38:03 +0000
commite391faded721cdb136102d4e00725f9d775dc168 (patch)
tree67f9029da5f20328967c395d0d3b9575bd70687b /src/runtime/traceback.go
parentf045ddc624dea292257249a618e4ad1bd2bd5c6e (diff)
downloadgo-e391faded721cdb136102d4e00725f9d775dc168.tar.gz
go-e391faded721cdb136102d4e00725f9d775dc168.zip
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>
Diffstat (limited to 'src/runtime/traceback.go')
-rw-r--r--src/runtime/traceback.go11
1 files changed, 9 insertions, 2 deletions
diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go
index 0fd7ef1987..cc5e01eb8b 100644
--- a/src/runtime/traceback.go
+++ b/src/runtime/traceback.go
@@ -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
}