command.go 25.6 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

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
}

D
Derek Parker 已提交
46
// Commands represents the commands for Delve terminal process.
D
Dan Mace 已提交
47
type Commands struct {
D
Derek Parker 已提交
48 49 50
	cmds    []command
	lastCmd cmdfunc
	client  service.Client
D
Dan Mace 已提交
51 52
}

D
Derek Parker 已提交
53
// DebugCommands returns a Commands struct with default commands defined.
D
Dan Mace 已提交
54 55 56 57 58
func DebugCommands(client service.Client) *Commands {
	c := &Commands{client: client}

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

	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 已提交
105
// If it cannot find the command it will default to noCmdAvailable().
D
Dan Mace 已提交
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
// 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
}

126 127 128 129 130 131 132 133 134
// 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...)
		}
	}
}

135
func noCmdAvailable(t *Term, args string) error {
D
Derek Parker 已提交
136
	return fmt.Errorf("command not available")
D
Dan Mace 已提交
137 138
}

139
func nullCommand(t *Term, args string) error {
D
Dan Mace 已提交
140 141 142
	return nil
}

143
func (c *Commands) help(t *Term, args string) error {
D
Dan Mace 已提交
144
	fmt.Println("The following commands are available:")
D
Derek Parker 已提交
145 146
	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 0, '-', 0)
D
Dan Mace 已提交
147
	for _, cmd := range c.cmds {
D
Derek Parker 已提交
148 149 150 151 152
		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 已提交
153
	}
D
Derek Parker 已提交
154
	return w.Flush()
D
Dan Mace 已提交
155 156
}

I
Ilia Choly 已提交
157
type byThreadID []*api.Thread
I
Ilia Choly 已提交
158

I
Ilia Choly 已提交
159 160 161
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 已提交
162

163
func threads(t *Term, args string) error {
164
	threads, err := t.client.ListThreads()
D
Dan Mace 已提交
165 166 167
	if err != nil {
		return err
	}
168
	state, err := t.client.GetState()
D
Dan Mace 已提交
169 170 171
	if err != nil {
		return err
	}
I
Ilia Choly 已提交
172
	sort.Sort(byThreadID(threads))
D
Dan Mace 已提交
173 174 175 176 177 178 179
	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",
180
				prefix, th.ID, th.PC, ShortenFilePath(th.File),
D
Dan Mace 已提交
181 182
				th.Line, th.Function.Name)
		} else {
183
			fmt.Printf("%sThread %s\n", prefix, formatThread(th))
D
Dan Mace 已提交
184 185 186 187 188
		}
	}
	return nil
}

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

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

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

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

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

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

291
	return scopePrefix(t, "goroutine "+argstr)
292 293
}

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

298
func scopePrefix(t *Term, cmdstr string) error {
D
Derek Parker 已提交
299
	scope := api.EvalScope{GoroutineID: -1, Frame: 0}
300
	lastcmd := ""
301 302 303 304 305 306 307 308 309 310 311
	rest := cmdstr

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

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

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

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

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

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

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

403 404 405 406 407 408 409 410 411 412 413 414 415
type formatGoroutineLoc int

const (
	fglRuntimeCurrent = formatGoroutineLoc(iota)
	fglUserCurrent
	fglGo
)

func formatLocation(loc api.Location) string {
	fname := ""
	if loc.Function != nil {
		fname = loc.Function.Name
	}
416
	return fmt.Sprintf("%s:%d %s (%#v)", ShortenFilePath(loc.File), loc.Line, fname, loc.PC)
417 418 419
}

func formatGoroutine(g *api.Goroutine, fgl formatGoroutineLoc) string {
420 421 422
	if g == nil {
		return "<nil>"
	}
423 424 425 426 427
	var locname string
	var loc api.Location
	switch fgl {
	case fglRuntimeCurrent:
		locname = "Runtime"
428
		loc = g.CurrentLoc
429 430
	case fglUserCurrent:
		locname = "User"
431
		loc = g.UserCurrentLoc
432 433
	case fglGo:
		locname = "Go"
434
		loc = g.GoStatementLoc
A
aarzilli 已提交
435
	}
436 437 438 439 440 441
	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,
442 443 444
		prefix, formatLocation(g.CurrentLoc),
		prefix, formatLocation(g.UserCurrentLoc),
		prefix, formatLocation(g.GoStatementLoc))
A
aarzilli 已提交
445 446
}

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

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

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

475 476 477 478 479 480 481 482 483
func stepInstruction(t *Term, args string) error {
	state, err := t.client.StepInstruction()
	if err != nil {
		return err
	}
	printcontext(t, state)
	return nil
}

484
func next(t *Term, args string) error {
485
	state, err := t.client.Next()
D
Dan Mace 已提交
486 487 488
	if err != nil {
		return err
	}
489
	printcontext(t, state)
D
Dan Mace 已提交
490 491 492
	return nil
}

493
func clear(t *Term, args string) error {
D
Dan Mace 已提交
494
	if len(args) == 0 {
D
Derek Parker 已提交
495
		return fmt.Errorf("not enough arguments")
D
Dan Mace 已提交
496
	}
497
	id, err := strconv.Atoi(args)
D
Dan Mace 已提交
498 499 500
	if err != nil {
		return err
	}
501
	bp, err := t.client.ClearBreakpoint(id)
D
Dan Mace 已提交
502 503 504
	if err != nil {
		return err
	}
505
	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 已提交
506 507 508
	return nil
}

509
func clearAll(t *Term, args string) error {
510
	breakPoints, err := t.client.ListBreakpoints()
D
Dan Mace 已提交
511 512 513
	if err != nil {
		return err
	}
514 515

	var locPCs map[uint64]struct{}
516
	if args != "" {
D
Derek Parker 已提交
517
		locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args)
518 519 520 521 522 523 524 525 526
		if err != nil {
			return err
		}
		locPCs = make(map[uint64]struct{})
		for _, loc := range locs {
			locPCs[loc.PC] = struct{}{}
		}
	}

D
Dan Mace 已提交
527
	for _, bp := range breakPoints {
528 529 530 531 532 533
		if locPCs != nil {
			if _, ok := locPCs[bp.Addr]; !ok {
				continue
			}
		}

534
		_, err := t.client.ClearBreakpoint(bp.ID)
D
Dan Mace 已提交
535
		if err != nil {
536
			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 已提交
537
		}
538
		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 已提交
539 540 541 542
	}
	return nil
}

D
Derek Parker 已提交
543 544
// ByID sorts breakpoints by ID.
type ByID []*api.Breakpoint
D
Dan Mace 已提交
545

D
Derek Parker 已提交
546 547 548
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 }
D
Dan Mace 已提交
549

550
func breakpoints(t *Term, args string) error {
551
	breakPoints, err := t.client.ListBreakpoints()
D
Dan Mace 已提交
552 553 554
	if err != nil {
		return err
	}
D
Derek Parker 已提交
555
	sort.Sort(ByID(breakPoints))
D
Dan Mace 已提交
556
	for _, bp := range breakPoints {
A
aarzilli 已提交
557 558 559 560
		thing := "Breakpoint"
		if bp.Tracepoint {
			thing = "Tracepoint"
		}
561
		fmt.Printf("%s %d at %#v %s:%d (%d)\n", thing, bp.ID, bp.Addr, ShortenFilePath(bp.File), bp.Line, bp.TotalHitCount)
A
aarzilli 已提交
562 563 564 565 566 567 568 569 570

		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 已提交
571 572
		for i := range bp.Variables {
			attrs = append(attrs, bp.Variables[i])
A
aarzilli 已提交
573 574 575 576
		}
		if len(attrs) > 0 {
			fmt.Printf("\t%s\n", strings.Join(attrs, " "))
		}
D
Dan Mace 已提交
577 578 579 580
	}
	return nil
}

581 582
func setBreakpoint(t *Term, tracepoint bool, argstr string) error {
	args := strings.Split(argstr, " ")
A
aarzilli 已提交
583 584
	if len(args) < 1 {
		return fmt.Errorf("address required, specify either a function name or <file:line>")
D
Dan Mace 已提交
585
	}
D
Derek Parker 已提交
586
	requestedBp := &api.Breakpoint{}
D
Dan Mace 已提交
587

A
aarzilli 已提交
588 589 590 591 592 593 594 595 596 597 598 599
	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 已提交
600
			requestedBp.Variables = append(requestedBp.Variables, args[i])
A
aarzilli 已提交
601 602 603 604
		}
	}

	requestedBp.Tracepoint = tracepoint
D
Derek Parker 已提交
605
	locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args[0])
D
Dan Mace 已提交
606 607 608
	if err != nil {
		return err
	}
A
aarzilli 已提交
609 610 611 612
	thing := "Breakpoint"
	if tracepoint {
		thing = "Tracepoint"
	}
613 614 615
	for _, loc := range locs {
		requestedBp.Addr = loc.PC

616
		bp, err := t.client.CreateBreakpoint(requestedBp)
617 618 619 620
		if err != nil {
			return err
		}

621
		fmt.Printf("%s %d set at %#v for %s %s:%d\n", thing, bp.ID, bp.Addr, bp.FunctionName, ShortenFilePath(bp.File), bp.Line)
622
	}
D
Dan Mace 已提交
623 624 625
	return nil
}

626 627
func breakpoint(t *Term, args string) error {
	return setBreakpoint(t, false, args)
A
aarzilli 已提交
628 629
}

630 631
func tracepoint(t *Term, args string) error {
	return setBreakpoint(t, true, args)
A
aarzilli 已提交
632 633
}

634
func g0f0(fn scopedCmdfunc) cmdfunc {
635
	return func(t *Term, args string) error {
D
Derek Parker 已提交
636
		return fn(t, api.EvalScope{GoroutineID: -1, Frame: 0}, args)
637 638 639 640
	}
}

func g0f0filter(fn scopedFilteringFunc) filteringFunc {
641
	return func(t *Term, filter string) ([]string, error) {
D
Derek Parker 已提交
642
		return fn(t, api.EvalScope{GoroutineID: -1, Frame: 0}, filter)
643 644 645
	}
}

646
func printVar(t *Term, scope api.EvalScope, args string) error {
D
Dan Mace 已提交
647
	if len(args) == 0 {
D
Derek Parker 已提交
648
		return fmt.Errorf("not enough arguments")
D
Dan Mace 已提交
649
	}
650
	val, err := t.client.EvalVariable(scope, args)
D
Dan Mace 已提交
651 652 653
	if err != nil {
		return err
	}
654 655

	fmt.Println(val.MultilineString(""))
D
Dan Mace 已提交
656 657 658
	return nil
}

659 660 661 662 663
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")
664 665
	}

666 667 668 669 670 671 672 673
	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)
674 675
}

D
Derek Parker 已提交
676 677 678 679 680 681
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 已提交
682 683
	data := make([]string, 0, len(vars))
	for _, v := range vars {
D
Derek Parker 已提交
684
		if reg == nil || reg.Match([]byte(v.Name)) {
685
			data = append(data, fmt.Sprintf("%s = %s", v.Name, v.SinglelineString()))
D
Dan Mace 已提交
686 687 688 689 690
		}
	}
	return data
}

691 692
func sources(t *Term, filter string) ([]string, error) {
	return t.client.ListSources(filter)
D
Derek Parker 已提交
693
}
D
Dan Mace 已提交
694

695 696
func funcs(t *Term, filter string) ([]string, error) {
	return t.client.ListFunctions(filter)
D
Derek Parker 已提交
697
}
D
Dan Mace 已提交
698

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

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

715 716
func vars(t *Term, filter string) ([]string, error) {
	vars, err := t.client.ListPackageVariables(filter)
D
Derek Parker 已提交
717 718 719 720 721
	if err != nil {
		return nil, err
	}
	return filterVariables(vars, filter), nil
}
D
Dan Mace 已提交
722

723
func regs(t *Term, args string) error {
724
	regs, err := t.client.ListRegisters()
D
Derek Parker 已提交
725 726 727 728 729 730
	if err != nil {
		return err
	}
	fmt.Println(regs)
	return nil
}
731

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

753
func stackCommand(t *Term, args string) error {
754 755 756 757 758 759 760 761
	var (
		err         error
		goroutineid = -1
	)
	depth, full, err := parseStackArgs(args)
	if err != nil {
		return err
	}
762
	stack, err := t.client.Stacktrace(goroutineid, depth, full)
763 764 765 766 767 768
	if err != nil {
		return err
	}
	printStack(stack, "")
	return nil
}
A
aarzilli 已提交
769

770
func parseStackArgs(argstr string) (int, bool, error) {
771 772 773 774
	var (
		depth = 10
		full  = false
	)
775 776 777 778 779 780 781 782 783 784 785
	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
786
			}
A
aarzilli 已提交
787 788
		}
	}
789
	return depth, full, nil
A
aarzilli 已提交
790 791
}

792
func listCommand(t *Term, args string) error {
793
	if len(args) == 0 {
794
		state, err := t.client.GetState()
795 796 797
		if err != nil {
			return err
		}
798
		printcontext(t, state)
799 800 801
		return nil
	}

D
Derek Parker 已提交
802
	locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args)
803 804 805 806
	if err != nil {
		return err
	}
	if len(locs) > 1 {
807
		return debugger.AmbiguousLocationError{Location: args, CandidatesLocation: locs}
808
	}
809
	printfile(t, locs[0].File, locs[0].Line, false)
810 811 812
	return nil
}

D
Derek Parker 已提交
813
func (c *Commands) sourceCommand(t *Term, args string) error {
814
	if len(args) == 0 {
815 816 817
		return fmt.Errorf("wrong number of arguments: source <filename>")
	}

D
Derek Parker 已提交
818
	return c.executeFile(t, args)
819 820
}

821
func digits(n int) int {
822 823 824
	if n <= 0 {
		return 1
	}
825 826 827 828
	return int(math.Floor(math.Log10(float64(n)))) + 1
}

func printStack(stack []api.Stackframe, ind string) {
829 830 831
	if len(stack) == 0 {
		return
	}
832 833
	d := digits(len(stack) - 1)
	fmtstr := "%s%" + strconv.Itoa(d) + "d  0x%016x in %s\n"
834
	s := strings.Repeat(" ", d+2+len(ind))
835

A
aarzilli 已提交
836 837 838 839 840
	for i := range stack {
		name := "(nil)"
		if stack[i].Function != nil {
			name = stack[i].Function.Name
		}
841
		fmt.Printf(fmtstr, ind, i, stack[i].PC, name)
842
		fmt.Printf("%sat %s:%d\n", s, ShortenFilePath(stack[i].File), stack[i].Line)
843 844

		for j := range stack[i].Arguments {
845
			fmt.Printf("%s    %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
846 847
		}
		for j := range stack[i].Locals {
848
			fmt.Printf("%s    %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
849
		}
A
aarzilli 已提交
850 851 852
	}
}

853
func printcontext(t *Term, state *api.DebuggerState) error {
854 855 856 857 858 859 860 861 862
	for i := range state.Threads {
		if (state.CurrentThread != nil) && (state.Threads[i].ID == state.CurrentThread.ID) {
			continue
		}
		if state.Threads[i].Breakpoint != nil {
			printcontextThread(t, state.Threads[i])
		}
	}

D
Dan Mace 已提交
863 864 865 866 867 868
	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)
869
		t.Println("=>", "no source available")
D
Dan Mace 已提交
870 871
		return nil
	}
872 873 874 875 876

	printcontextThread(t, state.CurrentThread)

	if state.CurrentThread.Breakpoint == nil || !state.CurrentThread.Breakpoint.Tracepoint {
		return printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true)
D
Derek Parker 已提交
877
	}
878 879
	return nil
}
880

881 882 883 884 885 886 887 888 889
func printcontextThread(t *Term, th *api.Thread) {
	fn := th.Function

	if th.Breakpoint == nil {
		fmt.Printf("> %s() %s:%d\n", fn.Name, ShortenFilePath(th.File), th.Line)
		return
	}

	args := ""
890
	if th.Breakpoint.Tracepoint {
891
		var arg []string
892
		for _, ar := range th.BreakpointInfo.Arguments {
893
			arg = append(arg, ar.SinglelineString())
D
Derek Parker 已提交
894
		}
895 896 897 898 899 900 901 902 903 904 905 906
		args = strings.Join(arg, ", ")
	}

	if hitCount, ok := th.Breakpoint.HitCount[strconv.Itoa(th.GoroutineID)]; ok {
		fmt.Printf("> %s(%s) %s:%d (hits goroutine(%d):%d total:%d)\n",
			fn.Name,
			args,
			ShortenFilePath(th.File),
			th.Line,
			th.GoroutineID,
			hitCount,
			th.Breakpoint.TotalHitCount)
D
Derek Parker 已提交
907
	} else {
908 909 910 911 912 913
		fmt.Printf("> %s(%s) %s:%d (hits total:%d)\n",
			fn.Name,
			args,
			ShortenFilePath(th.File),
			th.Line,
			th.Breakpoint.TotalHitCount)
D
Dan Mace 已提交
914 915
	}

916 917
	if th.BreakpointInfo != nil {
		bpi := th.BreakpointInfo
A
aarzilli 已提交
918 919

		if bpi.Goroutine != nil {
920
			writeGoroutineLong(os.Stdout, bpi.Goroutine, "\t")
A
aarzilli 已提交
921 922
		}

923 924 925 926 927 928
		if len(bpi.Variables) > 0 {
			ss := make([]string, len(bpi.Variables))
			for i, v := range bpi.Variables {
				ss[i] = fmt.Sprintf("%s: %s", v.Name, v.MultilineString(""))
			}
			fmt.Printf("\t%s\n", strings.Join(ss, ", "))
A
aarzilli 已提交
929 930 931 932 933 934 935
		}

		if bpi.Stacktrace != nil {
			fmt.Printf("\tStack:\n")
			printStack(bpi.Stacktrace, "\t\t")
		}
	}
936 937
}

938
func printfile(t *Term, filename string, line int, showArrow bool) error {
939
	file, err := os.Open(filename)
D
Dan Mace 已提交
940 941 942 943 944
	if err != nil {
		return err
	}
	defer file.Close()

945
	buf := bufio.NewScanner(file)
946
	l := line
D
Dan Mace 已提交
947
	for i := 1; i < l-5; i++ {
948 949
		if !buf.Scan() {
			return nil
D
Dan Mace 已提交
950 951 952
		}
	}

953 954 955 956 957 958
	s := l - 5
	if s < 1 {
		s = 1
	}

	for i := s; i <= l+5; i++ {
959 960
		if !buf.Scan() {
			return nil
D
Dan Mace 已提交
961 962
		}

963
		var prefix string
964
		if showArrow {
965
			prefix = "  "
966
			if i == l {
967
				prefix = "=>"
968
			}
D
Dan Mace 已提交
969 970
		}

971 972
		prefix = fmt.Sprintf("%s%4d:\t", prefix, i)
		t.Println(prefix, buf.Text())
D
Dan Mace 已提交
973 974 975
	}
	return nil
}
D
Derek Parker 已提交
976

D
Derek Parker 已提交
977 978
// ExitRequestError is returned when the user
// exits Delve.
D
Derek Parker 已提交
979 980 981 982 983 984
type ExitRequestError struct{}

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

985
func exitCommand(t *Term, args string) error {
D
Derek Parker 已提交
986 987
	return ExitRequestError{}
}
988

D
Derek Parker 已提交
989 990
// ShortenFilePath take a full file path and attempts to shorten
// it by replacing the current directory to './'.
991
func ShortenFilePath(fullPath string) string {
992 993 994
	workingDir, _ := os.Getwd()
	return strings.Replace(fullPath, workingDir, ".", 1)
}
995

D
Derek Parker 已提交
996
func (c *Commands) executeFile(t *Term, name string) error {
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
	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)
D
Derek Parker 已提交
1014
		cmd := c.Find(cmdstr)
1015
		err := cmd(t, args)
1016 1017 1018 1019 1020 1021 1022 1023

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

	return scanner.Err()
}