Skip to content

Commit 032678e

Browse files
committed
runtime: don't elide wrapper functions that call panic or at TOS
CL 45412 started hiding autogenerated wrapper functions from call stacks so that call stack semantics better matched language semantics. This is based on the theory that the wrapper function will call the "real" function and all the programmer knows about is the real function. However, this theory breaks down in two cases: 1. If the wrapper is at the top of the stack, then it didn't call anything. This can happen, for example, if the "stack" was actually synthesized by the user. 2. If the wrapper panics, for example by calling panicwrap or by dereferencing a nil pointer, then it didn't call the wrapped function and the user needs to see what panicked, even if we can't attribute it nicely. This commit modifies the traceback logic to include the wrapper function in both of these cases. Fixes #22231. Change-Id: I6e4339a652f73038bd8331884320f0b8edd86eb1 Reviewed-on: https://go-review.googlesource.com/76770 Run-TryBot: Austin Clements <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent e8905d2 commit 032678e

File tree

4 files changed

+122
-12
lines changed

4 files changed

+122
-12
lines changed

src/runtime/extern.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,11 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool) {
178178
// We asked for one extra, so skip that one. If this is sigpanic,
179179
// stepping over this frame will set up state in Frames so the
180180
// next frame is correct.
181-
callers, _, ok = stackExpander.next(callers)
181+
callers, _, ok = stackExpander.next(callers, true)
182182
if !ok {
183183
return
184184
}
185-
_, frame, _ := stackExpander.next(callers)
185+
_, frame, _ := stackExpander.next(callers, true)
186186
pc = frame.PC
187187
file = frame.File
188188
line = frame.Line

src/runtime/stack_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package runtime_test
77
import (
88
"bytes"
99
"fmt"
10+
"reflect"
1011
. "runtime"
1112
"strings"
1213
"sync"
@@ -650,6 +651,8 @@ func (s structWithMethod) stack() string {
650651
return string(buf[:Stack(buf, false)])
651652
}
652653

654+
func (s structWithMethod) nop() {}
655+
653656
func TestStackWrapperCaller(t *testing.T) {
654657
var d structWithMethod
655658
// Force the compiler to construct a wrapper method.
@@ -687,6 +690,81 @@ func TestStackWrapperStack(t *testing.T) {
687690
}
688691
}
689692

693+
type I interface {
694+
M()
695+
}
696+
697+
func TestStackWrapperStackPanic(t *testing.T) {
698+
t.Run("sigpanic", func(t *testing.T) {
699+
// nil calls to interface methods cause a sigpanic.
700+
testStackWrapperPanic(t, func() { I.M(nil) }, "runtime_test.I.M")
701+
})
702+
t.Run("panicwrap", func(t *testing.T) {
703+
// Nil calls to value method wrappers call panicwrap.
704+
wrapper := (*structWithMethod).nop
705+
testStackWrapperPanic(t, func() { wrapper(nil) }, "runtime_test.(*structWithMethod).nop")
706+
})
707+
}
708+
709+
func testStackWrapperPanic(t *testing.T, cb func(), expect string) {
710+
// Test that the stack trace from a panicking wrapper includes
711+
// the wrapper, even though elide these when they don't panic.
712+
t.Run("CallersFrames", func(t *testing.T) {
713+
defer func() {
714+
err := recover()
715+
if err == nil {
716+
t.Fatalf("expected panic")
717+
}
718+
pcs := make([]uintptr, 10)
719+
n := Callers(0, pcs)
720+
frames := CallersFrames(pcs[:n])
721+
for {
722+
frame, more := frames.Next()
723+
t.Log(frame.Function)
724+
if frame.Function == expect {
725+
return
726+
}
727+
if !more {
728+
break
729+
}
730+
}
731+
t.Fatalf("panicking wrapper %s missing from stack trace", expect)
732+
}()
733+
cb()
734+
})
735+
t.Run("Stack", func(t *testing.T) {
736+
defer func() {
737+
err := recover()
738+
if err == nil {
739+
t.Fatalf("expected panic")
740+
}
741+
buf := make([]byte, 4<<10)
742+
stk := string(buf[:Stack(buf, false)])
743+
if !strings.Contains(stk, "\n"+expect) {
744+
t.Fatalf("panicking wrapper %s missing from stack trace:\n%s", expect, stk)
745+
}
746+
}()
747+
cb()
748+
})
749+
}
750+
751+
func TestCallersFromWrapper(t *testing.T) {
752+
// Test that invoking CallersFrames on a stack where the first
753+
// PC is an autogenerated wrapper keeps the wrapper in the
754+
// trace. Normally we elide these, assuming that the wrapper
755+
// calls the thing you actually wanted to see, but in this
756+
// case we need to keep it.
757+
pc := reflect.ValueOf(I.M).Pointer()
758+
frames := CallersFrames([]uintptr{pc})
759+
frame, more := frames.Next()
760+
if frame.Function != "runtime_test.I.M" {
761+
t.Fatalf("want function %s, got %s", "runtime_test.I.M", frame.Function)
762+
}
763+
if more {
764+
t.Fatalf("want 1 frame, got > 1")
765+
}
766+
}
767+
690768
func TestTracebackSystemstack(t *testing.T) {
691769
if GOARCH == "ppc64" || GOARCH == "ppc64le" {
692770
t.Skip("systemstack tail call not implemented on ppc64x")

src/runtime/symtab.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ type Frames struct {
1919
// stackExpander expands callers into a sequence of Frames,
2020
// tracking the necessary state across PCs.
2121
stackExpander stackExpander
22+
23+
// elideWrapper indicates that, if the next frame is an
24+
// autogenerated wrapper function, it should be elided from
25+
// the stack.
26+
elideWrapper bool
2227
}
2328

2429
// Frame is the information returned by Frames for each call frame.
@@ -112,11 +117,12 @@ func (se *stackExpander) init(callers []uintptr) []uintptr {
112117
// Next returns frame information for the next caller.
113118
// If more is false, there are no more callers (the Frame value is valid).
114119
func (ci *Frames) Next() (frame Frame, more bool) {
115-
ci.callers, frame, more = ci.stackExpander.next(ci.callers)
120+
ci.callers, frame, more = ci.stackExpander.next(ci.callers, ci.elideWrapper)
121+
ci.elideWrapper = elideWrapperCalling(frame.Function)
116122
return
117123
}
118124

119-
func (se *stackExpander) next(callers []uintptr) (ncallers []uintptr, frame Frame, more bool) {
125+
func (se *stackExpander) next(callers []uintptr, elideWrapper bool) (ncallers []uintptr, frame Frame, more bool) {
120126
ncallers = callers
121127
again:
122128
if !se.pcExpander.more {
@@ -145,7 +151,7 @@ again:
145151
}
146152

147153
frame = se.pcExpander.next()
148-
if frame.File == "<autogenerated>" {
154+
if elideWrapper && frame.File == "<autogenerated>" {
149155
// Ignore autogenerated functions such as pointer
150156
// method forwarding functions. These are an
151157
// implementation detail that doesn't reflect the

src/runtime/traceback.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
184184
cgoCtxt := gp.cgoCtxt
185185
printing := pcbuf == nil && callback == nil
186186
_defer := gp._defer
187+
elideWrapper := false
187188

188189
for _defer != nil && _defer.sp == _NoArgs {
189190
_defer = _defer.link
@@ -386,8 +387,15 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
386387
}
387388

388389
if printing {
389-
// assume skip=0 for printing
390-
if (flags&_TraceRuntimeFrames) != 0 || showframe(f, gp, nprint == 0) {
390+
// assume skip=0 for printing.
391+
//
392+
// Never elide wrappers if we haven't printed
393+
// any frames. And don't elide wrappers that
394+
// called panic rather than the wrapped
395+
// function. Otherwise, leave them out.
396+
name := funcname(f)
397+
nextElideWrapper := elideWrapperCalling(name)
398+
if (flags&_TraceRuntimeFrames) != 0 || showframe(f, gp, nprint == 0, elideWrapper && nprint != 0) {
391399
// Print during crash.
392400
// main(0x1, 0x2, 0x3)
393401
// /home/rsc/go/src/runtime/x.go:23 +0xf
@@ -411,7 +419,6 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
411419
ix = inltree[ix].parent
412420
}
413421
}
414-
name := funcname(f)
415422
if name == "runtime.gopanic" {
416423
name = "panic"
417424
}
@@ -438,6 +445,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
438445
print("\n")
439446
nprint++
440447
}
448+
elideWrapper = nextElideWrapper
441449
}
442450
n++
443451

@@ -647,7 +655,7 @@ func printcreatedby(gp *g) {
647655
// Show what created goroutine, except main goroutine (goid 1).
648656
pc := gp.gopc
649657
f := findfunc(pc)
650-
if f.valid() && showframe(f, gp, false) && gp.goid != 1 {
658+
if f.valid() && showframe(f, gp, false, false) && gp.goid != 1 {
651659
print("created by ", funcname(f), "\n")
652660
tracepc := pc // back up to CALL instruction for funcline.
653661
if pc > f.entry {
@@ -727,7 +735,7 @@ func gcallers(gp *g, skip int, pcbuf []uintptr) int {
727735
return gentraceback(^uintptr(0), ^uintptr(0), 0, gp, skip, &pcbuf[0], len(pcbuf), nil, nil, 0)
728736
}
729737

730-
func showframe(f funcInfo, gp *g, firstFrame bool) bool {
738+
func showframe(f funcInfo, gp *g, firstFrame, elideWrapper bool) bool {
731739
g := getg()
732740
if g.m.throwing > 0 && gp != nil && (gp == g.m.curg || gp == g.m.caughtsig.ptr()) {
733741
return true
@@ -738,8 +746,18 @@ func showframe(f funcInfo, gp *g, firstFrame bool) bool {
738746
return true
739747
}
740748

749+
if !f.valid() {
750+
return false
751+
}
752+
753+
if elideWrapper {
754+
file, _ := funcline(f, f.entry)
755+
if file == "<autogenerated>" {
756+
return false
757+
}
758+
}
759+
741760
name := funcname(f)
742-
file, _ := funcline(f, f.entry)
743761

744762
// Special case: always show runtime.gopanic frame
745763
// in the middle of a stack trace, so that we can
@@ -750,7 +768,7 @@ func showframe(f funcInfo, gp *g, firstFrame bool) bool {
750768
return true
751769
}
752770

753-
return f.valid() && contains(name, ".") && (!hasprefix(name, "runtime.") || isExportedRuntime(name)) && file != "<autogenerated>"
771+
return contains(name, ".") && (!hasprefix(name, "runtime.") || isExportedRuntime(name))
754772
}
755773

756774
// isExportedRuntime reports whether name is an exported runtime function.
@@ -760,6 +778,14 @@ func isExportedRuntime(name string) bool {
760778
return len(name) > n && name[:n] == "runtime." && 'A' <= name[n] && name[n] <= 'Z'
761779
}
762780

781+
// elideWrapperCalling returns whether a wrapper function that called
782+
// function "name" should be elided from stack traces.
783+
func elideWrapperCalling(name string) bool {
784+
// If the wrapper called a panic function instead of the
785+
// wrapped function, we want to include it in stacks.
786+
return !(name == "runtime.gopanic" || name == "runtime.sigpanic" || name == "runtime.panicwrap")
787+
}
788+
763789
var gStatusStrings = [...]string{
764790
_Gidle: "idle",
765791
_Grunnable: "runnable",

0 commit comments

Comments
 (0)