mirror of https://github.com/golang/go.git
runtime: proper panic tracebacks with mid-stack inlining
As a followon to CL 152537, modify the panic-printing traceback to also handle mid-stack inlining correctly. Also declare -fm functions (aka method functions) as wrappers, so that they get elided during traceback. This fixes part 2 of #26839. Fixes #28640 Fixes #24488 Update #26839 Change-Id: I1c535a9b87a9a1ea699621be1e6526877b696c21 Reviewed-on: https://go-review.googlesource.com/c/153477 Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
parent
688667716e
commit
af134b17da
|
|
@ -92,5 +92,8 @@ func GetFuncID(name, file string) FuncID {
|
||||||
return FuncID_wrapper
|
return FuncID_wrapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if strings.HasSuffix(name, "-fm") {
|
||||||
|
return FuncID_wrapper
|
||||||
|
}
|
||||||
return FuncID_normal
|
return FuncID_normal
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,6 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
|
||||||
cgoCtxt := gp.cgoCtxt
|
cgoCtxt := gp.cgoCtxt
|
||||||
printing := pcbuf == nil && callback == nil
|
printing := pcbuf == nil && callback == nil
|
||||||
_defer := gp._defer
|
_defer := gp._defer
|
||||||
elideWrapper := false
|
|
||||||
|
|
||||||
for _defer != nil && _defer.sp == _NoArgs {
|
for _defer != nil && _defer.sp == _NoArgs {
|
||||||
_defer = _defer.link
|
_defer = _defer.link
|
||||||
|
|
@ -392,32 +391,39 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
|
||||||
// any frames. And don't elide wrappers that
|
// any frames. And don't elide wrappers that
|
||||||
// called panic rather than the wrapped
|
// called panic rather than the wrapped
|
||||||
// function. Otherwise, leave them out.
|
// function. Otherwise, leave them out.
|
||||||
name := funcname(f)
|
|
||||||
nextElideWrapper := elideWrapperCalling(f.funcID)
|
// backup to CALL instruction to read inlining info (same logic as below)
|
||||||
if (flags&_TraceRuntimeFrames) != 0 || showframe(f, gp, nprint == 0, elideWrapper && nprint != 0) {
|
tracepc := frame.pc
|
||||||
|
if (n > 0 || flags&_TraceTrap == 0) && frame.pc > f.entry && !waspanic {
|
||||||
|
tracepc--
|
||||||
|
}
|
||||||
|
// If there is inlining info, print the inner frames.
|
||||||
|
if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
|
||||||
|
inltree := (*[1 << 20]inlinedCall)(inldata)
|
||||||
|
for {
|
||||||
|
ix := pcdatavalue(f, _PCDATA_InlTreeIndex, tracepc, nil)
|
||||||
|
if ix < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (flags&_TraceRuntimeFrames) != 0 || showframe(f, gp, nprint == 0, inltree[ix].funcID, lastFuncID) {
|
||||||
|
name := funcnameFromNameoff(f, inltree[ix].func_)
|
||||||
|
file, line := funcline(f, tracepc)
|
||||||
|
print(name, "(...)\n")
|
||||||
|
print("\t", file, ":", line, "\n")
|
||||||
|
nprint++
|
||||||
|
}
|
||||||
|
lastFuncID = inltree[ix].funcID
|
||||||
|
// Back up to an instruction in the "caller".
|
||||||
|
tracepc = frame.fn.entry + uintptr(inltree[ix].parentPc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (flags&_TraceRuntimeFrames) != 0 || showframe(f, gp, nprint == 0, f.funcID, lastFuncID) {
|
||||||
// Print during crash.
|
// Print during crash.
|
||||||
// main(0x1, 0x2, 0x3)
|
// main(0x1, 0x2, 0x3)
|
||||||
// /home/rsc/go/src/runtime/x.go:23 +0xf
|
// /home/rsc/go/src/runtime/x.go:23 +0xf
|
||||||
//
|
//
|
||||||
tracepc := frame.pc // back up to CALL instruction for funcline.
|
name := funcname(f)
|
||||||
if (n > 0 || flags&_TraceTrap == 0) && frame.pc > f.entry && !waspanic {
|
|
||||||
tracepc--
|
|
||||||
}
|
|
||||||
file, line := funcline(f, tracepc)
|
file, line := funcline(f, tracepc)
|
||||||
inldata := funcdata(f, _FUNCDATA_InlTree)
|
|
||||||
if inldata != nil {
|
|
||||||
inltree := (*[1 << 20]inlinedCall)(inldata)
|
|
||||||
ix := pcdatavalue(f, _PCDATA_InlTreeIndex, tracepc, nil)
|
|
||||||
for ix != -1 {
|
|
||||||
name := funcnameFromNameoff(f, inltree[ix].func_)
|
|
||||||
print(name, "(...)\n")
|
|
||||||
print("\t", file, ":", line, "\n")
|
|
||||||
|
|
||||||
file = funcfile(f, inltree[ix].file)
|
|
||||||
line = inltree[ix].line
|
|
||||||
ix = int32(inltree[ix].parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if name == "runtime.gopanic" {
|
if name == "runtime.gopanic" {
|
||||||
name = "panic"
|
name = "panic"
|
||||||
}
|
}
|
||||||
|
|
@ -444,7 +450,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
|
||||||
print("\n")
|
print("\n")
|
||||||
nprint++
|
nprint++
|
||||||
}
|
}
|
||||||
elideWrapper = nextElideWrapper
|
lastFuncID = f.funcID
|
||||||
}
|
}
|
||||||
n++
|
n++
|
||||||
|
|
||||||
|
|
@ -669,7 +675,7 @@ func printcreatedby(gp *g) {
|
||||||
// Show what created goroutine, except main goroutine (goid 1).
|
// Show what created goroutine, except main goroutine (goid 1).
|
||||||
pc := gp.gopc
|
pc := gp.gopc
|
||||||
f := findfunc(pc)
|
f := findfunc(pc)
|
||||||
if f.valid() && showframe(f, gp, false, false) && gp.goid != 1 {
|
if f.valid() && showframe(f, gp, false, funcID_normal, funcID_normal) && gp.goid != 1 {
|
||||||
printcreatedby1(f, pc)
|
printcreatedby1(f, pc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -756,11 +762,10 @@ func traceback1(pc, sp, lr uintptr, gp *g, flags uint) {
|
||||||
// TODO: Unify this with gentraceback and CallersFrames.
|
// TODO: Unify this with gentraceback and CallersFrames.
|
||||||
func printAncestorTraceback(ancestor ancestorInfo) {
|
func printAncestorTraceback(ancestor ancestorInfo) {
|
||||||
print("[originating from goroutine ", ancestor.goid, "]:\n")
|
print("[originating from goroutine ", ancestor.goid, "]:\n")
|
||||||
elideWrapper := false
|
|
||||||
for fidx, pc := range ancestor.pcs {
|
for fidx, pc := range ancestor.pcs {
|
||||||
f := findfunc(pc) // f previously validated
|
f := findfunc(pc) // f previously validated
|
||||||
if showfuncinfo(f, fidx == 0, elideWrapper && fidx != 0) {
|
if showfuncinfo(f, fidx == 0, funcID_normal, funcID_normal) {
|
||||||
elideWrapper = printAncestorTracebackFuncInfo(f, pc)
|
printAncestorTracebackFuncInfo(f, pc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(ancestor.pcs) == _TracebackMaxFrames {
|
if len(ancestor.pcs) == _TracebackMaxFrames {
|
||||||
|
|
@ -768,7 +773,7 @@ func printAncestorTraceback(ancestor ancestorInfo) {
|
||||||
}
|
}
|
||||||
// Show what created goroutine, except main goroutine (goid 1).
|
// Show what created goroutine, except main goroutine (goid 1).
|
||||||
f := findfunc(ancestor.gopc)
|
f := findfunc(ancestor.gopc)
|
||||||
if f.valid() && showfuncinfo(f, false, false) && ancestor.goid != 1 {
|
if f.valid() && showfuncinfo(f, false, funcID_normal, funcID_normal) && ancestor.goid != 1 {
|
||||||
printcreatedby1(f, ancestor.gopc)
|
printcreatedby1(f, ancestor.gopc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -777,27 +782,16 @@ func printAncestorTraceback(ancestor ancestorInfo) {
|
||||||
// within an ancestor traceback. The precision of this info is reduced
|
// within an ancestor traceback. The precision of this info is reduced
|
||||||
// due to only have access to the pcs at the time of the caller
|
// due to only have access to the pcs at the time of the caller
|
||||||
// goroutine being created.
|
// goroutine being created.
|
||||||
func printAncestorTracebackFuncInfo(f funcInfo, pc uintptr) bool {
|
func printAncestorTracebackFuncInfo(f funcInfo, pc uintptr) {
|
||||||
tracepc := pc // back up to CALL instruction for funcline.
|
name := funcname(f)
|
||||||
if pc > f.entry {
|
if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
|
||||||
tracepc -= sys.PCQuantum
|
|
||||||
}
|
|
||||||
file, line := funcline(f, tracepc)
|
|
||||||
inldata := funcdata(f, _FUNCDATA_InlTree)
|
|
||||||
if inldata != nil {
|
|
||||||
inltree := (*[1 << 20]inlinedCall)(inldata)
|
inltree := (*[1 << 20]inlinedCall)(inldata)
|
||||||
ix := pcdatavalue(f, _PCDATA_InlTreeIndex, tracepc, nil)
|
ix := pcdatavalue(f, _PCDATA_InlTreeIndex, pc, nil)
|
||||||
for ix != -1 {
|
if ix >= 0 {
|
||||||
name := funcnameFromNameoff(f, inltree[ix].func_)
|
name = funcnameFromNameoff(f, inltree[ix].func_)
|
||||||
print(name, "(...)\n")
|
|
||||||
print("\t", file, ":", line, "\n")
|
|
||||||
|
|
||||||
file = funcfile(f, inltree[ix].file)
|
|
||||||
line = inltree[ix].line
|
|
||||||
ix = int32(inltree[ix].parent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
name := funcname(f)
|
file, line := funcline(f, pc)
|
||||||
if name == "runtime.gopanic" {
|
if name == "runtime.gopanic" {
|
||||||
name = "panic"
|
name = "panic"
|
||||||
}
|
}
|
||||||
|
|
@ -807,7 +801,6 @@ func printAncestorTracebackFuncInfo(f funcInfo, pc uintptr) bool {
|
||||||
print(" +", hex(pc-f.entry))
|
print(" +", hex(pc-f.entry))
|
||||||
}
|
}
|
||||||
print("\n")
|
print("\n")
|
||||||
return elideWrapperCalling(f.funcID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func callers(skip int, pcbuf []uintptr) int {
|
func callers(skip int, pcbuf []uintptr) int {
|
||||||
|
|
@ -825,15 +818,19 @@ func gcallers(gp *g, skip int, pcbuf []uintptr) int {
|
||||||
return gentraceback(^uintptr(0), ^uintptr(0), 0, gp, skip, &pcbuf[0], len(pcbuf), nil, nil, 0)
|
return gentraceback(^uintptr(0), ^uintptr(0), 0, gp, skip, &pcbuf[0], len(pcbuf), nil, nil, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func showframe(f funcInfo, gp *g, firstFrame, elideWrapper bool) bool {
|
// showframe reports whether the frame with the given characteristics should
|
||||||
|
// be printed during a traceback.
|
||||||
|
func showframe(f funcInfo, gp *g, firstFrame bool, funcID, childID funcID) bool {
|
||||||
g := getg()
|
g := getg()
|
||||||
if g.m.throwing > 0 && gp != nil && (gp == g.m.curg || gp == g.m.caughtsig.ptr()) {
|
if g.m.throwing > 0 && gp != nil && (gp == g.m.curg || gp == g.m.caughtsig.ptr()) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return showfuncinfo(f, firstFrame, elideWrapper)
|
return showfuncinfo(f, firstFrame, funcID, childID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func showfuncinfo(f funcInfo, firstFrame, elideWrapper bool) bool {
|
// showfuncinfo reports whether a function with the given characteristics should
|
||||||
|
// be printed during a traceback.
|
||||||
|
func showfuncinfo(f funcInfo, firstFrame bool, funcID, childID funcID) bool {
|
||||||
level, _, _ := gotraceback()
|
level, _, _ := gotraceback()
|
||||||
if level > 1 {
|
if level > 1 {
|
||||||
// Show all frames.
|
// Show all frames.
|
||||||
|
|
@ -844,11 +841,8 @@ func showfuncinfo(f funcInfo, firstFrame, elideWrapper bool) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if elideWrapper {
|
if funcID == funcID_wrapper && elideWrapperCalling(childID) {
|
||||||
file, _ := funcline(f, f.entry)
|
return false
|
||||||
if file == "<autogenerated>" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
name := funcname(f)
|
name := funcname(f)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
// run
|
||||||
|
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Func func()
|
||||||
|
|
||||||
|
func (f Func) Foo() {
|
||||||
|
if f != nil {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Func) Bar() {
|
||||||
|
if f != nil {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
buf := make([]byte, 4000)
|
||||||
|
n := runtime.Stack(buf, true)
|
||||||
|
s := string(buf[:n])
|
||||||
|
if strings.Contains(s, "-fm") {
|
||||||
|
panic("wrapper present in stack trace:\n" + s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
foo := Func(func() {})
|
||||||
|
foo = foo.Bar
|
||||||
|
foo.Foo()
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue