提交 da39258b 编写于 作者: A aarzilli

stack command: -full flag prints local variables and arguments of all the...

stack command: -full flag prints local variables and arguments of all the functions on the stack trace
上级 6527f15e
package main
import "runtime"
import "fmt"
func f() {
runtime.Breakpoint()
}
func g() int {
runtime.Breakpoint()
return 3
}
func main() {
f()
n := g() + 1
fmt.Println(n)
}
......@@ -776,7 +776,7 @@ func (dbp *Process) ConvertEvalScope(gid, frame int) (*EvalScope, error) {
return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
}
out.PC, out.CFA = locs[frame].PC, locs[frame].CFA
out.PC, out.CFA = locs[frame].Current.PC, locs[frame].CFA
return &out, nil
}
......@@ -534,18 +534,17 @@ type loc struct {
func (l1 *loc) match(l2 Stackframe) bool {
if l1.line >= 0 {
if l1.line != l2.Line-1 {
if l1.line != l2.Call.Line {
return false
}
}
return l1.fn == l2.Fn.Name
return l1.fn == l2.Call.Fn.Name
}
func TestStacktrace(t *testing.T) {
stacks := [][]loc{
[]loc{{3, "main.stacktraceme"}, {8, "main.func1"}, {16, "main.main"}},
[]loc{{3, "main.stacktraceme"}, {8, "main.func1"}, {12, "main.func2"}, {17, "main.main"}},
[]loc{{4, "main.stacktraceme"}, {8, "main.func1"}, {16, "main.main"}},
[]loc{{4, "main.stacktraceme"}, {8, "main.func1"}, {12, "main.func2"}, {17, "main.main"}},
}
withTestProcess("stacktraceprog", t, func(p *Process, fixture protest.Fixture) {
bp, err := setFunctionBreakpoint(p, "main.stacktraceme")
......@@ -560,7 +559,10 @@ func TestStacktrace(t *testing.T) {
t.Fatalf("Wrong stack trace size %d %d\n", len(locations), len(stacks[i])+2)
}
t.Logf("Stacktrace %d: %v\n", i, locations)
t.Logf("Stacktrace %d:\n", i)
for i := range locations {
t.Logf("\t%s:%d\n", locations[i].Call.File, locations[i].Call.Line)
}
for j := range stacks[i] {
if !stacks[i][j].match(locations[j]) {
......@@ -574,6 +576,32 @@ func TestStacktrace(t *testing.T) {
})
}
func TestStacktrace2(t *testing.T) {
withTestProcess("retstack", t, func(p *Process, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue()")
locations, err := p.CurrentThread.Stacktrace(40)
assertNoError(err, t, "Stacktrace()")
if !stackMatch([]loc{loc{-1, "main.f"}, loc{16, "main.main"}}, locations) {
for i := range locations {
t.Logf("\t%s:%d [%s]\n", locations[i].Call.File, locations[i].Call.Line, locations[i].Call.Fn.Name)
}
t.Fatalf("Stack error at main.f()\n", locations)
}
assertNoError(p.Continue(), t, "Continue()")
locations, err = p.CurrentThread.Stacktrace(40)
assertNoError(err, t, "Stacktrace()")
if !stackMatch([]loc{loc{-1, "main.g"}, loc{17, "main.main"}}, locations) {
for i := range locations {
t.Logf("\t%s:%d [%s]\n", locations[i].Call.File, locations[i].Call.Line, locations[i].Call.Fn.Name)
}
t.Fatalf("Stack error at main.g()\n", locations)
}
})
}
func stackMatch(stack []loc, locations []Stackframe) bool {
if len(stack) > len(locations) {
return false
......@@ -587,7 +615,7 @@ func stackMatch(stack []loc, locations []Stackframe) bool {
}
func TestStacktraceGoroutine(t *testing.T) {
mainStack := []loc{{11, "main.stacktraceme"}, {21, "main.main"}}
mainStack := []loc{{12, "main.stacktraceme"}, {21, "main.main"}}
agoroutineStack := []loc{{-1, "runtime.gopark"}, {-1, "runtime.goparkunlock"}, {-1, "runtime.chansend"}, {-1, "runtime.chansend1"}, {8, "main.agoroutine"}}
withTestProcess("goroutinestackprog", t, func(p *Process, fixture protest.Fixture) {
......@@ -616,10 +644,10 @@ func TestStacktraceGoroutine(t *testing.T) {
t.Logf("Non-goroutine stack: %d (%d)", i, len(locations))
for i := range locations {
name := ""
if locations[i].Fn != nil {
name = locations[i].Fn.Name
if locations[i].Call.Fn != nil {
name = locations[i].Call.Fn.Name
}
t.Logf("\t%s:%d %s\n", locations[i].File, locations[i].Line, name)
t.Logf("\t%s:%d %s\n", locations[i].Call.File, locations[i].Call.Line, name)
}
}
}
......
......@@ -14,9 +14,16 @@ func (nra NoReturnAddr) Error() string {
}
type Stackframe struct {
Location
CFA int64
Ret uint64
// Address the function above this one on the call stack will return to
Current Location
// Address of the call instruction for the function above on the call stack.
Call Location
CFA int64
Ret uint64
}
func (frame *Stackframe) Scope(thread *Thread) *EvalScope {
return &EvalScope{Thread: thread, PC: frame.Current.PC, CFA: frame.CFA}
}
// Takes an offset from RSP and returns the address of the
......@@ -27,9 +34,9 @@ func (thread *Thread) ReturnAddress() (uint64, error) {
return 0, err
}
if len(locations) < 2 {
return 0, NoReturnAddr{locations[0].Fn.BaseName()}
return 0, NoReturnAddr{locations[0].Current.Fn.BaseName()}
}
return locations[1].PC, nil
return locations[1].Current.PC, nil
}
// Returns the stack trace for thread.
......@@ -63,7 +70,7 @@ func (n NullAddrError) Error() string {
return "NULL address"
}
func (dbp *Process) frameInfo(pc, sp uint64) (Stackframe, error) {
func (dbp *Process) frameInfo(pc, sp uint64, top bool) (Stackframe, error) {
f, l, fn := dbp.PCToLine(pc)
fde, err := dbp.frameEntries.FDEForPC(pc)
if err != nil {
......@@ -80,18 +87,25 @@ func (dbp *Process) frameInfo(pc, sp uint64) (Stackframe, error) {
if err != nil {
return Stackframe{}, err
}
return Stackframe{Location: Location{PC: pc, File: f, Line: l, Fn: fn}, CFA: cfa, Ret: binary.LittleEndian.Uint64(data)}, nil
r := Stackframe{Current: Location{PC: pc, File: f, Line: l, Fn: fn}, CFA: cfa, Ret: binary.LittleEndian.Uint64(data)}
if !top {
r.Call.File, r.Call.Line, r.Call.Fn = dbp.PCToLine(pc - 1)
r.Call.PC, _, _ = dbp.goSymTable.LineToPC(r.Call.File, r.Call.Line)
} else {
r.Call = r.Current
}
return r, nil
}
func (dbp *Process) stacktrace(pc, sp uint64, depth int) ([]Stackframe, error) {
frames := make([]Stackframe, 0, depth+1)
for i := 0; i < depth+1; i++ {
frame, err := dbp.frameInfo(pc, sp)
frame, err := dbp.frameInfo(pc, sp, i == 0)
if err != nil {
return nil, err
}
if frame.Fn == nil {
if frame.Current.Fn == nil {
break
}
frames = append(frames, frame)
......@@ -99,7 +113,7 @@ func (dbp *Process) stacktrace(pc, sp uint64, depth int) ([]Stackframe, error) {
break
}
// Look for "top of stack" functions.
if frame.Fn.Name == "runtime.goexit" || frame.Fn.Name == "runtime.rt0_go" {
if frame.Current.Fn.Name == "runtime.goexit" || frame.Current.Fn.Name == "runtime.rt0_go" {
break
}
......
......@@ -327,5 +327,5 @@ func (thread *Thread) Scope() (*EvalScope, error) {
if err != nil {
return nil, err
}
return &EvalScope{Thread: thread, PC: locations[0].PC, CFA: locations[0].CFA}, nil
return locations[0].Scope(thread), nil
}
......@@ -104,7 +104,7 @@ func (g *G) chanRecvReturnAddr(dbp *Process) (uint64, error) {
return 0, err
}
topLoc := locs[len(locs)-1]
return topLoc.PC, nil
return topLoc.Current.PC, nil
}
// NoGError returned when a G could not be found
......
......@@ -252,7 +252,7 @@ func TestFrameEvaluation(t *testing.T) {
frames, err := p.GoroutineStacktrace(g, 10)
assertNoError(err, t, "GoroutineStacktrace()")
for i := range frames {
if frames[i].Fn != nil && frames[i].Fn.Name == "main.agoroutine" {
if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" {
frame = i
break
}
......
......@@ -65,6 +65,26 @@ type Location struct {
Function *Function `json:"function,omitempty"`
}
type Stackframe struct {
Location
Locals []Variable
Arguments []Variable
}
func (frame *Stackframe) Var(name string) *Variable {
for i := range frame.Locals {
if frame.Locals[i].Name == name {
return &frame.Locals[i]
}
}
for i := range frame.Arguments {
if frame.Arguments[i].Name == name {
return &frame.Arguments[i]
}
}
return nil
}
// Function represents thread-scoped function information.
type Function struct {
// Name is the function name.
......@@ -114,10 +134,10 @@ type DebuggerCommand struct {
// Informations about the current breakpoint
type BreakpointInfo struct {
Stacktrace []Location `json:"stacktrace,omitempty"`
Goroutine *Goroutine `json:"goroutine,omitempty"`
Variables []Variable `json:"variables,omitempty"`
Arguments []Variable `json:"arguments,omitempty"`
Stacktrace []Stackframe `json:"stacktrace,omitempty"`
Goroutine *Goroutine `json:"goroutine,omitempty"`
Variables []Variable `json:"variables,omitempty"`
Arguments []Variable `json:"arguments,omitempty"`
}
type EvalScope struct {
......
......@@ -68,7 +68,7 @@ type Client interface {
ListGoroutines() ([]*api.Goroutine, error)
// Returns stacktrace
Stacktrace(goroutineId, depth int) ([]api.Location, error)
Stacktrace(goroutineId, depth int, full bool) ([]api.Stackframe, error)
// Returns whether we attached to a running process or not
AttachedToExistingProcess() bool
......
......@@ -288,7 +288,10 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
if err != nil {
return err
}
bpi.Stacktrace = convertStacktrace(rawlocs)
bpi.Stacktrace, err = d.convertStacktrace(rawlocs, false)
if err != nil {
return err
}
}
s, err := d.process.CurrentThread.Scope()
......@@ -386,6 +389,14 @@ func (d *Debugger) Registers(threadID int) (string, error) {
return regs.String(), err
}
func convertVars(pv []*proc.Variable) []api.Variable {
vars := make([]api.Variable, 0, len(pv))
for _, v := range pv {
vars = append(vars, api.ConvertVar(v))
}
return vars
}
func (d *Debugger) LocalVariables(scope api.EvalScope) ([]api.Variable, error) {
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
if err != nil {
......@@ -395,11 +406,7 @@ func (d *Debugger) LocalVariables(scope api.EvalScope) ([]api.Variable, error) {
if err != nil {
return nil, err
}
vars := make([]api.Variable, 0, len(pv))
for _, v := range pv {
vars = append(vars, api.ConvertVar(v))
}
return vars, err
return convertVars(pv), err
}
func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error) {
......@@ -447,7 +454,7 @@ func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
return goroutines, err
}
func (d *Debugger) Stacktrace(goroutineId, depth int) ([]api.Location, error) {
func (d *Debugger) Stacktrace(goroutineId, depth int, full bool) ([]api.Stackframe, error) {
var rawlocs []proc.Stackframe
var err error
......@@ -483,17 +490,30 @@ func (d *Debugger) Stacktrace(goroutineId, depth int) ([]api.Location, error) {
}
}
return convertStacktrace(rawlocs), nil
return d.convertStacktrace(rawlocs, full)
}
func convertStacktrace(rawlocs []proc.Stackframe) []api.Location {
locations := make([]api.Location, 0, len(rawlocs))
func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, full bool) ([]api.Stackframe, error) {
locations := make([]api.Stackframe, 0, len(rawlocs))
for i := range rawlocs {
rawlocs[i].Line--
locations = append(locations, api.ConvertLocation(rawlocs[i].Location))
frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)}
if full {
scope := rawlocs[i].Scope(d.process.CurrentThread)
lv, err := scope.LocalVariables()
if err != nil {
return nil, err
}
av, err := scope.FunctionArguments()
if err != nil {
return nil, err
}
frame.Locals = convertVars(lv)
frame.Arguments = convertVars(av)
}
locations = append(locations, frame)
}
return locations
return locations, nil
}
func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Location, error) {
......
......@@ -203,9 +203,9 @@ func (c *RPCClient) ListGoroutines() ([]*api.Goroutine, error) {
return goroutines, err
}
func (c *RPCClient) Stacktrace(goroutineId, depth int) ([]api.Location, error) {
var locations []api.Location
err := c.call("StacktraceGoroutine", &StacktraceGoroutineArgs{Id: goroutineId, Depth: depth}, &locations)
func (c *RPCClient) Stacktrace(goroutineId, depth int, full bool) ([]api.Stackframe, error) {
var locations []api.Stackframe
err := c.call("StacktraceGoroutine", &StacktraceGoroutineArgs{Id: goroutineId, Depth: depth, Full: full}, &locations)
return locations, err
}
......
......@@ -113,10 +113,11 @@ func (s *RPCServer) GetBreakpoint(id int, breakpoint *api.Breakpoint) error {
type StacktraceGoroutineArgs struct {
Id int
Depth int
Full bool
}
func (s *RPCServer) StacktraceGoroutine(args *StacktraceGoroutineArgs, locations *[]api.Location) error {
locs, err := s.debugger.Stacktrace(args.Id, args.Depth)
func (s *RPCServer) StacktraceGoroutine(args *StacktraceGoroutineArgs, locations *[]api.Stackframe) error {
locs, err := s.debugger.Stacktrace(args.Id, args.Depth, args.Full)
if err != nil {
return err
}
......
......@@ -20,6 +20,14 @@ func init() {
runtime.GOMAXPROCS(2)
}
func assertNoError(err error, t *testing.T, s string) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
fname := filepath.Base(file)
t.Fatalf("failed assertion at %s:%d: %s - %s\n", fname, line, s, err)
}
}
func TestMain(m *testing.M) {
os.Exit(protest.RunTestsWithFixtures(m))
}
......@@ -607,9 +615,7 @@ func TestClientServer_EvalVariable(t *testing.T) {
withTestClient("testvariables", t, func(c service.Client) {
fp := testProgPath(t, "testvariables")
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 59})
if err != nil {
t.Fatalf("CreateBreakpoint(): %v", err)
}
assertNoError(err, t, "CreateBreakpoint()")
state := <-c.Continue()
......@@ -618,9 +624,7 @@ func TestClientServer_EvalVariable(t *testing.T) {
}
var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "a1")
if err != nil {
t.Fatalf("EvalVariable(): %v", err)
}
assertNoError(err, t, "EvalVariable")
t.Logf("var1: <%s>", var1.Value)
......@@ -629,3 +633,74 @@ func TestClientServer_EvalVariable(t *testing.T) {
}
})
}
func TestClientServer_FullStacktrace(t *testing.T) {
withTestClient("goroutinestackprog", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1})
assertNoError(err, t, "CreateBreakpoint()")
state := <-c.Continue()
if state.Err != nil {
t.Fatalf("Continue(): %v\n", state.Err)
}
gs, err := c.ListGoroutines()
assertNoError(err, t, "GoroutinesInfo()")
found := make([]bool, 10)
for _, g := range gs {
frames, err := c.Stacktrace(g.ID, 10, true)
assertNoError(err, t, fmt.Sprintf("Stacktrace(%d)", g.ID))
for i, frame := range frames {
if frame.Function == nil {
continue
}
if frame.Function.Name != "main.agoroutine" {
continue
}
t.Logf("frame %d: %v", i, frame)
for _, arg := range frame.Arguments {
if arg.Name != "i" {
continue
}
n, err := strconv.Atoi(arg.Value)
assertNoError(err, t, fmt.Sprintf("Wrong value for i in goroutine %d (%s)", g.ID, arg.Value))
found[n] = true
}
}
}
for i := range found {
if !found[i] {
t.Fatalf("Goroutine %d not found", i)
}
}
state = <-c.Continue()
if state.Err != nil {
t.Fatalf("Continue(): %v\n", state.Err)
}
frames, err := c.Stacktrace(-1, 10, true)
assertNoError(err, t, "Stacktrace")
cur := 3
for i, frame := range frames {
if i == 0 {
continue
}
t.Logf("frame %d: %v", i, frame)
v := frame.Var("n")
if v == nil {
t.Fatalf("Could not find value of variable n in frame %d", i)
}
n, err := strconv.Atoi(v.Value)
assertNoError(err, t, fmt.Sprintf("Wrong value for n: %s", v.Value))
if n != cur {
t.Fatalf("Expected value %d got %d", cur, n)
}
cur--
if cur < 0 {
break
}
}
})
}
......@@ -6,6 +6,7 @@ import (
"bufio"
"fmt"
"io"
"math"
"os"
"regexp"
"sort"
......@@ -73,8 +74,8 @@ func DebugCommands(client service.Client) *Commands {
{aliases: []string{"vars"}, cmdFn: filterSortAndOutput(vars), helpMsg: "Print package variables, optionally filtered by a regexp."},
{aliases: []string{"regs"}, cmdFn: regs, helpMsg: "Print contents of CPU registers."},
{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."},
{aliases: []string{"stack", "bt"}, cmdFn: stackCommand, helpMsg: "stack [<depth> [<goroutine id>]]. Prints stack."},
{aliases: []string{"list", "ls"}, cmdFn: listCommand, helpMsg: "list <linespec>. Show source around current point or provided linespec."},
{aliases: []string{"stack", "bt"}, cmdFn: stackCommand, helpMsg: "stack [-<depth>] [-full] [<goroutine id>]. Prints stack."},
{aliases: []string{"frame"}, cmdFn: frame, helpMsg: "Sets current stack frame (0 is the top of the stack)"},
}
......@@ -313,7 +314,7 @@ func scopePrefix(client service.Client, cmdname string, pargs ...string) error {
i++
case "list", "ls":
frame, gid := scope.Frame, scope.GoroutineID
locs, err := client.Stacktrace(gid, frame)
locs, err := client.Stacktrace(gid, frame, false)
if err != nil {
return err
}
......@@ -627,27 +628,27 @@ func stackCommand(client service.Client, args ...string) error {
goroutineid := -1
depth := 10
full := false
switch len(args) {
case 0:
// nothing to do
case 2:
goroutineid, err = strconv.Atoi(args[1])
if err != nil {
return fmt.Errorf("Wrong argument: expected integer")
}
fallthrough
case 1:
depth, err = strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("Wrong argument: expected integer")
for i := range args {
if args[i] == "-full" {
full = true
} else if args[i][0] == '-' {
n, err := strconv.Atoi(args[i][1:])
if err != nil {
return fmt.Errorf("unknown option: %s", args[i])
}
depth = n
} else {
n, err := strconv.Atoi(args[i])
if err != nil {
return fmt.Errorf("goroutine id must be a number")
}
goroutineid = n
}
default:
return fmt.Errorf("Wrong number of arguments to stack")
}
stack, err := client.Stacktrace(goroutineid, depth)
stack, err := client.Stacktrace(goroutineid, depth, full)
if err != nil {
return err
}
......@@ -676,13 +677,37 @@ func listCommand(client service.Client, args ...string) error {
return nil
}
func printStack(stack []api.Location, ind string) {
func digits(n int) int {
return int(math.Floor(math.Log10(float64(n)))) + 1
}
func spaces(n int) string {
spaces := make([]byte, n)
for i := range spaces {
spaces[i] = ' '
}
return string(spaces)
}
func printStack(stack []api.Stackframe, ind string) {
d := digits(len(stack) - 1)
fmtstr := "%s%" + strconv.Itoa(d) + "d 0x%016x in %s\n"
s := spaces(d + 2 + len(ind))
for i := range stack {
name := "(nil)"
if stack[i].Function != nil {
name = stack[i].Function.Name
}
fmt.Printf("%s%d. %s %s:%d (%#v)\n", ind, i, name, shortenFilePath(stack[i].File), stack[i].Line, stack[i].PC)
fmt.Printf(fmtstr, ind, i, stack[i].PC, name)
fmt.Printf("%sat %s:%d\n", s, shortenFilePath(stack[i].File), stack[i].Line)
for j := range stack[i].Arguments {
fmt.Printf("%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].Value)
}
for j := range stack[i].Locals {
fmt.Printf("%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].Value)
}
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册