package proc import ( "errors" "fmt" "github.com/derekparker/delve/pkg/dwarf/frame" ) // This code is partly adaped from runtime.gentraceback in // $GOROOT/src/runtime/traceback.go const runtimeStackBarrier = "runtime.stackBarrier" // NoReturnAddr is returned when return address // could not be found during stack trace. type NoReturnAddr struct { fn string } func (nra NoReturnAddr) Error() string { return fmt.Sprintf("could not find return address for %s", nra.fn) } // Stackframe represents a frame in a system stack. type Stackframe struct { // Address the function above this one on the call stack will return to. Current Location // Address of the call instruction for the function above on the call stack. Call Location // Start address of the stack frame. CFA int64 // Description of the stack frame. FDE *frame.FrameDescriptionEntry // 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 } // Scope returns a new EvalScope using this frame. func (frame *Stackframe) Scope(thread *Thread) *EvalScope { return &EvalScope{Thread: thread, PC: frame.Current.PC, CFA: frame.CFA} } // ReturnAddress returns the return address of the function // this thread is executing. func (t *Thread) ReturnAddress() (uint64, error) { locations, err := t.Stacktrace(2) if err != nil { return 0, err } if len(locations) < 2 { return 0, NoReturnAddr{locations[0].Current.Fn.BaseName()} } return locations[1].Current.PC, nil } func (t *Thread) stackIterator(stkbar []savedLR, stkbarPos int) (*stackIterator, error) { regs, err := t.Registers(false) if err != nil { return nil, err } return newStackIterator(t.dbp, regs.PC(), regs.SP(), regs.BP(), stkbar, stkbarPos), nil } // Stacktrace returns the stack trace for thread. // Note the locations in the array are return addresses not call addresses. func (t *Thread) Stacktrace(depth int) ([]Stackframe, error) { it, err := t.stackIterator(nil, -1) if err != nil { return nil, err } return it.stacktrace(depth) } func (g *G) stackIterator() (*stackIterator, error) { stkbar, err := g.stkbar() if err != nil { return nil, err } if g.thread != nil { return g.thread.stackIterator(stkbar, g.stkbarPos) } return newStackIterator(g.dbp, g.PC, g.SP, 0, stkbar, g.stkbarPos), 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) ([]Stackframe, error) { it, err := g.stackIterator() if err != nil { return nil, err } return it.stacktrace(depth) } // GoroutineLocation returns the location of the given // goroutine. func (dbp *Process) GoroutineLocation(g *G) *Location { f, l, fn := dbp.PCToLine(g.PC) return &Location{PC: g.PC, File: f, Line: l, Fn: fn} } // 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, sp, bp uint64 top bool atend bool frame Stackframe dbp *Process err error stackBarrierPC uint64 stkbar []savedLR } type savedLR struct { ptr uint64 val uint64 } func newStackIterator(dbp *Process, pc, sp, bp uint64, stkbar []savedLR, stkbarPos int) *stackIterator { stackBarrierFunc := dbp.goSymTable.LookupFunc(runtimeStackBarrier) // stack barriers were removed in Go 1.9 var stackBarrierPC uint64 if stackBarrierFunc != nil && stkbar != nil { stackBarrierPC = stackBarrierFunc.Entry fn := dbp.goSymTable.PCToFunc(pc) if fn != nil && fn.Name == runtimeStackBarrier { // 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 < sp { // runtime.stackBarrier has not incremented stkbarPos. } else if stkbarPos > 0 && stkbar[stkbarPos-1].ptr < sp { // runtime.stackBarrier has incremented stkbarPos. stkbarPos-- } else { return &stackIterator{err: fmt.Errorf("failed to unwind through stackBarrier at SP %x", sp)} } } stkbar = stkbar[stkbarPos:] } return &stackIterator{pc: pc, sp: sp, bp: bp, top: true, dbp: dbp, err: nil, atend: false, stackBarrierPC: stackBarrierPC, stkbar: stkbar} } // Next points the iterator to the next stack frame. func (it *stackIterator) Next() bool { if it.err != nil || it.atend { return false } it.frame, it.err = it.dbp.frameInfo(it.pc, it.sp, it.bp, it.top) if it.err != nil { if _, nofde := it.err.(*frame.NoFDEForPCError); nofde && !it.top { it.frame = Stackframe{Current: Location{PC: it.pc, File: "?", Line: -1}, Call: Location{PC: it.pc, File: "?", Line: -1}, CFA: 0, Ret: 0} it.atend = true it.err = nil return true } return false } if it.frame.Ret <= 0 { it.atend = true return true } 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:] } // Look for "top of stack" functions. if it.frame.Current.Fn != nil && (it.frame.Current.Fn.Name == "runtime.goexit" || it.frame.Current.Fn.Name == "runtime.rt0_go" || it.frame.Current.Fn.Name == "runtime.mcall") { it.atend = true return true } it.top = false it.pc = it.frame.Ret it.sp = uint64(it.frame.CFA) it.bp, _ = readUintRaw(it.dbp.currentThread, uintptr(it.bp), int64(it.dbp.arch.PtrSize())) return true } // Frame returns the frame the iterator is pointing at. func (it *stackIterator) Frame() Stackframe { if it.err != nil { panic(it.err) } return it.frame } // Err returns the error encountered during stack iteration. func (it *stackIterator) Err() error { return it.err } func (dbp *Process) frameInfo(pc, sp, bp uint64, top bool) (Stackframe, error) { fde, err := dbp.frameEntries.FDEForPC(pc) if _, nofde := err.(*frame.NoFDEForPCError); nofde { if bp == 0 { return Stackframe{}, err } // When no FDE is available attempt to use BP instead retaddr := uintptr(int(bp) + dbp.arch.PtrSize()) cfa := int64(retaddr) + int64(dbp.arch.PtrSize()) return dbp.newStackframe(pc, cfa, retaddr, nil, top) } spoffset, retoffset := fde.ReturnAddressOffset(pc) cfa := int64(sp) + spoffset retaddr := uintptr(cfa + retoffset) return dbp.newStackframe(pc, cfa, retaddr, fde, top) } func (dbp *Process) newStackframe(pc uint64, cfa int64, retaddr uintptr, fde *frame.FrameDescriptionEntry, top bool) (Stackframe, error) { if retaddr == 0 { return Stackframe{}, NullAddrError{} } f, l, fn := dbp.PCToLine(pc) ret, err := readUintRaw(dbp.currentThread, retaddr, int64(dbp.arch.PtrSize())) if err != nil { return Stackframe{}, err } r := Stackframe{Current: Location{PC: pc, File: f, Line: l, Fn: fn}, CFA: cfa, FDE: fde, Ret: ret, addrret: uint64(retaddr)} if !top { r.Call.File, r.Call.Line, r.Call.Fn = dbp.PCToLine(pc - 1) r.Call.PC = r.Current.PC } else { r.Call = r.Current } return r, nil } 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 = append(frames, it.Frame()) if len(frames) >= depth+1 { break } } if err := it.Err(); err != nil { return nil, err } return frames, nil }