bee/vendor/github.com/derekparker/delve/pkg/proc/stack.go

688 lines
22 KiB
Go

package proc
import (
"debug/dwarf"
"errors"
"fmt"
"go/constant"
"strings"
"github.com/derekparker/delve/pkg/dwarf/frame"
"github.com/derekparker/delve/pkg/dwarf/op"
"github.com/derekparker/delve/pkg/dwarf/reader"
)
// This code is partly adapted from runtime.gentraceback in
// $GOROOT/src/runtime/traceback.go
// Stackframe represents a frame in a system stack.
//
// Each stack frame has two locations Current and Call.
//
// For the topmost stackframe Current and Call are the same location.
//
// For stackframes after the first Current is the location corresponding to
// the return address and Call is the location of the CALL instruction that
// was last executed on the frame. Note however that Call.PC is always equal
// to Current.PC, because finding the correct value for Call.PC would
// require disassembling each function in the stacktrace.
//
// For synthetic stackframes generated for inlined function calls Current.Fn
// is the function containing the inlining and Call.Fn in the inlined
// function.
type Stackframe struct {
Current, Call Location
// Frame registers.
Regs op.DwarfRegisters
// High address of the stack.
stackHi uint64
// Return address for this stack frame (as read from the stack frame itself).
Ret uint64
// Address to the memory location containing the return address
addrret uint64
// Err is set if an error occurred during stacktrace
Err error
// SystemStack is true if this frame belongs to a system stack.
SystemStack bool
// Inlined is true if this frame is actually an inlined call.
Inlined bool
// Bottom is true if this is the bottom of the stack
Bottom bool
// lastpc is a memory address guaranteed to belong to the last instruction
// executed in this stack frame.
// For the topmost stack frame this will be the same as Current.PC and
// Call.PC, for other stack frames it will usually be Current.PC-1, but
// could be different when inlined calls are involved in the stacktrace.
// Note that this address isn't guaranteed to belong to the start of an
// instruction and, for this reason, should not be propagated outside of
// pkg/proc.
// Use this value to determine active lexical scopes for the stackframe.
lastpc uint64
// TopmostDefer is the defer that would be at the top of the stack when a
// panic unwind would get to this call frame, in other words it's the first
// deferred function that will be called if the runtime unwinds past this
// call frame.
TopmostDefer *Defer
// Defers is the list of functions deferred by this stack frame (so far).
Defers []*Defer
}
// FrameOffset returns the address of the stack frame, absolute for system
// stack frames or as an offset from stackhi for goroutine stacks (a
// negative value).
func (frame *Stackframe) FrameOffset() int64 {
if frame.SystemStack {
return frame.Regs.CFA
}
return frame.Regs.CFA - int64(frame.stackHi)
}
// FramePointerOffset returns the value of the frame pointer, absolute for
// system stack frames or as an offset from stackhi for goroutine stacks (a
// negative value).
func (frame *Stackframe) FramePointerOffset() int64 {
if frame.SystemStack {
return int64(frame.Regs.BP())
}
return int64(frame.Regs.BP()) - int64(frame.stackHi)
}
// ThreadStacktrace returns the stack trace for thread.
// Note the locations in the array are return addresses not call addresses.
func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
g, _ := GetG(thread)
if g == nil {
regs, err := thread.Registers(true)
if err != nil {
return nil, err
}
it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(regs, thread.BinInfo().staticBase), 0, nil, -1, nil)
return it.stacktrace(depth)
}
return g.Stacktrace(depth, false)
}
func (g *G) stackIterator() (*stackIterator, error) {
stkbar, err := g.stkbar()
if err != nil {
return nil, err
}
if g.Thread != nil {
regs, err := g.Thread.Registers(true)
if err != nil {
return nil, err
}
return newStackIterator(g.variable.bi, g.Thread, g.variable.bi.Arch.RegistersToDwarfRegisters(regs, g.variable.bi.staticBase), g.stackhi, stkbar, g.stkbarPos, g), nil
}
return newStackIterator(g.variable.bi, g.variable.mem, g.variable.bi.Arch.GoroutineToDwarfRegisters(g), g.stackhi, stkbar, g.stkbarPos, g), nil
}
// Stacktrace returns the stack trace for a goroutine.
// Note the locations in the array are return addresses not call addresses.
func (g *G) Stacktrace(depth int, readDefers bool) ([]Stackframe, error) {
it, err := g.stackIterator()
if err != nil {
return nil, err
}
frames, err := it.stacktrace(depth)
if err != nil {
return nil, err
}
if readDefers {
g.readDefers(frames)
}
return frames, nil
}
// NullAddrError is an error for a null address.
type NullAddrError struct{}
func (n NullAddrError) Error() string {
return "NULL address"
}
// stackIterator holds information
// required to iterate and walk the program
// stack.
type stackIterator struct {
pc uint64
top bool
atend bool
frame Stackframe
bi *BinaryInfo
mem MemoryReadWriter
err error
stackhi uint64
systemstack bool
stackBarrierPC uint64
stkbar []savedLR
// regs is the register set for the current frame
regs op.DwarfRegisters
g *G // the goroutine being stacktraced, nil if we are stacktracing a goroutine-less thread
g0_sched_sp uint64 // value of g0.sched.sp (see comments around its use)
dwarfReader *dwarf.Reader
}
type savedLR struct {
ptr uint64
val uint64
}
func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegisters, stackhi uint64, stkbar []savedLR, stkbarPos int, g *G) *stackIterator {
stackBarrierFunc := bi.LookupFunc["runtime.stackBarrier"] // stack barriers were removed in Go 1.9
var stackBarrierPC uint64
if stackBarrierFunc != nil && stkbar != nil {
stackBarrierPC = stackBarrierFunc.Entry
fn := bi.PCToFunc(regs.PC())
if fn != nil && fn.Name == "runtime.stackBarrier" {
// We caught the goroutine as it's executing the stack barrier, we must
// determine whether or not g.stackPos has already been incremented or not.
if len(stkbar) > 0 && stkbar[stkbarPos].ptr < regs.SP() {
// runtime.stackBarrier has not incremented stkbarPos.
} else if stkbarPos > 0 && stkbar[stkbarPos-1].ptr < regs.SP() {
// runtime.stackBarrier has incremented stkbarPos.
stkbarPos--
} else {
return &stackIterator{err: fmt.Errorf("failed to unwind through stackBarrier at SP %x", regs.SP())}
}
}
stkbar = stkbar[stkbarPos:]
}
var g0_sched_sp uint64
systemstack := true
if g != nil {
systemstack = g.SystemStack
g0var, _ := g.variable.fieldVariable("m").structMember("g0")
if g0var != nil {
g0, _ := g0var.parseG()
if g0 != nil {
g0_sched_sp = g0.SP
}
}
}
return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar, systemstack: systemstack, g: g, g0_sched_sp: g0_sched_sp, dwarfReader: bi.dwarf.Reader()}
}
// Next points the iterator to the next stack frame.
func (it *stackIterator) Next() bool {
if it.err != nil || it.atend {
return false
}
callFrameRegs, ret, retaddr := it.advanceRegs()
it.frame = it.newStackframe(ret, retaddr)
if it.stkbar != nil && it.frame.Ret == it.stackBarrierPC && it.frame.addrret == it.stkbar[0].ptr {
// Skip stack barrier frames
it.frame.Ret = it.stkbar[0].val
it.stkbar = it.stkbar[1:]
}
if it.switchStack() {
return true
}
if it.frame.Ret <= 0 {
it.atend = true
return true
}
it.top = false
it.pc = it.frame.Ret
it.regs = callFrameRegs
return true
}
// asmcgocallSPOffsetSaveSlot is the offset from systemstack.SP where
// (goroutine.SP - StackHi) is saved in runtime.asmcgocall after the stack
// switch happens.
const asmcgocallSPOffsetSaveSlot = 0x28
// switchStack will use the current frame to determine if it's time to
// switch between the system stack and the goroutine stack or vice versa.
// Sets it.atend when the top of the stack is reached.
func (it *stackIterator) switchStack() bool {
if it.frame.Current.Fn == nil {
return false
}
switch it.frame.Current.Fn.Name {
case "runtime.asmcgocall":
if it.top || !it.systemstack {
return false
}
// This function is called by a goroutine to execute a C function and
// switches from the goroutine stack to the system stack.
// Since we are unwinding the stack from callee to caller we have switch
// from the system stack to the goroutine stack.
off, _ := readIntRaw(it.mem, uintptr(it.regs.SP()+asmcgocallSPOffsetSaveSlot), int64(it.bi.Arch.PtrSize())) // reads "offset of SP from StackHi" from where runtime.asmcgocall saved it
oldsp := it.regs.SP()
it.regs.Reg(it.regs.SPRegNum).Uint64Val = uint64(int64(it.stackhi) - off)
// runtime.asmcgocall can also be called from inside the system stack,
// in that case no stack switch actually happens
if it.regs.SP() == oldsp {
return false
}
it.systemstack = false
// advances to the next frame in the call stack
it.frame.addrret = uint64(int64(it.regs.SP()) + int64(it.bi.Arch.PtrSize()))
it.frame.Ret, _ = readUintRaw(it.mem, uintptr(it.frame.addrret), int64(it.bi.Arch.PtrSize()))
it.pc = it.frame.Ret
it.top = false
return true
case "runtime.cgocallback_gofunc":
// For a detailed description of how this works read the long comment at
// the start of $GOROOT/src/runtime/cgocall.go and the source code of
// runtime.cgocallback_gofunc in $GOROOT/src/runtime/asm_amd64.s
//
// When a C functions calls back into go it will eventually call into
// runtime.cgocallback_gofunc which is the function that does the stack
// switch from the system stack back into the goroutine stack
// Since we are going backwards on the stack here we see the transition
// as goroutine stack -> system stack.
if it.top || it.systemstack {
return false
}
if it.g0_sched_sp <= 0 {
return false
}
// entering the system stack
it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g0_sched_sp
// reads the previous value of g0.sched.sp that runtime.cgocallback_gofunc saved on the stack
it.g0_sched_sp, _ = readUintRaw(it.mem, uintptr(it.regs.SP()), int64(it.bi.Arch.PtrSize()))
it.top = false
callFrameRegs, ret, retaddr := it.advanceRegs()
frameOnSystemStack := it.newStackframe(ret, retaddr)
it.pc = frameOnSystemStack.Ret
it.regs = callFrameRegs
it.systemstack = true
return true
case "runtime.goexit", "runtime.rt0_go", "runtime.mcall":
// Look for "top of stack" functions.
it.atend = true
return true
default:
if it.systemstack && it.top && it.g != nil && strings.HasPrefix(it.frame.Current.Fn.Name, "runtime.") {
// The runtime switches to the system stack in multiple places.
// This usually happens through a call to runtime.systemstack but there
// are functions that switch to the system stack manually (for example
// runtime.morestack).
// Since we are only interested in printing the system stack for cgo
// calls we switch directly to the goroutine stack if we detect that the
// function at the top of the stack is a runtime function.
it.systemstack = false
it.top = false
it.pc = it.g.PC
it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g.SP
it.regs.Reg(it.regs.BPRegNum).Uint64Val = it.g.BP
return true
}
return false
}
}
// Frame returns the frame the iterator is pointing at.
func (it *stackIterator) Frame() Stackframe {
it.frame.Bottom = it.atend
return it.frame
}
// Err returns the error encountered during stack iteration.
func (it *stackIterator) Err() error {
return it.err
}
// frameBase calculates the frame base pseudo-register for DWARF for fn and
// the current frame.
func (it *stackIterator) frameBase(fn *Function) int64 {
it.dwarfReader.Seek(fn.offset)
e, err := it.dwarfReader.Next()
if err != nil {
return 0
}
fb, _, _, _ := it.bi.Location(e, dwarf.AttrFrameBase, it.pc, it.regs)
return fb
}
func (it *stackIterator) newStackframe(ret, retaddr uint64) Stackframe {
if retaddr == 0 {
it.err = NullAddrError{}
return Stackframe{}
}
f, l, fn := it.bi.PCToLine(it.pc)
if fn == nil {
f = "?"
l = -1
} else {
it.regs.FrameBase = it.frameBase(fn)
}
r := Stackframe{Current: Location{PC: it.pc, File: f, Line: l, Fn: fn}, Regs: it.regs, Ret: ret, addrret: retaddr, stackHi: it.stackhi, SystemStack: it.systemstack, lastpc: it.pc}
if !it.top {
fnname := ""
if r.Current.Fn != nil {
fnname = r.Current.Fn.Name
}
switch fnname {
case "runtime.mstart", "runtime.systemstack_switch":
// these frames are inserted by runtime.systemstack and there is no CALL
// instruction to look for at pc - 1
r.Call = r.Current
default:
if r.Current.Fn != nil && it.pc == r.Current.Fn.Entry {
// if the return address is the entry point of the function that
// contains it then this is some kind of fake return frame (for example
// runtime.sigreturn) that didn't actually call the current frame,
// attempting to get the location of the CALL instruction would just
// obfuscate what's going on, since there is no CALL instruction.
r.Call = r.Current
} else {
r.lastpc = it.pc - 1
r.Call.File, r.Call.Line, r.Call.Fn = it.bi.PCToLine(it.pc - 1)
if r.Call.Fn == nil {
r.Call.File = "?"
r.Call.Line = -1
}
r.Call.PC = r.Current.PC
}
}
} else {
r.Call = r.Current
}
return r
}
func (it *stackIterator) stacktrace(depth int) ([]Stackframe, error) {
if depth < 0 {
return nil, errors.New("negative maximum stack depth")
}
frames := make([]Stackframe, 0, depth+1)
for it.Next() {
frames = it.appendInlineCalls(frames, it.Frame())
if len(frames) >= depth+1 {
break
}
}
if err := it.Err(); err != nil {
if len(frames) == 0 {
return nil, err
}
frames = append(frames, Stackframe{Err: err})
}
return frames, nil
}
func (it *stackIterator) appendInlineCalls(frames []Stackframe, frame Stackframe) []Stackframe {
if frame.Call.Fn == nil {
return append(frames, frame)
}
if frame.Call.Fn.cu.lineInfo == nil {
return append(frames, frame)
}
callpc := frame.Call.PC
if len(frames) > 0 {
callpc--
}
irdr := reader.InlineStack(it.bi.dwarf, frame.Call.Fn.offset, reader.ToRelAddr(callpc, it.bi.staticBase))
for irdr.Next() {
entry, offset := reader.LoadAbstractOrigin(irdr.Entry(), it.dwarfReader)
fnname, okname := entry.Val(dwarf.AttrName).(string)
fileidx, okfileidx := entry.Val(dwarf.AttrCallFile).(int64)
line, okline := entry.Val(dwarf.AttrCallLine).(int64)
if !okname || !okfileidx || !okline {
break
}
if fileidx-1 < 0 || fileidx-1 >= int64(len(frame.Current.Fn.cu.lineInfo.FileNames)) {
break
}
inlfn := &Function{Name: fnname, Entry: frame.Call.Fn.Entry, End: frame.Call.Fn.End, offset: offset, cu: frame.Call.Fn.cu}
frames = append(frames, Stackframe{
Current: frame.Current,
Call: Location{
frame.Call.PC,
frame.Call.File,
frame.Call.Line,
inlfn,
},
Regs: frame.Regs,
stackHi: frame.stackHi,
Ret: frame.Ret,
addrret: frame.addrret,
Err: frame.Err,
SystemStack: frame.SystemStack,
Inlined: true,
lastpc: frame.lastpc,
})
frame.Call.File = frame.Current.Fn.cu.lineInfo.FileNames[fileidx-1].Path
frame.Call.Line = int(line)
}
return append(frames, frame)
}
// advanceRegs calculates it.callFrameRegs using it.regs and the frame
// descriptor entry for the current stack frame.
// it.regs.CallFrameCFA is updated.
func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uint64, retaddr uint64) {
fde, err := it.bi.frameEntries.FDEForPC(it.pc)
var framectx *frame.FrameContext
if _, nofde := err.(*frame.ErrNoFDEForPC); nofde {
framectx = it.bi.Arch.FixFrameUnwindContext(nil, it.pc, it.bi)
} else {
framectx = it.bi.Arch.FixFrameUnwindContext(fde.EstablishFrame(it.pc), it.pc, it.bi)
}
cfareg, err := it.executeFrameRegRule(0, framectx.CFA, 0)
if cfareg == nil {
it.err = fmt.Errorf("CFA becomes undefined at PC %#x", it.pc)
return op.DwarfRegisters{StaticBase: it.bi.staticBase}, 0, 0
}
it.regs.CFA = int64(cfareg.Uint64Val)
callFrameRegs = op.DwarfRegisters{StaticBase: it.bi.staticBase, ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum}
// According to the standard the compiler should be responsible for emitting
// rules for the RSP register so that it can then be used to calculate CFA,
// however neither Go nor GCC do this.
// In the following line we copy GDB's behaviour by assuming this is
// implicit.
// See also the comment in dwarf2_frame_default_init in
// $GDB_SOURCE/dwarf2-frame.c
callFrameRegs.AddReg(uint64(amd64DwarfSPRegNum), cfareg)
for i, regRule := range framectx.Regs {
reg, err := it.executeFrameRegRule(i, regRule, it.regs.CFA)
callFrameRegs.AddReg(i, reg)
if i == framectx.RetAddrReg {
if reg == nil {
if err == nil {
err = fmt.Errorf("Undefined return address at %#x", it.pc)
}
it.err = err
} else {
ret = reg.Uint64Val
}
retaddr = uint64(it.regs.CFA + regRule.Offset)
}
}
return callFrameRegs, ret, retaddr
}
func (it *stackIterator) executeFrameRegRule(regnum uint64, rule frame.DWRule, cfa int64) (*op.DwarfRegister, error) {
switch rule.Rule {
default:
fallthrough
case frame.RuleUndefined:
return nil, nil
case frame.RuleSameVal:
reg := *it.regs.Reg(regnum)
return &reg, nil
case frame.RuleOffset:
return it.readRegisterAt(regnum, uint64(cfa+rule.Offset))
case frame.RuleValOffset:
return op.DwarfRegisterFromUint64(uint64(cfa + rule.Offset)), nil
case frame.RuleRegister:
return it.regs.Reg(rule.Reg), nil
case frame.RuleExpression:
v, _, err := op.ExecuteStackProgram(it.regs, rule.Expression)
if err != nil {
return nil, err
}
return it.readRegisterAt(regnum, uint64(v))
case frame.RuleValExpression:
v, _, err := op.ExecuteStackProgram(it.regs, rule.Expression)
if err != nil {
return nil, err
}
return op.DwarfRegisterFromUint64(uint64(v)), nil
case frame.RuleArchitectural:
return nil, errors.New("architectural frame rules are unsupported")
case frame.RuleCFA:
if it.regs.Reg(rule.Reg) == nil {
return nil, nil
}
return op.DwarfRegisterFromUint64(uint64(int64(it.regs.Uint64Val(rule.Reg)) + rule.Offset)), nil
case frame.RuleFramePointer:
curReg := it.regs.Reg(rule.Reg)
if curReg == nil {
return nil, nil
}
if curReg.Uint64Val <= uint64(cfa) {
return it.readRegisterAt(regnum, curReg.Uint64Val)
}
newReg := *curReg
return &newReg, nil
}
}
func (it *stackIterator) readRegisterAt(regnum uint64, addr uint64) (*op.DwarfRegister, error) {
buf := make([]byte, it.bi.Arch.RegSize(regnum))
_, err := it.mem.ReadMemory(buf, uintptr(addr))
if err != nil {
return nil, err
}
return op.DwarfRegisterFromBytes(buf), nil
}
// Defer represents one deferred call
type Defer struct {
DeferredPC uint64 // Value of field _defer.fn.fn, the deferred function
DeferPC uint64 // PC address of instruction that added this defer
SP uint64 // Value of SP register when this function was deferred (this field gets adjusted when the stack is moved to match the new stack space)
link *Defer // Next deferred function
variable *Variable
Unreadable error
}
// readDefers decorates the frames with the function deferred at each stack frame.
func (g *G) readDefers(frames []Stackframe) {
curdefer := g.Defer()
i := 0
// scan simultaneously frames and the curdefer linked list, assigning
// defers to their associated frames.
for {
if curdefer == nil || i >= len(frames) {
return
}
if curdefer.Unreadable != nil {
// Current defer is unreadable, stick it into the first available frame
// (so that it can be reported to the user) and exit
frames[i].Defers = append(frames[i].Defers, curdefer)
return
}
if frames[i].Err != nil {
return
}
if frames[i].TopmostDefer == nil {
frames[i].TopmostDefer = curdefer
}
if frames[i].SystemStack || curdefer.SP >= uint64(frames[i].Regs.CFA) {
// frames[i].Regs.CFA is the value that SP had before the function of
// frames[i] was called.
// This means that when curdefer.SP == frames[i].Regs.CFA then curdefer
// was added by the previous frame.
//
// curdefer.SP < frames[i].Regs.CFA means curdefer was added by a
// function further down the stack.
//
// SystemStack frames live on a different physical stack and can't be
// compared with deferred frames.
i++
} else {
frames[i].Defers = append(frames[i].Defers, curdefer)
curdefer = curdefer.Next()
}
}
}
func (d *Defer) load() {
d.variable.loadValue(LoadConfig{false, 1, 0, 0, -1})
if d.variable.Unreadable != nil {
d.Unreadable = d.variable.Unreadable
return
}
fnvar := d.variable.fieldVariable("fn").maybeDereference()
if fnvar.Addr != 0 {
fnvar = fnvar.loadFieldNamed("fn")
if fnvar.Unreadable == nil {
d.DeferredPC, _ = constant.Uint64Val(fnvar.Value)
}
}
d.DeferPC, _ = constant.Uint64Val(d.variable.fieldVariable("pc").Value)
d.SP, _ = constant.Uint64Val(d.variable.fieldVariable("sp").Value)
linkvar := d.variable.fieldVariable("link").maybeDereference()
if linkvar.Addr != 0 {
d.link = &Defer{variable: linkvar}
}
}
// errSPDecreased is used when (*Defer).Next detects a corrupted linked
// list, specifically when after followin a link pointer the value of SP
// decreases rather than increasing or staying the same (the defer list is a
// FIFO list, nodes further down the list have been added by function calls
// further down the call stack and therefore the SP should always increase).
var errSPDecreased = errors.New("corrupted defer list: SP decreased")
// Next returns the next defer in the linked list
func (d *Defer) Next() *Defer {
if d.link == nil {
return nil
}
d.link.load()
if d.link.SP < d.SP {
d.link.Unreadable = errSPDecreased
}
return d.link
}