runtime/debug: implement Stack using runtime.Stack

Fixes #12363

Change-Id: I1a025ab6a1cbd5a58f5c2bce5416788387495428
Reviewed-on: https://go-review.googlesource.com/14604
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: David Crawshaw <crawshaw@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
David Crawshaw 2015-09-15 17:16:38 -04:00
parent fb30270037
commit 2d697b2401
2 changed files with 28 additions and 92 deletions

View file

@ -7,92 +7,26 @@
package debug package debug
import ( import (
"bytes"
"fmt"
"io/ioutil"
"os" "os"
"runtime" "runtime"
) )
var (
dunno = []byte("???")
centerDot = []byte("·")
dot = []byte(".")
slash = []byte("/")
)
// PrintStack prints to standard error the stack trace returned by Stack. // PrintStack prints to standard error the stack trace returned by Stack.
func PrintStack() { func PrintStack() {
os.Stderr.Write(stack()) os.Stderr.Write(Stack())
} }
// Stack returns a formatted stack trace of the goroutine that calls it. // Stack returns a formatted stack trace of the goroutine that calls it.
// For each routine, it includes the source line information and PC value, // For each routine, it includes the source line information and PC value,
// then attempts to discover, for Go functions, the calling function or // then attempts to discover, for Go functions, the calling function or
// method and the text of the line containing the invocation. // method.
//
// Deprecated: Use package runtime's Stack instead.
func Stack() []byte { func Stack() []byte {
return stack() buf := make([]byte, 1024)
} for {
n := runtime.Stack(buf, false)
// stack implements Stack, skipping 2 frames if n < len(buf) {
func stack() []byte { return buf[:n]
buf := new(bytes.Buffer) // the returned data
// As we loop, we open files and read them. These variables record the currently
// loaded file.
var lines [][]byte
var lastFile string
for i := 2; ; i++ { // Caller we care about is the user, 2 frames up
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
} }
// Print this much at least. If we can't find the source, it won't show. buf = make([]byte, 2*len(buf))
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
if file != lastFile {
data, err := ioutil.ReadFile(file)
if err != nil {
continue
}
lines = bytes.Split(data, []byte{'\n'})
lastFile = file
}
line-- // in stack trace, lines are 1-indexed but our array is 0-indexed
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
} }
return buf.Bytes()
}
// source returns a space-trimmed slice of the n'th line.
func source(lines [][]byte, n int) []byte {
if n < 0 || n >= len(lines) {
return dunno
}
return bytes.Trim(lines[n], " \t")
}
// function returns, if possible, the name of the function containing the PC.
func function(pc uintptr) []byte {
fn := runtime.FuncForPC(pc)
if fn == nil {
return dunno
}
name := []byte(fn.Name())
// The name includes the path name to the package, which is unnecessary
// since the file name is already included. Plus, it has center dots.
// That is, we see
// runtime/debug.*T·ptrmethod
// and want
// *T.ptrmethod
// Since the package path might contains dots (e.g. code.google.com/...),
// we first remove the path prefix if there is one.
if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
name = name[lastslash+1:]
}
if period := bytes.Index(name, dot); period >= 0 {
name = name[period+1:]
}
name = bytes.Replace(name, centerDot, dot, -1)
return name
} }

View file

@ -22,16 +22,19 @@ func (t T) method() []byte {
The traceback should look something like this, modulo line numbers and hex constants. The traceback should look something like this, modulo line numbers and hex constants.
Don't worry much about the base levels, but check the ones in our own package. Don't worry much about the base levels, but check the ones in our own package.
/Users/r/go/src/runtime/debug/stack_test.go:15 (0x13878) goroutine 10 [running]:
(*T).ptrmethod: return Stack() runtime/debug.Stack(0x0, 0x0, 0x0)
/Users/r/go/src/runtime/debug/stack_test.go:18 (0x138dd) /Users/r/go/src/runtime/debug/stack.go:28 +0x80
T.method: return t.ptrmethod() runtime/debug.(*T).ptrmethod(0xc82005ee70, 0x0, 0x0, 0x0)
/Users/r/go/src/runtime/debug/stack_test.go:23 (0x13920) /Users/r/go/src/runtime/debug/stack_test.go:15 +0x29
TestStack: b := T(0).method() runtime/debug.T.method(0x0, 0x0, 0x0, 0x0)
/Users/r/go/src/testing/testing.go:132 (0x14a7a) /Users/r/go/src/runtime/debug/stack_test.go:18 +0x32
tRunner: test.F(t) runtime/debug.TestStack(0xc8201ce000)
/Users/r/go/src/runtime/proc.c:145 (0xc970) /Users/r/go/src/runtime/debug/stack_test.go:37 +0x38
???: runtime·unlock(&runtime·sched); testing.tRunner(0xc8201ce000, 0x664b58)
/Users/r/go/src/testing/testing.go:456 +0x98
created by testing.RunTests
/Users/r/go/src/testing/testing.go:561 +0x86d
*/ */
func TestStack(t *testing.T) { func TestStack(t *testing.T) {
b := T(0).method() b := T(0).method()
@ -41,17 +44,16 @@ func TestStack(t *testing.T) {
} }
n := 0 n := 0
frame := func(line, code string) { frame := func(line, code string) {
check(t, lines[n], code)
n++
check(t, lines[n], line) check(t, lines[n], line)
n++ n++
// The source might not be available while running the test.
if strings.HasPrefix(lines[n], "\t") {
check(t, lines[n], code)
n++
}
} }
frame("src/runtime/debug/stack_test.go", "\t(*T).ptrmethod: return Stack()") n++
frame("src/runtime/debug/stack_test.go", "\tT.method: return t.ptrmethod()") frame("src/runtime/debug/stack.go", "runtime/debug.Stack")
frame("src/runtime/debug/stack_test.go", "\tTestStack: b := T(0).method()") frame("src/runtime/debug/stack_test.go", "runtime/debug.(*T).ptrmethod")
frame("src/runtime/debug/stack_test.go", "runtime/debug.T.method")
frame("src/runtime/debug/stack_test.go", "runtime/debug.TestStack")
frame("src/testing/testing.go", "") frame("src/testing/testing.go", "")
} }