mirror of
https://github.com/go-delve/delve.git
synced 2025-10-28 20:53:42 +08:00
terminal,proc: Improved goroutine printing
Three locations are returned for goroutines: its current location, its current location excluding unexported runtime functions and the location of its go instruction. The command 'goroutines' takes a new parameter to select which location to print (defaulting to current location w/o runtime)
This commit is contained in:
@ -487,9 +487,7 @@ func (dbp *Process) GoroutinesInfo() ([]*G, error) {
|
|||||||
}
|
}
|
||||||
g.thread = thread
|
g.thread = thread
|
||||||
// Prefer actual thread location information.
|
// Prefer actual thread location information.
|
||||||
g.File = loc.File
|
g.Current = *loc
|
||||||
g.Line = loc.Line
|
|
||||||
g.Func = loc.Fn
|
|
||||||
}
|
}
|
||||||
if g.Status != Gdead {
|
if g.Status != Gdead {
|
||||||
allg = append(allg, g)
|
allg = append(allg, g)
|
||||||
|
|||||||
@ -70,6 +70,59 @@ func (n NullAddrError) Error() string {
|
|||||||
return "NULL address"
|
return "NULL address"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StackIterator struct {
|
||||||
|
pc, sp uint64
|
||||||
|
top bool
|
||||||
|
frame Stackframe
|
||||||
|
dbp *Process
|
||||||
|
atend bool
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStackIterator(dbp *Process, pc, sp uint64) *StackIterator {
|
||||||
|
return &StackIterator{pc: pc, sp: sp, top: true, dbp: dbp, err: nil, atend: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if it.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.frame.Current.Fn == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.frame.Ret <= 0 {
|
||||||
|
it.atend = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Look for "top of stack" functions.
|
||||||
|
if it.frame.Current.Fn.Name == "runtime.goexit" || it.frame.Current.Fn.Name == "runtime.rt0_go" {
|
||||||
|
it.atend = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
it.top = false
|
||||||
|
it.pc = it.frame.Ret
|
||||||
|
it.sp = uint64(it.frame.CFA)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *StackIterator) Frame() Stackframe {
|
||||||
|
if it.err != nil {
|
||||||
|
panic(it.err)
|
||||||
|
}
|
||||||
|
return it.frame
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *StackIterator) Err() error {
|
||||||
|
return it.err
|
||||||
|
}
|
||||||
|
|
||||||
func (dbp *Process) frameInfo(pc, sp uint64, top bool) (Stackframe, error) {
|
func (dbp *Process) frameInfo(pc, sp uint64, top bool) (Stackframe, error) {
|
||||||
f, l, fn := dbp.PCToLine(pc)
|
f, l, fn := dbp.PCToLine(pc)
|
||||||
fde, err := dbp.frameEntries.FDEForPC(pc)
|
fde, err := dbp.frameEntries.FDEForPC(pc)
|
||||||
@ -99,26 +152,15 @@ func (dbp *Process) frameInfo(pc, sp uint64, top bool) (Stackframe, error) {
|
|||||||
|
|
||||||
func (dbp *Process) stacktrace(pc, sp uint64, depth int) ([]Stackframe, error) {
|
func (dbp *Process) stacktrace(pc, sp uint64, depth int) ([]Stackframe, error) {
|
||||||
frames := make([]Stackframe, 0, depth+1)
|
frames := make([]Stackframe, 0, depth+1)
|
||||||
|
it := newStackIterator(dbp, pc, sp)
|
||||||
for i := 0; i < depth+1; i++ {
|
for it.Next() {
|
||||||
frame, err := dbp.frameInfo(pc, sp, i == 0)
|
frames = append(frames, it.Frame())
|
||||||
if err != nil {
|
if len(frames) >= depth+1 {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if frame.Current.Fn == nil {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
frames = append(frames, frame)
|
}
|
||||||
if frame.Ret <= 0 {
|
if err := it.Err(); err != nil {
|
||||||
break
|
return nil, err
|
||||||
}
|
|
||||||
// Look for "top of stack" functions.
|
|
||||||
if frame.Current.Fn.Name == "runtime.goexit" || frame.Current.Fn.Name == "runtime.rt0_go" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
pc = frame.Ret
|
|
||||||
sp = uint64(frame.CFA)
|
|
||||||
}
|
}
|
||||||
return frames, nil
|
return frames, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package proc
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"debug/dwarf"
|
"debug/dwarf"
|
||||||
"debug/gosym"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
@ -73,16 +72,16 @@ type G struct {
|
|||||||
WaitReason string // Reason for goroutine being parked.
|
WaitReason string // Reason for goroutine being parked.
|
||||||
Status uint64
|
Status uint64
|
||||||
|
|
||||||
// Information on goroutine location.
|
// Information on goroutine location
|
||||||
File string
|
Current Location
|
||||||
Line int
|
|
||||||
Func *gosym.Func
|
|
||||||
|
|
||||||
// PC of entry to top-most deferred function.
|
// PC of entry to top-most deferred function.
|
||||||
DeferPC uint64
|
DeferPC uint64
|
||||||
|
|
||||||
// Thread that this goroutine is currently allocated to
|
// Thread that this goroutine is currently allocated to
|
||||||
thread *Thread
|
thread *Thread
|
||||||
|
|
||||||
|
dbp *Process
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scope for variable evaluation
|
// Scope for variable evaluation
|
||||||
@ -292,16 +291,48 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
|
|||||||
GoPC: gopc,
|
GoPC: gopc,
|
||||||
PC: pc,
|
PC: pc,
|
||||||
SP: sp,
|
SP: sp,
|
||||||
File: f,
|
Current: Location{PC: pc, File: f, Line: l, Fn: fn},
|
||||||
Line: l,
|
|
||||||
Func: fn,
|
|
||||||
WaitReason: waitreason,
|
WaitReason: waitreason,
|
||||||
DeferPC: deferPC,
|
DeferPC: deferPC,
|
||||||
Status: atomicStatus,
|
Status: atomicStatus,
|
||||||
|
dbp: thread.dbp,
|
||||||
}
|
}
|
||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// From $GOROOT/src/runtime/traceback.go:597
|
||||||
|
// isExportedRuntime reports whether name is an exported runtime function.
|
||||||
|
// It is only for runtime functions, so ASCII A-Z is fine.
|
||||||
|
func isExportedRuntime(name string) bool {
|
||||||
|
const n = len("runtime.")
|
||||||
|
return len(name) > n && name[:n] == "runtime." && 'A' <= name[n] && name[n] <= 'Z'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *G) UserCurrent() Location {
|
||||||
|
pc, sp := g.PC, g.SP
|
||||||
|
if g.thread != nil {
|
||||||
|
regs, err := g.thread.Registers()
|
||||||
|
if err != nil {
|
||||||
|
return g.Current
|
||||||
|
}
|
||||||
|
pc, sp = regs.PC(), regs.SP()
|
||||||
|
}
|
||||||
|
it := newStackIterator(g.dbp, pc, sp)
|
||||||
|
for it.Next() {
|
||||||
|
frame := it.Frame()
|
||||||
|
name := frame.Call.Fn.Name
|
||||||
|
if (strings.Index(name, ".") >= 0) && (!strings.HasPrefix(name, "runtime.") || isExportedRuntime(name)) {
|
||||||
|
return frame.Call
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return g.Current
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *G) Go() Location {
|
||||||
|
f, l, fn := g.dbp.goSymTable.PCToLine(g.GoPC)
|
||||||
|
return Location{PC: g.GoPC, File: f, Line: l, Fn: fn}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns information for the named variable.
|
// Returns information for the named variable.
|
||||||
func (scope *EvalScope) ExtractVariableInfo(name string) (*Variable, error) {
|
func (scope *EvalScope) ExtractVariableInfo(name string) (*Variable, error) {
|
||||||
parts := strings.Split(name, ".")
|
parts := strings.Split(name, ".")
|
||||||
|
|||||||
@ -81,11 +81,10 @@ func ConvertFunction(fn *gosym.Func) *Function {
|
|||||||
// convertGoroutine converts an internal Goroutine to an API Goroutine.
|
// convertGoroutine converts an internal Goroutine to an API Goroutine.
|
||||||
func ConvertGoroutine(g *proc.G) *Goroutine {
|
func ConvertGoroutine(g *proc.G) *Goroutine {
|
||||||
return &Goroutine{
|
return &Goroutine{
|
||||||
ID: g.Id,
|
ID: g.Id,
|
||||||
PC: g.PC,
|
Current: ConvertLocation(g.Current),
|
||||||
File: g.File,
|
UserCurrent: ConvertLocation(g.UserCurrent()),
|
||||||
Line: g.Line,
|
Go: ConvertLocation(g.Go()),
|
||||||
Function: ConvertFunction(g.Func),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -114,14 +114,12 @@ type Variable struct {
|
|||||||
type Goroutine struct {
|
type Goroutine struct {
|
||||||
// ID is a unique identifier for the goroutine.
|
// ID is a unique identifier for the goroutine.
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
// PC is the current program counter for the goroutine.
|
// Current location of the goroutine
|
||||||
PC uint64 `json:"pc"`
|
Current Location
|
||||||
// File is the file for the program counter.
|
// Current location of the goroutine, excluding calls inside runtime
|
||||||
File string `json:"file"`
|
UserCurrent Location
|
||||||
// Line is the line number for the program counter.
|
// Location of the go instruction that started this goroutine
|
||||||
Line int `json:"line"`
|
Go Location
|
||||||
// Function is function information at the program counter. May be nil.
|
|
||||||
Function *Function `json:"function,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebuggerCommand is a command which changes the debugger's execution state.
|
// DebuggerCommand is a command which changes the debugger's execution state.
|
||||||
|
|||||||
@ -5,6 +5,7 @@ package terminal
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -223,6 +224,25 @@ func (a byGoroutineID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|||||||
func (a byGoroutineID) Less(i, j int) bool { return a[i].ID < a[j].ID }
|
func (a byGoroutineID) Less(i, j int) bool { return a[i].ID < a[j].ID }
|
||||||
|
|
||||||
func goroutines(t *Term, args ...string) error {
|
func goroutines(t *Term, args ...string) error {
|
||||||
|
var fgl = fglUserCurrent
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
// nothing to do
|
||||||
|
case 1:
|
||||||
|
switch args[0] {
|
||||||
|
case "-u":
|
||||||
|
fgl = fglUserCurrent
|
||||||
|
case "-r":
|
||||||
|
fgl = fglRuntimeCurrent
|
||||||
|
case "-g":
|
||||||
|
fgl = fglGo
|
||||||
|
default:
|
||||||
|
fmt.Errorf("wrong argument: '%s'", args[0])
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("too many arguments")
|
||||||
|
}
|
||||||
state, err := t.client.GetState()
|
state, err := t.client.GetState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -238,7 +258,7 @@ func goroutines(t *Term, args ...string) error {
|
|||||||
if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
|
if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
|
||||||
prefix = "* "
|
prefix = "* "
|
||||||
}
|
}
|
||||||
fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g))
|
fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -355,7 +375,10 @@ func printscope(t *Term) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Thread %s\nGoroutine %s\n", formatThread(state.CurrentThread), formatGoroutine(state.SelectedGoroutine))
|
fmt.Printf("Thread %s\n", formatThread(state.CurrentThread))
|
||||||
|
if state.SelectedGoroutine != nil {
|
||||||
|
writeGoroutineLong(os.Stdout, state.SelectedGoroutine, "")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,15 +389,48 @@ func formatThread(th *api.Thread) string {
|
|||||||
return fmt.Sprintf("%d at %s:%d", th.ID, shortenFilePath(th.File), th.Line)
|
return fmt.Sprintf("%d at %s:%d", th.ID, shortenFilePath(th.File), th.Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatGoroutine(g *api.Goroutine) string {
|
type formatGoroutineLoc int
|
||||||
|
|
||||||
|
const (
|
||||||
|
fglRuntimeCurrent = formatGoroutineLoc(iota)
|
||||||
|
fglUserCurrent
|
||||||
|
fglGo
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatLocation(loc api.Location) string {
|
||||||
|
fname := ""
|
||||||
|
if loc.Function != nil {
|
||||||
|
fname = loc.Function.Name
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d %s (%#v)", shortenFilePath(loc.File), loc.Line, fname, loc.PC)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatGoroutine(g *api.Goroutine, fgl formatGoroutineLoc) string {
|
||||||
if g == nil {
|
if g == nil {
|
||||||
return "<nil>"
|
return "<nil>"
|
||||||
}
|
}
|
||||||
fname := ""
|
var locname string
|
||||||
if g.Function != nil {
|
var loc api.Location
|
||||||
fname = g.Function.Name
|
switch fgl {
|
||||||
|
case fglRuntimeCurrent:
|
||||||
|
locname = "Runtime"
|
||||||
|
loc = g.Current
|
||||||
|
case fglUserCurrent:
|
||||||
|
locname = "User"
|
||||||
|
loc = g.UserCurrent
|
||||||
|
case fglGo:
|
||||||
|
locname = "Go"
|
||||||
|
loc = g.Go
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%d - %s:%d %s (%#v)", g.ID, shortenFilePath(g.File), g.Line, fname, g.PC)
|
return fmt.Sprintf("%d - %s: %s", g.ID, locname, formatLocation(loc))
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeGoroutineLong(w io.Writer, g *api.Goroutine, prefix string) {
|
||||||
|
fmt.Fprintf(w, "%sGoroutine %d:\n%s\tRuntime: %s\n%s\tUser: %s\n%s\tGo: %s\n",
|
||||||
|
prefix, g.ID,
|
||||||
|
prefix, formatLocation(g.Current),
|
||||||
|
prefix, formatLocation(g.UserCurrent),
|
||||||
|
prefix, formatLocation(g.Go))
|
||||||
}
|
}
|
||||||
|
|
||||||
func restart(t *Term, args ...string) error {
|
func restart(t *Term, args ...string) error {
|
||||||
@ -803,7 +859,7 @@ func printcontext(t *Term, state *api.DebuggerState) error {
|
|||||||
bpi := state.BreakpointInfo
|
bpi := state.BreakpointInfo
|
||||||
|
|
||||||
if bpi.Goroutine != nil {
|
if bpi.Goroutine != nil {
|
||||||
fmt.Printf("\tGoroutine %s\n", formatGoroutine(bpi.Goroutine))
|
writeGoroutineLong(os.Stdout, bpi.Goroutine, "\t")
|
||||||
}
|
}
|
||||||
|
|
||||||
ss := make([]string, len(bpi.Variables))
|
ss := make([]string, len(bpi.Variables))
|
||||||
|
|||||||
Reference in New Issue
Block a user