runtime: include inlined calls in result of CallersFrames

Change-Id: If1a3396175f2afa607d56efd1444181334a9ae3e
Reviewed-on: https://go-review.googlesource.com/37862
Reviewed-by: Austin Clements <austin@google.com>
diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go
index a31cf55..52dd7df 100644
--- a/src/runtime/symtab.go
+++ b/src/runtime/symtab.go
@@ -21,8 +21,15 @@
 	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 @@
 // 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 @@
 	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 @@
 	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 @@
 	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 35a14f7..682e490 100644
--- a/src/runtime/traceback.go
+++ b/src/runtime/traceback.go
@@ -52,6 +52,7 @@
 	systemstack_switchPC uintptr
 	systemstackPC        uintptr
 	cgocallback_gofuncPC uintptr
+	skipPC               uintptr
 
 	gogoPC uintptr
 
@@ -78,6 +79,7 @@
 	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 c387362..fb6ff6c 100644
--- a/test/inline_callers.go
+++ b/test/inline_callers.go
@@ -40,6 +40,21 @@
 	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 @@
 	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 @@
 		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)
+		}
 	}
 }