|
@@ -1,3 +1,5 @@
|
|
|
|
|
+// +build go1.7
|
|
|
|
|
+
|
|
|
// Package stack implements utilities to capture, manipulate, and format call
|
|
// Package stack implements utilities to capture, manipulate, and format call
|
|
|
// stacks. It provides a simpler API than package runtime.
|
|
// stacks. It provides a simpler API than package runtime.
|
|
|
//
|
|
//
|
|
@@ -21,29 +23,31 @@ import (
|
|
|
|
|
|
|
|
// Call records a single function invocation from a goroutine stack.
|
|
// Call records a single function invocation from a goroutine stack.
|
|
|
type Call struct {
|
|
type Call struct {
|
|
|
- fn *runtime.Func
|
|
|
|
|
- pc uintptr
|
|
|
|
|
|
|
+ frame runtime.Frame
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Caller returns a Call from the stack of the current goroutine. The argument
|
|
// Caller returns a Call from the stack of the current goroutine. The argument
|
|
|
// skip is the number of stack frames to ascend, with 0 identifying the
|
|
// skip is the number of stack frames to ascend, with 0 identifying the
|
|
|
// calling function.
|
|
// calling function.
|
|
|
func Caller(skip int) Call {
|
|
func Caller(skip int) Call {
|
|
|
- var pcs [2]uintptr
|
|
|
|
|
|
|
+ // As of Go 1.9 we need room for up to three PC entries.
|
|
|
|
|
+ //
|
|
|
|
|
+ // 0. An entry for the stack frame prior to the target to check for
|
|
|
|
|
+ // special handling needed if that prior entry is runtime.sigpanic.
|
|
|
|
|
+ // 1. A possible second entry to hold metadata about skipped inlined
|
|
|
|
|
+ // functions. If inline functions were not skipped the target frame
|
|
|
|
|
+ // PC will be here.
|
|
|
|
|
+ // 2. A third entry for the target frame PC when the second entry
|
|
|
|
|
+ // is used for skipped inline functions.
|
|
|
|
|
+ var pcs [3]uintptr
|
|
|
n := runtime.Callers(skip+1, pcs[:])
|
|
n := runtime.Callers(skip+1, pcs[:])
|
|
|
|
|
+ frames := runtime.CallersFrames(pcs[:n])
|
|
|
|
|
+ frame, _ := frames.Next()
|
|
|
|
|
+ frame, _ = frames.Next()
|
|
|
|
|
|
|
|
- var c Call
|
|
|
|
|
-
|
|
|
|
|
- if n < 2 {
|
|
|
|
|
- return c
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- c.pc = pcs[1]
|
|
|
|
|
- if runtime.FuncForPC(pcs[0]) != sigpanic {
|
|
|
|
|
- c.pc--
|
|
|
|
|
|
|
+ return Call{
|
|
|
|
|
+ frame: frame,
|
|
|
}
|
|
}
|
|
|
- c.fn = runtime.FuncForPC(c.pc)
|
|
|
|
|
- return c
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
|
|
// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
|
|
@@ -54,9 +58,10 @@ func (c Call) String() string {
|
|
|
// MarshalText implements encoding.TextMarshaler. It formats the Call the same
|
|
// MarshalText implements encoding.TextMarshaler. It formats the Call the same
|
|
|
// as fmt.Sprintf("%v", c).
|
|
// as fmt.Sprintf("%v", c).
|
|
|
func (c Call) MarshalText() ([]byte, error) {
|
|
func (c Call) MarshalText() ([]byte, error) {
|
|
|
- if c.fn == nil {
|
|
|
|
|
|
|
+ if c.frame == (runtime.Frame{}) {
|
|
|
return nil, ErrNoFunc
|
|
return nil, ErrNoFunc
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
buf := bytes.Buffer{}
|
|
buf := bytes.Buffer{}
|
|
|
fmt.Fprint(&buf, c)
|
|
fmt.Fprint(&buf, c)
|
|
|
return buf.Bytes(), nil
|
|
return buf.Bytes(), nil
|
|
@@ -71,6 +76,7 @@ var ErrNoFunc = errors.New("no call stack information")
|
|
|
// %s source file
|
|
// %s source file
|
|
|
// %d line number
|
|
// %d line number
|
|
|
// %n function name
|
|
// %n function name
|
|
|
|
|
+// %k last segment of the package path
|
|
|
// %v equivalent to %s:%d
|
|
// %v equivalent to %s:%d
|
|
|
//
|
|
//
|
|
|
// It accepts the '+' and '#' flags for most of the verbs as follows.
|
|
// It accepts the '+' and '#' flags for most of the verbs as follows.
|
|
@@ -78,22 +84,23 @@ var ErrNoFunc = errors.New("no call stack information")
|
|
|
// %+s path of source file relative to the compile time GOPATH
|
|
// %+s path of source file relative to the compile time GOPATH
|
|
|
// %#s full path of source file
|
|
// %#s full path of source file
|
|
|
// %+n import path qualified function name
|
|
// %+n import path qualified function name
|
|
|
|
|
+// %+k full package path
|
|
|
// %+v equivalent to %+s:%d
|
|
// %+v equivalent to %+s:%d
|
|
|
// %#v equivalent to %#s:%d
|
|
// %#v equivalent to %#s:%d
|
|
|
func (c Call) Format(s fmt.State, verb rune) {
|
|
func (c Call) Format(s fmt.State, verb rune) {
|
|
|
- if c.fn == nil {
|
|
|
|
|
|
|
+ if c.frame == (runtime.Frame{}) {
|
|
|
fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
|
|
fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
switch verb {
|
|
switch verb {
|
|
|
case 's', 'v':
|
|
case 's', 'v':
|
|
|
- file, line := c.fn.FileLine(c.pc)
|
|
|
|
|
|
|
+ file := c.frame.File
|
|
|
switch {
|
|
switch {
|
|
|
case s.Flag('#'):
|
|
case s.Flag('#'):
|
|
|
// done
|
|
// done
|
|
|
case s.Flag('+'):
|
|
case s.Flag('+'):
|
|
|
- file = file[pkgIndex(file, c.fn.Name()):]
|
|
|
|
|
|
|
+ file = file[pkgIndex(file, c.frame.Function):]
|
|
|
default:
|
|
default:
|
|
|
const sep = "/"
|
|
const sep = "/"
|
|
|
if i := strings.LastIndex(file, sep); i != -1 {
|
|
if i := strings.LastIndex(file, sep); i != -1 {
|
|
@@ -103,16 +110,31 @@ func (c Call) Format(s fmt.State, verb rune) {
|
|
|
io.WriteString(s, file)
|
|
io.WriteString(s, file)
|
|
|
if verb == 'v' {
|
|
if verb == 'v' {
|
|
|
buf := [7]byte{':'}
|
|
buf := [7]byte{':'}
|
|
|
- s.Write(strconv.AppendInt(buf[:1], int64(line), 10))
|
|
|
|
|
|
|
+ s.Write(strconv.AppendInt(buf[:1], int64(c.frame.Line), 10))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
case 'd':
|
|
case 'd':
|
|
|
- _, line := c.fn.FileLine(c.pc)
|
|
|
|
|
buf := [6]byte{}
|
|
buf := [6]byte{}
|
|
|
- s.Write(strconv.AppendInt(buf[:0], int64(line), 10))
|
|
|
|
|
|
|
+ s.Write(strconv.AppendInt(buf[:0], int64(c.frame.Line), 10))
|
|
|
|
|
+
|
|
|
|
|
+ case 'k':
|
|
|
|
|
+ name := c.frame.Function
|
|
|
|
|
+ const pathSep = "/"
|
|
|
|
|
+ start, end := 0, len(name)
|
|
|
|
|
+ if i := strings.LastIndex(name, pathSep); i != -1 {
|
|
|
|
|
+ start = i + len(pathSep)
|
|
|
|
|
+ }
|
|
|
|
|
+ const pkgSep = "."
|
|
|
|
|
+ if i := strings.Index(name[start:], pkgSep); i != -1 {
|
|
|
|
|
+ end = start + i
|
|
|
|
|
+ }
|
|
|
|
|
+ if s.Flag('+') {
|
|
|
|
|
+ start = 0
|
|
|
|
|
+ }
|
|
|
|
|
+ io.WriteString(s, name[start:end])
|
|
|
|
|
|
|
|
case 'n':
|
|
case 'n':
|
|
|
- name := c.fn.Name()
|
|
|
|
|
|
|
+ name := c.frame.Function
|
|
|
if !s.Flag('+') {
|
|
if !s.Flag('+') {
|
|
|
const pathSep = "/"
|
|
const pathSep = "/"
|
|
|
if i := strings.LastIndex(name, pathSep); i != -1 {
|
|
if i := strings.LastIndex(name, pathSep); i != -1 {
|
|
@@ -127,35 +149,17 @@ func (c Call) Format(s fmt.State, verb rune) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Frame returns the call frame infomation for the Call.
|
|
|
|
|
+func (c Call) Frame() runtime.Frame {
|
|
|
|
|
+ return c.frame
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// PC returns the program counter for this call frame; multiple frames may
|
|
// PC returns the program counter for this call frame; multiple frames may
|
|
|
// have the same PC value.
|
|
// have the same PC value.
|
|
|
|
|
+//
|
|
|
|
|
+// Deprecated: Use Call.Frame instead.
|
|
|
func (c Call) PC() uintptr {
|
|
func (c Call) PC() uintptr {
|
|
|
- return c.pc
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// name returns the import path qualified name of the function containing the
|
|
|
|
|
-// call.
|
|
|
|
|
-func (c Call) name() string {
|
|
|
|
|
- if c.fn == nil {
|
|
|
|
|
- return "???"
|
|
|
|
|
- }
|
|
|
|
|
- return c.fn.Name()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (c Call) file() string {
|
|
|
|
|
- if c.fn == nil {
|
|
|
|
|
- return "???"
|
|
|
|
|
- }
|
|
|
|
|
- file, _ := c.fn.FileLine(c.pc)
|
|
|
|
|
- return file
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (c Call) line() int {
|
|
|
|
|
- if c.fn == nil {
|
|
|
|
|
- return 0
|
|
|
|
|
- }
|
|
|
|
|
- _, line := c.fn.FileLine(c.pc)
|
|
|
|
|
- return line
|
|
|
|
|
|
|
+ return c.frame.PC
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// CallStack records a sequence of function invocations from a goroutine
|
|
// CallStack records a sequence of function invocations from a goroutine
|
|
@@ -179,9 +183,6 @@ func (cs CallStack) MarshalText() ([]byte, error) {
|
|
|
buf := bytes.Buffer{}
|
|
buf := bytes.Buffer{}
|
|
|
buf.Write(openBracketBytes)
|
|
buf.Write(openBracketBytes)
|
|
|
for i, pc := range cs {
|
|
for i, pc := range cs {
|
|
|
- if pc.fn == nil {
|
|
|
|
|
- return nil, ErrNoFunc
|
|
|
|
|
- }
|
|
|
|
|
if i > 0 {
|
|
if i > 0 {
|
|
|
buf.Write(spaceBytes)
|
|
buf.Write(spaceBytes)
|
|
|
}
|
|
}
|
|
@@ -205,49 +206,22 @@ func (cs CallStack) Format(s fmt.State, verb rune) {
|
|
|
s.Write(closeBracketBytes)
|
|
s.Write(closeBracketBytes)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// findSigpanic intentionally executes faulting code to generate a stack trace
|
|
|
|
|
-// containing an entry for runtime.sigpanic.
|
|
|
|
|
-func findSigpanic() *runtime.Func {
|
|
|
|
|
- var fn *runtime.Func
|
|
|
|
|
- var p *int
|
|
|
|
|
- func() int {
|
|
|
|
|
- defer func() {
|
|
|
|
|
- if p := recover(); p != nil {
|
|
|
|
|
- var pcs [512]uintptr
|
|
|
|
|
- n := runtime.Callers(2, pcs[:])
|
|
|
|
|
- for _, pc := range pcs[:n] {
|
|
|
|
|
- f := runtime.FuncForPC(pc)
|
|
|
|
|
- if f.Name() == "runtime.sigpanic" {
|
|
|
|
|
- fn = f
|
|
|
|
|
- break
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }()
|
|
|
|
|
- // intentional nil pointer dereference to trigger sigpanic
|
|
|
|
|
- return *p
|
|
|
|
|
- }()
|
|
|
|
|
- return fn
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-var sigpanic = findSigpanic()
|
|
|
|
|
-
|
|
|
|
|
// Trace returns a CallStack for the current goroutine with element 0
|
|
// Trace returns a CallStack for the current goroutine with element 0
|
|
|
// identifying the calling function.
|
|
// identifying the calling function.
|
|
|
func Trace() CallStack {
|
|
func Trace() CallStack {
|
|
|
var pcs [512]uintptr
|
|
var pcs [512]uintptr
|
|
|
- n := runtime.Callers(2, pcs[:])
|
|
|
|
|
- cs := make([]Call, n)
|
|
|
|
|
|
|
+ n := runtime.Callers(1, pcs[:])
|
|
|
|
|
|
|
|
- for i, pc := range pcs[:n] {
|
|
|
|
|
- pcFix := pc
|
|
|
|
|
- if i > 0 && cs[i-1].fn != sigpanic {
|
|
|
|
|
- pcFix--
|
|
|
|
|
- }
|
|
|
|
|
- cs[i] = Call{
|
|
|
|
|
- fn: runtime.FuncForPC(pcFix),
|
|
|
|
|
- pc: pcFix,
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ frames := runtime.CallersFrames(pcs[:n])
|
|
|
|
|
+ cs := make(CallStack, 0, n)
|
|
|
|
|
+
|
|
|
|
|
+ // Skip extra frame retrieved just to make sure the runtime.sigpanic
|
|
|
|
|
+ // special case is handled.
|
|
|
|
|
+ frame, more := frames.Next()
|
|
|
|
|
+
|
|
|
|
|
+ for more {
|
|
|
|
|
+ frame, more = frames.Next()
|
|
|
|
|
+ cs = append(cs, Call{frame: frame})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return cs
|
|
return cs
|
|
@@ -256,7 +230,7 @@ func Trace() CallStack {
|
|
|
// TrimBelow returns a slice of the CallStack with all entries below c
|
|
// TrimBelow returns a slice of the CallStack with all entries below c
|
|
|
// removed.
|
|
// removed.
|
|
|
func (cs CallStack) TrimBelow(c Call) CallStack {
|
|
func (cs CallStack) TrimBelow(c Call) CallStack {
|
|
|
- for len(cs) > 0 && cs[0].pc != c.pc {
|
|
|
|
|
|
|
+ for len(cs) > 0 && cs[0] != c {
|
|
|
cs = cs[1:]
|
|
cs = cs[1:]
|
|
|
}
|
|
}
|
|
|
return cs
|
|
return cs
|
|
@@ -265,7 +239,7 @@ func (cs CallStack) TrimBelow(c Call) CallStack {
|
|
|
// TrimAbove returns a slice of the CallStack with all entries above c
|
|
// TrimAbove returns a slice of the CallStack with all entries above c
|
|
|
// removed.
|
|
// removed.
|
|
|
func (cs CallStack) TrimAbove(c Call) CallStack {
|
|
func (cs CallStack) TrimAbove(c Call) CallStack {
|
|
|
- for len(cs) > 0 && cs[len(cs)-1].pc != c.pc {
|
|
|
|
|
|
|
+ for len(cs) > 0 && cs[len(cs)-1] != c {
|
|
|
cs = cs[:len(cs)-1]
|
|
cs = cs[:len(cs)-1]
|
|
|
}
|
|
}
|
|
|
return cs
|
|
return cs
|
|
@@ -314,12 +288,13 @@ func pkgIndex(file, funcName string) int {
|
|
|
var runtimePath string
|
|
var runtimePath string
|
|
|
|
|
|
|
|
func init() {
|
|
func init() {
|
|
|
- var pcs [1]uintptr
|
|
|
|
|
|
|
+ var pcs [3]uintptr
|
|
|
runtime.Callers(0, pcs[:])
|
|
runtime.Callers(0, pcs[:])
|
|
|
- fn := runtime.FuncForPC(pcs[0])
|
|
|
|
|
- file, _ := fn.FileLine(pcs[0])
|
|
|
|
|
|
|
+ frames := runtime.CallersFrames(pcs[:])
|
|
|
|
|
+ frame, _ := frames.Next()
|
|
|
|
|
+ file := frame.File
|
|
|
|
|
|
|
|
- idx := pkgIndex(file, fn.Name())
|
|
|
|
|
|
|
+ idx := pkgIndex(frame.File, frame.Function)
|
|
|
|
|
|
|
|
runtimePath = file[:idx]
|
|
runtimePath = file[:idx]
|
|
|
if runtime.GOOS == "windows" {
|
|
if runtime.GOOS == "windows" {
|
|
@@ -328,7 +303,7 @@ func init() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func inGoroot(c Call) bool {
|
|
func inGoroot(c Call) bool {
|
|
|
- file := c.file()
|
|
|
|
|
|
|
+ file := c.frame.File
|
|
|
if len(file) == 0 || file[0] == '?' {
|
|
if len(file) == 0 || file[0] == '?' {
|
|
|
return true
|
|
return true
|
|
|
}
|
|
}
|