mirror of https://github.com/golang/go.git
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>
This commit is contained in:
parent
ee97216a17
commit
7bf0adc6ad
|
|
@ -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 {
|
||||||
// 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) {
|
||||||
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) {
|
||||||
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) {
|
||||||
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 (
|
||||||
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() {
|
||||||
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) {
|
||||||
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{
|
||||||
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() {
|
||||||
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue