delve/command/command.go

332 lines
7.6 KiB
Go
Raw Normal View History

2014-05-21 15:23:14 +00:00
// Package command implements functions for responding to user
// input and dispatching to appropriate backend commands.
2014-05-20 21:28:24 +00:00
package command
import (
"bufio"
2014-05-20 21:28:24 +00:00
"fmt"
"io"
"os"
2014-12-28 08:02:37 +00:00
"regexp"
"sort"
"strings"
2014-10-15 14:21:46 +00:00
"github.com/derekparker/delve/proctl"
2014-05-20 21:28:24 +00:00
)
type cmdfunc func(proc *proctl.DebuggedProcess, args ...string) error
2014-05-20 21:28:24 +00:00
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
}
2014-05-20 21:28:24 +00:00
type Commands struct {
cmds []command
lastCmd cmdfunc
2014-05-20 21:28:24 +00:00
}
// Returns a Commands struct with default commands defined.
2014-05-20 21:28:24 +00:00
func DebugCommands() *Commands {
c := &Commands{}
c.cmds = []command{
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."},
command{aliases: []string{"info"}, cmdFn: info, helpMsg: "Provides info about source, locals, args, or funcs."},
command{aliases: []string{"exit"}, cmdFn: nullCommand, helpMsg: "Exit the debugger."},
2014-05-20 21:28:24 +00:00
}
return c
2014-05-20 21:28:24 +00:00
}
2014-05-21 15:23:14 +00:00
// 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})
2014-05-20 23:09:34 +00:00
}
// 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.
2014-05-20 21:28:24 +00:00
func (c *Commands) Find(cmdstr string) cmdfunc {
// If <enter> use last command, if there was one.
if cmdstr == "" {
if c.lastCmd != nil {
return c.lastCmd
}
return nullCommand
2014-05-20 21:28:24 +00:00
}
for _, v := range c.cmds {
if v.match(cmdstr) {
c.lastCmd = v.cmdFn
return v.cmdFn
}
}
return noCmdAvailable
2014-05-20 21:28:24 +00:00
}
2014-05-27 15:44:29 +00:00
func CommandFunc(fn func() error) cmdfunc {
return func(p *proctl.DebuggedProcess, args ...string) error {
2014-05-27 15:44:29 +00:00
return fn()
}
}
func noCmdAvailable(p *proctl.DebuggedProcess, ars ...string) error {
2014-05-20 21:28:24 +00:00
return fmt.Errorf("command not available")
}
func nullCommand(p *proctl.DebuggedProcess, ars ...string) error {
return nil
}
func (c *Commands) help(p *proctl.DebuggedProcess, ars ...string) error {
fmt.Println("The following commands are available:")
for _, cmd := range c.cmds {
fmt.Printf("\t%s - %s\n", strings.Join(cmd.aliases, "|"), cmd.helpMsg)
}
2014-10-20 17:06:36 +00:00
return nil
}
func threads(p *proctl.DebuggedProcess, ars ...string) error {
return p.PrintThreadInfo()
}
func goroutines(p *proctl.DebuggedProcess, ars ...string) error {
return p.PrintGoroutinesInfo()
}
func cont(p *proctl.DebuggedProcess, ars ...string) error {
err := p.Continue()
if err != nil {
return err
}
return printcontext(p)
}
func step(p *proctl.DebuggedProcess, args ...string) error {
err := p.Step()
if err != nil {
return err
}
return printcontext(p)
}
2014-07-11 21:18:07 +00:00
func next(p *proctl.DebuggedProcess, args ...string) error {
err := p.Next()
if err != nil {
return err
}
return printcontext(p)
2014-07-11 21:18:07 +00:00
}
func clear(p *proctl.DebuggedProcess, args ...string) error {
if len(args) == 0 {
return fmt.Errorf("not enough arguments")
}
bp, err := p.ClearByLocation(args[0])
if err != nil {
return err
}
fmt.Printf("Breakpoint %d cleared at %#v for %s %s:%d\n", bp.ID, bp.Addr, bp.FunctionName, bp.File, bp.Line)
return nil
}
2014-06-27 01:36:39 +00:00
func breakpoint(p *proctl.DebuggedProcess, args ...string) error {
if len(args) == 0 {
return fmt.Errorf("not enough arguments")
}
bp, err := p.BreakByLocation(args[0])
if err != nil {
return err
}
fmt.Printf("Breakpoint %d set at %#v for %s %s:%d\n", bp.ID, bp.Addr, bp.FunctionName, bp.File, bp.Line)
return nil
}
2014-10-07 22:03:20 +00:00
func printVar(p *proctl.DebuggedProcess, args ...string) error {
if len(args) == 0 {
return fmt.Errorf("not enough arguments")
}
2014-10-07 22:03:20 +00:00
val, err := p.EvalSymbol(args[0])
if err != nil {
return err
}
fmt.Println(val.Value)
return nil
2014-12-28 08:02:37 +00:00
}
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
}
2014-12-28 08:02:37 +00:00
func info(p *proctl.DebuggedProcess, args ...string) error {
if len(args) == 0 {
2014-12-28 08:14:17 +00:00
return fmt.Errorf("not enough arguments. expected info type [regex].")
2014-12-28 08:02:37 +00:00
}
// 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())
}
}
2014-12-28 08:14:17 +00:00
var data []string
2014-12-28 08:02:37 +00:00
switch args[0] {
case "sources":
2014-12-28 08:14:17 +00:00
data = make([]string, 0, len(p.GoSymTable.Files))
2014-12-28 08:02:37 +00:00
for f := range p.GoSymTable.Files {
if filter == nil || filter.Match([]byte(f)) {
2014-12-28 08:14:17 +00:00
data = append(data, f)
2014-12-28 08:02:37 +00:00
}
}
2014-12-29 05:06:04 +00:00
case "funcs":
2014-12-28 08:14:17 +00:00
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)
}
2014-12-28 08:02:37 +00:00
}
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)
case "vars":
vars, err := p.CurrentThread.PackageVariables()
if err != nil {
return nil
}
data = filterVariables(vars, filter)
2014-12-28 08:02:37 +00:00
default:
return fmt.Errorf("unsupported info type, must be args, funcs, locals, sources, or vars")
2014-12-28 08:14:17 +00:00
}
// sort and output data
sort.Sort(sort.StringSlice(data))
for _, d := range data {
2014-12-29 03:14:21 +00:00
fmt.Println(d)
2014-12-28 08:02:37 +00:00
}
return nil
2014-10-07 22:03:20 +00:00
}
func printcontext(p *proctl.DebuggedProcess) error {
var context []string
regs, err := p.Registers()
if err != nil {
return err
}
f, l, fn := p.GoSymTable.PCToLine(regs.PC())
if fn != nil {
2014-12-02 18:40:53 +00:00
fmt.Printf("current loc: %s %s:%d\n", fn.Name, f, l)
file, err := os.Open(f)
if err != nil {
return err
}
defer file.Close()
buf := bufio.NewReader(file)
for i := 1; i < l-5; i++ {
_, err := buf.ReadString('\n')
if err != nil && err != io.EOF {
2014-10-17 19:33:40 +00:00
return err
}
}
for i := l - 5; i <= l+5; i++ {
line, err := buf.ReadString('\n')
if err != nil {
if err != io.EOF {
return err
}
2014-10-17 19:33:40 +00:00
if err == io.EOF {
break
}
2014-10-17 19:33:40 +00:00
}
2014-12-02 03:13:08 +00:00
arrow := " "
if i == l {
2014-12-02 03:13:08 +00:00
arrow = "=>"
}
2014-10-17 19:33:40 +00:00
2014-12-02 03:13:08 +00:00
context = append(context, fmt.Sprintf("\033[34m%s %d\033[0m: %s", arrow, i, line))
}
} else {
fmt.Printf("Stopped at: 0x%x\n", regs.PC())
context = append(context, "\033[34m=>\033[0m no source available")
}
2014-10-17 19:33:40 +00:00
fmt.Println(strings.Join(context, ""))
return nil
}