]> Cypherpunks.ru repositories - gostls13.git/commitdiff
runtime: include inlined calls in result of CallersFrames
authorDavid Lazar <lazard@golang.org>
Wed, 8 Mar 2017 02:14:12 +0000 (21:14 -0500)
committerDavid Lazar <lazard@golang.org>
Wed, 29 Mar 2017 17:27:38 +0000 (17:27 +0000)
Change-Id: If1a3396175f2afa607d56efd1444181334a9ae3e
Reviewed-on: https://go-review.googlesource.com/37862
Reviewed-by: Austin Clements <austin@google.com>
src/runtime/symtab.go
src/runtime/traceback.go
test/inline_callers.go

index a31cf55c29c3cc97fcbc8ff032aafdbba5e4e88e..52dd7dfdcc722ae677d0a604af954222eaa94333 100644 (file)
@@ -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
index 35a14f7b538aec925f82529d7c846db7dc72162c..682e490debecbd7fc350f9589605be0779d0ebfb 100644 (file)
@@ -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)
index c387362fa81da7a07a276422b0bbad9bf84e0980..fb6ff6c769e82765e43ecbf84b2e3e1d0ec3d0c1 100644 (file)
@@ -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)
+               }
        }
 }