From: David Lazar Date: Wed, 8 Mar 2017 02:14:12 +0000 (-0500) Subject: runtime: include inlined calls in result of CallersFrames X-Git-Tag: go1.9beta1~933 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=7bf0adc6add500054ccbc37a868ab0fab120fa24;p=gostls13.git runtime: include inlined calls in result of CallersFrames Change-Id: If1a3396175f2afa607d56efd1444181334a9ae3e Reviewed-on: https://go-review.googlesource.com/37862 Reviewed-by: Austin Clements --- diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index a31cf55c29..52dd7dfdcc 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -21,8 +21,15 @@ type Frames struct { wasPanic bool // Frames to return for subsequent calls to the Next method. - // Used for non-Go frames. - frames *[]Frame + // Used for non-Go or inlined frames. + 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. @@ -51,21 +58,40 @@ type Frame struct { // 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: 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. // If more is false, there are no more callers (the Frame value is valid). func (ci *Frames) Next() (frame Frame, more bool) { - if ci.frames != nil { + if len(ci.framesNext) > 0 { // We have saved up frames to return. - f := (*ci.frames)[0] - if len(*ci.frames) == 1 { - ci.frames = nil - } else { - *ci.frames = (*ci.frames)[1:] - } - return f, ci.frames != nil || len(ci.callers) > 0 + f := ci.framesNext[0] + ci.framesNext = ci.framesNext[1:] + return f, len(ci.framesNext) > 0 || len(ci.callers) > 0 } if len(ci.callers) == 0 { @@ -75,13 +101,27 @@ func (ci *Frames) Next() (frame Frame, more bool) { pc := ci.callers[0] ci.callers = ci.callers[1:] 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) if f == nil { ci.wasPanic = false if cgoSymbolizer != nil { - return ci.cgoNext(pc, more) + frames = expandCgoFrames(frames, pc) } - return Frame{}, more + return frames } entry := f.Entry() @@ -89,35 +129,68 @@ func (ci *Frames) Next() (frame Frame, more bool) { if xpc > entry && !ci.wasPanic { xpc-- } - file, line := f.FileLine(xpc) - - function := f.Name() 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, - Func: f, - Function: function, + Func: fn, + Function: fn.Name(), File: file, Line: line, Entry: entry, } + frames = append(frames, physicalFrame) - return frame, more + return frames } -// cgoNext returns frame information for pc, known to be a non-Go function, -// using the cgoSymbolizer hook. -func (ci *Frames) cgoNext(pc uintptr, more bool) (Frame, bool) { +// expandCgoFrames expands frame information for pc, known to be +// a non-Go function, using the cgoSymbolizer hook. expandCgoFrames +// appends to frames and returns the new slice. +func expandCgoFrames(frames []Frame, pc uintptr) []Frame { arg := cgoSymbolizerArg{pc: pc} callCgoSymbolizer(&arg) if arg.file == nil && arg.funcName == nil { // No useful information from symbolizer. - return Frame{}, more + return frames } - var frames []Frame for { frames = append(frames, Frame{ PC: pc, @@ -140,18 +213,7 @@ func (ci *Frames) cgoNext(pc uintptr, more bool) (Frame, bool) { arg.pc = 0 callCgoSymbolizer(&arg) - if len(frames) == 1 { - // 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 + return frames } // NOTE: Func does not expose the actual unexported fields, because we return *Func diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index 35a14f7b53..682e490deb 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -52,6 +52,7 @@ var ( systemstack_switchPC uintptr systemstackPC uintptr cgocallback_gofuncPC uintptr + skipPC uintptr gogoPC uintptr @@ -78,6 +79,7 @@ func tracebackinit() { systemstack_switchPC = funcPC(systemstack_switch) systemstackPC = funcPC(systemstack) cgocallback_gofuncPC = funcPC(cgocallback_gofunc) + skipPC = funcPC(skipPleaseUseCallersFrames) // used by sigprof handler gogoPC = funcPC(gogo) diff --git a/test/inline_callers.go b/test/inline_callers.go index c387362fa8..fb6ff6c769 100644 --- a/test/inline_callers.go +++ b/test/inline_callers.go @@ -40,6 +40,21 @@ func testCallers(skp int) (frames []string) { 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{ 0: {"runtime.Callers", "main.testCallers", "main.main"}, 1: {"main.testCallers", "main.main"}, @@ -49,6 +64,8 @@ var expectedFrames [][]string = [][]string{ 5: {"main.main"}, } +var allFrames = []string{"runtime.Callers", "main.h", "main.g", "main.f", "main.testCallersFrames", "main.main"} + func same(xs, ys []string) bool { if len(xs) != len(ys) { return false @@ -68,5 +85,11 @@ func main() { if !same(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) + } } }