command.go 31.2 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
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
D
Dan Mace 已提交
40 41

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

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

65 66 67 68 69
var (
	LongLoadConfig  = api.LoadConfig{true, 1, 64, 64, -1}
	ShortLoadConfig = api.LoadConfig{false, 0, 64, 0, 3}
)

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

	c.cmds = []command{
75
		{aliases: []string{"help", "h"}, cmdFn: c.help, helpMsg: "Prints the help message."},
76
		{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "break [name] <linespec>"},
D
Derek Parker 已提交
77
		{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: "Set tracepoint, takes the same arguments as break."},
D
Derek Parker 已提交
78
		{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: "Restart process."},
D
Dan Mace 已提交
79
		{aliases: []string{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
80 81
		{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 已提交
82 83
		{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 已提交
84
		{aliases: []string{"thread", "tr"}, cmdFn: thread, helpMsg: "Switch to the specified thread."},
D
Dan Mace 已提交
85
		{aliases: []string{"clear"}, cmdFn: clear, helpMsg: "Deletes breakpoint."},
86
		{aliases: []string{"clearall"}, cmdFn: clearAll, helpMsg: "clearall [<linespec>]. Deletes all breakpoints. If <linespec> is provided, only matching breakpoints will be deleted."},
87
		{aliases: []string{"goroutines"}, cmdFn: goroutines, helpMsg: "goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)] Print out info for every goroutine."},
88
		{aliases: []string{"goroutine"}, allowedPrefixes: onPrefix | scopePrefix, cmdFn: c.goroutine, helpMsg: "Sets current goroutine."},
D
Dan Mace 已提交
89
		{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
90 91
		{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."},
92 93 94 95 96 97
		{aliases: []string{"sources"}, cmdFn: sources, helpMsg: "Print list of source files, optionally filtered by a regexp."},
		{aliases: []string{"funcs"}, cmdFn: funcs, helpMsg: "Print list of functions, optionally filtered by a regexp."},
		{aliases: []string{"types"}, cmdFn: types, helpMsg: "Print list of types, optionally filtered by a regexp."},
		{aliases: []string{"args"}, allowedPrefixes: scopePrefix | onPrefix, cmdFn: args, helpMsg: "args [-v] <filter>. Print function arguments, optionally filtered by a regexp."},
		{aliases: []string{"locals"}, allowedPrefixes: scopePrefix | onPrefix, cmdFn: locals, helpMsg: "locals [-v] <filter>. Print function locals, optionally filtered by a regexp."},
		{aliases: []string{"vars"}, cmdFn: vars, helpMsg: "vars [-v] <filter>. Print package variables, optionally filtered by a regexp."},
D
Derek Parker 已提交
98
		{aliases: []string{"regs"}, cmdFn: regs, helpMsg: "Print contents of CPU registers."},
D
Derek Parker 已提交
99
		{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."},
100 101
		{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."},
102
		{aliases: []string{"frame"}, allowedPrefixes: scopePrefix, cmdFn: c.frame, helpMsg: "frame <frame index> <command>. Executes command on the specified stack frame"},
103
		{aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: "Executes a file containing a list of delve commands"},
104 105
		{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)"},
106
		{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 已提交
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
	}

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

	return noCmdAvailable
}

150 151 152 153 154 155 156 157 158
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)
}

159 160 161 162 163 164 165 166 167
// 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...)
		}
	}
}

168
func noCmdAvailable(t *Term, ctx callContext, args string) error {
D
Derek Parker 已提交
169
	return fmt.Errorf("command not available")
D
Dan Mace 已提交
170 171
}

172
func nullCommand(t *Term, ctx callContext, args string) error {
D
Dan Mace 已提交
173 174 175
	return nil
}

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

I
Ilia Choly 已提交
190
type byThreadID []*api.Thread
I
Ilia Choly 已提交
191

I
Ilia Choly 已提交
192 193 194
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 已提交
195

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

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

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

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

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

311 312 313 314
	switch len(args) {
	case 1:
		if ctx.Prefix == scopePrefix {
			return errors.New("no command passed to goroutine")
315
		}
316 317
		if args[0] == "" {
			return printscope(t)
H
Hubert Krauze 已提交
318 319 320 321 322
		}
		gid, err := strconv.Atoi(argstr)
		if err != nil {
			return err
		}
323

H
Hubert Krauze 已提交
324 325 326 327 328 329 330
		oldState, err := t.client.GetState()
		if err != nil {
			return err
		}
		newState, err := t.client.SwitchGoroutine(gid)
		if err != nil {
			return err
331
		}
H
Hubert Krauze 已提交
332 333 334

		fmt.Printf("Switched from %d to %d (thread %d)\n", oldState.SelectedGoroutine.ID, gid, newState.CurrentThread.ID)
		return nil
335 336
	case 2:
		args = append(args, "")
337 338
	}

339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
	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)

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

H
Hubert Krauze 已提交
358
	var err error
359 360 361 362 363 364
	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)
365 366
}

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

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

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

387 388 389 390 391 392 393 394 395 396 397 398 399
type formatGoroutineLoc int

const (
	fglRuntimeCurrent = formatGoroutineLoc(iota)
	fglUserCurrent
	fglGo
)

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

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

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

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

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

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

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

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

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

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

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

520 521 522 523
		if bp.ID < 0 {
			continue
		}

524
		_, err := t.client.ClearBreakpoint(bp.ID)
D
Dan Mace 已提交
525
		if err != nil {
526
			fmt.Printf("Couldn't delete %s at %s: %s\n", formatBreakpointName(bp, false), formatBreakpointLocation(bp), err)
D
Dan Mace 已提交
527
		}
528
		fmt.Printf("%s cleared at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp))
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, ctx callContext, 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 {
547
		fmt.Printf("%s at %v (%d)\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp), bp.TotalHitCount)
A
aarzilli 已提交
548 549

		var attrs []string
550 551 552
		if bp.Cond != "" {
			attrs = append(attrs, fmt.Sprintf("\tcond %s", bp.Cond))
		}
A
aarzilli 已提交
553
		if bp.Stacktrace > 0 {
554
			attrs = append(attrs, fmt.Sprintf("\tstack %d", bp.Stacktrace))
A
aarzilli 已提交
555 556
		}
		if bp.Goroutine {
557
			attrs = append(attrs, "\tgoroutine")
A
aarzilli 已提交
558
		}
559 560 561 562 563 564 565 566 567 568 569 570 571 572
		if bp.LoadArgs != nil {
			if *(bp.LoadArgs) == LongLoadConfig {
				attrs = append(attrs, "\targs -v")
			} else {
				attrs = append(attrs, "\targs")
			}
		}
		if bp.LoadLocals != nil {
			if *(bp.LoadLocals) == LongLoadConfig {
				attrs = append(attrs, "\tlocals -v")
			} else {
				attrs = append(attrs, "\tlocals")
			}
		}
D
Derek Parker 已提交
573
		for i := range bp.Variables {
574
			attrs = append(attrs, fmt.Sprintf("\tprint %s", bp.Variables[i]))
A
aarzilli 已提交
575 576
		}
		if len(attrs) > 0 {
577
			fmt.Printf("%s\n", strings.Join(attrs, "\n"))
A
aarzilli 已提交
578
		}
D
Dan Mace 已提交
579 580 581 582
	}
	return nil
}

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

586 587 588 589 590 591 592 593 594 595 596
	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 已提交
597
		}
598 599
	default:
		return fmt.Errorf("address required")
A
aarzilli 已提交
600 601 602
	}

	requestedBp.Tracepoint = tracepoint
603
	locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, locspec)
D
Dan Mace 已提交
604
	if err != nil {
605 606 607 608 609 610
		if requestedBp.Name == "" {
			return err
		}
		requestedBp.Name = ""
		locspec = argstr
		var err2 error
H
Hubert Krauze 已提交
611
		locs, err2 = t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, locspec)
612 613 614
		if err2 != nil {
			return err
		}
A
aarzilli 已提交
615
	}
616 617 618
	for _, loc := range locs {
		requestedBp.Addr = loc.PC

619
		bp, err := t.client.CreateBreakpoint(requestedBp)
620 621 622 623
		if err != nil {
			return err
		}

624
		fmt.Printf("%s set at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp))
625
	}
D
Dan Mace 已提交
626 627 628
	return nil
}

629
func breakpoint(t *Term, ctx callContext, args string) error {
630
	return setBreakpoint(t, false, args)
A
aarzilli 已提交
631 632
}

633
func tracepoint(t *Term, ctx callContext, args string) error {
634
	return setBreakpoint(t, true, args)
A
aarzilli 已提交
635 636
}

637
func printVar(t *Term, ctx callContext, args string) error {
D
Dan Mace 已提交
638
	if len(args) == 0 {
D
Derek Parker 已提交
639
		return fmt.Errorf("not enough arguments")
D
Dan Mace 已提交
640
	}
641 642 643 644
	if ctx.Prefix == onPrefix {
		ctx.Breakpoint.Variables = append(ctx.Breakpoint.Variables, args)
		return nil
	}
645
	val, err := t.client.EvalVariable(ctx.Scope, args, LongLoadConfig)
D
Dan Mace 已提交
646 647 648
	if err != nil {
		return err
	}
649 650

	fmt.Println(val.MultilineString(""))
D
Dan Mace 已提交
651 652 653
	return nil
}

654
func setVar(t *Term, ctx callContext, args string) error {
655 656 657 658
	// 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")
659 660
	}

661 662 663 664 665 666 667
	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:]
668
	return t.client.SetVariable(ctx.Scope, lexpr, rexpr)
669 670
}

671
func printFilteredVariables(varType string, vars []api.Variable, filter string, cfg api.LoadConfig) error {
D
Derek Parker 已提交
672 673
	reg, err := regexp.Compile(filter)
	if err != nil {
674
		return err
D
Derek Parker 已提交
675
	}
676
	match := false
D
Dan Mace 已提交
677
	for _, v := range vars {
D
Derek Parker 已提交
678
		if reg == nil || reg.Match([]byte(v.Name)) {
679 680 681 682 683 684
			match = true
			if cfg == ShortLoadConfig {
				fmt.Printf("%s = %s\n", v.Name, v.SinglelineString())
			} else {
				fmt.Printf("%s = %s\n", v.Name, v.MultilineString(""))
			}
D
Dan Mace 已提交
685 686
		}
	}
687 688 689 690
	if !match {
		fmt.Printf("(no %s)\n", varType)
	}
	return nil
D
Dan Mace 已提交
691 692
}

693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709
func printSortedStrings(v []string, err error) error {
	if err != nil {
		return err
	}
	sort.Strings(v)
	for _, d := range v {
		fmt.Println(d)
	}
	return nil
}

func sources(t *Term, ctx callContext, args string) error {
	return printSortedStrings(t.client.ListSources(args))
}

func funcs(t *Term, ctx callContext, args string) error {
	return printSortedStrings(t.client.ListFunctions(args))
D
Derek Parker 已提交
710
}
D
Dan Mace 已提交
711

712 713
func types(t *Term, ctx callContext, args string) error {
	return printSortedStrings(t.client.ListTypes(args))
D
Derek Parker 已提交
714
}
D
Dan Mace 已提交
715

716 717 718 719 720 721 722 723 724
func parseVarArguments(args string) (filter string, cfg api.LoadConfig) {
	if v := strings.SplitN(args, " ", 2); len(v) >= 1 && v[0] == "-v" {
		if len(v) == 2 {
			return v[1], LongLoadConfig
		} else {
			return "", LongLoadConfig
		}
	}
	return args, ShortLoadConfig
A
aarzilli 已提交
725 726
}

727 728 729 730 731 732 733 734 735 736
func args(t *Term, ctx callContext, args string) error {
	filter, cfg := parseVarArguments(args)
	if ctx.Prefix == onPrefix {
		if filter != "" {
			return fmt.Errorf("filter not supported on breakpoint")
		}
		ctx.Breakpoint.LoadArgs = &cfg
		return nil
	}
	vars, err := t.client.ListFunctionArgs(ctx.Scope, cfg)
D
Derek Parker 已提交
737
	if err != nil {
738
		return err
D
Derek Parker 已提交
739
	}
740
	return printFilteredVariables("args", vars, filter, cfg)
D
Derek Parker 已提交
741
}
D
Dan Mace 已提交
742

743 744 745 746 747 748 749 750 751 752
func locals(t *Term, ctx callContext, args string) error {
	filter, cfg := parseVarArguments(args)
	if ctx.Prefix == onPrefix {
		if filter != "" {
			return fmt.Errorf("filter not supported on breakpoint")
		}
		ctx.Breakpoint.LoadLocals = &cfg
		return nil
	}
	locals, err := t.client.ListLocalVariables(ctx.Scope, cfg)
D
Derek Parker 已提交
753
	if err != nil {
754
		return err
D
Derek Parker 已提交
755
	}
756
	return printFilteredVariables("locals", locals, filter, cfg)
D
Derek Parker 已提交
757
}
D
Dan Mace 已提交
758

759 760 761
func vars(t *Term, ctx callContext, args string) error {
	filter, cfg := parseVarArguments(args)
	vars, err := t.client.ListPackageVariables(filter, cfg)
D
Derek Parker 已提交
762
	if err != nil {
763
		return err
D
Derek Parker 已提交
764
	}
765
	return printFilteredVariables("vars", vars, filter, cfg)
D
Derek Parker 已提交
766
}
D
Dan Mace 已提交
767

768
func regs(t *Term, ctx callContext, args string) error {
769
	regs, err := t.client.ListRegisters()
D
Derek Parker 已提交
770 771 772 773 774 775
	if err != nil {
		return err
	}
	fmt.Println(regs)
	return nil
}
776

777
func stackCommand(t *Term, ctx callContext, args string) error {
778 779 780 781
	depth, full, err := parseStackArgs(args)
	if err != nil {
		return err
	}
782 783 784 785
	if ctx.Prefix == onPrefix {
		ctx.Breakpoint.Stacktrace = depth
		return nil
	}
786 787 788 789 790
	var cfg *api.LoadConfig
	if full {
		cfg = &ShortLoadConfig
	}
	stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, depth, cfg)
791 792 793 794 795 796
	if err != nil {
		return err
	}
	printStack(stack, "")
	return nil
}
A
aarzilli 已提交
797

798
func parseStackArgs(argstr string) (int, bool, error) {
799 800 801 802
	var (
		depth = 10
		full  = false
	)
803 804 805 806 807 808 809 810 811 812 813
	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
814
			}
A
aarzilli 已提交
815 816
		}
	}
817
	return depth, full, nil
A
aarzilli 已提交
818 819
}

820 821
func listCommand(t *Term, ctx callContext, args string) error {
	if ctx.Prefix == scopePrefix {
822
		locs, err := t.client.Stacktrace(ctx.Scope.GoroutineID, ctx.Scope.Frame, nil)
823 824 825 826 827 828 829 830 831 832
		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)
	}

833
	if len(args) == 0 {
834
		state, err := t.client.GetState()
835 836 837
		if err != nil {
			return err
		}
838
		printcontext(t, state)
839 840 841
		return nil
	}

D
Derek Parker 已提交
842
	locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args)
843 844 845 846
	if err != nil {
		return err
	}
	if len(locs) > 1 {
847
		return debugger.AmbiguousLocationError{Location: args, CandidatesLocation: locs}
848
	}
849
	printfile(t, locs[0].File, locs[0].Line, false)
850 851 852
	return nil
}

853
func (c *Commands) sourceCommand(t *Term, ctx callContext, args string) error {
854
	if len(args) == 0 {
855 856 857
		return fmt.Errorf("wrong number of arguments: source <filename>")
	}

D
Derek Parker 已提交
858
	return c.executeFile(t, args)
859 860
}

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

863
func disassCommand(t *Term, ctx callContext, args string) error {
A
aarzilli 已提交
864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
	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 "":
880
		locs, err := t.client.FindLocation(ctx.Scope, "+0")
A
aarzilli 已提交
881 882 883
		if err != nil {
			return err
		}
884
		disasm, disasmErr = t.client.DisassemblePC(ctx.Scope, locs[0].PC, api.IntelFlavour)
A
aarzilli 已提交
885 886 887 888 889 890 891 892 893 894 895 896 897
	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])
		}
898
		disasm, disasmErr = t.client.DisassembleRange(ctx.Scope, uint64(startpc), uint64(endpc), api.IntelFlavour)
A
aarzilli 已提交
899
	case "-l":
900
		locs, err := t.client.FindLocation(ctx.Scope, rest)
A
aarzilli 已提交
901 902 903 904 905 906
		if err != nil {
			return err
		}
		if len(locs) != 1 {
			return errors.New("expression specifies multiple locations")
		}
907
		disasm, disasmErr = t.client.DisassemblePC(ctx.Scope, locs[0].PC, api.IntelFlavour)
A
aarzilli 已提交
908 909 910 911 912 913 914 915 916 917 918 919 920 921
	default:
		return disasmUsageError
	}

	if disasmErr != nil {
		return disasmErr
	}

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

	return nil
}

922
func digits(n int) int {
923 924 925
	if n <= 0 {
		return 1
	}
926 927 928 929
	return int(math.Floor(math.Log10(float64(n)))) + 1
}

func printStack(stack []api.Stackframe, ind string) {
930 931 932
	if len(stack) == 0 {
		return
	}
933 934
	d := digits(len(stack) - 1)
	fmtstr := "%s%" + strconv.Itoa(d) + "d  0x%016x in %s\n"
935
	s := ind + strings.Repeat(" ", d+2+len(ind))
936

A
aarzilli 已提交
937 938 939 940 941
	for i := range stack {
		name := "(nil)"
		if stack[i].Function != nil {
			name = stack[i].Function.Name
		}
942
		fmt.Printf(fmtstr, ind, i, stack[i].PC, name)
943
		fmt.Printf("%sat %s:%d\n", s, ShortenFilePath(stack[i].File), stack[i].Line)
944 945

		for j := range stack[i].Arguments {
946
			fmt.Printf("%s    %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
947 948
		}
		for j := range stack[i].Locals {
949
			fmt.Printf("%s    %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
950
		}
A
aarzilli 已提交
951 952 953
	}
}

954
func printcontext(t *Term, state *api.DebuggerState) error {
955 956 957 958 959 960 961 962 963
	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 已提交
964 965 966 967 968 969
	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)
970
		t.Println("=>", "no source available")
D
Dan Mace 已提交
971 972
		return nil
	}
973 974 975

	printcontextThread(t, state.CurrentThread)

976
	if state.CurrentThread.Breakpoint == nil || !state.CurrentThread.Breakpoint.Tracepoint || state.CurrentThread.BreakpointInfo == nil {
977
		return printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true)
D
Derek Parker 已提交
978
	}
979 980
	return nil
}
981

982 983 984 985
func printcontextThread(t *Term, th *api.Thread) {
	fn := th.Function

	if th.Breakpoint == nil {
A
aarzilli 已提交
986
		fmt.Printf("> %s() %s:%d (PC: %#v)\n", fn.Name, ShortenFilePath(th.File), th.Line, th.PC)
987 988 989 990
		return
	}

	args := ""
991
	if th.BreakpointInfo != nil && th.Breakpoint.LoadArgs != nil && *th.Breakpoint.LoadArgs == ShortLoadConfig {
992
		var arg []string
993
		for _, ar := range th.BreakpointInfo.Arguments {
994
			arg = append(arg, ar.SinglelineString())
D
Derek Parker 已提交
995
		}
996 997 998
		args = strings.Join(arg, ", ")
	}

999 1000 1001 1002 1003
	bpname := ""
	if th.Breakpoint.Name != "" {
		bpname = fmt.Sprintf("[%s] ", th.Breakpoint.Name)
	}

1004
	if hitCount, ok := th.Breakpoint.HitCount[strconv.Itoa(th.GoroutineID)]; ok {
1005 1006
		fmt.Printf("> %s%s(%s) %s:%d (hits goroutine(%d):%d total:%d) (PC: %#v)\n",
			bpname,
1007 1008 1009 1010 1011 1012
			fn.Name,
			args,
			ShortenFilePath(th.File),
			th.Line,
			th.GoroutineID,
			hitCount,
A
aarzilli 已提交
1013 1014
			th.Breakpoint.TotalHitCount,
			th.PC)
D
Derek Parker 已提交
1015
	} else {
1016 1017
		fmt.Printf("> %s%s(%s) %s:%d (hits total:%d) (PC: %#v)\n",
			bpname,
1018 1019 1020 1021
			fn.Name,
			args,
			ShortenFilePath(th.File),
			th.Line,
A
aarzilli 已提交
1022 1023
			th.Breakpoint.TotalHitCount,
			th.PC)
D
Dan Mace 已提交
1024 1025
	}

1026
	if th.BreakpointInfo != nil {
1027
		bp := th.Breakpoint
1028
		bpi := th.BreakpointInfo
A
aarzilli 已提交
1029 1030

		if bpi.Goroutine != nil {
1031
			writeGoroutineLong(os.Stdout, bpi.Goroutine, "\t")
A
aarzilli 已提交
1032 1033
		}

1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
		for _, v := range bpi.Variables {
			fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t"))
		}

		for _, v := range bpi.Locals {
			if *bp.LoadLocals == LongLoadConfig {
				fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t"))
			} else {
				fmt.Printf("\t%s: %s\n", v.Name, v.SinglelineString())
			}
		}

		if bp.LoadArgs != nil && *bp.LoadArgs == LongLoadConfig {
			for _, v := range bpi.Arguments {
				fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t"))
1049
			}
A
aarzilli 已提交
1050 1051 1052 1053 1054 1055 1056
		}

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

1059
func printfile(t *Term, filename string, line int, showArrow bool) error {
1060
	file, err := os.Open(filename)
D
Dan Mace 已提交
1061 1062 1063 1064 1065
	if err != nil {
		return err
	}
	defer file.Close()

1066
	buf := bufio.NewScanner(file)
1067
	l := line
D
Dan Mace 已提交
1068
	for i := 1; i < l-5; i++ {
1069 1070
		if !buf.Scan() {
			return nil
D
Dan Mace 已提交
1071 1072 1073
		}
	}

1074 1075 1076 1077 1078 1079
	s := l - 5
	if s < 1 {
		s = 1
	}

	for i := s; i <= l+5; i++ {
1080 1081
		if !buf.Scan() {
			return nil
D
Dan Mace 已提交
1082 1083
		}

1084
		var prefix string
1085
		if showArrow {
1086
			prefix = "  "
1087
			if i == l {
1088
				prefix = "=>"
1089
			}
D
Dan Mace 已提交
1090 1091
		}

1092 1093
		prefix = fmt.Sprintf("%s%4d:\t", prefix, i)
		t.Println(prefix, buf.Text())
D
Dan Mace 已提交
1094 1095 1096
	}
	return nil
}
D
Derek Parker 已提交
1097

D
Derek Parker 已提交
1098 1099
// ExitRequestError is returned when the user
// exits Delve.
D
Derek Parker 已提交
1100 1101 1102 1103 1104 1105
type ExitRequestError struct{}

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

1106
func exitCommand(t *Term, ctx callContext, args string) error {
D
Derek Parker 已提交
1107 1108
	return ExitRequestError{}
}
1109

H
Hubert Krauze 已提交
1110
func getBreakpointByIDOrName(t *Term, arg string) (*api.Breakpoint, error) {
1111
	if id, err := strconv.Atoi(arg); err == nil {
H
Hubert Krauze 已提交
1112
		return t.client.GetBreakpoint(id)
1113
	}
H
Hubert Krauze 已提交
1114
	return t.client.GetBreakpointByName(arg)
1115 1116
}

1117
func (c *Commands) onCmd(t *Term, ctx callContext, argstr string) error {
1118 1119 1120
	args := strings.SplitN(argstr, " ", 3)

	if len(args) < 2 {
1121 1122 1123 1124 1125
		return errors.New("not enough arguments")
	}

	if len(args) < 3 {
		args = append(args, "")
1126 1127 1128 1129 1130 1131 1132
	}

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

1133 1134 1135 1136 1137
	ctx.Prefix = onPrefix
	ctx.Breakpoint = bp
	err = c.CallWithContext(args[1], args[2], t, ctx)
	if err != nil {
		return err
1138
	}
1139
	return t.client.AmendBreakpoint(ctx.Breakpoint)
1140 1141
}

1142
func conditionCmd(t *Term, ctx callContext, argstr string) error {
1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157
	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 已提交
1158 1159
// ShortenFilePath take a full file path and attempts to shorten
// it by replacing the current directory to './'.
1160
func ShortenFilePath(fullPath string) string {
1161 1162 1163
	workingDir, _ := os.Getwd()
	return strings.Replace(fullPath, workingDir, ".", 1)
}
1164

D
Derek Parker 已提交
1165
func (c *Commands) executeFile(t *Term, name string) error {
1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183
	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)

1184
		if err := c.Call(cmdstr, args, t); err != nil {
1185 1186 1187 1188 1189 1190
			fmt.Printf("%s:%d: %v\n", name, lineno, err)
		}
	}

	return scanner.Err()
}
1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211

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)
	}
H
Hubert Krauze 已提交
1212
	return fmt.Sprintf("%#v for %s:%d", bp.Addr, p, bp.Line)
1213
}