// We asked for one extra, so skip that one. If this is sigpanic,
// stepping over this frame will set up state in Frames so the
// next frame is correct.
- callers, _, ok = stackExpander.next(callers)
+ callers, _, ok = stackExpander.next(callers, true)
if !ok {
return
}
- _, frame, _ := stackExpander.next(callers)
+ _, frame, _ := stackExpander.next(callers, true)
pc = frame.PC
file = frame.File
line = frame.Line
import (
"bytes"
"fmt"
+ "reflect"
. "runtime"
"strings"
"sync"
return string(buf[:Stack(buf, false)])
}
+func (s structWithMethod) nop() {}
+
func TestStackWrapperCaller(t *testing.T) {
var d structWithMethod
// Force the compiler to construct a wrapper method.
}
}
+type I interface {
+ M()
+}
+
+func TestStackWrapperStackPanic(t *testing.T) {
+ t.Run("sigpanic", func(t *testing.T) {
+ // nil calls to interface methods cause a sigpanic.
+ testStackWrapperPanic(t, func() { I.M(nil) }, "runtime_test.I.M")
+ })
+ t.Run("panicwrap", func(t *testing.T) {
+ // Nil calls to value method wrappers call panicwrap.
+ wrapper := (*structWithMethod).nop
+ testStackWrapperPanic(t, func() { wrapper(nil) }, "runtime_test.(*structWithMethod).nop")
+ })
+}
+
+func testStackWrapperPanic(t *testing.T, cb func(), expect string) {
+ // Test that the stack trace from a panicking wrapper includes
+ // the wrapper, even though elide these when they don't panic.
+ t.Run("CallersFrames", func(t *testing.T) {
+ defer func() {
+ err := recover()
+ if err == nil {
+ t.Fatalf("expected panic")
+ }
+ pcs := make([]uintptr, 10)
+ n := Callers(0, pcs)
+ frames := CallersFrames(pcs[:n])
+ for {
+ frame, more := frames.Next()
+ t.Log(frame.Function)
+ if frame.Function == expect {
+ return
+ }
+ if !more {
+ break
+ }
+ }
+ t.Fatalf("panicking wrapper %s missing from stack trace", expect)
+ }()
+ cb()
+ })
+ t.Run("Stack", func(t *testing.T) {
+ defer func() {
+ err := recover()
+ if err == nil {
+ t.Fatalf("expected panic")
+ }
+ buf := make([]byte, 4<<10)
+ stk := string(buf[:Stack(buf, false)])
+ if !strings.Contains(stk, "\n"+expect) {
+ t.Fatalf("panicking wrapper %s missing from stack trace:\n%s", expect, stk)
+ }
+ }()
+ cb()
+ })
+}
+
+func TestCallersFromWrapper(t *testing.T) {
+ // Test that invoking CallersFrames on a stack where the first
+ // PC is an autogenerated wrapper keeps the wrapper in the
+ // trace. Normally we elide these, assuming that the wrapper
+ // calls the thing you actually wanted to see, but in this
+ // case we need to keep it.
+ pc := reflect.ValueOf(I.M).Pointer()
+ frames := CallersFrames([]uintptr{pc})
+ frame, more := frames.Next()
+ if frame.Function != "runtime_test.I.M" {
+ t.Fatalf("want function %s, got %s", "runtime_test.I.M", frame.Function)
+ }
+ if more {
+ t.Fatalf("want 1 frame, got > 1")
+ }
+}
+
func TestTracebackSystemstack(t *testing.T) {
if GOARCH == "ppc64" || GOARCH == "ppc64le" {
t.Skip("systemstack tail call not implemented on ppc64x")
// stackExpander expands callers into a sequence of Frames,
// tracking the necessary state across PCs.
stackExpander stackExpander
+
+ // elideWrapper indicates that, if the next frame is an
+ // autogenerated wrapper function, it should be elided from
+ // the stack.
+ elideWrapper bool
}
// Frame is the information returned by Frames for each call frame.
// Next returns frame information for the next caller.
// If more is false, there are no more callers (the Frame value is valid).
func (ci *Frames) Next() (frame Frame, more bool) {
- ci.callers, frame, more = ci.stackExpander.next(ci.callers)
+ ci.callers, frame, more = ci.stackExpander.next(ci.callers, ci.elideWrapper)
+ ci.elideWrapper = elideWrapperCalling(frame.Function)
return
}
-func (se *stackExpander) next(callers []uintptr) (ncallers []uintptr, frame Frame, more bool) {
+func (se *stackExpander) next(callers []uintptr, elideWrapper bool) (ncallers []uintptr, frame Frame, more bool) {
ncallers = callers
again:
if !se.pcExpander.more {
}
frame = se.pcExpander.next()
- if frame.File == "<autogenerated>" {
+ if elideWrapper && frame.File == "<autogenerated>" {
// Ignore autogenerated functions such as pointer
// method forwarding functions. These are an
// implementation detail that doesn't reflect the
cgoCtxt := gp.cgoCtxt
printing := pcbuf == nil && callback == nil
_defer := gp._defer
+ elideWrapper := false
for _defer != nil && _defer.sp == _NoArgs {
_defer = _defer.link
}
if printing {
- // assume skip=0 for printing
- if (flags&_TraceRuntimeFrames) != 0 || showframe(f, gp, nprint == 0) {
+ // assume skip=0 for printing.
+ //
+ // Never elide wrappers if we haven't printed
+ // any frames. And don't elide wrappers that
+ // called panic rather than the wrapped
+ // function. Otherwise, leave them out.
+ name := funcname(f)
+ nextElideWrapper := elideWrapperCalling(name)
+ if (flags&_TraceRuntimeFrames) != 0 || showframe(f, gp, nprint == 0, elideWrapper && nprint != 0) {
// Print during crash.
// main(0x1, 0x2, 0x3)
// /home/rsc/go/src/runtime/x.go:23 +0xf
ix = inltree[ix].parent
}
}
- name := funcname(f)
if name == "runtime.gopanic" {
name = "panic"
}
print("\n")
nprint++
}
+ elideWrapper = nextElideWrapper
}
n++
// Show what created goroutine, except main goroutine (goid 1).
pc := gp.gopc
f := findfunc(pc)
- if f.valid() && showframe(f, gp, false) && gp.goid != 1 {
+ if f.valid() && showframe(f, gp, false, false) && gp.goid != 1 {
print("created by ", funcname(f), "\n")
tracepc := pc // back up to CALL instruction for funcline.
if pc > f.entry {
return gentraceback(^uintptr(0), ^uintptr(0), 0, gp, skip, &pcbuf[0], len(pcbuf), nil, nil, 0)
}
-func showframe(f funcInfo, gp *g, firstFrame bool) bool {
+func showframe(f funcInfo, gp *g, firstFrame, elideWrapper bool) bool {
g := getg()
if g.m.throwing > 0 && gp != nil && (gp == g.m.curg || gp == g.m.caughtsig.ptr()) {
return true
return true
}
+ if !f.valid() {
+ return false
+ }
+
+ if elideWrapper {
+ file, _ := funcline(f, f.entry)
+ if file == "<autogenerated>" {
+ return false
+ }
+ }
+
name := funcname(f)
- file, _ := funcline(f, f.entry)
// Special case: always show runtime.gopanic frame
// in the middle of a stack trace, so that we can
return true
}
- return f.valid() && contains(name, ".") && (!hasprefix(name, "runtime.") || isExportedRuntime(name)) && file != "<autogenerated>"
+ return contains(name, ".") && (!hasprefix(name, "runtime.") || isExportedRuntime(name))
}
// isExportedRuntime reports whether name is an exported runtime function.
return len(name) > n && name[:n] == "runtime." && 'A' <= name[n] && name[n] <= 'Z'
}
+// elideWrapperCalling returns whether a wrapper function that called
+// function "name" should be elided from stack traces.
+func elideWrapperCalling(name string) bool {
+ // If the wrapper called a panic function instead of the
+ // wrapped function, we want to include it in stacks.
+ return !(name == "runtime.gopanic" || name == "runtime.sigpanic" || name == "runtime.panicwrap")
+}
+
var gStatusStrings = [...]string{
_Gidle: "idle",
_Grunnable: "runnable",