Commit 7bf0adc6 authored by David Lazar's avatar David Lazar

runtime: include inlined calls in result of CallersFrames

Change-Id: If1a3396175f2afa607d56efd1444181334a9ae3e
Reviewed-on: https://go-review.googlesource.com/37862Reviewed-by: default avatarAustin Clements <austin@google.com>
parent ee97216a
...@@ -21,8 +21,15 @@ type Frames struct { ...@@ -21,8 +21,15 @@ type Frames struct {
wasPanic bool wasPanic bool
// Frames to return for subsequent calls to the Next method. // Frames to return for subsequent calls to the Next method.
// Used for non-Go frames. // Used for non-Go or inlined frames.
frames *[]Frame framesNext []Frame
// This buffer is used when expanding PCs into multiple frames.
// Initially it points to the scratch space.
frames []Frame
// Scratch space to avoid allocation.
scratch [4]Frame
} }
// Frame is the information returned by Frames for each call frame. // Frame is the information returned by Frames for each call frame.
...@@ -51,21 +58,40 @@ type Frame struct { ...@@ -51,21 +58,40 @@ type Frame struct {
// prepares to return function/file/line information. // prepares to return function/file/line information.
// Do not change the slice until you are done with the Frames. // Do not change the slice until you are done with the Frames.
func CallersFrames(callers []uintptr) *Frames { func CallersFrames(callers []uintptr) *Frames {
return &Frames{callers: callers} ci := &Frames{}
ci.frames = ci.scratch[:0]
if len(callers) >= 1 {
pc := callers[0]
s := pc - skipPC
if s >= 0 && s < sizeofSkipFunction {
// Ignore skip frame callers[0] since this means the caller trimmed the PC slice.
ci.callers = callers[1:]
return ci
}
}
if len(callers) >= 2 {
pc := callers[1]
s := pc - skipPC
if s >= 0 && s < sizeofSkipFunction {
// Expand callers[0] and skip s logical frames at this PC.
ci.frames = ci.expandPC(ci.frames[:0], callers[0])
ci.framesNext = ci.frames[int(s):]
ci.callers = callers[2:]
return ci
}
}
ci.callers = callers
return ci
} }
// Next returns frame information for the next caller. // Next returns frame information for the next caller.
// If more is false, there are no more callers (the Frame value is valid). // If more is false, there are no more callers (the Frame value is valid).
func (ci *Frames) Next() (frame Frame, more bool) { func (ci *Frames) Next() (frame Frame, more bool) {
if ci.frames != nil { if len(ci.framesNext) > 0 {
// We have saved up frames to return. // We have saved up frames to return.
f := (*ci.frames)[0] f := ci.framesNext[0]
if len(*ci.frames) == 1 { ci.framesNext = ci.framesNext[1:]
ci.frames = nil return f, len(ci.framesNext) > 0 || len(ci.callers) > 0
} else {
*ci.frames = (*ci.frames)[1:]
}
return f, ci.frames != nil || len(ci.callers) > 0
} }
if len(ci.callers) == 0 { if len(ci.callers) == 0 {
...@@ -75,13 +101,27 @@ func (ci *Frames) Next() (frame Frame, more bool) { ...@@ -75,13 +101,27 @@ func (ci *Frames) Next() (frame Frame, more bool) {
pc := ci.callers[0] pc := ci.callers[0]
ci.callers = ci.callers[1:] ci.callers = ci.callers[1:]
more = len(ci.callers) > 0 more = len(ci.callers) > 0
ci.frames = ci.expandPC(ci.frames[:0], pc)
if len(ci.frames) == 0 {
// Expansion failed, so there's no useful symbolic information.
return Frame{}, more
}
ci.framesNext = ci.frames[1:]
return ci.frames[0], more || len(ci.framesNext) > 0
}
// expandPC appends the frames corresponding to pc to frames
// and returns the new slice.
func (ci *Frames) expandPC(frames []Frame, pc uintptr) []Frame {
f := FuncForPC(pc) f := FuncForPC(pc)
if f == nil { if f == nil {
ci.wasPanic = false ci.wasPanic = false
if cgoSymbolizer != nil { if cgoSymbolizer != nil {
return ci.cgoNext(pc, more) frames = expandCgoFrames(frames, pc)
} }
return Frame{}, more return frames
} }
entry := f.Entry() entry := f.Entry()
...@@ -89,35 +129,68 @@ func (ci *Frames) Next() (frame Frame, more bool) { ...@@ -89,35 +129,68 @@ func (ci *Frames) Next() (frame Frame, more bool) {
if xpc > entry && !ci.wasPanic { if xpc > entry && !ci.wasPanic {
xpc-- xpc--
} }
file, line := f.FileLine(xpc)
function := f.Name()
ci.wasPanic = entry == sigpanicPC ci.wasPanic = entry == sigpanicPC
frame = Frame{ frames = expandInlinedCalls(frames, xpc, f)
return frames
}
// expandInlinedCalls expands xpc into multiple frames using the inlining
// info in fn. expandInlinedCalls appends to frames and returns the new
// slice. The resulting slice has at least one frame for the physical frame
// that contains xpc (i.e., the function represented by fn).
func expandInlinedCalls(frames []Frame, xpc uintptr, fn *Func) []Frame {
entry := fn.Entry()
// file and line are the innermost position at xpc.
file, line := fn.FileLine(xpc)
funcInfo := fn.funcInfo()
inldata := funcdata(funcInfo, _FUNCDATA_InlTree)
if inldata != nil {
inltree := (*[1 << 20]inlinedCall)(inldata)
ix := pcdatavalue(funcInfo, _PCDATA_InlTreeIndex, xpc, nil)
for ix >= 0 {
call := inltree[ix]
frames = append(frames, Frame{
PC: xpc,
Func: nil, // nil for inlined functions
Function: funcnameFromNameoff(funcInfo, call.func_),
File: file,
Line: line,
Entry: entry,
})
file = funcfile(funcInfo, call.file)
line = int(call.line)
ix = call.parent
}
}
physicalFrame := Frame{
PC: xpc, PC: xpc,
Func: f, Func: fn,
Function: function, Function: fn.Name(),
File: file, File: file,
Line: line, Line: line,
Entry: entry, Entry: entry,
} }
frames = append(frames, physicalFrame)
return frame, more return frames
} }
// cgoNext returns frame information for pc, known to be a non-Go function, // expandCgoFrames expands frame information for pc, known to be
// using the cgoSymbolizer hook. // a non-Go function, using the cgoSymbolizer hook. expandCgoFrames
func (ci *Frames) cgoNext(pc uintptr, more bool) (Frame, bool) { // appends to frames and returns the new slice.
func expandCgoFrames(frames []Frame, pc uintptr) []Frame {
arg := cgoSymbolizerArg{pc: pc} arg := cgoSymbolizerArg{pc: pc}
callCgoSymbolizer(&arg) callCgoSymbolizer(&arg)
if arg.file == nil && arg.funcName == nil { if arg.file == nil && arg.funcName == nil {
// No useful information from symbolizer. // No useful information from symbolizer.
return Frame{}, more return frames
} }
var frames []Frame
for { for {
frames = append(frames, Frame{ frames = append(frames, Frame{
PC: pc, PC: pc,
...@@ -140,18 +213,7 @@ func (ci *Frames) cgoNext(pc uintptr, more bool) (Frame, bool) { ...@@ -140,18 +213,7 @@ func (ci *Frames) cgoNext(pc uintptr, more bool) (Frame, bool) {
arg.pc = 0 arg.pc = 0
callCgoSymbolizer(&arg) callCgoSymbolizer(&arg)
if len(frames) == 1 { return frames
// Return a single frame.
return frames[0], more
}
// Return the first frame we saw and store the rest to be
// returned by later calls to Next.
rf := frames[0]
frames = frames[1:]
ci.frames = new([]Frame)
*ci.frames = frames
return rf, true
} }
// 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
......
...@@ -52,6 +52,7 @@ var ( ...@@ -52,6 +52,7 @@ var (
systemstack_switchPC uintptr systemstack_switchPC uintptr
systemstackPC uintptr systemstackPC uintptr
cgocallback_gofuncPC uintptr cgocallback_gofuncPC uintptr
skipPC uintptr
gogoPC uintptr gogoPC uintptr
...@@ -78,6 +79,7 @@ func tracebackinit() { ...@@ -78,6 +79,7 @@ func tracebackinit() {
systemstack_switchPC = funcPC(systemstack_switch) systemstack_switchPC = funcPC(systemstack_switch)
systemstackPC = funcPC(systemstack) systemstackPC = funcPC(systemstack)
cgocallback_gofuncPC = funcPC(cgocallback_gofunc) cgocallback_gofuncPC = funcPC(cgocallback_gofunc)
skipPC = funcPC(skipPleaseUseCallersFrames)
// used by sigprof handler // used by sigprof handler
gogoPC = funcPC(gogo) gogoPC = funcPC(gogo)
......
...@@ -40,6 +40,21 @@ func testCallers(skp int) (frames []string) { ...@@ -40,6 +40,21 @@ func testCallers(skp int) (frames []string) {
return return
} }
func testCallersFrames(skp int) (frames []string) {
skip = skp
f()
callers := pcs[:npcs]
ci := runtime.CallersFrames(callers)
for {
frame, more := ci.Next()
frames = append(frames, frame.Function)
if !more || frame.Function == "main.main" {
break
}
}
return
}
var expectedFrames [][]string = [][]string{ var expectedFrames [][]string = [][]string{
0: {"runtime.Callers", "main.testCallers", "main.main"}, 0: {"runtime.Callers", "main.testCallers", "main.main"},
1: {"main.testCallers", "main.main"}, 1: {"main.testCallers", "main.main"},
...@@ -49,6 +64,8 @@ var expectedFrames [][]string = [][]string{ ...@@ -49,6 +64,8 @@ var expectedFrames [][]string = [][]string{
5: {"main.main"}, 5: {"main.main"},
} }
var allFrames = []string{"runtime.Callers", "main.h", "main.g", "main.f", "main.testCallersFrames", "main.main"}
func same(xs, ys []string) bool { func same(xs, ys []string) bool {
if len(xs) != len(ys) { if len(xs) != len(ys) {
return false return false
...@@ -68,5 +85,11 @@ func main() { ...@@ -68,5 +85,11 @@ func main() {
if !same(frames, expected) { if !same(frames, expected) {
log.Fatalf("testCallers(%d):\n got %v\n want %v", i, frames, expected) log.Fatalf("testCallers(%d):\n got %v\n want %v", i, frames, expected)
} }
frames = testCallersFrames(i)
expected = allFrames[i:]
if !same(frames, expected) {
log.Fatalf("testCallersFrames(%d):\n got %v\n want %v", i, frames, expected)
}
} }
} }
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