diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 2ced0eb0..c83bde52 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -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 diff --git a/pkg/proc/registers.go b/pkg/proc/registers.go index d1032faf..4818ed5b 100644 --- a/pkg/proc/registers.go +++ b/pkg/proc/registers.go @@ -17,6 +17,7 @@ import ( type Registers interface { PC() uint64 SP() uint64 + BP() uint64 CX() uint64 TLS() uint64 Get(int) (uint64, error) diff --git a/pkg/proc/registers_darwin_amd64.go b/pkg/proc/registers_darwin_amd64.go index 0357c91a..9c0733ac 100644 --- a/pkg/proc/registers_darwin_amd64.go +++ b/pkg/proc/registers_darwin_amd64.go @@ -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 diff --git a/pkg/proc/registers_linux_amd64.go b/pkg/proc/registers_linux_amd64.go index 07b79e25..139d159e 100644 --- a/pkg/proc/registers_linux_amd64.go +++ b/pkg/proc/registers_linux_amd64.go @@ -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 diff --git a/pkg/proc/registers_windows_amd64.go b/pkg/proc/registers_windows_amd64.go index ac4ed7c0..eefd9083 100644 --- a/pkg/proc/registers_windows_amd64.go +++ b/pkg/proc/registers_windows_amd64.go @@ -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 diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index 8b282a9a..096cf7a2 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -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 diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 6e92e8de..5c8da193 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -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 { diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 5466303d..705a2f1f 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -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) diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index bc9c7a2e..6c0c931a 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -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