command.go 24.8 KB
Newer Older
1
// Package terminal implements functions for responding to user
D
Dan Mace 已提交
2 3 4 5 6 7
// input and dispatching to appropriate backend commands.
package terminal

import (
	"bufio"
	"fmt"
8 9
	"go/parser"
	"go/scanner"
10
	"io"
11
	"math"
D
Dan Mace 已提交
12 13 14 15 16
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
D
Derek Parker 已提交
17
	"text/tabwriter"
D
Dan Mace 已提交
18 19 20

	"github.com/derekparker/delve/service"
	"github.com/derekparker/delve/service/api"
21
	"github.com/derekparker/delve/service/debugger"
D
Dan Mace 已提交
22 23
)

24 25
type cmdfunc func(t *Term, args string) error
type scopedCmdfunc func(t *Term, scope api.EvalScope, args string) error
26

27 28
type filteringFunc func(t *Term, filter string) ([]string, error)
type scopedFilteringFunc func(t *Term, scope api.EvalScope, filter string) ([]string, error)
D
Dan Mace 已提交
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

type command struct {
	aliases []string
	helpMsg string
	cmdFn   cmdfunc
}

// Returns true if the command string matches one of the aliases for this command
func (c command) match(cmdstr string) bool {
	for _, v := range c.aliases {
		if v == cmdstr {
			return true
		}
	}
	return false
}

type Commands struct {
D
Derek Parker 已提交
47 48 49
	cmds    []command
	lastCmd cmdfunc
	client  service.Client
D
Dan Mace 已提交
50 51 52 53 54 55 56 57
}

// Returns a Commands struct with default commands defined.
func DebugCommands(client service.Client) *Commands {
	c := &Commands{client: client}

	c.cmds = []command{
		{aliases: []string{"help"}, cmdFn: c.help, helpMsg: "Prints the help message."},
58
		{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "break <linespec> [-stack <n>|-goroutine|<variable name>]*"},
D
Derek Parker 已提交
59
		{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: "Set tracepoint, takes the same arguments as break."},
D
Derek Parker 已提交
60
		{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: "Restart process."},
D
Dan Mace 已提交
61 62 63 64
		{aliases: []string{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
		{aliases: []string{"step", "si"}, cmdFn: step, helpMsg: "Single step through program."},
		{aliases: []string{"next", "n"}, cmdFn: next, helpMsg: "Step over to next source line."},
		{aliases: []string{"threads"}, cmdFn: threads, helpMsg: "Print out info for every traced thread."},
D
Derek Parker 已提交
65
		{aliases: []string{"thread", "tr"}, cmdFn: thread, helpMsg: "Switch to the specified thread."},
D
Dan Mace 已提交
66
		{aliases: []string{"clear"}, cmdFn: clear, helpMsg: "Deletes breakpoint."},
67
		{aliases: []string{"clearall"}, cmdFn: clearAll, helpMsg: "clearall [<linespec>]. Deletes all breakpoints. If <linespec> is provided, only matching breakpoints will be deleted."},
68
		{aliases: []string{"goroutines"}, cmdFn: goroutines, helpMsg: "goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)] Print out info for every goroutine."},
69
		{aliases: []string{"goroutine"}, cmdFn: goroutine, helpMsg: "Sets current goroutine."},
D
Dan Mace 已提交
70
		{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
71
		{aliases: []string{"print", "p"}, cmdFn: g0f0(printVar), helpMsg: "Evaluate a variable."},
72
		{aliases: []string{"set"}, cmdFn: g0f0(setVar), helpMsg: "Changes the value of a variable."},
D
Derek Parker 已提交
73 74
		{aliases: []string{"sources"}, cmdFn: filterSortAndOutput(sources), helpMsg: "Print list of source files, optionally filtered by a regexp."},
		{aliases: []string{"funcs"}, cmdFn: filterSortAndOutput(funcs), helpMsg: "Print list of functions, optionally filtered by a regexp."},
75 76
		{aliases: []string{"args"}, cmdFn: filterSortAndOutput(g0f0filter(args)), helpMsg: "Print function arguments, optionally filtered by a regexp."},
		{aliases: []string{"locals"}, cmdFn: filterSortAndOutput(g0f0filter(locals)), helpMsg: "Print function locals, optionally filtered by a regexp."},
D
Derek Parker 已提交
77 78
		{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."},
D
Derek Parker 已提交
79
		{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."},
80
		{aliases: []string{"list", "ls"}, cmdFn: listCommand, helpMsg: "list <linespec>.  Show source around current point or provided linespec."},
81
		{aliases: []string{"stack", "bt"}, cmdFn: stackCommand, helpMsg: "stack [<depth>] [-full]. Prints stack."},
82
		{aliases: []string{"frame"}, cmdFn: frame, helpMsg: "Sets current stack frame (0 is the top of the stack)"},
83
		{aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: "Executes a file containing a list of delve commands"},
D
Dan Mace 已提交
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
	}

	return c
}

// Register custom commands. Expects cf to be a func of type cmdfunc,
// returning only an error.
func (c *Commands) Register(cmdstr string, cf cmdfunc, helpMsg string) {
	for _, v := range c.cmds {
		if v.match(cmdstr) {
			v.cmdFn = cf
			return
		}
	}

	c.cmds = append(c.cmds, command{aliases: []string{cmdstr}, cmdFn: cf, helpMsg: helpMsg})
}

// Find will look up the command function for the given command input.
D
Derek Parker 已提交
103
// If it cannot find the command it will default to noCmdAvailable().
D
Dan Mace 已提交
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
// If the command is an empty string it will replay the last command.
func (c *Commands) Find(cmdstr string) cmdfunc {
	// If <enter> use last command, if there was one.
	if cmdstr == "" {
		if c.lastCmd != nil {
			return c.lastCmd
		}
		return nullCommand
	}

	for _, v := range c.cmds {
		if v.match(cmdstr) {
			c.lastCmd = v.cmdFn
			return v.cmdFn
		}
	}

	return noCmdAvailable
}

124 125 126 127 128 129 130 131 132
// Merge takes aliases defined in the config struct and merges them with the default aliases.
func (c *Commands) Merge(allAliases map[string][]string) {
	for i := range c.cmds {
		if aliases, ok := allAliases[c.cmds[i].aliases[0]]; ok {
			c.cmds[i].aliases = append(c.cmds[i].aliases, aliases...)
		}
	}
}

D
Dan Mace 已提交
133
func CommandFunc(fn func() error) cmdfunc {
134
	return func(t *Term, args string) error {
D
Dan Mace 已提交
135 136 137 138
		return fn()
	}
}

139
func noCmdAvailable(t *Term, args string) error {
D
Derek Parker 已提交
140
	return fmt.Errorf("command not available")
D
Dan Mace 已提交
141 142
}

143
func nullCommand(t *Term, args string) error {
D
Dan Mace 已提交
144 145 146
	return nil
}

147
func (c *Commands) help(t *Term, args string) error {
D
Dan Mace 已提交
148
	fmt.Println("The following commands are available:")
D
Derek Parker 已提交
149 150
	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 0, '-', 0)
D
Dan Mace 已提交
151
	for _, cmd := range c.cmds {
D
Derek Parker 已提交
152 153 154 155 156
		if len(cmd.aliases) > 1 {
			fmt.Fprintf(w, "    %s (alias: %s) \t %s\n", cmd.aliases[0], strings.Join(cmd.aliases[1:], " | "), cmd.helpMsg)
		} else {
			fmt.Fprintf(w, "    %s \t %s\n", cmd.aliases[0], cmd.helpMsg)
		}
D
Dan Mace 已提交
157
	}
D
Derek Parker 已提交
158
	return w.Flush()
D
Dan Mace 已提交
159 160
}

I
Ilia Choly 已提交
161
type byThreadID []*api.Thread
I
Ilia Choly 已提交
162

I
Ilia Choly 已提交
163 164 165
func (a byThreadID) Len() int           { return len(a) }
func (a byThreadID) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byThreadID) Less(i, j int) bool { return a[i].ID < a[j].ID }
I
Ilia Choly 已提交
166

167
func threads(t *Term, args string) error {
168
	threads, err := t.client.ListThreads()
D
Dan Mace 已提交
169 170 171
	if err != nil {
		return err
	}
172
	state, err := t.client.GetState()
D
Dan Mace 已提交
173 174 175
	if err != nil {
		return err
	}
I
Ilia Choly 已提交
176
	sort.Sort(byThreadID(threads))
D
Dan Mace 已提交
177 178 179 180 181 182 183
	for _, th := range threads {
		prefix := "  "
		if state.CurrentThread != nil && state.CurrentThread.ID == th.ID {
			prefix = "* "
		}
		if th.Function != nil {
			fmt.Printf("%sThread %d at %#v %s:%d %s\n",
184
				prefix, th.ID, th.PC, shortenFilePath(th.File),
D
Dan Mace 已提交
185 186
				th.Line, th.Function.Name)
		} else {
187
			fmt.Printf("%sThread %s\n", prefix, formatThread(th))
D
Dan Mace 已提交
188 189 190 191 192
		}
	}
	return nil
}

193
func thread(t *Term, args string) error {
194 195 196
	if len(args) == 0 {
		return fmt.Errorf("you must specify a thread")
	}
197
	tid, err := strconv.Atoi(args)
D
Dan Mace 已提交
198 199 200
	if err != nil {
		return err
	}
201
	oldState, err := t.client.GetState()
D
Dan Mace 已提交
202 203 204
	if err != nil {
		return err
	}
205
	newState, err := t.client.SwitchThread(tid)
D
Dan Mace 已提交
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
	if err != nil {
		return err
	}

	oldThread := "<none>"
	newThread := "<none>"
	if oldState.CurrentThread != nil {
		oldThread = strconv.Itoa(oldState.CurrentThread.ID)
	}
	if newState.CurrentThread != nil {
		newThread = strconv.Itoa(newState.CurrentThread.ID)
	}
	fmt.Printf("Switched from %s to %s\n", oldThread, newThread)
	return nil
}

I
Ilia Choly 已提交
222 223 224 225 226 227
type byGoroutineID []*api.Goroutine

func (a byGoroutineID) Len() int           { return len(a) }
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 }

228 229
func goroutines(t *Term, argstr string) error {
	args := strings.Split(argstr, " ")
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
	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")
	}
249
	state, err := t.client.GetState()
250 251 252
	if err != nil {
		return err
	}
253
	gs, err := t.client.ListGoroutines()
D
Dan Mace 已提交
254 255 256
	if err != nil {
		return err
	}
I
Ilia Choly 已提交
257
	sort.Sort(byGoroutineID(gs))
D
Dan Mace 已提交
258 259
	fmt.Printf("[%d goroutines]\n", len(gs))
	for _, g := range gs {
260
		prefix := "  "
261
		if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
262 263
			prefix = "* "
		}
264
		fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl))
265 266 267 268
	}
	return nil
}

269 270
func goroutine(t *Term, argstr string) error {
	if argstr == "" {
271
		return printscope(t)
272
	}
273

274 275
	if strings.Index(argstr, " ") < 0 {
		gid, err := strconv.Atoi(argstr)
276 277 278 279
		if err != nil {
			return err
		}

280
		oldState, err := t.client.GetState()
281 282 283
		if err != nil {
			return err
		}
284
		newState, err := t.client.SwitchGoroutine(gid)
285 286 287 288 289 290 291 292
		if err != nil {
			return err
		}

		fmt.Printf("Switched from %d to %d (thread %d)\n", oldState.SelectedGoroutine.ID, gid, newState.CurrentThread.ID)
		return nil
	}

293
	return scopePrefix(t, "goroutine "+argstr)
294 295
}

296 297 298
func frame(t *Term, args string) error {
	return scopePrefix(t, "frame "+args)
}
299

300
func scopePrefix(t *Term, cmdstr string) error {
301 302
	scope := api.EvalScope{-1, 0}
	lastcmd := ""
303 304 305 306 307 308 309 310 311 312 313
	rest := cmdstr

	nexttok := func() string {
		v := strings.SplitN(rest, " ", 2)
		if len(v) > 1 {
			rest = v[1]
		} else {
			rest = ""
		}
		return v[0]
	}
314

315
	callFilterSortAndOutput := func(fn scopedFilteringFunc, fnargs string) error {
316 317
		outfn := filterSortAndOutput(func(t *Term, filter string) ([]string, error) {
			return fn(t, scope, filter)
318
		})
319
		return outfn(t, fnargs)
320 321
	}

322 323 324 325 326 327
	for {
		cmd := nexttok()
		if cmd == "" && rest == "" {
			break
		}
		switch cmd {
328
		case "goroutine":
329
			if rest == "" {
330 331
				return fmt.Errorf("goroutine command needs an argument")
			}
332
			n, err := strconv.Atoi(nexttok())
333 334 335 336 337
			if err != nil {
				return fmt.Errorf("invalid argument to goroutine, expected integer")
			}
			scope.GoroutineID = int(n)
		case "frame":
338
			if rest == "" {
339 340
				return fmt.Errorf("frame command needs an argument")
			}
341
			n, err := strconv.Atoi(nexttok())
342 343 344 345
			if err != nil {
				return fmt.Errorf("invalid argument to frame, expected integer")
			}
			scope.Frame = int(n)
346 347
		case "list", "ls":
			frame, gid := scope.Frame, scope.GoroutineID
348
			locs, err := t.client.Stacktrace(gid, frame, false)
349 350 351 352 353 354 355
			if err != nil {
				return err
			}
			if frame >= len(locs) {
				return fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
			}
			loc := locs[frame]
356
			return printfile(t, loc.File, loc.Line, true)
357
		case "stack", "bt":
358
			depth, full, err := parseStackArgs(rest)
359 360 361
			if err != nil {
				return err
			}
362
			stack, err := t.client.Stacktrace(scope.GoroutineID, depth, full)
363 364 365 366 367
			if err != nil {
				return err
			}
			printStack(stack, "")
			return nil
368
		case "locals":
369
			return callFilterSortAndOutput(locals, rest)
370
		case "args":
371
			return callFilterSortAndOutput(args, rest)
372
		case "print", "p":
373 374 375
			return printVar(t, scope, rest)
		case "set":
			return setVar(t, scope, rest)
376
		default:
377
			return fmt.Errorf("unknown command %s", cmd)
378
		}
379
		lastcmd = cmd
380 381 382 383 384
	}

	return fmt.Errorf("no command passed to %s", lastcmd)
}

385 386
func printscope(t *Term) error {
	state, err := t.client.GetState()
387 388
	if err != nil {
		return err
D
Dan Mace 已提交
389
	}
390

391 392 393 394
	fmt.Printf("Thread %s\n", formatThread(state.CurrentThread))
	if state.SelectedGoroutine != nil {
		writeGoroutineLong(os.Stdout, state.SelectedGoroutine, "")
	}
D
Dan Mace 已提交
395 396 397
	return nil
}

398 399 400 401 402 403 404
func formatThread(th *api.Thread) string {
	if th == nil {
		return "<nil>"
	}
	return fmt.Sprintf("%d at %s:%d", th.ID, shortenFilePath(th.File), th.Line)
}

405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
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 {
422 423 424
	if g == nil {
		return "<nil>"
	}
425 426 427 428 429
	var locname string
	var loc api.Location
	switch fgl {
	case fglRuntimeCurrent:
		locname = "Runtime"
430
		loc = g.CurrentLoc
431 432
	case fglUserCurrent:
		locname = "User"
433
		loc = g.UserCurrentLoc
434 435
	case fglGo:
		locname = "Go"
436
		loc = g.GoStatementLoc
A
aarzilli 已提交
437
	}
438 439 440 441 442 443
	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,
444 445 446
		prefix, formatLocation(g.CurrentLoc),
		prefix, formatLocation(g.UserCurrentLoc),
		prefix, formatLocation(g.GoStatementLoc))
A
aarzilli 已提交
447 448
}

449
func restart(t *Term, args string) error {
450
	if err := t.client.Restart(); err != nil {
D
Derek Parker 已提交
451 452
		return err
	}
453
	fmt.Println("Process restarted with PID", t.client.ProcessPid())
D
Derek Parker 已提交
454 455 456
	return nil
}

457
func cont(t *Term, args string) error {
458
	stateChan := t.client.Continue()
D
Derek Parker 已提交
459
	for state := range stateChan {
A
aarzilli 已提交
460 461 462
		if state.Err != nil {
			return state.Err
		}
463
		printcontext(t, state)
D
Dan Mace 已提交
464 465 466 467
	}
	return nil
}

468
func step(t *Term, args string) error {
469
	state, err := t.client.Step()
D
Dan Mace 已提交
470 471 472
	if err != nil {
		return err
	}
473
	printcontext(t, state)
D
Dan Mace 已提交
474 475 476
	return nil
}

477
func next(t *Term, args string) error {
478
	state, err := t.client.Next()
D
Dan Mace 已提交
479 480 481
	if err != nil {
		return err
	}
482
	printcontext(t, state)
D
Dan Mace 已提交
483 484 485
	return nil
}

486
func clear(t *Term, args string) error {
D
Dan Mace 已提交
487
	if len(args) == 0 {
D
Derek Parker 已提交
488
		return fmt.Errorf("not enough arguments")
D
Dan Mace 已提交
489
	}
490
	id, err := strconv.Atoi(args)
D
Dan Mace 已提交
491 492 493
	if err != nil {
		return err
	}
494
	bp, err := t.client.ClearBreakpoint(id)
D
Dan Mace 已提交
495 496 497
	if err != nil {
		return err
	}
498
	fmt.Printf("Breakpoint %d cleared at %#v for %s %s:%d\n", bp.ID, bp.Addr, bp.FunctionName, shortenFilePath(bp.File), bp.Line)
D
Dan Mace 已提交
499 500 501
	return nil
}

502
func clearAll(t *Term, args string) error {
503
	breakPoints, err := t.client.ListBreakpoints()
D
Dan Mace 已提交
504 505 506
	if err != nil {
		return err
	}
507 508

	var locPCs map[uint64]struct{}
509 510
	if args != "" {
		locs, err := t.client.FindLocation(api.EvalScope{-1, 0}, args)
511 512 513 514 515 516 517 518 519
		if err != nil {
			return err
		}
		locPCs = make(map[uint64]struct{})
		for _, loc := range locs {
			locPCs[loc.PC] = struct{}{}
		}
	}

D
Dan Mace 已提交
520
	for _, bp := range breakPoints {
521 522 523 524 525 526
		if locPCs != nil {
			if _, ok := locPCs[bp.Addr]; !ok {
				continue
			}
		}

527
		_, err := t.client.ClearBreakpoint(bp.ID)
D
Dan Mace 已提交
528
		if err != nil {
529
			fmt.Printf("Couldn't delete breakpoint %d at %#v %s:%d: %s\n", bp.ID, bp.Addr, shortenFilePath(bp.File), bp.Line, err)
D
Dan Mace 已提交
530
		}
531
		fmt.Printf("Breakpoint %d cleared at %#v for %s %s:%d\n", bp.ID, bp.Addr, bp.FunctionName, shortenFilePath(bp.File), bp.Line)
D
Dan Mace 已提交
532 533 534 535
	}
	return nil
}

D
Derek Parker 已提交
536
type ById []*api.Breakpoint
D
Dan Mace 已提交
537 538 539 540 541

func (a ById) Len() int           { return len(a) }
func (a ById) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ById) Less(i, j int) bool { return a[i].ID < a[j].ID }

542
func breakpoints(t *Term, args string) error {
543
	breakPoints, err := t.client.ListBreakpoints()
D
Dan Mace 已提交
544 545 546 547 548
	if err != nil {
		return err
	}
	sort.Sort(ById(breakPoints))
	for _, bp := range breakPoints {
A
aarzilli 已提交
549 550 551 552
		thing := "Breakpoint"
		if bp.Tracepoint {
			thing = "Tracepoint"
		}
553
		fmt.Printf("%s %d at %#v %s:%d (%d)\n", thing, bp.ID, bp.Addr, shortenFilePath(bp.File), bp.Line, bp.TotalHitCount)
A
aarzilli 已提交
554 555 556 557 558 559 560 561 562

		var attrs []string
		if bp.Stacktrace > 0 {
			attrs = append(attrs, "-stack")
			attrs = append(attrs, strconv.Itoa(bp.Stacktrace))
		}
		if bp.Goroutine {
			attrs = append(attrs, "-goroutine")
		}
D
Derek Parker 已提交
563 564
		for i := range bp.Variables {
			attrs = append(attrs, bp.Variables[i])
A
aarzilli 已提交
565 566 567 568
		}
		if len(attrs) > 0 {
			fmt.Printf("\t%s\n", strings.Join(attrs, " "))
		}
D
Dan Mace 已提交
569 570 571 572
	}
	return nil
}

573 574
func setBreakpoint(t *Term, tracepoint bool, argstr string) error {
	args := strings.Split(argstr, " ")
A
aarzilli 已提交
575 576
	if len(args) < 1 {
		return fmt.Errorf("address required, specify either a function name or <file:line>")
D
Dan Mace 已提交
577
	}
D
Derek Parker 已提交
578
	requestedBp := &api.Breakpoint{}
D
Dan Mace 已提交
579

A
aarzilli 已提交
580 581 582 583 584 585 586 587 588 589 590 591
	for i := 1; i < len(args); i++ {
		switch args[i] {
		case "-stack":
			i++
			n, err := strconv.Atoi(args[i])
			if err != nil {
				return fmt.Errorf("argument of -stack must be a number")
			}
			requestedBp.Stacktrace = n
		case "-goroutine":
			requestedBp.Goroutine = true
		default:
D
Derek Parker 已提交
592
			requestedBp.Variables = append(requestedBp.Variables, args[i])
A
aarzilli 已提交
593 594 595 596
		}
	}

	requestedBp.Tracepoint = tracepoint
597
	locs, err := t.client.FindLocation(api.EvalScope{-1, 0}, args[0])
D
Dan Mace 已提交
598 599 600
	if err != nil {
		return err
	}
A
aarzilli 已提交
601 602 603 604
	thing := "Breakpoint"
	if tracepoint {
		thing = "Tracepoint"
	}
605 606 607
	for _, loc := range locs {
		requestedBp.Addr = loc.PC

608
		bp, err := t.client.CreateBreakpoint(requestedBp)
609 610 611 612
		if err != nil {
			return err
		}

613
		fmt.Printf("%s %d set at %#v for %s %s:%d\n", thing, bp.ID, bp.Addr, bp.FunctionName, shortenFilePath(bp.File), bp.Line)
614
	}
D
Dan Mace 已提交
615 616 617
	return nil
}

618 619
func breakpoint(t *Term, args string) error {
	return setBreakpoint(t, false, args)
A
aarzilli 已提交
620 621
}

622 623
func tracepoint(t *Term, args string) error {
	return setBreakpoint(t, true, args)
A
aarzilli 已提交
624 625
}

626
func g0f0(fn scopedCmdfunc) cmdfunc {
627 628
	return func(t *Term, args string) error {
		return fn(t, api.EvalScope{-1, 0}, args)
629 630 631 632
	}
}

func g0f0filter(fn scopedFilteringFunc) filteringFunc {
633 634
	return func(t *Term, filter string) ([]string, error) {
		return fn(t, api.EvalScope{-1, 0}, filter)
635 636 637
	}
}

638
func printVar(t *Term, scope api.EvalScope, args string) error {
D
Dan Mace 已提交
639
	if len(args) == 0 {
D
Derek Parker 已提交
640
		return fmt.Errorf("not enough arguments")
D
Dan Mace 已提交
641
	}
642
	val, err := t.client.EvalVariable(scope, args)
D
Dan Mace 已提交
643 644 645
	if err != nil {
		return err
	}
646 647

	fmt.Println(val.MultilineString(""))
D
Dan Mace 已提交
648 649 650
	return nil
}

651 652 653 654 655
func setVar(t *Term, scope api.EvalScope, args string) error {
	// HACK: in go '=' is not an operator, we detect the error and try to recover from it by splitting the input string
	_, err := parser.ParseExpr(args)
	if err == nil {
		return fmt.Errorf("syntax error '=' not found")
656 657
	}

658 659 660 661 662 663 664 665
	el, ok := err.(scanner.ErrorList)
	if !ok || el[0].Msg != "expected '==', found '='" {
		return err
	}

	lexpr := args[:el[0].Pos.Offset]
	rexpr := args[el[0].Pos.Offset+1:]
	return t.client.SetVariable(scope, lexpr, rexpr)
666 667
}

D
Derek Parker 已提交
668 669 670 671 672 673
func filterVariables(vars []api.Variable, filter string) []string {
	reg, err := regexp.Compile(filter)
	if err != nil {
		fmt.Fprintf(os.Stderr, err.Error())
		return nil
	}
D
Dan Mace 已提交
674 675
	data := make([]string, 0, len(vars))
	for _, v := range vars {
D
Derek Parker 已提交
676
		if reg == nil || reg.Match([]byte(v.Name)) {
677
			data = append(data, fmt.Sprintf("%s = %s", v.Name, v.SinglelineString()))
D
Dan Mace 已提交
678 679 680 681 682
		}
	}
	return data
}

683 684
func sources(t *Term, filter string) ([]string, error) {
	return t.client.ListSources(filter)
D
Derek Parker 已提交
685
}
D
Dan Mace 已提交
686

687 688
func funcs(t *Term, filter string) ([]string, error) {
	return t.client.ListFunctions(filter)
D
Derek Parker 已提交
689
}
D
Dan Mace 已提交
690

691 692
func args(t *Term, scope api.EvalScope, filter string) ([]string, error) {
	vars, err := t.client.ListFunctionArgs(scope)
D
Derek Parker 已提交
693 694 695 696 697
	if err != nil {
		return nil, err
	}
	return filterVariables(vars, filter), nil
}
D
Dan Mace 已提交
698

699 700
func locals(t *Term, scope api.EvalScope, filter string) ([]string, error) {
	locals, err := t.client.ListLocalVariables(scope)
D
Derek Parker 已提交
701 702 703 704 705
	if err != nil {
		return nil, err
	}
	return filterVariables(locals, filter), nil
}
D
Dan Mace 已提交
706

707 708
func vars(t *Term, filter string) ([]string, error) {
	vars, err := t.client.ListPackageVariables(filter)
D
Derek Parker 已提交
709 710 711 712 713
	if err != nil {
		return nil, err
	}
	return filterVariables(vars, filter), nil
}
D
Dan Mace 已提交
714

715
func regs(t *Term, args string) error {
716
	regs, err := t.client.ListRegisters()
D
Derek Parker 已提交
717 718 719 720 721 722
	if err != nil {
		return err
	}
	fmt.Println(regs)
	return nil
}
723

724
func filterSortAndOutput(fn filteringFunc) cmdfunc {
725
	return func(t *Term, args string) error {
D
Derek Parker 已提交
726
		var filter string
727 728
		if len(args) > 0 {
			if _, err := regexp.Compile(args); err != nil {
D
Derek Parker 已提交
729 730
				return fmt.Errorf("invalid filter argument: %s", err.Error())
			}
731
			filter = args
D
Dan Mace 已提交
732
		}
733
		data, err := fn(t, filter)
D
Dan Mace 已提交
734 735 736
		if err != nil {
			return err
		}
D
Derek Parker 已提交
737 738 739
		sort.Sort(sort.StringSlice(data))
		for _, d := range data {
			fmt.Println(d)
D
Dan Mace 已提交
740
		}
D
Derek Parker 已提交
741
		return nil
D
Dan Mace 已提交
742 743 744
	}
}

745
func stackCommand(t *Term, args string) error {
746 747 748 749 750 751 752 753
	var (
		err         error
		goroutineid = -1
	)
	depth, full, err := parseStackArgs(args)
	if err != nil {
		return err
	}
754
	stack, err := t.client.Stacktrace(goroutineid, depth, full)
755 756 757 758 759 760
	if err != nil {
		return err
	}
	printStack(stack, "")
	return nil
}
A
aarzilli 已提交
761

762
func parseStackArgs(argstr string) (int, bool, error) {
763 764 765 766
	var (
		depth = 10
		full  = false
	)
767 768 769 770 771 772 773 774 775 776 777
	if argstr != "" {
		args := strings.Split(argstr, " ")
		for i := range args {
			if args[i] == "-full" {
				full = true
			} else {
				n, err := strconv.Atoi(args[i])
				if err != nil {
					return 0, false, fmt.Errorf("depth must be a number")
				}
				depth = n
778
			}
A
aarzilli 已提交
779 780
		}
	}
781
	return depth, full, nil
A
aarzilli 已提交
782 783
}

784
func listCommand(t *Term, args string) error {
785
	if len(args) == 0 {
786
		state, err := t.client.GetState()
787 788 789
		if err != nil {
			return err
		}
790
		printcontext(t, state)
791 792 793
		return nil
	}

794
	locs, err := t.client.FindLocation(api.EvalScope{-1, 0}, args)
795 796 797 798
	if err != nil {
		return err
	}
	if len(locs) > 1 {
799
		return debugger.AmbiguousLocationError{Location: args, CandidatesLocation: locs}
800
	}
801
	printfile(t, locs[0].File, locs[0].Line, false)
802 803 804
	return nil
}

805
func (cmds *Commands) sourceCommand(t *Term, args string) error {
806 807 808 809
	if len(args) != 1 {
		return fmt.Errorf("wrong number of arguments: source <filename>")
	}

810
	return cmds.executeFile(t, args)
811 812
}

813 814 815 816 817 818 819
func digits(n int) int {
	return int(math.Floor(math.Log10(float64(n)))) + 1
}

func printStack(stack []api.Stackframe, ind string) {
	d := digits(len(stack) - 1)
	fmtstr := "%s%" + strconv.Itoa(d) + "d  0x%016x in %s\n"
820
	s := strings.Repeat(" ", d+2+len(ind))
821

A
aarzilli 已提交
822 823 824 825 826
	for i := range stack {
		name := "(nil)"
		if stack[i].Function != nil {
			name = stack[i].Function.Name
		}
827 828 829 830
		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 {
831
			fmt.Printf("%s    %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
832 833
		}
		for j := range stack[i].Locals {
834
			fmt.Printf("%s    %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
835
		}
A
aarzilli 已提交
836 837 838
	}
}

839
func printcontext(t *Term, state *api.DebuggerState) error {
D
Dan Mace 已提交
840 841 842 843 844 845
	if state.CurrentThread == nil {
		fmt.Println("No current thread available")
		return nil
	}
	if len(state.CurrentThread.File) == 0 {
		fmt.Printf("Stopped at: 0x%x\n", state.CurrentThread.PC)
846
		t.Println("=>", "no source available")
D
Dan Mace 已提交
847 848
		return nil
	}
D
Derek Parker 已提交
849
	var fn *api.Function
D
Dan Mace 已提交
850
	if state.CurrentThread.Function != nil {
D
Derek Parker 已提交
851 852
		fn = state.CurrentThread.Function
	}
853 854 855 856 857 858

	if state.Breakpoint != nil {
		args := ""
		if state.Breakpoint.Tracepoint {
			var arg []string
			for _, ar := range state.CurrentThread.Function.Args {
859
				arg = append(arg, ar.SinglelineString())
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
			}
			args = strings.Join(arg, ", ")
		}

		if hitCount, ok := state.Breakpoint.HitCount[strconv.Itoa(state.SelectedGoroutine.ID)]; ok {
			fmt.Printf("> %s(%s) %s:%d (hits goroutine(%d):%d total:%d)\n",
				fn.Name,
				args,
				shortenFilePath(state.CurrentThread.File),
				state.CurrentThread.Line,
				state.SelectedGoroutine.ID,
				hitCount,
				state.Breakpoint.TotalHitCount)
		} else {
			fmt.Printf("> %s(%s) %s:%d (hits total:%d)\n",
				fn.Name,
				args,
				shortenFilePath(state.CurrentThread.File),
				state.CurrentThread.Line,
				state.Breakpoint.TotalHitCount)
D
Derek Parker 已提交
880 881
		}
	} else {
882
		fmt.Printf("> %s() %s:%d\n", fn.Name, shortenFilePath(state.CurrentThread.File), state.CurrentThread.Line)
D
Dan Mace 已提交
883 884
	}

A
aarzilli 已提交
885 886 887 888
	if state.BreakpointInfo != nil {
		bpi := state.BreakpointInfo

		if bpi.Goroutine != nil {
889
			writeGoroutineLong(os.Stdout, bpi.Goroutine, "\t")
A
aarzilli 已提交
890 891 892 893
		}

		ss := make([]string, len(bpi.Variables))
		for i, v := range bpi.Variables {
894
			ss[i] = fmt.Sprintf("%s: %v", v.Name, v.MultilineString(""))
A
aarzilli 已提交
895 896 897 898 899 900 901 902 903 904 905
		}
		fmt.Printf("\t%s\n", strings.Join(ss, ", "))

		if bpi.Stacktrace != nil {
			fmt.Printf("\tStack:\n")
			printStack(bpi.Stacktrace, "\t\t")
		}
	}
	if state.Breakpoint != nil && state.Breakpoint.Tracepoint {
		return nil
	}
906
	return printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true)
907 908
}

909
func printfile(t *Term, filename string, line int, showArrow bool) error {
910
	file, err := os.Open(filename)
D
Dan Mace 已提交
911 912 913 914 915
	if err != nil {
		return err
	}
	defer file.Close()

916
	buf := bufio.NewScanner(file)
917
	l := line
D
Dan Mace 已提交
918
	for i := 1; i < l-5; i++ {
919 920
		if !buf.Scan() {
			return nil
D
Dan Mace 已提交
921 922 923
		}
	}

924 925 926 927 928 929
	s := l - 5
	if s < 1 {
		s = 1
	}

	for i := s; i <= l+5; i++ {
930 931
		if !buf.Scan() {
			return nil
D
Dan Mace 已提交
932 933
		}

934
		var prefix string
935
		if showArrow {
936
			prefix = "  "
937
			if i == l {
938
				prefix = "=>"
939
			}
D
Dan Mace 已提交
940 941
		}

942 943
		prefix = fmt.Sprintf("%s%4d:\t", prefix, i)
		t.Println(prefix, buf.Text())
D
Dan Mace 已提交
944 945 946
	}
	return nil
}
D
Derek Parker 已提交
947 948 949 950 951 952 953

type ExitRequestError struct{}

func (ere ExitRequestError) Error() string {
	return ""
}

954
func exitCommand(t *Term, args string) error {
D
Derek Parker 已提交
955 956
	return ExitRequestError{}
}
957 958 959 960 961

func shortenFilePath(fullPath string) string {
	workingDir, _ := os.Getwd()
	return strings.Replace(fullPath, workingDir, ".", 1)
}
962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981

func (cmds *Commands) executeFile(t *Term, name string) error {
	fh, err := os.Open(name)
	if err != nil {
		return err
	}
	defer fh.Close()

	scanner := bufio.NewScanner(fh)
	lineno := 0
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		lineno++

		if line == "" || line[0] == '#' {
			continue
		}

		cmdstr, args := parseCommand(line)
		cmd := cmds.Find(cmdstr)
982
		err := cmd(t, args)
983 984 985 986 987 988 989 990

		if err != nil {
			fmt.Printf("%s:%d: %v\n", name, lineno, err)
		}
	}

	return scanner.Err()
}