From c25b12fb815938d6fa894cd1552c3c78825c6254 Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Fri, 15 Apr 2022 12:23:06 -0400 Subject: [release-branch.go1.17] runtime: use saved LR when unwinding through morestack On LR machine, consider F calling G calling H, which grows stack. The stack looks like ... G's frame: ... locals ... saved LR = return PC in F <- SP points here at morestack H's frame (to be created) At morestack, we save gp.sched.pc = H's morestack call gp.sched.sp = H's entry SP (the arrow above) gp.sched.lr = return PC in G Currently, when unwinding through morestack (if _TraceJumpStack is set), we switch PC and SP but not LR. We then have frame.pc = H's morestack call frame.sp = H's entry SP (the arrow above) As LR is not set, we load it from stack at *sp, so frame.lr = return PC in F As the SP hasn't decremented at the morestack call, frame.fp = frame.sp = H's entry SP Unwinding a frame, we have frame.pc = old frame.lr = return PC in F frame.sp = old frame.fp = H's entry SP a.k.a. G's SP The PC and SP don't match. The unwinding will go off if F and G have different frame sizes. Fix this by preserving the LR when switching stack. Also add code to detect infinite loop in unwinding. TODO: add some test. I can reproduce the infinite loop (or throw with added check) but the frequency is low. Fixes #53111. Updates #52116. Change-Id: I6e1294f1c6e55f664c962767a1cf6c466a0c0eff Reviewed-on: https://go-review.googlesource.com/c/go/+/400575 TryBot-Result: Gopher Robot Run-TryBot: Cherry Mui Reviewed-by: Eric Fang Reviewed-by: Benny Siegert (cherry picked from commit 74f00094220f26c80fbaab6eca28c3a664897d24) Reviewed-on: https://go-review.googlesource.com/c/go/+/408822 Reviewed-by: Austin Clements --- src/runtime/traceback.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index 814c323634..de593491a1 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -116,6 +116,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in } waspanic := false cgoCtxt := gp.cgoCtxt + stack := gp.stack printing := pcbuf == nil && callback == nil // If the PC is zero, it's likely a nil function call. @@ -134,7 +135,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in if !f.valid() { if callback != nil || printing { print("runtime: unknown pc ", hex(frame.pc), "\n") - tracebackHexdump(gp.stack, &frame, 0) + tracebackHexdump(stack, &frame, 0) } if callback != nil { throw("unknown pc") @@ -194,12 +195,15 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in frame.fn = findfunc(frame.pc) f = frame.fn flag = f.flag + frame.lr = gp.m.curg.sched.lr frame.sp = gp.m.curg.sched.sp + stack = gp.m.curg.stack cgoCtxt = gp.m.curg.cgoCtxt case funcID_systemstack: // systemstack returns normally, so just follow the // stack transition. frame.sp = gp.m.curg.sched.sp + stack = gp.m.curg.stack cgoCtxt = gp.m.curg.cgoCtxt flag &^= funcFlag_SPWRITE } @@ -268,7 +272,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in } if callback != nil || doPrint { print("runtime: unexpected return pc for ", funcname(f), " called from ", hex(frame.lr), "\n") - tracebackHexdump(gp.stack, &frame, lrPtr) + tracebackHexdump(stack, &frame, lrPtr) } if callback != nil { throw("unknown caller pc") @@ -497,6 +501,13 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in break } + if frame.pc == frame.lr && frame.sp == frame.fp { + // If the next frame is identical to the current frame, we cannot make progress. + print("runtime: traceback stuck. pc=", hex(frame.pc), " sp=", hex(frame.sp), "\n") + tracebackHexdump(stack, &frame, frame.sp) + throw("traceback stuck") + } + // Unwind to next frame. frame.fn = flr frame.pc = frame.lr -- cgit v1.2.3-54-g00ecf