command.go 25.3 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 63 64 65
		{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 已提交
66
		{aliases: []string{"thread", "tr"}, cmdFn: thread, helpMsg: "Switch to the specified thread."},
D
Dan Mace 已提交
67
		{aliases: []string{"clear"}, cmdFn: clear, helpMsg: "Deletes breakpoint."},
68
		{aliases: []string{"clearall"}, cmdFn: clearAll, helpMsg: "clearall [<linespec>]. Deletes all breakpoints. If <linespec> is provided, only matching breakpoints will be deleted."},
69
		{aliases: []string{"goroutines"}, cmdFn: goroutines, helpMsg: "goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)] Print out info for every goroutine."},
70
		{aliases: []string{"goroutine"}, cmdFn: goroutine, helpMsg: "Sets current goroutine."},
D
Dan Mace 已提交
71
		{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
72
		{aliases: []string{"print", "p"}, cmdFn: g0f0(printVar), helpMsg: "Evaluate a variable."},
73
		{aliases: []string{"set"}, cmdFn: g0f0(setVar), helpMsg: "Changes the value of a variable."},
D
Derek Parker 已提交
74 75
		{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."},
76 77
		{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 已提交
78 79
		{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 已提交
80
		{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."},
81
		{aliases: []string{"list", "ls"}, cmdFn: listCommand, helpMsg: "list <linespec>.  Show source around current point or provided linespec."},
82
		{aliases: []string{"stack", "bt"}, cmdFn: stackCommand, helpMsg: "stack [<depth>] [-full]. Prints stack."},
83
		{aliases: []string{"frame"}, cmdFn: frame, helpMsg: "Sets current stack frame (0 is the top of the stack)"},
84
		{aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: "Executes a file containing a list of delve commands"},
D
Dan Mace 已提交
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

const (
	fglRuntimeCurrent = formatGoroutineLoc(iota)
	fglUserCurrent
	fglGo
)

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

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

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

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

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

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

483
func clear(t *Term, args string) error {
D
Dan Mace 已提交
484
	if len(args) == 0 {
D
Derek Parker 已提交
485
		return fmt.Errorf("not enough arguments")
D
Dan Mace 已提交
486
	}
487
	id, err := strconv.Atoi(args)
D
Dan Mace 已提交
488 489 490
	if err != nil {
		return err
	}
491
	bp, err := t.client.ClearBreakpoint(id)
D
Dan Mace 已提交
492 493 494
	if err != nil {
		return err
	}
495
	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 已提交
496 497 498
	return nil
}

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

	var locPCs map[uint64]struct{}
506
	if args != "" {
D
Derek Parker 已提交
507
		locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args)
508 509 510 511 512 513 514 515 516
		if err != nil {
			return err
		}
		locPCs = make(map[uint64]struct{})
		for _, loc := range locs {
			locPCs[loc.PC] = struct{}{}
		}
	}

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

524
		_, err := t.client.ClearBreakpoint(bp.ID)
D
Dan Mace 已提交
525
		if err != nil {
526
			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 已提交
527
		}
528
		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 已提交
529 530 531 532
	}
	return nil
}

D
Derek Parker 已提交
533 534
// ByID sorts breakpoints by ID.
type ByID []*api.Breakpoint
D
Dan Mace 已提交
535

D
Derek Parker 已提交
536 537 538
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 已提交
539

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

		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 已提交
561 562
		for i := range bp.Variables {
			attrs = append(attrs, bp.Variables[i])
A
aarzilli 已提交
563 564 565 566
		}
		if len(attrs) > 0 {
			fmt.Printf("\t%s\n", strings.Join(attrs, " "))
		}
D
Dan Mace 已提交
567 568 569 570
	}
	return nil
}

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

A
aarzilli 已提交
578 579 580 581 582 583 584 585 586 587 588 589
	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 已提交
590
			requestedBp.Variables = append(requestedBp.Variables, args[i])
A
aarzilli 已提交
591 592 593 594
		}
	}

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

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

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

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

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

624
func g0f0(fn scopedCmdfunc) cmdfunc {
625
	return func(t *Term, args string) error {
D
Derek Parker 已提交
626
		return fn(t, api.EvalScope{GoroutineID: -1, Frame: 0}, args)
627 628 629 630
	}
}

func g0f0filter(fn scopedFilteringFunc) filteringFunc {
631
	return func(t *Term, filter string) ([]string, error) {
D
Derek Parker 已提交
632
		return fn(t, api.EvalScope{GoroutineID: -1, Frame: 0}, filter)
633 634 635
	}
}

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

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

649 650 651 652 653
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")
654 655
	}

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

D
Derek Parker 已提交
666 667 668 669 670 671
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 已提交
672 673
	data := make([]string, 0, len(vars))
	for _, v := range vars {
D
Derek Parker 已提交
674
		if reg == nil || reg.Match([]byte(v.Name)) {
675
			data = append(data, fmt.Sprintf("%s = %s", v.Name, v.SinglelineString()))
D
Dan Mace 已提交
676 677 678 679 680
		}
	}
	return data
}

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

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

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

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

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

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

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

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

760
func parseStackArgs(argstr string) (int, bool, error) {
761 762 763 764
	var (
		depth = 10
		full  = false
	)
765 766 767 768 769 770 771 772 773 774 775
	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
776
			}
A
aarzilli 已提交
777 778
		}
	}
779
	return depth, full, nil
A
aarzilli 已提交
780 781
}

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

D
Derek Parker 已提交
792
	locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args)
793 794 795 796
	if err != nil {
		return err
	}
	if len(locs) > 1 {
797
		return debugger.AmbiguousLocationError{Location: args, CandidatesLocation: locs}
798
	}
799
	printfile(t, locs[0].File, locs[0].Line, false)
800 801 802
	return nil
}

D
Derek Parker 已提交
803
func (c *Commands) sourceCommand(t *Term, args string) error {
804
	if len(args) == 0 {
805 806 807
		return fmt.Errorf("wrong number of arguments: source <filename>")
	}

D
Derek Parker 已提交
808
	return c.executeFile(t, args)
809 810
}

811 812 813 814 815 816 817
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"
818
	s := strings.Repeat(" ", d+2+len(ind))
819

A
aarzilli 已提交
820 821 822 823 824
	for i := range stack {
		name := "(nil)"
		if stack[i].Function != nil {
			name = stack[i].Function.Name
		}
825
		fmt.Printf(fmtstr, ind, i, stack[i].PC, name)
826
		fmt.Printf("%sat %s:%d\n", s, ShortenFilePath(stack[i].File), stack[i].Line)
827 828

		for j := range stack[i].Arguments {
829
			fmt.Printf("%s    %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
830 831
		}
		for j := range stack[i].Locals {
832
			fmt.Printf("%s    %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
833
		}
A
aarzilli 已提交
834 835 836
	}
}

837
func printcontext(t *Term, state *api.DebuggerState) error {
838 839 840 841 842 843 844 845 846
	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 已提交
847 848 849 850 851 852
	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)
853
		t.Println("=>", "no source available")
D
Dan Mace 已提交
854 855
		return nil
	}
856 857 858 859 860

	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 已提交
861
	}
862 863
	return nil
}
864

865 866 867 868 869 870 871 872 873 874 875 876 877
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 := ""
	if th.Breakpoint.Tracepoint && fn != nil {
		var arg []string
		for _, ar := range fn.Args {
			arg = append(arg, ar.SinglelineString())
D
Derek Parker 已提交
878
		}
879 880 881 882 883 884 885 886 887 888 889 890
		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 已提交
891
	} else {
892 893 894 895 896 897
		fmt.Printf("> %s(%s) %s:%d (hits total:%d)\n",
			fn.Name,
			args,
			ShortenFilePath(th.File),
			th.Line,
			th.Breakpoint.TotalHitCount)
D
Dan Mace 已提交
898 899
	}

900 901
	if th.BreakpointInfo != nil {
		bpi := th.BreakpointInfo
A
aarzilli 已提交
902 903

		if bpi.Goroutine != nil {
904
			writeGoroutineLong(os.Stdout, bpi.Goroutine, "\t")
A
aarzilli 已提交
905 906
		}

907 908 909 910 911 912
		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 已提交
913 914 915 916 917 918 919
		}

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

922
func printfile(t *Term, filename string, line int, showArrow bool) error {
923
	file, err := os.Open(filename)
D
Dan Mace 已提交
924 925 926 927 928
	if err != nil {
		return err
	}
	defer file.Close()

929
	buf := bufio.NewScanner(file)
930
	l := line
D
Dan Mace 已提交
931
	for i := 1; i < l-5; i++ {
932 933
		if !buf.Scan() {
			return nil
D
Dan Mace 已提交
934 935 936
		}
	}

937 938 939 940 941 942
	s := l - 5
	if s < 1 {
		s = 1
	}

	for i := s; i <= l+5; i++ {
943 944
		if !buf.Scan() {
			return nil
D
Dan Mace 已提交
945 946
		}

947
		var prefix string
948
		if showArrow {
949
			prefix = "  "
950
			if i == l {
951
				prefix = "=>"
952
			}
D
Dan Mace 已提交
953 954
		}

955 956
		prefix = fmt.Sprintf("%s%4d:\t", prefix, i)
		t.Println(prefix, buf.Text())
D
Dan Mace 已提交
957 958 959
	}
	return nil
}
D
Derek Parker 已提交
960

D
Derek Parker 已提交
961 962
// ExitRequestError is returned when the user
// exits Delve.
D
Derek Parker 已提交
963 964 965 966 967 968
type ExitRequestError struct{}

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

969
func exitCommand(t *Term, args string) error {
D
Derek Parker 已提交
970 971
	return ExitRequestError{}
}
972

D
Derek Parker 已提交
973 974
// ShortenFilePath take a full file path and attempts to shorten
// it by replacing the current directory to './'.
975
func ShortenFilePath(fullPath string) string {
976 977 978
	workingDir, _ := os.Getwd()
	return strings.Replace(fullPath, workingDir, ".", 1)
}
979

D
Derek Parker 已提交
980
func (c *Commands) executeFile(t *Term, name string) error {
981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997
	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 已提交
998
		cmd := c.Find(cmdstr)
999
		err := cmd(t, args)
1000 1001 1002 1003 1004 1005 1006 1007

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

	return scanner.Err()
}