stack command: -full flag prints local variables and arguments of all the functions on the stack trace
This commit is contained in:
parent
6527f15e4d
commit
da39258bec
19
_fixtures/retstack.go
Normal file
19
_fixtures/retstack.go
Normal file
@ -0,0 +1,19 @@
|
||||
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")
|
||||
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
|
||||
}
|
||||
fallthrough
|
||||
case 1:
|
||||
depth, err = strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Wrong argument: expected integer")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user