mirror of
https://github.com/golang/go
synced 2024-11-19 13:14:42 -07:00
runtime: expand inlining iteratively in CallersFrames
Currently CallersFrames expands each PC to a slice of Frames and then iteratively returns those Frames. However, this makes it very difficult to avoid heap allocation: either the Frames slice will be heap allocated, or, if it uses internal scratch space for small slices (as it currently does), the Frames object itself has to be heap allocated. Fix this, at least in the common case, by expanding each PC iteratively. We introduce a new pcExpander type that's responsible for expanding a single PC. This maintains state from one Frame to the next in the same PC. Frames then becomes a wrapper around this responsible for feeding it the next PC when the pcExpander runs out of frames for the current PC. This makes it possible to stack-allocate a Frames object, which will make it possible to use this API for PC expansion from within the runtime itself. Change-Id: I993463945ab574557cf1d6bedbe79ce7e9cbbdcd Reviewed-on: https://go-review.googlesource.com/40434 Run-TryBot: Austin Clements <austin@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: David Lazar <lazard@golang.org>
This commit is contained in:
parent
746441f97f
commit
7f32d41e5d
@ -13,6 +13,7 @@ import (
|
|||||||
// Frames may be used to get function/file/line information for a
|
// Frames may be used to get function/file/line information for a
|
||||||
// slice of PC values returned by Callers.
|
// slice of PC values returned by Callers.
|
||||||
type Frames struct {
|
type Frames struct {
|
||||||
|
// callers is a slice of PCs that have not yet been expanded.
|
||||||
callers []uintptr
|
callers []uintptr
|
||||||
|
|
||||||
// If previous caller in iteration was a panic, then
|
// If previous caller in iteration was a panic, then
|
||||||
@ -20,16 +21,12 @@ type Frames struct {
|
|||||||
// instead of the return address of the call.
|
// instead of the return address of the call.
|
||||||
wasPanic bool
|
wasPanic bool
|
||||||
|
|
||||||
// Frames to return for subsequent calls to the Next method.
|
// expander expands the current PC into a sequence of Frames.
|
||||||
// Used for non-Go or inlined frames.
|
expander pcExpander
|
||||||
framesNext []Frame
|
|
||||||
|
|
||||||
// This buffer is used when expanding PCs into multiple frames.
|
// skip > 0 indicates that skip frames in the first expansion
|
||||||
// Initially it points to the scratch space.
|
// should be skipped over and callers[1] should also be skipped.
|
||||||
frames []Frame
|
skip int
|
||||||
|
|
||||||
// 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.
|
||||||
@ -59,138 +56,194 @@ type Frame struct {
|
|||||||
// 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 {
|
||||||
ci := &Frames{}
|
ci := &Frames{}
|
||||||
ci.frames = ci.scratch[:0]
|
ci.init(callers)
|
||||||
|
return ci
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *Frames) init(callers []uintptr) {
|
||||||
if len(callers) >= 1 {
|
if len(callers) >= 1 {
|
||||||
pc := callers[0]
|
pc := callers[0]
|
||||||
s := pc - skipPC
|
s := pc - skipPC
|
||||||
if s >= 0 && s < sizeofSkipFunction {
|
if s >= 0 && s < sizeofSkipFunction {
|
||||||
// Ignore skip frame callers[0] since this means the caller trimmed the PC slice.
|
// Ignore skip frame callers[0] since this means the caller trimmed the PC slice.
|
||||||
ci.callers = callers[1:]
|
ci.callers = callers[1:]
|
||||||
return ci
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(callers) >= 2 {
|
if len(callers) >= 2 {
|
||||||
pc := callers[1]
|
pc := callers[1]
|
||||||
s := pc - skipPC
|
s := pc - skipPC
|
||||||
if s >= 0 && s < sizeofSkipFunction {
|
if s > 0 && s < sizeofSkipFunction {
|
||||||
// Expand callers[0] and skip s logical frames at this PC.
|
// Skip the first s inlined frames when we expand the first PC.
|
||||||
ci.frames = ci.expandPC(ci.frames[:0], callers[0])
|
ci.skip = int(s)
|
||||||
ci.framesNext = ci.frames[int(s):]
|
|
||||||
ci.callers = callers[2:]
|
|
||||||
return ci
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ci.callers = callers
|
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 len(ci.framesNext) > 0 {
|
if !ci.expander.more {
|
||||||
// We have saved up frames to return.
|
// Expand the next PC.
|
||||||
f := ci.framesNext[0]
|
if len(ci.callers) == 0 {
|
||||||
ci.framesNext = ci.framesNext[1:]
|
ci.wasPanic = false
|
||||||
return f, len(ci.framesNext) > 0 || len(ci.callers) > 0
|
return Frame{}, false
|
||||||
|
}
|
||||||
|
ci.expander.init(ci.callers[0], ci.wasPanic)
|
||||||
|
ci.callers = ci.callers[1:]
|
||||||
|
ci.wasPanic = ci.expander.funcInfo.valid() && ci.expander.funcInfo.entry == sigpanicPC
|
||||||
|
if ci.skip > 0 {
|
||||||
|
for ; ci.skip > 0; ci.skip-- {
|
||||||
|
ci.expander.next()
|
||||||
|
}
|
||||||
|
ci.skip = 0
|
||||||
|
// Drop skipPleaseUseCallersFrames.
|
||||||
|
ci.callers = ci.callers[1:]
|
||||||
|
}
|
||||||
|
if !ci.expander.more {
|
||||||
|
// No symbolic information for this PC.
|
||||||
|
// However, we return at least one frame for
|
||||||
|
// every PC, so return an invalid frame.
|
||||||
|
return Frame{}, len(ci.callers) > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ci.callers) == 0 {
|
frame = ci.expander.next()
|
||||||
ci.wasPanic = false
|
return frame, ci.expander.more || len(ci.callers) > 0
|
||||||
return Frame{}, false
|
|
||||||
}
|
|
||||||
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
|
// A pcExpander expands a single PC into a sequence of Frames.
|
||||||
// and returns the new slice.
|
type pcExpander struct {
|
||||||
func (ci *Frames) expandPC(frames []Frame, pc uintptr) []Frame {
|
// more indicates that the next call to next will return a
|
||||||
f := FuncForPC(pc)
|
// valid frame.
|
||||||
if f == nil {
|
more bool
|
||||||
ci.wasPanic = false
|
|
||||||
|
// pc is the pc being expanded.
|
||||||
|
pc uintptr
|
||||||
|
|
||||||
|
// frames is a pre-expanded set of Frames to return from the
|
||||||
|
// iterator. If this is set, then this is everything that will
|
||||||
|
// be returned from the iterator.
|
||||||
|
frames []Frame
|
||||||
|
|
||||||
|
// funcInfo is the funcInfo of the function containing pc.
|
||||||
|
funcInfo funcInfo
|
||||||
|
|
||||||
|
// inlTree is the inlining tree of the function containing pc.
|
||||||
|
inlTree *[1 << 20]inlinedCall
|
||||||
|
|
||||||
|
// file and line are the file name and line number of the next
|
||||||
|
// frame.
|
||||||
|
file string
|
||||||
|
line int32
|
||||||
|
|
||||||
|
// inlIndex is the inlining index of the next frame, or -1 if
|
||||||
|
// the next frame is an outermost frame.
|
||||||
|
inlIndex int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// init initializes this pcExpander to expand pc. It sets ex.more if
|
||||||
|
// pc expands to any Frames.
|
||||||
|
//
|
||||||
|
// A pcExpander can be reused by calling init again.
|
||||||
|
//
|
||||||
|
// If pc was a "call" to sigpanic, panicCall should be true. In this
|
||||||
|
// case, pc is treated as the address of a faulting instruction
|
||||||
|
// instead of the return address of a call.
|
||||||
|
func (ex *pcExpander) init(pc uintptr, panicCall bool) {
|
||||||
|
ex.more = false
|
||||||
|
|
||||||
|
ex.funcInfo = findfunc(pc)
|
||||||
|
if !ex.funcInfo.valid() {
|
||||||
if cgoSymbolizer != nil {
|
if cgoSymbolizer != nil {
|
||||||
frames = expandCgoFrames(frames, pc)
|
// Pre-expand cgo frames. We could do this
|
||||||
|
// incrementally, too, but there's no way to
|
||||||
|
// avoid allocation in this case anyway.
|
||||||
|
ex.frames = expandCgoFrames(pc)
|
||||||
|
ex.more = len(ex.frames) > 0
|
||||||
}
|
}
|
||||||
return frames
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
entry := f.Entry()
|
ex.more = true
|
||||||
xpc := pc
|
entry := ex.funcInfo.entry
|
||||||
if xpc > entry && !ci.wasPanic {
|
ex.pc = pc
|
||||||
xpc--
|
if ex.pc > entry && !panicCall {
|
||||||
|
ex.pc--
|
||||||
}
|
}
|
||||||
ci.wasPanic = entry == sigpanicPC
|
|
||||||
|
|
||||||
frames = expandInlinedCalls(frames, xpc, f)
|
// file and line are the innermost position at pc.
|
||||||
return frames
|
ex.file, ex.line = funcline1(ex.funcInfo, ex.pc, false)
|
||||||
|
|
||||||
|
// Get inlining tree at pc
|
||||||
|
inldata := funcdata(ex.funcInfo, _FUNCDATA_InlTree)
|
||||||
|
if inldata != nil {
|
||||||
|
ex.inlTree = (*[1 << 20]inlinedCall)(inldata)
|
||||||
|
ex.inlIndex = pcdatavalue(ex.funcInfo, _PCDATA_InlTreeIndex, ex.pc, nil)
|
||||||
|
} else {
|
||||||
|
ex.inlTree = nil
|
||||||
|
ex.inlIndex = -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// expandInlinedCalls expands xpc into multiple frames using the inlining
|
// next returns the next Frame in the expansion of pc and sets ex.more
|
||||||
// info in fn. expandInlinedCalls appends to frames and returns the new
|
// if there are more Frames to follow.
|
||||||
// slice. The resulting slice has at least one frame for the physical frame
|
func (ex *pcExpander) next() Frame {
|
||||||
// that contains xpc (i.e., the function represented by fn).
|
if !ex.more {
|
||||||
func expandInlinedCalls(frames []Frame, xpc uintptr, fn *Func) []Frame {
|
return Frame{}
|
||||||
entry := fn.Entry()
|
}
|
||||||
|
|
||||||
// file and line are the innermost position at xpc.
|
if len(ex.frames) > 0 {
|
||||||
file, line := fn.FileLine(xpc)
|
// Return pre-expended frame.
|
||||||
|
frame := ex.frames[0]
|
||||||
|
ex.frames = ex.frames[1:]
|
||||||
|
ex.more = len(ex.frames) > 0
|
||||||
|
return frame
|
||||||
|
}
|
||||||
|
|
||||||
funcInfo := fn.funcInfo()
|
if ex.inlIndex >= 0 {
|
||||||
inldata := funcdata(funcInfo, _FUNCDATA_InlTree)
|
// Return inner inlined frame.
|
||||||
if inldata != nil {
|
call := ex.inlTree[ex.inlIndex]
|
||||||
inltree := (*[1 << 20]inlinedCall)(inldata)
|
frame := Frame{
|
||||||
ix := pcdatavalue(funcInfo, _PCDATA_InlTreeIndex, xpc, nil)
|
PC: ex.pc,
|
||||||
for ix >= 0 {
|
Func: nil, // nil for inlined functions
|
||||||
call := inltree[ix]
|
Function: funcnameFromNameoff(ex.funcInfo, call.func_),
|
||||||
frames = append(frames, Frame{
|
File: ex.file,
|
||||||
PC: xpc,
|
Line: int(ex.line),
|
||||||
Func: nil, // nil for inlined functions
|
Entry: ex.funcInfo.entry,
|
||||||
Function: funcnameFromNameoff(funcInfo, call.func_),
|
|
||||||
File: file,
|
|
||||||
Line: line,
|
|
||||||
Entry: entry,
|
|
||||||
})
|
|
||||||
file = funcfile(funcInfo, call.file)
|
|
||||||
line = int(call.line)
|
|
||||||
ix = call.parent
|
|
||||||
}
|
}
|
||||||
|
ex.file = funcfile(ex.funcInfo, call.file)
|
||||||
|
ex.line = call.line
|
||||||
|
ex.inlIndex = call.parent
|
||||||
|
return frame
|
||||||
}
|
}
|
||||||
|
|
||||||
physicalFrame := Frame{
|
// No inlining or pre-expanded frames.
|
||||||
PC: xpc,
|
ex.more = false
|
||||||
Func: fn,
|
return Frame{
|
||||||
Function: fn.Name(),
|
PC: ex.pc,
|
||||||
File: file,
|
Func: ex.funcInfo._Func(),
|
||||||
Line: line,
|
Function: funcname(ex.funcInfo),
|
||||||
Entry: entry,
|
File: ex.file,
|
||||||
|
Line: int(ex.line),
|
||||||
|
Entry: ex.funcInfo.entry,
|
||||||
}
|
}
|
||||||
frames = append(frames, physicalFrame)
|
|
||||||
|
|
||||||
return frames
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// expandCgoFrames expands frame information for pc, known to be
|
// expandCgoFrames expands frame information for pc, known to be
|
||||||
// a non-Go function, using the cgoSymbolizer hook. expandCgoFrames
|
// a non-Go function, using the cgoSymbolizer hook. expandCgoFrames
|
||||||
// appends to frames and returns the new slice.
|
// returns nil if pc could not be expanded.
|
||||||
func expandCgoFrames(frames []Frame, pc uintptr) []Frame {
|
func expandCgoFrames(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 frames
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var frames []Frame
|
||||||
for {
|
for {
|
||||||
frames = append(frames, Frame{
|
frames = append(frames, Frame{
|
||||||
PC: pc,
|
PC: pc,
|
||||||
@ -487,7 +540,7 @@ func moduledataverify1(datap *moduledata) {
|
|||||||
// FuncForPC returns a *Func describing the function that contains the
|
// FuncForPC returns a *Func describing the function that contains the
|
||||||
// given program counter address, or else nil.
|
// given program counter address, or else nil.
|
||||||
func FuncForPC(pc uintptr) *Func {
|
func FuncForPC(pc uintptr) *Func {
|
||||||
return (*Func)(unsafe.Pointer(findfunc(pc)._func))
|
return findfunc(pc)._Func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the name of the function.
|
// Name returns the name of the function.
|
||||||
@ -529,6 +582,10 @@ func (f funcInfo) valid() bool {
|
|||||||
return f._func != nil
|
return f._func != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f funcInfo) _Func() *Func {
|
||||||
|
return (*Func)(unsafe.Pointer(f._func))
|
||||||
|
}
|
||||||
|
|
||||||
func findfunc(pc uintptr) funcInfo {
|
func findfunc(pc uintptr) funcInfo {
|
||||||
datap := findmoduledatap(pc)
|
datap := findmoduledatap(pc)
|
||||||
if datap == nil {
|
if datap == nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user