
This patch aims to improve how Delve tracks the current goroutine, especially in very highly parallel programs. The main spirit of this patch is to ensure that even in situations where the goroutine we care about is not executing (common for len(g) > len(m)) we still end up back on that goroutine as a result of executing the 'next' command. We accomplish this by tracking our original goroutine id, and any time a breakpoint is hit or a threads stops, we examine the stopped threads and see if any are executing the goroutine we care about. If not, we set 'next' breakpoint for them again and continue them. This is done so that one of those threads can eventually pick up the goroutine we care about and begin executing it again.
99 lines
2.4 KiB
Go
99 lines
2.4 KiB
Go
package proc
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
)
|
|
|
|
type NoReturnAddr struct {
|
|
fn string
|
|
}
|
|
|
|
func (nra NoReturnAddr) Error() string {
|
|
return fmt.Sprintf("could not find return address for %s", nra.fn)
|
|
}
|
|
|
|
// Takes an offset from RSP and returns the address of the
|
|
// instruction the current function is going to return to.
|
|
func (thread *Thread) ReturnAddress() (uint64, error) {
|
|
locations, err := thread.Stacktrace(2)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(locations) < 2 {
|
|
return 0, NoReturnAddr{locations[0].Fn.BaseName()}
|
|
}
|
|
return locations[1].PC, nil
|
|
}
|
|
|
|
// Returns the stack trace for thread.
|
|
// Note the locations in the array are return addresses not call addresses.
|
|
func (thread *Thread) Stacktrace(depth int) ([]Location, error) {
|
|
regs, err := thread.Registers()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return thread.dbp.stacktrace(regs.PC(), regs.SP(), depth)
|
|
}
|
|
|
|
// Returns the stack trace for a goroutine.
|
|
// Note the locations in the array are return addresses not call addresses.
|
|
func (dbp *Process) GoroutineStacktrace(g *G, depth int) ([]Location, error) {
|
|
if g.thread != nil {
|
|
return g.thread.Stacktrace(depth)
|
|
}
|
|
locs, err := dbp.stacktrace(g.PC, g.SP, depth)
|
|
return locs, err
|
|
}
|
|
|
|
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}
|
|
}
|
|
|
|
type NullAddrError struct{}
|
|
|
|
func (n NullAddrError) Error() string {
|
|
return "NULL address"
|
|
}
|
|
|
|
func (dbp *Process) stacktrace(pc, sp uint64, depth int) ([]Location, error) {
|
|
var (
|
|
ret = pc
|
|
btoffset int64
|
|
locations []Location
|
|
retaddr uintptr
|
|
)
|
|
f, l, fn := dbp.PCToLine(pc)
|
|
locations = append(locations, Location{PC: pc, File: f, Line: l, Fn: fn})
|
|
for i := 0; i < depth; i++ {
|
|
fde, err := dbp.frameEntries.FDEForPC(ret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
btoffset += fde.ReturnAddressOffset(ret)
|
|
retaddr = uintptr(int64(sp) + btoffset + int64(i*dbp.arch.PtrSize()))
|
|
if retaddr == 0 {
|
|
return nil, NullAddrError{}
|
|
}
|
|
data, err := dbp.CurrentThread.readMemory(retaddr, dbp.arch.PtrSize())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret = binary.LittleEndian.Uint64(data)
|
|
if ret <= 0 {
|
|
break
|
|
}
|
|
f, l, fn = dbp.goSymTable.PCToLine(ret)
|
|
if fn == nil {
|
|
break
|
|
}
|
|
locations = append(locations, Location{PC: ret, File: f, Line: l, Fn: fn})
|
|
// Look for "top of stack" functions.
|
|
if fn.Name == "runtime.rt0_go" {
|
|
break
|
|
}
|
|
}
|
|
return locations, nil
|
|
}
|