proc/stack: use BP when FDE is not available
On Windows we can sometimes encounter threads stopped in locations for which we do not have entries in debug_frame. These cases seem to be due to calls to Windows API in the go runtime, we can still produce a (partial) stack trace in this circumstance by following frame pointers (starting with BP). We still prefer debug_frame entries when available since go functions do not have frame pointers before go1.8.
This commit is contained in:
parent
1a68f8d351
commit
92faa95bf9
@ -1119,7 +1119,10 @@ func TestFrameEvaluation(t *testing.T) {
|
||||
for _, g := range gs {
|
||||
frame := -1
|
||||
frames, err := g.Stacktrace(10)
|
||||
assertNoError(err, t, "GoroutineStacktrace()")
|
||||
if err != nil {
|
||||
t.Logf("could not stacktrace goroutine %d: %v\n", g.ID, err)
|
||||
continue
|
||||
}
|
||||
for i := range frames {
|
||||
if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" {
|
||||
frame = i
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
type Registers interface {
|
||||
PC() uint64
|
||||
SP() uint64
|
||||
BP() uint64
|
||||
CX() uint64
|
||||
TLS() uint64
|
||||
Get(int) (uint64, error)
|
||||
|
@ -88,6 +88,10 @@ func (r *Regs) SP() uint64 {
|
||||
return r.rsp
|
||||
}
|
||||
|
||||
func (r *Regs) BP() uint64 {
|
||||
return r.rbp
|
||||
}
|
||||
|
||||
// CX returns the value of the RCX register.
|
||||
func (r *Regs) CX() uint64 {
|
||||
return r.rcx
|
||||
|
@ -69,6 +69,10 @@ func (r *Regs) SP() uint64 {
|
||||
return r.regs.Rsp
|
||||
}
|
||||
|
||||
func (r *Regs) BP() uint64 {
|
||||
return r.regs.Rbp
|
||||
}
|
||||
|
||||
// CX returns the value of RCX register.
|
||||
func (r *Regs) CX() uint64 {
|
||||
return r.regs.Rcx
|
||||
|
@ -107,6 +107,10 @@ func (r *Regs) SP() uint64 {
|
||||
return r.rsp
|
||||
}
|
||||
|
||||
func (r *Regs) BP() uint64 {
|
||||
return r.rbp
|
||||
}
|
||||
|
||||
// CX returns the value of the RCX register.
|
||||
func (r *Regs) CX() uint64 {
|
||||
return r.rcx
|
||||
|
@ -1,7 +1,6 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
@ -62,7 +61,7 @@ func (t *Thread) stackIterator(stkbar []savedLR, stkbarPos int) (*stackIterator,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newStackIterator(t.dbp, regs.PC(), regs.SP(), stkbar, stkbarPos), nil
|
||||
return newStackIterator(t.dbp, regs.PC(), regs.SP(), regs.BP(), stkbar, stkbarPos), nil
|
||||
}
|
||||
|
||||
// Stacktrace returns the stack trace for thread.
|
||||
@ -83,7 +82,7 @@ func (g *G) stackIterator() (*stackIterator, error) {
|
||||
if g.thread != nil {
|
||||
return g.thread.stackIterator(stkbar, g.stkbarPos)
|
||||
}
|
||||
return newStackIterator(g.dbp, g.PC, g.SP, stkbar, g.stkbarPos), nil
|
||||
return newStackIterator(g.dbp, g.PC, g.SP, 0, stkbar, g.stkbarPos), nil
|
||||
}
|
||||
|
||||
// Stacktrace returns the stack trace for a goroutine.
|
||||
@ -114,12 +113,12 @@ func (n NullAddrError) Error() string {
|
||||
// required to iterate and walk the program
|
||||
// stack.
|
||||
type stackIterator struct {
|
||||
pc, sp uint64
|
||||
top bool
|
||||
atend bool
|
||||
frame Stackframe
|
||||
dbp *Process
|
||||
err error
|
||||
pc, sp, bp uint64
|
||||
top bool
|
||||
atend bool
|
||||
frame Stackframe
|
||||
dbp *Process
|
||||
err error
|
||||
|
||||
stackBarrierPC uint64
|
||||
stkbar []savedLR
|
||||
@ -130,7 +129,7 @@ type savedLR struct {
|
||||
val uint64
|
||||
}
|
||||
|
||||
func newStackIterator(dbp *Process, pc, sp uint64, stkbar []savedLR, stkbarPos int) *stackIterator {
|
||||
func newStackIterator(dbp *Process, pc, sp, bp uint64, stkbar []savedLR, stkbarPos int) *stackIterator {
|
||||
stackBarrierPC := dbp.goSymTable.LookupFunc(runtimeStackBarrier).Entry
|
||||
if stkbar != nil {
|
||||
fn := dbp.goSymTable.PCToFunc(pc)
|
||||
@ -148,7 +147,7 @@ func newStackIterator(dbp *Process, pc, sp uint64, stkbar []savedLR, stkbarPos i
|
||||
}
|
||||
stkbar = stkbar[stkbarPos:]
|
||||
}
|
||||
return &stackIterator{pc: pc, sp: sp, top: true, dbp: dbp, err: nil, atend: false, stackBarrierPC: stackBarrierPC, stkbar: stkbar}
|
||||
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.
|
||||
@ -156,7 +155,7 @@ 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.top)
|
||||
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}
|
||||
@ -167,13 +166,6 @@ func (it *stackIterator) Next() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if it.frame.Current.Fn == nil {
|
||||
if it.top {
|
||||
it.err = fmt.Errorf("PC not associated to any function")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if it.frame.Ret <= 0 {
|
||||
it.atend = true
|
||||
return true
|
||||
@ -186,7 +178,7 @@ func (it *stackIterator) Next() bool {
|
||||
}
|
||||
|
||||
// Look for "top of stack" functions.
|
||||
if it.frame.Current.Fn.Name == "runtime.goexit" || it.frame.Current.Fn.Name == "runtime.rt0_go" || it.frame.Current.Fn.Name == "runtime.mcall" {
|
||||
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
|
||||
}
|
||||
@ -194,6 +186,7 @@ func (it *stackIterator) Next() bool {
|
||||
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
|
||||
}
|
||||
|
||||
@ -210,24 +203,35 @@ func (it *stackIterator) Err() error {
|
||||
return it.err
|
||||
}
|
||||
|
||||
func (dbp *Process) frameInfo(pc, sp uint64, top bool) (Stackframe, error) {
|
||||
f, l, fn := dbp.PCToLine(pc)
|
||||
func (dbp *Process) frameInfo(pc, sp, bp uint64, top bool) (Stackframe, error) {
|
||||
fde, err := dbp.frameEntries.FDEForPC(pc)
|
||||
if err != nil {
|
||||
return Stackframe{}, err
|
||||
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{}
|
||||
}
|
||||
data, err := dbp.currentThread.readMemory(retaddr, dbp.arch.PtrSize())
|
||||
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: binary.LittleEndian.Uint64(data), addrret: uint64(retaddr)}
|
||||
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
|
||||
|
@ -212,6 +212,7 @@ func TestScopePrefix(t *testing.T) {
|
||||
|
||||
goroutinesOut := strings.Split(term.MustExec("goroutines"), "\n")
|
||||
agoroutines := []int{}
|
||||
nonagoroutines := []int{}
|
||||
curgid := -1
|
||||
|
||||
for _, line := range goroutinesOut {
|
||||
@ -235,14 +236,31 @@ func TestScopePrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
if idx := strings.Index(line, " main.agoroutine "); idx < 0 {
|
||||
nonagoroutines = append(nonagoroutines, gid)
|
||||
continue
|
||||
}
|
||||
|
||||
agoroutines = append(agoroutines, gid)
|
||||
}
|
||||
|
||||
if len(agoroutines) != 10 {
|
||||
t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine: %q", goroutinesOut)
|
||||
if len(agoroutines) > 10 {
|
||||
t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d found): %q", len(agoroutines), goroutinesOut)
|
||||
}
|
||||
|
||||
if len(agoroutines) < 10 {
|
||||
extraAgoroutines := 0
|
||||
for _, gid := range nonagoroutines {
|
||||
stackOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d stack", gid)), "\n")
|
||||
for _, line := range stackOut {
|
||||
if strings.HasSuffix(line, " main.agoroutine") {
|
||||
extraAgoroutines++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(agoroutines)+extraAgoroutines < 10 {
|
||||
t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d+%d found): %q", len(agoroutines), extraAgoroutines, goroutinesOut)
|
||||
}
|
||||
}
|
||||
|
||||
if curgid < 0 {
|
||||
|
@ -13,8 +13,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/derekparker/delve/pkg/target"
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/derekparker/delve/pkg/target"
|
||||
"github.com/derekparker/delve/service/api"
|
||||
)
|
||||
|
||||
@ -750,7 +750,7 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo
|
||||
locations := make([]api.Stackframe, 0, len(rawlocs))
|
||||
for i := range rawlocs {
|
||||
frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)}
|
||||
if cfg != nil {
|
||||
if cfg != nil && rawlocs[i].Current.Fn != nil {
|
||||
var err error
|
||||
scope := rawlocs[i].Scope(d.target.CurrentThread())
|
||||
locals, err := scope.LocalVariables(*cfg)
|
||||
|
@ -763,7 +763,7 @@ func TestClientServer_FullStacktrace(t *testing.T) {
|
||||
if arg.Name != "i" {
|
||||
continue
|
||||
}
|
||||
t.Logf("frame %d, variable i is %v\n", arg)
|
||||
t.Logf("frame %v, variable i is %v\n", frame, arg)
|
||||
argn, err := strconv.Atoi(arg.Value)
|
||||
if err == nil {
|
||||
found[argn] = true
|
||||
|
Loading…
Reference in New Issue
Block a user