mirror of
https://github.com/golang/go
synced 2025-05-03 03:01:34 +00:00
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:
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user