command.go 7.6 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 13
	"strings"

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

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

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

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

	c.cmds = []command{
J
Jason Del Ponte 已提交
45 46 47 48 49 50 51 52 53
		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."},
		command{aliases: []string{"clear"}, cmdFn: clear, helpMsg: "Deletes breakpoint."},
		command{aliases: []string{"goroutines"}, cmdFn: goroutines, helpMsg: "Print out info for every goroutine."},
		command{aliases: []string{"print", "p"}, cmdFn: printVar, helpMsg: "Evaluate a variable."},
54
		command{aliases: []string{"info"}, cmdFn: info, helpMsg: "Provides info about args, funcs, locals, sources, or vars."},
J
Jason Del Ponte 已提交
55
		command{aliases: []string{"exit"}, cmdFn: nullCommand, helpMsg: "Exit the debugger."},
D
Derek Parker 已提交
56 57
	}

J
Jason Del Ponte 已提交
58
	return c
D
Derek Parker 已提交
59 60
}

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

74 75 76
// 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 已提交
77
func (c *Commands) Find(cmdstr string) cmdfunc {
J
Jason Del Ponte 已提交
78 79 80 81 82 83
	// If <enter> use last command, if there was one.
	if cmdstr == "" {
		if c.lastCmd != nil {
			return c.lastCmd
		}
		return nullCommand
D
Derek Parker 已提交
84 85
	}

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

J
Jason Del Ponte 已提交
93
	return noCmdAvailable
D
Derek Parker 已提交
94 95
}

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

102
func noCmdAvailable(p *proctl.DebuggedProcess, ars ...string) error {
D
Derek Parker 已提交
103 104 105
	return fmt.Errorf("command not available")
}

106 107 108 109
func nullCommand(p *proctl.DebuggedProcess, ars ...string) error {
	return nil
}

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

func threads(p *proctl.DebuggedProcess, ars ...string) error {
	return p.PrintThreadInfo()
}

122 123 124 125
func goroutines(p *proctl.DebuggedProcess, ars ...string) error {
	return p.PrintGoroutinesInfo()
}

126
func cont(p *proctl.DebuggedProcess, ars ...string) error {
127
	err := p.Continue()
128 129 130 131
	if err != nil {
		return err
	}

132 133 134 135 136
	return printcontext(p)
}

func step(p *proctl.DebuggedProcess, args ...string) error {
	err := p.Step()
137 138 139 140
	if err != nil {
		return err
	}

141
	return printcontext(p)
142 143
}

D
Derek Parker 已提交
144 145 146 147 148 149
func next(p *proctl.DebuggedProcess, args ...string) error {
	err := p.Next()
	if err != nil {
		return err
	}

150
	return printcontext(p)
D
Derek Parker 已提交
151 152
}

153
func clear(p *proctl.DebuggedProcess, args ...string) error {
154 155 156 157
	if len(args) == 0 {
		return fmt.Errorf("not enough arguments")
	}

158
	bp, err := p.ClearByLocation(args[0])
159 160 161 162
	if err != nil {
		return err
	}

163
	fmt.Printf("Breakpoint %d cleared at %#v for %s %s:%d\n", bp.ID, bp.Addr, bp.FunctionName, bp.File, bp.Line)
164 165 166 167

	return nil
}

D
Derek Parker 已提交
168
func breakpoint(p *proctl.DebuggedProcess, args ...string) error {
169 170 171 172
	if len(args) == 0 {
		return fmt.Errorf("not enough arguments")
	}

173
	bp, err := p.BreakByLocation(args[0])
174 175 176 177
	if err != nil {
		return err
	}

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

180 181
	return nil
}
182

D
Derek Parker 已提交
183
func printVar(p *proctl.DebuggedProcess, args ...string) error {
184
	if len(args) == 0 {
185
		return fmt.Errorf("not enough arguments")
186 187
	}

D
Derek Parker 已提交
188 189 190 191 192 193 194
	val, err := p.EvalSymbol(args[0])
	if err != nil {
		return err
	}

	fmt.Println(val.Value)
	return nil
E
epipho 已提交
195 196
}

E
epipho 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209
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 已提交
210 211
func info(p *proctl.DebuggedProcess, args ...string) error {
	if len(args) == 0 {
E
epipho 已提交
212
		return fmt.Errorf("not enough arguments. expected info type [regex].")
E
epipho 已提交
213 214 215 216 217 218 219 220 221 222 223
	}

	// 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 已提交
224 225
	var data []string

E
epipho 已提交
226 227
	switch args[0] {
	case "sources":
E
epipho 已提交
228
		data = make([]string, 0, len(p.GoSymTable.Files))
E
epipho 已提交
229 230
		for f := range p.GoSymTable.Files {
			if filter == nil || filter.Match([]byte(f)) {
E
epipho 已提交
231
				data = append(data, f)
E
epipho 已提交
232 233 234
			}
		}

D
Derek Parker 已提交
235
	case "funcs":
E
epipho 已提交
236 237 238 239 240
		data = make([]string, 0, len(p.GoSymTable.Funcs))
		for _, f := range p.GoSymTable.Funcs {
			if f.Sym != nil && (filter == nil || filter.Match([]byte(f.Name))) {
				data = append(data, f.Name)
			}
E
epipho 已提交
241 242
		}

E
epipho 已提交
243 244 245 246 247 248 249 250 251 252 253 254 255 256
	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)

257 258 259 260 261 262 263
	case "vars":
		vars, err := p.CurrentThread.PackageVariables()
		if err != nil {
			return nil
		}
		data = filterVariables(vars, filter)

E
epipho 已提交
264
	default:
265
		return fmt.Errorf("unsupported info type, must be args, funcs, locals, sources, or vars")
E
epipho 已提交
266 267 268 269 270 271
	}

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

	for _, d := range data {
E
epipho 已提交
272
		fmt.Println(d)
E
epipho 已提交
273 274 275
	}

	return nil
D
Derek Parker 已提交
276 277
}

278 279 280 281 282 283 284 285
func printcontext(p *proctl.DebuggedProcess) error {
	var context []string

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

286
	f, l, fn := p.GoSymTable.PCToLine(regs.PC())
287

288
	if fn != nil {
D
Derek Parker 已提交
289
		fmt.Printf("current loc: %s %s:%d\n", fn.Name, f, l)
290 291
		file, err := os.Open(f)
		if err != nil {
292 293
			return err
		}
294
		defer file.Close()
295

296 297 298 299
		buf := bufio.NewReader(file)
		for i := 1; i < l-5; i++ {
			_, err := buf.ReadString('\n')
			if err != nil && err != io.EOF {
D
Derek Parker 已提交
300
				return err
301
			}
302 303 304 305 306 307 308 309
		}

		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 已提交
310

311 312 313
				if err == io.EOF {
					break
				}
D
Derek Parker 已提交
314 315
			}

D
Derek Parker 已提交
316
			arrow := "  "
317
			if i == l {
D
Derek Parker 已提交
318
				arrow = "=>"
319
			}
D
Derek Parker 已提交
320

D
Derek Parker 已提交
321
			context = append(context, fmt.Sprintf("\033[34m%s %d\033[0m: %s", arrow, i, line))
322 323 324 325
		}
	} else {
		fmt.Printf("Stopped at: 0x%x\n", regs.PC())
		context = append(context, "\033[34m=>\033[0m    no source available")
326 327
	}

D
Derek Parker 已提交
328
	fmt.Println(strings.Join(context, ""))
329 330 331

	return nil
}