command.go 9.4 KB
Newer Older
D
Derek Parker 已提交
1 2
// Package command implements functions for responding to user
// input and dispatching to appropriate backend commands.
D
Derek Parker 已提交
3 4 5
package command

import (
6
	"bufio"
D
Derek Parker 已提交
7
	"fmt"
8 9
	"io"
	"os"
E
epipho 已提交
10 11
	"regexp"
	"sort"
12
	"strconv"
13 14
	"strings"

D
Derek Parker 已提交
15
	"github.com/derekparker/delve/proctl"
D
Derek Parker 已提交
16 17
)

18
type cmdfunc func(proc *proctl.DebuggedProcess, args ...string) error
D
Derek Parker 已提交
19

J
Jason Del Ponte 已提交
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
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 已提交
36
type Commands struct {
J
Jason Del Ponte 已提交
37 38
	cmds    []command
	lastCmd cmdfunc
D
Derek Parker 已提交
39 40
}

41
// Returns a Commands struct with default commands defined.
D
Derek Parker 已提交
42
func DebugCommands() *Commands {
J
Jason Del Ponte 已提交
43 44 45
	c := &Commands{}

	c.cmds = []command{
J
Jason Del Ponte 已提交
46 47 48 49 50 51
		command{aliases: []string{"help"}, cmdFn: c.help, helpMsg: "Prints the help message."},
		command{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "Set break point at the entry point of a function, or at a specific file/line. Example: break foo.go:13"},
		command{aliases: []string{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
		command{aliases: []string{"step", "si"}, cmdFn: step, helpMsg: "Single step through program."},
		command{aliases: []string{"next", "n"}, cmdFn: next, helpMsg: "Step over to next source line."},
		command{aliases: []string{"threads"}, cmdFn: threads, helpMsg: "Print out info for every traced thread."},
52
		command{aliases: []string{"thread", "t"}, cmdFn: thread, helpMsg: "Switch to the specified thread."},
J
Jason Del Ponte 已提交
53 54
		command{aliases: []string{"clear"}, cmdFn: clear, helpMsg: "Deletes breakpoint."},
		command{aliases: []string{"goroutines"}, cmdFn: goroutines, helpMsg: "Print out info for every goroutine."},
55
		command{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
J
Jason Del Ponte 已提交
56
		command{aliases: []string{"print", "p"}, cmdFn: printVar, helpMsg: "Evaluate a variable."},
57
		command{aliases: []string{"info"}, cmdFn: info, helpMsg: "Provides info about args, funcs, locals, sources, or vars."},
J
Jason Del Ponte 已提交
58
		command{aliases: []string{"exit"}, cmdFn: nullCommand, helpMsg: "Exit the debugger."},
D
Derek Parker 已提交
59 60
	}

J
Jason Del Ponte 已提交
61
	return c
D
Derek Parker 已提交
62 63
}

D
Derek Parker 已提交
64 65
// Register custom commands. Expects cf to be a func of type cmdfunc,
// returning only an error.
J
Jason Del Ponte 已提交
66 67 68 69 70 71 72 73 74
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})
D
Derek Parker 已提交
75 76
}

77 78 79
// Find will look up the command function for the given command input.
// If it cannot find the command it will defualt to noCmdAvailable().
// If the command is an empty string it will replay the last command.
D
Derek Parker 已提交
80
func (c *Commands) Find(cmdstr string) cmdfunc {
J
Jason Del Ponte 已提交
81 82 83 84 85 86
	// If <enter> use last command, if there was one.
	if cmdstr == "" {
		if c.lastCmd != nil {
			return c.lastCmd
		}
		return nullCommand
D
Derek Parker 已提交
87 88
	}

J
Jason Del Ponte 已提交
89 90 91 92 93 94
	for _, v := range c.cmds {
		if v.match(cmdstr) {
			c.lastCmd = v.cmdFn
			return v.cmdFn
		}
	}
95

J
Jason Del Ponte 已提交
96
	return noCmdAvailable
D
Derek Parker 已提交
97 98
}

D
Derek Parker 已提交
99
func CommandFunc(fn func() error) cmdfunc {
100
	return func(p *proctl.DebuggedProcess, args ...string) error {
D
Derek Parker 已提交
101 102 103 104
		return fn()
	}
}

D
Derek Parker 已提交
105
func noCmdAvailable(p *proctl.DebuggedProcess, args ...string) error {
D
Derek Parker 已提交
106 107 108
	return fmt.Errorf("command not available")
}

D
Derek Parker 已提交
109
func nullCommand(p *proctl.DebuggedProcess, args ...string) error {
110 111 112
	return nil
}

D
Derek Parker 已提交
113
func (c *Commands) help(p *proctl.DebuggedProcess, args ...string) error {
J
Jason Del Ponte 已提交
114 115
	fmt.Println("The following commands are available:")
	for _, cmd := range c.cmds {
J
Jason Del Ponte 已提交
116
		fmt.Printf("\t%s - %s\n", strings.Join(cmd.aliases, "|"), cmd.helpMsg)
J
Jason Del Ponte 已提交
117
	}
D
Derek Parker 已提交
118 119
	return nil
}
120

D
Derek Parker 已提交
121
func threads(p *proctl.DebuggedProcess, args ...string) error {
122 123 124 125 126 127 128 129 130
	for _, th := range p.Threads {
		prefix := "  "
		if th == p.CurrentThread {
			prefix = "* "
		}
		pc, err := th.CurrentPC()
		if err != nil {
			return err
		}
131
		f, l, fn := th.Process.PCToLine(pc)
132 133 134 135 136 137 138
		if fn != nil {
			fmt.Printf("%sThread %d at %#v %s:%d %s\n", prefix, th.Id, pc, f, l, fn.Name)
		} else {
			fmt.Printf("%sThread %d at %#v\n", prefix, th.Id, pc)
		}
	}
	return nil
139 140
}

D
Derek Parker 已提交
141 142
func thread(p *proctl.DebuggedProcess, args ...string) error {
	if len(args) == 0 {
D
Derek Parker 已提交
143 144
		return fmt.Errorf("you must specify a thread")
	}
145
	oldTid := p.CurrentThread.Id
D
Derek Parker 已提交
146
	tid, err := strconv.Atoi(args[0])
147 148 149 150 151 152 153 154 155 156 157 158 159
	if err != nil {
		return err
	}

	err = p.SwitchThread(tid)
	if err != nil {
		return err
	}

	fmt.Printf("Switched from %d to %d\n", oldTid, tid)
	return nil
}

D
Derek Parker 已提交
160
func goroutines(p *proctl.DebuggedProcess, args ...string) error {
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
	var fname string
	gs, err := p.GoroutinesInfo()
	if err != nil {
		return err
	}

	fmt.Printf("[%d goroutines]\n", len(gs))
	for _, g := range gs {
		if g.Func != nil {
			fname = g.Func.Name
		}
		fmt.Printf("Goroutine %d - %s:%d %s\n", g.Id, g.File, g.Line, fname)
	}

	return nil
176 177
}

D
Derek Parker 已提交
178
func cont(p *proctl.DebuggedProcess, args ...string) error {
179
	err := p.Continue()
180 181 182 183
	if err != nil {
		return err
	}

184 185 186 187 188
	return printcontext(p)
}

func step(p *proctl.DebuggedProcess, args ...string) error {
	err := p.Step()
189 190 191 192
	if err != nil {
		return err
	}

193
	return printcontext(p)
194 195
}

D
Derek Parker 已提交
196 197 198 199 200 201
func next(p *proctl.DebuggedProcess, args ...string) error {
	err := p.Next()
	if err != nil {
		return err
	}

202
	return printcontext(p)
D
Derek Parker 已提交
203 204
}

205
func clear(p *proctl.DebuggedProcess, args ...string) error {
206 207 208 209
	if len(args) == 0 {
		return fmt.Errorf("not enough arguments")
	}

210
	bp, err := p.ClearByLocation(args[0])
211 212 213 214
	if err != nil {
		return err
	}

215
	fmt.Printf("Breakpoint %d cleared at %#v for %s %s:%d\n", bp.ID, bp.Addr, bp.FunctionName, bp.File, bp.Line)
216 217 218 219

	return nil
}

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
type ById []*proctl.BreakPoint

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 }

func breakpoints(p *proctl.DebuggedProcess, args ...string) error {
	bps := make([]*proctl.BreakPoint, 0, len(p.BreakPoints)+4)

	for _, bp := range p.HWBreakPoints {
		if bp == nil {
			continue
		}
		bps = append(bps, bp)
	}

	for _, bp := range p.BreakPoints {
		if bp.Temp {
			continue
		}
		bps = append(bps, bp)
	}

	sort.Sort(ById(bps))
	for _, bp := range bps {
		fmt.Println(bp)
	}

	return nil
}

D
Derek Parker 已提交
251
func breakpoint(p *proctl.DebuggedProcess, args ...string) error {
252 253 254 255
	if len(args) == 0 {
		return fmt.Errorf("not enough arguments")
	}

256
	bp, err := p.BreakByLocation(args[0])
257 258 259 260
	if err != nil {
		return err
	}

261
	fmt.Printf("Breakpoint %d set at %#v for %s %s:%d\n", bp.ID, bp.Addr, bp.FunctionName, bp.File, bp.Line)
262

263 264
	return nil
}
265

D
Derek Parker 已提交
266
func printVar(p *proctl.DebuggedProcess, args ...string) error {
267
	if len(args) == 0 {
268
		return fmt.Errorf("not enough arguments")
269 270
	}

D
Derek Parker 已提交
271 272 273 274 275 276 277
	val, err := p.EvalSymbol(args[0])
	if err != nil {
		return err
	}

	fmt.Println(val.Value)
	return nil
E
epipho 已提交
278 279
}

E
epipho 已提交
280 281 282 283 284 285 286 287 288 289 290 291 292
func filterVariables(vars []*proctl.Variable, filter *regexp.Regexp) []string {
	data := make([]string, 0, len(vars))
	for _, v := range vars {
		if v == nil {
			continue
		}
		if filter == nil || filter.Match([]byte(v.Name)) {
			data = append(data, fmt.Sprintf("%s = %s", v.Name, v.Value))
		}
	}
	return data
}

E
epipho 已提交
293 294
func info(p *proctl.DebuggedProcess, args ...string) error {
	if len(args) == 0 {
E
epipho 已提交
295
		return fmt.Errorf("not enough arguments. expected info type [regex].")
E
epipho 已提交
296 297 298 299 300 301 302 303 304 305 306
	}

	// Allow for optional regex
	var filter *regexp.Regexp
	if len(args) >= 2 {
		var err error
		if filter, err = regexp.Compile(args[1]); err != nil {
			return fmt.Errorf("invalid filter argument: %s", err.Error())
		}
	}

E
epipho 已提交
307 308
	var data []string

E
epipho 已提交
309 310
	switch args[0] {
	case "sources":
311 312
		data = make([]string, 0, len(p.Sources()))
		for f := range p.Sources() {
E
epipho 已提交
313
			if filter == nil || filter.Match([]byte(f)) {
E
epipho 已提交
314
				data = append(data, f)
E
epipho 已提交
315 316 317
			}
		}

D
Derek Parker 已提交
318
	case "funcs":
319 320
		data = make([]string, 0, len(p.Funcs()))
		for _, f := range p.Funcs() {
E
epipho 已提交
321 322 323
			if f.Sym != nil && (filter == nil || filter.Match([]byte(f.Name))) {
				data = append(data, f.Name)
			}
E
epipho 已提交
324 325
		}

E
epipho 已提交
326 327 328 329 330 331 332 333 334 335 336 337 338 339
	case "args":
		vars, err := p.CurrentThread.FunctionArguments()
		if err != nil {
			return nil
		}
		data = filterVariables(vars, filter)

	case "locals":
		vars, err := p.CurrentThread.LocalVariables()
		if err != nil {
			return nil
		}
		data = filterVariables(vars, filter)

340 341 342 343 344 345 346
	case "vars":
		vars, err := p.CurrentThread.PackageVariables()
		if err != nil {
			return nil
		}
		data = filterVariables(vars, filter)

E
epipho 已提交
347
	default:
348
		return fmt.Errorf("unsupported info type, must be args, funcs, locals, sources, or vars")
E
epipho 已提交
349 350 351 352 353 354
	}

	// sort and output data
	sort.Sort(sort.StringSlice(data))

	for _, d := range data {
E
epipho 已提交
355
		fmt.Println(d)
E
epipho 已提交
356 357 358
	}

	return nil
D
Derek Parker 已提交
359 360
}

361 362 363 364 365 366 367 368
func printcontext(p *proctl.DebuggedProcess) error {
	var context []string

	regs, err := p.Registers()
	if err != nil {
		return err
	}

369
	f, l, fn := p.PCToLine(regs.PC())
370

371
	if fn != nil {
D
Derek Parker 已提交
372
		fmt.Printf("current loc: %s %s:%d\n", fn.Name, f, l)
373 374
		file, err := os.Open(f)
		if err != nil {
375 376
			return err
		}
377
		defer file.Close()
378

379 380 381 382
		buf := bufio.NewReader(file)
		for i := 1; i < l-5; i++ {
			_, err := buf.ReadString('\n')
			if err != nil && err != io.EOF {
D
Derek Parker 已提交
383
				return err
384
			}
385 386 387 388 389 390 391 392
		}

		for i := l - 5; i <= l+5; i++ {
			line, err := buf.ReadString('\n')
			if err != nil {
				if err != io.EOF {
					return err
				}
D
Derek Parker 已提交
393

394 395 396
				if err == io.EOF {
					break
				}
D
Derek Parker 已提交
397 398
			}

D
Derek Parker 已提交
399
			arrow := "  "
400
			if i == l {
D
Derek Parker 已提交
401
				arrow = "=>"
402
			}
D
Derek Parker 已提交
403

D
Derek Parker 已提交
404
			context = append(context, fmt.Sprintf("\033[34m%s %d\033[0m: %s", arrow, i, line))
405 406 407 408
		}
	} else {
		fmt.Printf("Stopped at: 0x%x\n", regs.PC())
		context = append(context, "\033[34m=>\033[0m    no source available")
409 410
	}

D
Derek Parker 已提交
411
	fmt.Println(strings.Join(context, ""))
412 413 414

	return nil
}