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

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

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

25
type cmdPrefix int
26

27 28 29 30 31 32 33 34 35 36 37 38 39 40
const (
	noPrefix    = cmdPrefix(0)
	scopePrefix = cmdPrefix(1 << iota)
	onPrefix
)

type callContext struct {
	Prefix     cmdPrefix
	Scope      api.EvalScope
	Breakpoint *api.Breakpoint
}

type cmdfunc func(t *Term, ctx callContext, args string) error
type filteringFunc func(t *Term, ctx callContext, args string) ([]string, error)
D
Dan Mace 已提交
41 42

type command struct {
43 44 45 46
	aliases         []string
	allowedPrefixes cmdPrefix
	helpMsg         string
	cmdFn           cmdfunc
D
Dan Mace 已提交
47 48 49 50 51 52 53 54 55 56 57 58
}

// 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 已提交
59
// Commands represents the commands for Delve terminal process.
D
Dan Mace 已提交
60
type Commands struct {
D
Derek Parker 已提交
61 62 63
	cmds    []command
	lastCmd cmdfunc
	client  service.Client
D
Dan Mace 已提交
64 65
}

D
Derek Parker 已提交
66
// DebugCommands returns a Commands struct with default commands defined.
D
Dan Mace 已提交
67 68 69 70 71
func DebugCommands(client service.Client) *Commands {
	c := &Commands{client: client}

	c.cmds = []command{
		{aliases: []string{"help"}, cmdFn: c.help, helpMsg: "Prints the help message."},
72
		{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "break [name] <linespec>"},
D
Derek Parker 已提交
73
		{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: "Set tracepoint, takes the same arguments as break."},
D
Derek Parker 已提交
74
		{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: "Restart process."},
D
Dan Mace 已提交
75
		{aliases: []string{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
76 77
		{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 已提交
78 79
		{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 已提交
80
		{aliases: []string{"thread", "tr"}, cmdFn: thread, helpMsg: "Switch to the specified thread."},
D
Dan Mace 已提交
81
		{aliases: []string{"clear"}, cmdFn: clear, helpMsg: "Deletes breakpoint."},
82
		{aliases: []string{"clearall"}, cmdFn: clearAll, helpMsg: "clearall [<linespec>]. Deletes all breakpoints. If <linespec> is provided, only matching breakpoints will be deleted."},
83
		{aliases: []string{"goroutines"}, cmdFn: goroutines, helpMsg: "goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)] Print out info for every goroutine."},
84
		{aliases: []string{"goroutine"}, allowedPrefixes: onPrefix | scopePrefix, cmdFn: c.goroutine, helpMsg: "Sets current goroutine."},
D
Dan Mace 已提交
85
		{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
86 87
		{aliases: []string{"print", "p"}, allowedPrefixes: onPrefix | scopePrefix, cmdFn: printVar, helpMsg: "Evaluate a variable."},
		{aliases: []string{"set"}, allowedPrefixes: scopePrefix, cmdFn: setVar, helpMsg: "Changes the value of a variable."},
D
Derek Parker 已提交
88 89
		{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."},
A
aarzilli 已提交
90
		{aliases: []string{"types"}, cmdFn: filterSortAndOutput(types), helpMsg: "Print list of types, optionally filtered by a regexp."},
91 92
		{aliases: []string{"args"}, allowedPrefixes: scopePrefix, cmdFn: filterSortAndOutput(args), helpMsg: "Print function arguments, optionally filtered by a regexp."},
		{aliases: []string{"locals"}, allowedPrefixes: scopePrefix, cmdFn: filterSortAndOutput(locals), helpMsg: "Print function locals, optionally filtered by a regexp."},
D
Derek Parker 已提交
93 94
		{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 已提交
95
		{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."},
96 97 98
		{aliases: []string{"list", "ls"}, allowedPrefixes: scopePrefix, cmdFn: listCommand, helpMsg: "list <linespec>.  Show source around current point or provided linespec."},
		{aliases: []string{"stack", "bt"}, allowedPrefixes: scopePrefix | onPrefix, cmdFn: stackCommand, helpMsg: "stack [<depth>] [-full]. Prints stack."},
		{aliases: []string{"frame"}, allowedPrefixes: scopePrefix, cmdFn: c.frame, helpMsg: "Sets current stack frame (0 is the top of the stack)"},
99
		{aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: "Executes a file containing a list of delve commands"},
100 101
		{aliases: []string{"disassemble", "disass"}, allowedPrefixes: scopePrefix, cmdFn: disassCommand, helpMsg: "Displays disassembly of specific function or address range: disassemble [-a <start> <end>] [-l <locspec>]"},
		{aliases: []string{"on"}, cmdFn: c.onCmd, helpMsg: "on <breakpoint name or id> <command>. Executes command when the specified breakpoint is hit (supported commands: print <expression>, stack [<depth>] [-full] and goroutine)"},
102
		{aliases: []string{"condition", "cond"}, cmdFn: conditionCmd, helpMsg: "cond <breakpoint name or id> <boolean expression>. Specifies that the breakpoint or tracepoint should break only if the boolean expression is true."},
D
Dan Mace 已提交
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
	}

	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 已提交
122
// If it cannot find the command it will default to noCmdAvailable().
D
Dan Mace 已提交
123
// If the command is an empty string it will replay the last command.
124
func (c *Commands) Find(cmdstr string, prefix cmdPrefix) cmdfunc {
D
Dan Mace 已提交
125 126 127 128 129 130 131 132 133 134
	// 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) {
135 136 137
			if prefix != noPrefix && v.allowedPrefixes&prefix == 0 {
				continue
			}
D
Dan Mace 已提交
138 139 140 141 142 143 144 145
			c.lastCmd = v.cmdFn
			return v.cmdFn
		}
	}

	return noCmdAvailable
}

146 147 148 149 150 151 152 153 154
func (c *Commands) CallWithContext(cmdstr, args string, t *Term, ctx callContext) error {
	return c.Find(cmdstr, ctx.Prefix)(t, ctx, args)
}

func (c *Commands) Call(cmdstr, args string, t *Term) error {
	ctx := callContext{Prefix: noPrefix, Scope: api.EvalScope{GoroutineID: -1, Frame: 0}}
	return c.CallWithContext(cmdstr, args, t, ctx)
}

155 156 157 158 159 160 161 162 163
// 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...)
		}
	}
}

164
func noCmdAvailable(t *Term, ctx callContext, args string) error {
D
Derek Parker 已提交
165
	return fmt.Errorf("command not available")
D
Dan Mace 已提交
166 167
}

168
func nullCommand(t *Term, ctx callContext, args string) error {
D
Dan Mace 已提交
169 170 171
	return nil
}

172
func (c *Commands) help(t *Term, ctx callContext, args string) error {
D
Dan Mace 已提交
173
	fmt.Println("The following commands are available:")
D
Derek Parker 已提交
174 175
	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 0, '-', 0)
D
Dan Mace 已提交
176
	for _, cmd := range c.cmds {
D
Derek Parker 已提交
177 178 179 180 181
		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 已提交
182
	}
D
Derek Parker 已提交
183
	return w.Flush()
D
Dan Mace 已提交
184 185
}

I
Ilia Choly 已提交
186
type byThreadID []*api.Thread
I
Ilia Choly 已提交
187

I
Ilia Choly 已提交
188 189 190
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 已提交
191

192
func threads(t *Term, ctx callContext, args string) error {
193
	threads, err := t.client.ListThreads()
D
Dan Mace 已提交
194 195 196
	if err != nil {
		return err
	}
197
	state, err := t.client.GetState()
D
Dan Mace 已提交
198 199 200
	if err != nil {
		return err
	}
I
Ilia Choly 已提交
201
	sort.Sort(byThreadID(threads))
D
Dan Mace 已提交
202 203 204 205 206 207 208
	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",
209
				prefix, th.ID, th.PC, ShortenFilePath(th.File),
D
Dan Mace 已提交
210 211
				th.Line, th.Function.Name)
		} else {
212
			fmt.Printf("%sThread %s\n", prefix, formatThread(th))
D
Dan Mace 已提交
213 214 215 216 217
		}
	}
	return nil
}

218
func thread(t *Term, ctx callContext, args string) error {
219 220 221
	if len(args) == 0 {
		return fmt.Errorf("you must specify a thread")
	}
222
	tid, err := strconv.Atoi(args)
D
Dan Mace 已提交
223 224 225
	if err != nil {
		return err
	}
226
	oldState, err := t.client.GetState()
D
Dan Mace 已提交
227 228 229
	if err != nil {
		return err
	}
230
	newState, err := t.client.SwitchThread(tid)
D
Dan Mace 已提交
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
	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 已提交
247 248 249 250 251 252
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 }

253
func goroutines(t *Term, ctx callContext, argstr string) error {
254
	args := strings.Split(argstr, " ")
255 256 257 258 259 260 261 262 263 264 265 266 267
	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
268 269
		case "":
			// nothing to do
270
		default:
D
Derek Parker 已提交
271
			return fmt.Errorf("wrong argument: '%s'", args[0])
272 273 274 275
		}
	default:
		return fmt.Errorf("too many arguments")
	}
276
	state, err := t.client.GetState()
277 278 279
	if err != nil {
		return err
	}
280
	gs, err := t.client.ListGoroutines()
D
Dan Mace 已提交
281 282 283
	if err != nil {
		return err
	}
I
Ilia Choly 已提交
284
	sort.Sort(byGoroutineID(gs))
D
Dan Mace 已提交
285 286
	fmt.Printf("[%d goroutines]\n", len(gs))
	for _, g := range gs {
287
		prefix := "  "
288
		if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
289 290
			prefix = "* "
		}
291
		fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl))
292 293 294 295
	}
	return nil
}

296 297
func (c *Commands) goroutine(t *Term, ctx callContext, argstr string) error {
	args := strings.SplitN(argstr, " ", 3)
298

299 300 301
	if ctx.Prefix == onPrefix {
		if len(args) != 1 || args[0] != "" {
			return errors.New("too many arguments to goroutine")
302
		}
303
		ctx.Breakpoint.Goroutine = true
304 305 306
		return nil
	}

307 308 309 310
	switch len(args) {
	case 1:
		if ctx.Prefix == scopePrefix {
			return errors.New("no command passed to goroutine")
311
		}
312 313 314 315
		if args[0] == "" {
			return printscope(t)
		} else {
			gid, err := strconv.Atoi(argstr)
316 317 318
			if err != nil {
				return err
			}
319 320

			oldState, err := t.client.GetState()
321 322 323
			if err != nil {
				return err
			}
324
			newState, err := t.client.SwitchGoroutine(gid)
325 326 327
			if err != nil {
				return err
			}
328 329

			fmt.Printf("Switched from %d to %d (thread %d)\n", oldState.SelectedGoroutine.ID, gid, newState.CurrentThread.ID)
330
			return nil
331
		}
332 333
	case 2:
		args = append(args, "")
334 335
	}

336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
	var err error
	ctx.Prefix = scopePrefix
	ctx.Scope.GoroutineID, err = strconv.Atoi(args[0])
	if err != nil {
		return err
	}
	return c.CallWithContext(args[1], args[2], t, ctx)
}

func (c *Commands) frame(t *Term, ctx callContext, args string) error {
	v := strings.SplitN(args, " ", 3)

	var err error

	switch len(v) {
	case 0, 1:
		return errors.New("not enough arguments")
	case 2:
		v = append(v, "")
	}

	ctx.Prefix = scopePrefix
	ctx.Scope.Frame, err = strconv.Atoi(v[0])
	if err != nil {
		return err
	}
	return c.CallWithContext(v[1], v[2], t, ctx)
363 364
}

365 366
func printscope(t *Term) error {
	state, err := t.client.GetState()
367 368
	if err != nil {
		return err
D
Dan Mace 已提交
369
	}
370

371 372 373 374
	fmt.Printf("Thread %s\n", formatThread(state.CurrentThread))
	if state.SelectedGoroutine != nil {
		writeGoroutineLong(os.Stdout, state.SelectedGoroutine, "")
	}
D
Dan Mace 已提交
375 376 377
	return nil
}

378 379 380 381
func formatThread(th *api.Thread) string {
	if th == nil {
		return "<nil>"
	}
382
	return fmt.Sprintf("%d at %s:%d", th.ID, ShortenFilePath(th.File), th.Line)
383 384
}

385 386 387 388 389 390 391 392 393 394 395 396 397
type formatGoroutineLoc int

const (
	fglRuntimeCurrent = formatGoroutineLoc(iota)
	fglUserCurrent
	fglGo
)

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

func formatGoroutine(g *api.Goroutine, fgl formatGoroutineLoc) string {
402 403 404
	if g == nil {
		return "<nil>"
	}
405 406 407 408 409
	var locname string
	var loc api.Location
	switch fgl {
	case fglRuntimeCurrent:
		locname = "Runtime"
410
		loc = g.CurrentLoc
411 412
	case fglUserCurrent:
		locname = "User"
413
		loc = g.UserCurrentLoc
414 415
	case fglGo:
		locname = "Go"
416
		loc = g.GoStatementLoc
A
aarzilli 已提交
417
	}
418 419 420 421 422 423
	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,
424 425 426
		prefix, formatLocation(g.CurrentLoc),
		prefix, formatLocation(g.UserCurrentLoc),
		prefix, formatLocation(g.GoStatementLoc))
A
aarzilli 已提交
427 428
}

429
func restart(t *Term, ctx callContext, args string) error {
430
	if err := t.client.Restart(); err != nil {
D
Derek Parker 已提交
431 432
		return err
	}
433
	fmt.Println("Process restarted with PID", t.client.ProcessPid())
D
Derek Parker 已提交
434 435 436
	return nil
}

437
func cont(t *Term, ctx callContext, args string) error {
438
	stateChan := t.client.Continue()
D
Derek Parker 已提交
439
	for state := range stateChan {
A
aarzilli 已提交
440 441 442
		if state.Err != nil {
			return state.Err
		}
443
		printcontext(t, state)
D
Dan Mace 已提交
444 445 446 447
	}
	return nil
}

448
func step(t *Term, ctx callContext, args string) error {
449
	state, err := t.client.Step()
D
Dan Mace 已提交
450 451 452
	if err != nil {
		return err
	}
453
	printcontext(t, state)
D
Dan Mace 已提交
454 455 456
	return nil
}

457
func stepInstruction(t *Term, ctx callContext, args string) error {
458 459 460 461 462 463 464 465
	state, err := t.client.StepInstruction()
	if err != nil {
		return err
	}
	printcontext(t, state)
	return nil
}

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

475
func clear(t *Term, ctx callContext, args string) error {
D
Dan Mace 已提交
476
	if len(args) == 0 {
D
Derek Parker 已提交
477
		return fmt.Errorf("not enough arguments")
D
Dan Mace 已提交
478
	}
479
	id, err := strconv.Atoi(args)
480 481 482 483 484
	var bp *api.Breakpoint
	if err == nil {
		bp, err = t.client.ClearBreakpoint(id)
	} else {
		bp, err = t.client.ClearBreakpointByName(args)
D
Dan Mace 已提交
485 486 487 488
	}
	if err != nil {
		return err
	}
489
	fmt.Printf("%s cleared at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp))
D
Dan Mace 已提交
490 491 492
	return nil
}

493
func clearAll(t *Term, ctx callContext, args string) error {
494
	breakPoints, err := t.client.ListBreakpoints()
D
Dan Mace 已提交
495 496 497
	if err != nil {
		return err
	}
498 499

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

D
Dan Mace 已提交
511
	for _, bp := range breakPoints {
512 513 514 515 516 517
		if locPCs != nil {
			if _, ok := locPCs[bp.Addr]; !ok {
				continue
			}
		}

518 519 520 521
		if bp.ID < 0 {
			continue
		}

522
		_, err := t.client.ClearBreakpoint(bp.ID)
D
Dan Mace 已提交
523
		if err != nil {
524
			fmt.Printf("Couldn't delete %s at %s: %s\n", formatBreakpointName(bp, false), formatBreakpointLocation(bp), err)
D
Dan Mace 已提交
525
		}
526
		fmt.Printf("%s cleared at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp))
D
Dan Mace 已提交
527 528 529 530
	}
	return nil
}

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

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

538
func breakpoints(t *Term, ctx callContext, args string) error {
539
	breakPoints, err := t.client.ListBreakpoints()
D
Dan Mace 已提交
540 541 542
	if err != nil {
		return err
	}
D
Derek Parker 已提交
543
	sort.Sort(ByID(breakPoints))
D
Dan Mace 已提交
544
	for _, bp := range breakPoints {
545
		fmt.Printf("%s at %v (%d)\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp), bp.TotalHitCount)
A
aarzilli 已提交
546 547

		var attrs []string
548 549 550
		if bp.Cond != "" {
			attrs = append(attrs, fmt.Sprintf("\tcond %s", bp.Cond))
		}
A
aarzilli 已提交
551
		if bp.Stacktrace > 0 {
552
			attrs = append(attrs, fmt.Sprintf("\tstack %d", bp.Stacktrace))
A
aarzilli 已提交
553 554
		}
		if bp.Goroutine {
555
			attrs = append(attrs, "\tgoroutine")
A
aarzilli 已提交
556
		}
D
Derek Parker 已提交
557
		for i := range bp.Variables {
558
			attrs = append(attrs, fmt.Sprintf("\tprint %s", bp.Variables[i]))
A
aarzilli 已提交
559 560
		}
		if len(attrs) > 0 {
561
			fmt.Printf("%s\n", strings.Join(attrs, "\n"))
A
aarzilli 已提交
562
		}
D
Dan Mace 已提交
563 564 565 566
	}
	return nil
}

567
func setBreakpoint(t *Term, tracepoint bool, argstr string) error {
568
	args := strings.SplitN(argstr, " ", 2)
D
Dan Mace 已提交
569

570 571 572 573 574 575 576 577 578 579 580
	requestedBp := &api.Breakpoint{}
	locspec := ""
	switch len(args) {
	case 1:
		locspec = argstr
	case 2:
		if api.ValidBreakpointName(args[0]) == nil {
			requestedBp.Name = args[0]
			locspec = args[1]
		} else {
			locspec = argstr
A
aarzilli 已提交
581
		}
582 583
	default:
		return fmt.Errorf("address required")
A
aarzilli 已提交
584 585 586
	}

	requestedBp.Tracepoint = tracepoint
587
	locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, locspec)
D
Dan Mace 已提交
588
	if err != nil {
589 590 591 592 593 594 595 596 597 598
		if requestedBp.Name == "" {
			return err
		}
		requestedBp.Name = ""
		locspec = argstr
		var err2 error
		locs, err2 = t.client.FindLocation(api.EvalScope{-1, 0}, locspec)
		if err2 != nil {
			return err
		}
A
aarzilli 已提交
599
	}
600 601 602
	for _, loc := range locs {
		requestedBp.Addr = loc.PC

603
		bp, err := t.client.CreateBreakpoint(requestedBp)
604 605 606 607
		if err != nil {
			return err
		}

608
		fmt.Printf("%s set at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp))
609
	}
D
Dan Mace 已提交
610 611 612
	return nil
}

613
func breakpoint(t *Term, ctx callContext, args string) error {
614
	return setBreakpoint(t, false, args)
A
aarzilli 已提交
615 616
}

617
func tracepoint(t *Term, ctx callContext, args string) error {
618
	return setBreakpoint(t, true, args)
A
aarzilli 已提交
619 620
}

621
func printVar(t *Term, ctx callContext, args string) error {
D
Dan Mace 已提交
622
	if len(args) == 0 {
D
Derek Parker 已提交
623
		return fmt.Errorf("not enough arguments")
D
Dan Mace 已提交
624
	}
625 626 627 628 629
	if ctx.Prefix == onPrefix {
		ctx.Breakpoint.Variables = append(ctx.Breakpoint.Variables, args)
		return nil
	}
	val, err := t.client.EvalVariable(ctx.Scope, args)
D
Dan Mace 已提交
630 631 632
	if err != nil {
		return err
	}
633 634

	fmt.Println(val.MultilineString(""))
D
Dan Mace 已提交
635 636 637
	return nil
}

638
func setVar(t *Term, ctx callContext, args string) error {
639 640 641 642
	// 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")
643 644
	}

645 646 647 648 649 650 651
	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:]
652
	return t.client.SetVariable(ctx.Scope, lexpr, rexpr)
653 654
}

D
Derek Parker 已提交
655 656 657 658 659 660
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 已提交
661 662
	data := make([]string, 0, len(vars))
	for _, v := range vars {
D
Derek Parker 已提交
663
		if reg == nil || reg.Match([]byte(v.Name)) {
664
			data = append(data, fmt.Sprintf("%s = %s", v.Name, v.SinglelineString()))
D
Dan Mace 已提交
665 666 667 668 669
		}
	}
	return data
}

670
func sources(t *Term, ctx callContext, filter string) ([]string, error) {
671
	return t.client.ListSources(filter)
D
Derek Parker 已提交
672
}
D
Dan Mace 已提交
673

674
func funcs(t *Term, ctx callContext, filter string) ([]string, error) {
675
	return t.client.ListFunctions(filter)
D
Derek Parker 已提交
676
}
D
Dan Mace 已提交
677

678
func types(t *Term, ctx callContext, filter string) ([]string, error) {
A
aarzilli 已提交
679 680 681
	return t.client.ListTypes(filter)
}

682 683
func args(t *Term, ctx callContext, filter string) ([]string, error) {
	vars, err := t.client.ListFunctionArgs(ctx.Scope)
D
Derek Parker 已提交
684 685 686 687 688
	if err != nil {
		return nil, err
	}
	return filterVariables(vars, filter), nil
}
D
Dan Mace 已提交
689

690 691
func locals(t *Term, ctx callContext, filter string) ([]string, error) {
	locals, err := t.client.ListLocalVariables(ctx.Scope)
D
Derek Parker 已提交
692 693 694 695 696
	if err != nil {
		return nil, err
	}
	return filterVariables(locals, filter), nil
}
D
Dan Mace 已提交
697

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

706
func regs(t *Term, ctx callContext, args string) error {
707
	regs, err := t.client.ListRegisters()
D
Derek Parker 已提交
708 709 710 711 712 713
	if err != nil {
		return err
	}
	fmt.Println(regs)
	return nil
}
714

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

736
func stackCommand(t *Term, ctx callContext, args string) error {
737 738 739 740
	depth, full, err := parseStackArgs(args)
	if err != nil {
		return err
	}
741 742 743 744 745
	if ctx.Prefix == onPrefix {
		ctx.Breakpoint.Stacktrace = depth
		return nil
	}
	stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, depth, full)
746 747 748 749 750 751
	if err != nil {
		return err
	}
	printStack(stack, "")
	return nil
}
A
aarzilli 已提交
752

753
func parseStackArgs(argstr string) (int, bool, error) {
754 755 756 757
	var (
		depth = 10
		full  = false
	)
758 759 760 761 762 763 764 765 766 767 768
	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
769
			}
A
aarzilli 已提交
770 771
		}
	}
772
	return depth, full, nil
A
aarzilli 已提交
773 774
}

775 776 777 778 779 780 781 782 783 784 785 786 787
func listCommand(t *Term, ctx callContext, args string) error {
	if ctx.Prefix == scopePrefix {
		locs, err := t.client.Stacktrace(ctx.Scope.GoroutineID, ctx.Scope.Frame, false)
		if err != nil {
			return err
		}
		if ctx.Scope.Frame >= len(locs) {
			return fmt.Errorf("Frame %d does not exist in goroutine %d", ctx.Scope.Frame, ctx.Scope.GoroutineID)
		}
		loc := locs[ctx.Scope.Frame]
		return printfile(t, loc.File, loc.Line, true)
	}

788
	if len(args) == 0 {
789
		state, err := t.client.GetState()
790 791 792
		if err != nil {
			return err
		}
793
		printcontext(t, state)
794 795 796
		return nil
	}

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

808
func (c *Commands) sourceCommand(t *Term, ctx callContext, args string) error {
809
	if len(args) == 0 {
810 811 812
		return fmt.Errorf("wrong number of arguments: source <filename>")
	}

D
Derek Parker 已提交
813
	return c.executeFile(t, args)
814 815
}

A
aarzilli 已提交
816 817
var disasmUsageError = errors.New("wrong number of arguments: disassemble [-a <start> <end>] [-l <locspec>]")

818
func disassCommand(t *Term, ctx callContext, args string) error {
A
aarzilli 已提交
819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834
	var cmd, rest string

	if args != "" {
		argv := strings.SplitN(args, " ", 2)
		if len(argv) != 2 {
			return disasmUsageError
		}
		cmd = argv[0]
		rest = argv[1]
	}

	var disasm api.AsmInstructions
	var disasmErr error

	switch cmd {
	case "":
835
		locs, err := t.client.FindLocation(ctx.Scope, "+0")
A
aarzilli 已提交
836 837 838
		if err != nil {
			return err
		}
839
		disasm, disasmErr = t.client.DisassemblePC(ctx.Scope, locs[0].PC, api.IntelFlavour)
A
aarzilli 已提交
840 841 842 843 844 845 846 847 848 849 850 851 852
	case "-a":
		v := strings.SplitN(rest, " ", 2)
		if len(v) != 2 {
			return disasmUsageError
		}
		startpc, err := strconv.ParseInt(v[0], 0, 64)
		if err != nil {
			return fmt.Errorf("wrong argument: %s is not a number", v[0])
		}
		endpc, err := strconv.ParseInt(v[1], 0, 64)
		if err != nil {
			return fmt.Errorf("wrong argument: %s is not a number", v[1])
		}
853
		disasm, disasmErr = t.client.DisassembleRange(ctx.Scope, uint64(startpc), uint64(endpc), api.IntelFlavour)
A
aarzilli 已提交
854
	case "-l":
855
		locs, err := t.client.FindLocation(ctx.Scope, rest)
A
aarzilli 已提交
856 857 858 859 860 861
		if err != nil {
			return err
		}
		if len(locs) != 1 {
			return errors.New("expression specifies multiple locations")
		}
862
		disasm, disasmErr = t.client.DisassemblePC(ctx.Scope, locs[0].PC, api.IntelFlavour)
A
aarzilli 已提交
863 864 865 866 867 868 869 870 871 872 873 874 875 876
	default:
		return disasmUsageError
	}

	if disasmErr != nil {
		return disasmErr
	}

	fmt.Printf("printing\n")
	DisasmPrint(disasm, os.Stdout)

	return nil
}

877
func digits(n int) int {
878 879 880
	if n <= 0 {
		return 1
	}
881 882 883 884
	return int(math.Floor(math.Log10(float64(n)))) + 1
}

func printStack(stack []api.Stackframe, ind string) {
885 886 887
	if len(stack) == 0 {
		return
	}
888 889
	d := digits(len(stack) - 1)
	fmtstr := "%s%" + strconv.Itoa(d) + "d  0x%016x in %s\n"
890
	s := strings.Repeat(" ", d+2+len(ind))
891

A
aarzilli 已提交
892 893 894 895 896
	for i := range stack {
		name := "(nil)"
		if stack[i].Function != nil {
			name = stack[i].Function.Name
		}
897
		fmt.Printf(fmtstr, ind, i, stack[i].PC, name)
898
		fmt.Printf("%sat %s:%d\n", s, ShortenFilePath(stack[i].File), stack[i].Line)
899 900

		for j := range stack[i].Arguments {
901
			fmt.Printf("%s    %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
902 903
		}
		for j := range stack[i].Locals {
904
			fmt.Printf("%s    %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
905
		}
A
aarzilli 已提交
906 907 908
	}
}

909
func printcontext(t *Term, state *api.DebuggerState) error {
910 911 912 913 914 915 916 917 918
	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 已提交
919 920 921 922 923 924
	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)
925
		t.Println("=>", "no source available")
D
Dan Mace 已提交
926 927
		return nil
	}
928 929 930

	printcontextThread(t, state.CurrentThread)

931
	if state.CurrentThread.Breakpoint == nil || !state.CurrentThread.Breakpoint.Tracepoint || state.CurrentThread.BreakpointInfo == nil {
932
		return printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true)
D
Derek Parker 已提交
933
	}
934 935
	return nil
}
936

937 938 939 940
func printcontextThread(t *Term, th *api.Thread) {
	fn := th.Function

	if th.Breakpoint == nil {
A
aarzilli 已提交
941
		fmt.Printf("> %s() %s:%d (PC: %#v)\n", fn.Name, ShortenFilePath(th.File), th.Line, th.PC)
942 943 944 945
		return
	}

	args := ""
946
	if th.Breakpoint.Tracepoint && th.BreakpointInfo != nil {
947
		var arg []string
948
		for _, ar := range th.BreakpointInfo.Arguments {
949
			arg = append(arg, ar.SinglelineString())
D
Derek Parker 已提交
950
		}
951 952 953
		args = strings.Join(arg, ", ")
	}

954 955 956 957 958
	bpname := ""
	if th.Breakpoint.Name != "" {
		bpname = fmt.Sprintf("[%s] ", th.Breakpoint.Name)
	}

959
	if hitCount, ok := th.Breakpoint.HitCount[strconv.Itoa(th.GoroutineID)]; ok {
960 961
		fmt.Printf("> %s%s(%s) %s:%d (hits goroutine(%d):%d total:%d) (PC: %#v)\n",
			bpname,
962 963 964 965 966 967
			fn.Name,
			args,
			ShortenFilePath(th.File),
			th.Line,
			th.GoroutineID,
			hitCount,
A
aarzilli 已提交
968 969
			th.Breakpoint.TotalHitCount,
			th.PC)
D
Derek Parker 已提交
970
	} else {
971 972
		fmt.Printf("> %s%s(%s) %s:%d (hits total:%d) (PC: %#v)\n",
			bpname,
973 974 975 976
			fn.Name,
			args,
			ShortenFilePath(th.File),
			th.Line,
A
aarzilli 已提交
977 978
			th.Breakpoint.TotalHitCount,
			th.PC)
D
Dan Mace 已提交
979 980
	}

981 982
	if th.BreakpointInfo != nil {
		bpi := th.BreakpointInfo
A
aarzilli 已提交
983 984

		if bpi.Goroutine != nil {
985
			writeGoroutineLong(os.Stdout, bpi.Goroutine, "\t")
A
aarzilli 已提交
986 987
		}

988 989 990 991 992 993
		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 已提交
994 995 996 997 998 999 1000
		}

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

1003
func printfile(t *Term, filename string, line int, showArrow bool) error {
1004
	file, err := os.Open(filename)
D
Dan Mace 已提交
1005 1006 1007 1008 1009
	if err != nil {
		return err
	}
	defer file.Close()

1010
	buf := bufio.NewScanner(file)
1011
	l := line
D
Dan Mace 已提交
1012
	for i := 1; i < l-5; i++ {
1013 1014
		if !buf.Scan() {
			return nil
D
Dan Mace 已提交
1015 1016 1017
		}
	}

1018 1019 1020 1021 1022 1023
	s := l - 5
	if s < 1 {
		s = 1
	}

	for i := s; i <= l+5; i++ {
1024 1025
		if !buf.Scan() {
			return nil
D
Dan Mace 已提交
1026 1027
		}

1028
		var prefix string
1029
		if showArrow {
1030
			prefix = "  "
1031
			if i == l {
1032
				prefix = "=>"
1033
			}
D
Dan Mace 已提交
1034 1035
		}

1036 1037
		prefix = fmt.Sprintf("%s%4d:\t", prefix, i)
		t.Println(prefix, buf.Text())
D
Dan Mace 已提交
1038 1039 1040
	}
	return nil
}
D
Derek Parker 已提交
1041

D
Derek Parker 已提交
1042 1043
// ExitRequestError is returned when the user
// exits Delve.
D
Derek Parker 已提交
1044 1045 1046 1047 1048 1049
type ExitRequestError struct{}

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

1050
func exitCommand(t *Term, ctx callContext, args string) error {
D
Derek Parker 已提交
1051 1052
	return ExitRequestError{}
}
1053

1054 1055 1056 1057 1058 1059 1060 1061 1062
func getBreakpointByIDOrName(t *Term, arg string) (bp *api.Breakpoint, err error) {
	if id, err := strconv.Atoi(arg); err == nil {
		bp, err = t.client.GetBreakpoint(id)
	} else {
		bp, err = t.client.GetBreakpointByName(arg)
	}
	return
}

1063
func (c *Commands) onCmd(t *Term, ctx callContext, argstr string) error {
1064 1065 1066
	args := strings.SplitN(argstr, " ", 3)

	if len(args) < 2 {
1067 1068 1069 1070 1071
		return errors.New("not enough arguments")
	}

	if len(args) < 3 {
		args = append(args, "")
1072 1073 1074 1075 1076 1077 1078
	}

	bp, err := getBreakpointByIDOrName(t, args[0])
	if err != nil {
		return err
	}

1079 1080 1081 1082 1083
	ctx.Prefix = onPrefix
	ctx.Breakpoint = bp
	err = c.CallWithContext(args[1], args[2], t, ctx)
	if err != nil {
		return err
1084
	}
1085
	return t.client.AmendBreakpoint(ctx.Breakpoint)
1086 1087
}

1088
func conditionCmd(t *Term, ctx callContext, argstr string) error {
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103
	args := strings.SplitN(argstr, " ", 2)

	if len(args) < 2 {
		return fmt.Errorf("not enough arguments")
	}

	bp, err := getBreakpointByIDOrName(t, args[0])
	if err != nil {
		return err
	}
	bp.Cond = args[1]

	return t.client.AmendBreakpoint(bp)
}

D
Derek Parker 已提交
1104 1105
// ShortenFilePath take a full file path and attempts to shorten
// it by replacing the current directory to './'.
1106
func ShortenFilePath(fullPath string) string {
1107 1108 1109
	workingDir, _ := os.Getwd()
	return strings.Replace(fullPath, workingDir, ".", 1)
}
1110

D
Derek Parker 已提交
1111
func (c *Commands) executeFile(t *Term, name string) error {
1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129
	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)

1130
		if err := c.Call(cmdstr, args, t); err != nil {
1131 1132 1133 1134 1135 1136
			fmt.Printf("%s:%d: %v\n", name, lineno, err)
		}
	}

	return scanner.Err()
}
1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160

func formatBreakpointName(bp *api.Breakpoint, upcase bool) string {
	thing := "breakpoint"
	if bp.Tracepoint {
		thing = "tracepoint"
	}
	if upcase {
		thing = strings.Title(thing)
	}
	id := bp.Name
	if id == "" {
		id = strconv.Itoa(bp.ID)
	}
	return fmt.Sprintf("%s %s", thing, id)
}

func formatBreakpointLocation(bp *api.Breakpoint) string {
	p := ShortenFilePath(bp.File)
	if bp.FunctionName != "" {
		return fmt.Sprintf("%#v for %s() %s:%d", bp.Addr, bp.FunctionName, p, bp.Line)
	} else {
		return fmt.Sprintf("%#v for %s:%d", bp.Addr, p, bp.Line)
	}
}