Commit ad03af66 authored by Ian Lance Taylor's avatar Ian Lance Taylor

runtime, runtime/pprof: add Frames to get file/line for Callers

This indirectly implements a small fix for runtime/pprof: it used to
look for runtime.gopanic when it should have been looking for
runtime.sigpanic.

Update #11432.

Change-Id: I5e3f5203b2ac5463efd85adf6636e64174aacb1d
Reviewed-on: https://go-review.googlesource.com/19869
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarDavid Chase <drchase@google.com>
parent 14113b3a
// Copyright 2016 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 runtime_test
import (
"runtime"
"strings"
"testing"
)
func f1(pan bool) []uintptr {
return f2(pan) // line 14
}
func f2(pan bool) []uintptr {
return f3(pan) // line 18
}
func f3(pan bool) []uintptr {
if pan {
panic("f3") // line 23
}
ret := make([]uintptr, 20)
return ret[:runtime.Callers(0, ret)] // line 26
}
func testCallers(t *testing.T, pcs []uintptr, pan bool) {
m := make(map[string]int, len(pcs))
frames := runtime.CallersFrames(pcs)
for {
frame, more := frames.Next()
if frame.Function != "" {
m[frame.Function] = frame.Line
}
if !more {
break
}
}
var seen []string
for k := range m {
seen = append(seen, k)
}
t.Logf("functions seen: %s", strings.Join(seen, " "))
var f3Line int
if pan {
f3Line = 23
} else {
f3Line = 26
}
want := []struct {
name string
line int
}{
{"f1", 14},
{"f2", 18},
{"f3", f3Line},
}
for _, w := range want {
if got := m["runtime_test."+w.name]; got != w.line {
t.Errorf("%s is line %d, want %d", w.name, got, w.line)
}
}
}
func TestCallers(t *testing.T) {
testCallers(t, f1(false), false)
}
func TestCallersPanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("did not panic")
}
pcs := make([]uintptr, 20)
pcs = pcs[:runtime.Callers(0, pcs)]
testCallers(t, pcs, true)
}()
f1(true)
}
...@@ -191,12 +191,9 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool) { ...@@ -191,12 +191,9 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool) {
// //
// Note that since each slice entry pc[i] is a return program counter, // Note that since each slice entry pc[i] is a return program counter,
// looking up the file and line for pc[i] (for example, using (*Func).FileLine) // looking up the file and line for pc[i] (for example, using (*Func).FileLine)
// will return the file and line number of the instruction immediately // will normally return the file and line number of the instruction immediately
// following the call. // following the call.
// To look up the file and line number of the call itself, use pc[i]-1. // To easily look up file/line information for the call sequence, use Frames.
// As an exception to this rule, if pc[i-1] corresponds to the function
// runtime.sigpanic, then pc[i] is the program counter of a faulting
// instruction and should be used without any subtraction.
func Callers(skip int, pc []uintptr) int { func Callers(skip int, pc []uintptr) int {
// runtime.callers uses pc.array==nil as a signal // runtime.callers uses pc.array==nil as a signal
// to print a stack trace. Pick off 0-length pc here // to print a stack trace. Pick off 0-length pc here
......
...@@ -325,33 +325,24 @@ func printCountProfile(w io.Writer, debug int, name string, p countProfile) erro ...@@ -325,33 +325,24 @@ func printCountProfile(w io.Writer, debug int, name string, p countProfile) erro
// for a single stack trace. // for a single stack trace.
func printStackRecord(w io.Writer, stk []uintptr, allFrames bool) { func printStackRecord(w io.Writer, stk []uintptr, allFrames bool) {
show := allFrames show := allFrames
wasPanic := false frames := runtime.CallersFrames(stk)
for i, pc := range stk { for {
f := runtime.FuncForPC(pc) frame, more := frames.Next()
if f == nil { name := frame.Function
if name == "" {
show = true show = true
fmt.Fprintf(w, "#\t%#x\n", pc) fmt.Fprintf(w, "#\t%#x\n", frame.PC)
wasPanic = false
} else { } else {
tracepc := pc
// Back up to call instruction.
if i > 0 && pc > f.Entry() && !wasPanic {
if runtime.GOARCH == "386" || runtime.GOARCH == "amd64" {
tracepc--
} else {
tracepc -= 4 // arm, etc
}
}
file, line := f.FileLine(tracepc)
name := f.Name()
// Hide runtime.goexit and any runtime functions at the beginning. // Hide runtime.goexit and any runtime functions at the beginning.
// This is useful mainly for allocation traces. // This is useful mainly for allocation traces.
wasPanic = name == "runtime.gopanic"
if name == "runtime.goexit" || !show && strings.HasPrefix(name, "runtime.") { if name == "runtime.goexit" || !show && strings.HasPrefix(name, "runtime.") {
continue continue
} }
show = true show = true
fmt.Fprintf(w, "#\t%#x\t%s+%#x\t%s:%d\n", pc, name, pc-f.Entry(), file, line) fmt.Fprintf(w, "#\t%#x\t%s+%#x\t%s:%d\n", frame.PC, name, frame.PC-frame.Entry, frame.File, frame.Line)
}
if !more {
break
} }
} }
if !show { if !show {
......
...@@ -9,6 +9,84 @@ import ( ...@@ -9,6 +9,84 @@ import (
"unsafe" "unsafe"
) )
// Frames may be used to get function/file/line information for a
// slice of PC values returned by Callers.
type Frames struct {
callers []uintptr
// If previous caller in iteration was a panic, then
// ci.callers[0] is the address of the faulting instruction
// instead of the return address of the call.
wasPanic bool
}
// Frame is the information returned by Frames for each call frame.
type Frame struct {
// Program counter for this frame; multiple frames may have
// the same PC value.
PC uintptr
// Func for this frame; may be nil for non-Go code or fully
// inlined functions.
Func *Func
// Function name, file name, and line number for this call frame.
// May be the empty string or zero if not known.
// If Func is not nil then Function == Func.Name().
Function string
File string
Line int
// Entry point for the function; may be zero if not known.
// If Func is not nil then Entry == Func.Entry().
Entry uintptr
}
// CallersFrames takes a slice of PC values returned by Callers and
// prepares to return function/file/line information.
// Do not change the slice until you are done with the Frames.
func CallersFrames(callers []uintptr) *Frames {
return &Frames{callers, false}
}
// 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) {
if len(ci.callers) == 0 {
ci.wasPanic = false
return Frame{}, false
}
pc := ci.callers[0]
ci.callers = ci.callers[1:]
more = len(ci.callers) > 0
f := FuncForPC(pc)
if f == nil {
ci.wasPanic = false
return Frame{}, more
}
entry := f.Entry()
xpc := pc
if xpc > entry && !ci.wasPanic {
xpc--
}
file, line := f.FileLine(xpc)
function := f.Name()
ci.wasPanic = entry == sigpanicPC
frame = Frame{
PC: xpc,
Func: f,
Function: function,
File: file,
Line: line,
Entry: entry,
}
return frame, more
}
// NOTE: Func does not expose the actual unexported fields, because we return *Func // NOTE: Func does not expose the actual unexported fields, because we return *Func
// values to users, and we want to keep them from being able to overwrite the data // values to users, and we want to keep them from being able to overwrite the data
// with (say) *f = Func{}. // with (say) *f = Func{}.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment