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 {
|
for _, g := range gs {
|
||||||
frame := -1
|
frame := -1
|
||||||
frames, err := g.Stacktrace(10)
|
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 {
|
for i := range frames {
|
||||||
if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" {
|
if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" {
|
||||||
frame = i
|
frame = i
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
type Registers interface {
|
type Registers interface {
|
||||||
PC() uint64
|
PC() uint64
|
||||||
SP() uint64
|
SP() uint64
|
||||||
|
BP() uint64
|
||||||
CX() uint64
|
CX() uint64
|
||||||
TLS() uint64
|
TLS() uint64
|
||||||
Get(int) (uint64, error)
|
Get(int) (uint64, error)
|
||||||
|
@ -88,6 +88,10 @@ func (r *Regs) SP() uint64 {
|
|||||||
return r.rsp
|
return r.rsp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Regs) BP() uint64 {
|
||||||
|
return r.rbp
|
||||||
|
}
|
||||||
|
|
||||||
// CX returns the value of the RCX register.
|
// CX returns the value of the RCX register.
|
||||||
func (r *Regs) CX() uint64 {
|
func (r *Regs) CX() uint64 {
|
||||||
return r.rcx
|
return r.rcx
|
||||||
|
@ -69,6 +69,10 @@ func (r *Regs) SP() uint64 {
|
|||||||
return r.regs.Rsp
|
return r.regs.Rsp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Regs) BP() uint64 {
|
||||||
|
return r.regs.Rbp
|
||||||
|
}
|
||||||
|
|
||||||
// CX returns the value of RCX register.
|
// CX returns the value of RCX register.
|
||||||
func (r *Regs) CX() uint64 {
|
func (r *Regs) CX() uint64 {
|
||||||
return r.regs.Rcx
|
return r.regs.Rcx
|
||||||
|
@ -107,6 +107,10 @@ func (r *Regs) SP() uint64 {
|
|||||||
return r.rsp
|
return r.rsp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Regs) BP() uint64 {
|
||||||
|
return r.rbp
|
||||||
|
}
|
||||||
|
|
||||||
// CX returns the value of the RCX register.
|
// CX returns the value of the RCX register.
|
||||||
func (r *Regs) CX() uint64 {
|
func (r *Regs) CX() uint64 {
|
||||||
return r.rcx
|
return r.rcx
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package proc
|
package proc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
@ -62,7 +61,7 @@ func (t *Thread) stackIterator(stkbar []savedLR, stkbarPos int) (*stackIterator,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// Stacktrace returns the stack trace for thread.
|
||||||
@ -83,7 +82,7 @@ func (g *G) stackIterator() (*stackIterator, error) {
|
|||||||
if g.thread != nil {
|
if g.thread != nil {
|
||||||
return g.thread.stackIterator(stkbar, g.stkbarPos)
|
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.
|
// Stacktrace returns the stack trace for a goroutine.
|
||||||
@ -114,12 +113,12 @@ func (n NullAddrError) Error() string {
|
|||||||
// required to iterate and walk the program
|
// required to iterate and walk the program
|
||||||
// stack.
|
// stack.
|
||||||
type stackIterator struct {
|
type stackIterator struct {
|
||||||
pc, sp uint64
|
pc, sp, bp uint64
|
||||||
top bool
|
top bool
|
||||||
atend bool
|
atend bool
|
||||||
frame Stackframe
|
frame Stackframe
|
||||||
dbp *Process
|
dbp *Process
|
||||||
err error
|
err error
|
||||||
|
|
||||||
stackBarrierPC uint64
|
stackBarrierPC uint64
|
||||||
stkbar []savedLR
|
stkbar []savedLR
|
||||||
@ -130,7 +129,7 @@ type savedLR struct {
|
|||||||
val uint64
|
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
|
stackBarrierPC := dbp.goSymTable.LookupFunc(runtimeStackBarrier).Entry
|
||||||
if stkbar != nil {
|
if stkbar != nil {
|
||||||
fn := dbp.goSymTable.PCToFunc(pc)
|
fn := dbp.goSymTable.PCToFunc(pc)
|
||||||
@ -148,7 +147,7 @@ func newStackIterator(dbp *Process, pc, sp uint64, stkbar []savedLR, stkbarPos i
|
|||||||
}
|
}
|
||||||
stkbar = stkbar[stkbarPos:]
|
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.
|
// Next points the iterator to the next stack frame.
|
||||||
@ -156,7 +155,7 @@ func (it *stackIterator) Next() bool {
|
|||||||
if it.err != nil || it.atend {
|
if it.err != nil || it.atend {
|
||||||
return false
|
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 it.err != nil {
|
||||||
if _, nofde := it.err.(*frame.NoFDEForPCError); nofde && !it.top {
|
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.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
|
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 {
|
if it.frame.Ret <= 0 {
|
||||||
it.atend = true
|
it.atend = true
|
||||||
return true
|
return true
|
||||||
@ -186,7 +178,7 @@ func (it *stackIterator) Next() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Look for "top of stack" functions.
|
// 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
|
it.atend = true
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -194,6 +186,7 @@ func (it *stackIterator) Next() bool {
|
|||||||
it.top = false
|
it.top = false
|
||||||
it.pc = it.frame.Ret
|
it.pc = it.frame.Ret
|
||||||
it.sp = uint64(it.frame.CFA)
|
it.sp = uint64(it.frame.CFA)
|
||||||
|
it.bp, _ = readUintRaw(it.dbp.currentThread, uintptr(it.bp), int64(it.dbp.arch.PtrSize()))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,24 +203,35 @@ func (it *stackIterator) Err() error {
|
|||||||
return it.err
|
return it.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *Process) frameInfo(pc, sp uint64, top bool) (Stackframe, error) {
|
func (dbp *Process) frameInfo(pc, sp, bp uint64, top bool) (Stackframe, error) {
|
||||||
f, l, fn := dbp.PCToLine(pc)
|
|
||||||
fde, err := dbp.frameEntries.FDEForPC(pc)
|
fde, err := dbp.frameEntries.FDEForPC(pc)
|
||||||
if err != nil {
|
if _, nofde := err.(*frame.NoFDEForPCError); nofde {
|
||||||
return Stackframe{}, err
|
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)
|
spoffset, retoffset := fde.ReturnAddressOffset(pc)
|
||||||
cfa := int64(sp) + spoffset
|
cfa := int64(sp) + spoffset
|
||||||
|
|
||||||
retaddr := uintptr(cfa + retoffset)
|
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 {
|
if retaddr == 0 {
|
||||||
return Stackframe{}, NullAddrError{}
|
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 {
|
if err != nil {
|
||||||
return Stackframe{}, err
|
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 {
|
if !top {
|
||||||
r.Call.File, r.Call.Line, r.Call.Fn = dbp.PCToLine(pc - 1)
|
r.Call.File, r.Call.Line, r.Call.Fn = dbp.PCToLine(pc - 1)
|
||||||
r.Call.PC = r.Current.PC
|
r.Call.PC = r.Current.PC
|
||||||
|
@ -212,6 +212,7 @@ func TestScopePrefix(t *testing.T) {
|
|||||||
|
|
||||||
goroutinesOut := strings.Split(term.MustExec("goroutines"), "\n")
|
goroutinesOut := strings.Split(term.MustExec("goroutines"), "\n")
|
||||||
agoroutines := []int{}
|
agoroutines := []int{}
|
||||||
|
nonagoroutines := []int{}
|
||||||
curgid := -1
|
curgid := -1
|
||||||
|
|
||||||
for _, line := range goroutinesOut {
|
for _, line := range goroutinesOut {
|
||||||
@ -235,14 +236,31 @@ func TestScopePrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if idx := strings.Index(line, " main.agoroutine "); idx < 0 {
|
if idx := strings.Index(line, " main.agoroutine "); idx < 0 {
|
||||||
|
nonagoroutines = append(nonagoroutines, gid)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
agoroutines = append(agoroutines, gid)
|
agoroutines = append(agoroutines, gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(agoroutines) != 10 {
|
if len(agoroutines) > 10 {
|
||||||
t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine: %q", goroutinesOut)
|
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 {
|
if curgid < 0 {
|
||||||
|
@ -13,8 +13,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/target"
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/derekparker/delve/pkg/proc"
|
||||||
|
"github.com/derekparker/delve/pkg/target"
|
||||||
"github.com/derekparker/delve/service/api"
|
"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))
|
locations := make([]api.Stackframe, 0, len(rawlocs))
|
||||||
for i := range rawlocs {
|
for i := range rawlocs {
|
||||||
frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)}
|
frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)}
|
||||||
if cfg != nil {
|
if cfg != nil && rawlocs[i].Current.Fn != nil {
|
||||||
var err error
|
var err error
|
||||||
scope := rawlocs[i].Scope(d.target.CurrentThread())
|
scope := rawlocs[i].Scope(d.target.CurrentThread())
|
||||||
locals, err := scope.LocalVariables(*cfg)
|
locals, err := scope.LocalVariables(*cfg)
|
||||||
|
@ -763,7 +763,7 @@ func TestClientServer_FullStacktrace(t *testing.T) {
|
|||||||
if arg.Name != "i" {
|
if arg.Name != "i" {
|
||||||
continue
|
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)
|
argn, err := strconv.Atoi(arg.Value)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
found[argn] = true
|
found[argn] = true
|
||||||
|
Loading…
Reference in New Issue
Block a user