From 7d69c16512dcaaf78d2f1a1d65fcf2f25bafe9f2 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Sat, 8 Nov 2014 08:02:31 -0600 Subject: [PATCH] Add command to print all goroutines info --- command/command.go | 23 ++-- dwarf/op/op.go | 11 ++ proctl/variables_linux_amd64.go | 186 +++++++++++++++++++++++++++----- 3 files changed, 187 insertions(+), 33 deletions(-) diff --git a/command/command.go b/command/command.go index 0307e93a..8cadc97d 100644 --- a/command/command.go +++ b/command/command.go @@ -24,15 +24,16 @@ type Commands struct { // Returns a Commands struct with default commands defined. func DebugCommands() *Commands { cmds := map[string]cmdfunc{ - "help": help, - "continue": cont, - "next": next, - "break": breakpoint, - "step": step, - "clear": clear, - "print": printVar, - "threads": threads, - "": nullCommand, + "help": help, + "continue": cont, + "next": next, + "break": breakpoint, + "step": step, + "clear": clear, + "print": printVar, + "threads": threads, + "goroutines": goroutines, + "": nullCommand, } return &Commands{cmds} @@ -89,6 +90,10 @@ func threads(p *proctl.DebuggedProcess, ars ...string) error { return p.PrintThreadInfo() } +func goroutines(p *proctl.DebuggedProcess, ars ...string) error { + return p.PrintGoroutinesInfo() +} + func cont(p *proctl.DebuggedProcess, ars ...string) error { err := p.Continue() if err != nil { diff --git a/dwarf/op/op.go b/dwarf/op/op.go index d2d92b32..0aa7a42e 100644 --- a/dwarf/op/op.go +++ b/dwarf/op/op.go @@ -2,12 +2,14 @@ package op import ( "bytes" + "encoding/binary" "fmt" "github.com/derekparker/delve/dwarf/util" ) const ( + DW_OP_addr = 0x3 DW_OP_call_frame_cfa = 0x9c DW_OP_plus = 0x22 DW_OP_consts = 0x11 @@ -19,6 +21,7 @@ var oplut = map[byte]stackfn{ DW_OP_call_frame_cfa: callframecfa, DW_OP_plus: plus, DW_OP_consts: consts, + DW_OP_addr: addr, } func ExecuteStackProgram(cfa int64, instructions []byte) (int64, error) { @@ -44,7 +47,15 @@ func callframecfa(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) return append(stack, int64(cfa)), nil } +func addr(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) { + return append(stack, int64(binary.LittleEndian.Uint64(buf.Next(8)))), nil +} + func plus(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) { + if len(stack) == 1 { + return stack, nil + } + var ( slen = len(stack) digits = stack[slen-2 : slen] diff --git a/proctl/variables_linux_amd64.go b/proctl/variables_linux_amd64.go index f4a1d14b..1dbd5a4f 100644 --- a/proctl/variables_linux_amd64.go +++ b/proctl/variables_linux_amd64.go @@ -19,6 +19,137 @@ type Variable struct { Type string } +func (dbp *DebuggedProcess) PrintGoroutinesInfo() error { + data, err := dbp.Executable.DWARF() + if err != nil { + return err + } + + allglen, err := allglenval(dbp, data) + if err != nil { + return err + } + goidoffset, err := parsegoidoffset(dbp, data) + if err != nil { + return err + } + schedoffset, err := parseschedoffset(dbp, data) + if err != nil { + return err + } + allgentryaddr, err := allgentryptr(dbp, data) + if err != nil { + return err + } + fmt.Printf("[%d goroutines]\n", allglen) + faddr, err := dbp.CurrentThread.readMemory(uintptr(allgentryaddr), 8) + allg := binary.LittleEndian.Uint64(faddr) + fmt.Println("sched", schedoffset) + + for i := uint64(0); i < allglen; i++ { + err = printGoroutineInfo(dbp, allg+(i*8), goidoffset, schedoffset) + if err != nil { + return err + } + } + + return nil +} + +func printGoroutineInfo(dbp *DebuggedProcess, addr uint64, goidoffset, schedoffset uint64) error { + gaddrbytes, err := dbp.CurrentThread.readMemory(uintptr(addr), 8) + if err != nil { + return fmt.Errorf("error derefing *G %s", err) + } + gaddr := binary.LittleEndian.Uint64(gaddrbytes) + + goidbytes, err := dbp.CurrentThread.readMemory(uintptr(gaddr+goidoffset), 8) + if err != nil { + return fmt.Errorf("error reading goid %s", err) + } + schedbytes, err := dbp.CurrentThread.readMemory(uintptr(gaddr+schedoffset+8), 8) + if err != nil { + return fmt.Errorf("error reading goid %s", err) + } + gopc := binary.LittleEndian.Uint64(schedbytes) + f, l, _ := dbp.GoSymTable.PCToLine(gopc) + fmt.Printf("Goroutine %d - %s:%d\n", binary.LittleEndian.Uint64(goidbytes), f, l) + return nil +} + +func allglenval(dbp *DebuggedProcess, data *dwarf.Data) (uint64, error) { + entry, err := findDwarfEntry("runtime.allglen", data) + if err != nil { + return 0, err + } + + instructions, ok := entry.Val(dwarf.AttrLocation).([]byte) + if !ok { + return 0, fmt.Errorf("type assertion failed") + } + addr, err := op.ExecuteStackProgram(0, instructions) + if err != nil { + return 0, err + } + val, err := dbp.CurrentThread.readMemory(uintptr(addr), 8) + if err != nil { + return 0, err + } + return binary.LittleEndian.Uint64(val), nil +} + +func allgentryptr(dbp *DebuggedProcess, data *dwarf.Data) (uint64, error) { + entry, err := findDwarfEntry("runtime.allg", data) + if err != nil { + return 0, err + } + + instructions, ok := entry.Val(dwarf.AttrLocation).([]byte) + if !ok { + return 0, fmt.Errorf("type assertion failed") + } + addr, err := op.ExecuteStackProgram(0, instructions) + if err != nil { + return 0, err + } + + return uint64(addr), nil +} + +func parsegoidoffset(dbp *DebuggedProcess, data *dwarf.Data) (uint64, error) { + entry, err := findDwarfEntry("goid", data) + if err != nil { + return 0, err + } + instructions, ok := entry.Val(dwarf.AttrDataMemberLoc).([]byte) + if !ok { + return 0, fmt.Errorf("type assertion failed") + } + offset, err := op.ExecuteStackProgram(0, instructions) + if err != nil { + return 0, err + } + + return uint64(offset), nil +} + +func parseschedoffset(dbp *DebuggedProcess, data *dwarf.Data) (uint64, error) { + entry, err := findDwarfEntry("sched", data) + if err != nil { + return 0, err + } + instructions, ok := entry.Val(dwarf.AttrDataMemberLoc).([]byte) + if !ok { + return 0, fmt.Errorf("type assertion failed") + } + offset, err := op.ExecuteStackProgram(0, instructions) + if err != nil { + return 0, err + } + + return uint64(offset), nil +} + // Returns the value of the named symbol. func (thread *ThreadContext) EvalSymbol(name string) (*Variable, error) { data, err := thread.Process.Executable.DWARF() @@ -26,6 +157,35 @@ func (thread *ThreadContext) EvalSymbol(name string) (*Variable, error) { return nil, err } + entry, err := findDwarfEntry(name, data) + if err != nil { + return nil, err + } + + offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset) + if !ok { + return nil, fmt.Errorf("type assertion failed") + } + + t, err := data.Type(offset) + if err != nil { + return nil, err + } + + instructions, ok := entry.Val(dwarf.AttrLocation).([]byte) + if !ok { + return nil, fmt.Errorf("type assertion failed") + } + + val, err := thread.extractValue(instructions, 0, t) + if err != nil { + return nil, err + } + + return &Variable{Name: name, Type: t.String(), Value: val}, nil +} + +func findDwarfEntry(name string, data *dwarf.Data) (*dwarf.Entry, error) { reader := data.Reader() for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { @@ -33,7 +193,7 @@ func (thread *ThreadContext) EvalSymbol(name string) (*Variable, error) { return nil, err } - if entry.Tag != dwarf.TagVariable && entry.Tag != dwarf.TagFormalParameter { + if entry.Tag != dwarf.TagVariable && entry.Tag != dwarf.TagFormalParameter && entry.Tag != dwarf.TagMember { continue } @@ -41,30 +201,8 @@ func (thread *ThreadContext) EvalSymbol(name string) (*Variable, error) { if !ok || n != name { continue } - - offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset) - if !ok { - continue - } - - t, err := data.Type(offset) - if err != nil { - return nil, err - } - - instructions, ok := entry.Val(dwarf.AttrLocation).([]byte) - if !ok { - continue - } - - val, err := thread.extractValue(instructions, 0, t) - if err != nil { - return nil, err - } - - return &Variable{Name: n, Type: t.String(), Value: val}, nil + return entry, nil } - return nil, fmt.Errorf("could not find symbol value for %s", name) }