x/debug: read stack frames of goroutines. Add some more fields and a String method to program.Frame, so they can be printed similarly to how the runtime prints them. Move stack-walking code to a separate function. Change-Id: I3bc8a24296890733d13823a58b7eabd42050ca02 Reviewed-on: https://go-review.googlesource.com/19408 Reviewed-by: Dave Day <djd@golang.org>
diff --git a/ogle/demo/ogler/ogler_test.go b/ogle/demo/ogler/ogler_test.go index 1e44705..729fa75 100644 --- a/ogle/demo/ogler/ogler_test.go +++ b/ogle/demo/ogler/ogler_test.go
@@ -464,6 +464,9 @@ } for _, g := range gs { fmt.Println(g) + for _, f := range g.StackFrames { + fmt.Println(f) + } } frames, err := prog.Frames(100)
diff --git a/ogle/program/program.go b/ogle/program/program.go index 6ac49c9..72c0389 100644 --- a/ogle/program/program.go +++ b/ogle/program/program.go
@@ -8,6 +8,7 @@ import ( "fmt" "io" + "strings" ) // Program is the interface to a (possibly remote) program being debugged. @@ -117,6 +118,7 @@ StatusString string // A human-readable string explaining the status in more detail. Function string // Name of the goroutine function. Caller string // Name of the function that created this goroutine. + StackFrames []Frame } type GoroutineStatus byte @@ -284,12 +286,24 @@ Line uint64 // Function is the name of this frame's function. Function string + // FunctionStart is the starting PC of the function. + FunctionStart uint64 // Params contains the function's parameters. Params []Param // Vars contains the function's local variables. Vars []LocalVar } +func (f Frame) String() string { + params := make([]string, len(f.Params)) + for i, p := range f.Params { + params[i] = p.Name // TODO: more information + } + p := strings.Join(params, ", ") + off := f.PC - f.FunctionStart + return fmt.Sprintf("%s(%s)\n\t%s:%d +0x%x", f.Function, p, f.File, f.Line, off) +} + // Param is a parameter of a function. type Param struct { Name string
diff --git a/ogle/program/server/server.go b/ogle/program/server/server.go index ffa72c3..2d86704 100644 --- a/ogle/program/server/server.go +++ b/ogle/program/server/server.go
@@ -16,6 +16,7 @@ "regexp" "strconv" "strings" + "sync" "syscall" "golang.org/x/debug/dwarf" @@ -55,6 +56,10 @@ breakpoints map[uint64]breakpoint files []*file // Index == file descriptor. printer *Printer + + // goroutineStack reads the stack of a (non-running) goroutine. + goroutineStack func(uint64) ([]program.Frame, error) + goroutineStackOnce sync.Once } // peek implements the Peeker interface required by the printer. @@ -614,40 +619,47 @@ if err != nil { return err } - pc, sp := regs.Rip, regs.Rsp + resp.Frames, err = s.walkStack(regs.Rip, regs.Rsp, req.Count) + return err +} + +// walkStack returns up to the requested number of stack frames. +func (s *Server) walkStack(pc, sp uint64, count int) ([]program.Frame, error) { + var frames []program.Frame var buf [8]byte b := new(bytes.Buffer) r := s.dwarfData.Reader() // TODO: handle walking over a split stack. - for i := 0; i < req.Count; i++ { + for i := 0; i < count; i++ { b.Reset() file, line, err := s.dwarfData.PCToLine(pc) if err != nil { - return err + return frames, err } fpOffset, err := s.dwarfData.PCToSPOffset(pc) if err != nil { - return err + return frames, err } fp := sp + uint64(fpOffset) entry, funcEntry, err := s.entryForPC(pc) if err != nil { - return err + return frames, err } frame := program.Frame{ - PC: pc, - SP: sp, - File: file, - Line: line, + PC: pc, + SP: sp, + File: file, + Line: line, + FunctionStart: funcEntry, } frame.Function, _ = entry.Val(dwarf.AttrName).(string) r.Seek(entry.Offset) for { entry, err := r.Next() if err != nil { - return err + return frames, err } if entry.Tag == 0 { break @@ -664,7 +676,7 @@ } } } - resp.Frames = append(resp.Frames, frame) + frames = append(frames, frame) // Walk to the caller's PC and SP. if s.topOfStack(funcEntry) { @@ -672,11 +684,11 @@ } err = s.ptracePeek(s.stoppedPid, uintptr(fp-uint64(s.arch.PointerSize)), buf[:s.arch.PointerSize]) if err != nil { - return fmt.Errorf("ptracePeek: %v", err) + return frames, fmt.Errorf("ptracePeek: %v", err) } pc, sp = s.arch.Uintptr(buf[:s.arch.PointerSize]), fp } - return nil + return frames, nil } // parseParameterOrLocal parses the entry for a function parameter or local @@ -951,6 +963,9 @@ } } + // Initialize s.goroutineStack. + s.goroutineStackOnce.Do(func() { s.goroutineStackInit(gType) }) + for i := uint64(0); i < allglen; i++ { // allg is an array of pointers to g structs. Read allg[i]. g, err := s.peekPtr(allg + i*uint64(s.arch.PointerSize)) @@ -1019,9 +1034,80 @@ if gopc, err := s.peekUintStructField(gType, g, "gopc"); err == nil { gr.Caller = functionName(gopc) } + if gr.Status != program.Running { + // TODO: running goroutines too. + gr.StackFrames, _ = s.goroutineStack(g) + } resp.Goroutines = append(resp.Goroutines, &gr) } return nil } + +// TODO: let users specify how many frames they want. 10 will be enough to +// determine the reason a goroutine is blocked. +const goroutineStackFrameCount = 10 + +// goroutineStackInit initializes s.goroutineStack. +func (s *Server) goroutineStackInit(gType *dwarf.StructType) { + // If we fail to read the DWARF data needed for s.goroutineStack, calling it + // will always return the error that occurred during initialization. + var err error // err is captured by the func below. + s.goroutineStack = func(gAddr uint64) ([]program.Frame, error) { + return nil, err + } + + // Get g field "sched", which contains fields pc and sp. + schedField, err := getField(gType, "sched") + if err != nil { + return + } + schedOffset := uint64(schedField.ByteOffset) + schedType, ok := followTypedefs(schedField.Type).(*dwarf.StructType) + if !ok { + err = errors.New(`g field "sched" has the wrong type`) + return + } + + // Get the size of the pc and sp fields and their offsets inside the g struct, + // so we can quickly peek those values for each goroutine later. + var ( + schedPCOffset, schedSPOffset uint64 + schedPCByteSize, schedSPByteSize int64 + ) + for _, x := range []struct { + field string + offset *uint64 + bytesize *int64 + }{ + {"pc", &schedPCOffset, &schedPCByteSize}, + {"sp", &schedSPOffset, &schedSPByteSize}, + } { + var f *dwarf.StructField + f, err = getField(schedType, x.field) + if err != nil { + return + } + *x.offset = schedOffset + uint64(f.ByteOffset) + switch t := followTypedefs(f.Type).(type) { + case *dwarf.UintType, *dwarf.IntType: + *x.bytesize = t.Common().ByteSize + default: + err = fmt.Errorf("gobuf field %q has the wrong type", x.field) + return + } + } + + s.goroutineStack = func(gAddr uint64) ([]program.Frame, error) { + schedPC, err := s.peekUint(gAddr+schedPCOffset, schedPCByteSize) + if err != nil { + return nil, err + } + schedSP, err := s.peekUint(gAddr+schedSPOffset, schedSPByteSize) + if err != nil { + return nil, err + } + return s.walkStack(schedPC, schedSP, goroutineStackFrameCount) + } +}