mirror of
https://github.com/go-delve/delve.git
synced 2025-11-01 20:20:40 +08:00
terminal: Mechanism to handle command prefixes
Implements extensible mechanism to specify which commands accept prefixes (goroutine, frame, on) instead of hardcoding them in a switch. Implements #240
This commit is contained in:
@ -262,10 +262,9 @@ func traceCmd(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmds := terminal.DebugCommands(client)
|
cmds := terminal.DebugCommands(client)
|
||||||
cmd := cmds.Find("continue")
|
|
||||||
t := terminal.New(client, nil)
|
t := terminal.New(client, nil)
|
||||||
defer t.Close()
|
defer t.Close()
|
||||||
err = cmd(t, "")
|
err = cmds.Call("continue", "", t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@ -22,16 +22,28 @@ import (
|
|||||||
"github.com/derekparker/delve/service/debugger"
|
"github.com/derekparker/delve/service/debugger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cmdfunc func(t *Term, args string) error
|
type cmdPrefix int
|
||||||
type scopedCmdfunc func(t *Term, scope api.EvalScope, args string) error
|
|
||||||
|
|
||||||
type filteringFunc func(t *Term, filter string) ([]string, error)
|
const (
|
||||||
type scopedFilteringFunc func(t *Term, scope api.EvalScope, filter string) ([]string, error)
|
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)
|
||||||
|
|
||||||
type command struct {
|
type command struct {
|
||||||
aliases []string
|
aliases []string
|
||||||
helpMsg string
|
allowedPrefixes cmdPrefix
|
||||||
cmdFn cmdfunc
|
helpMsg string
|
||||||
|
cmdFn cmdfunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the command string matches one of the aliases for this command
|
// Returns true if the command string matches one of the aliases for this command
|
||||||
@ -69,24 +81,24 @@ func DebugCommands(client service.Client) *Commands {
|
|||||||
{aliases: []string{"clear"}, cmdFn: clear, helpMsg: "Deletes breakpoint."},
|
{aliases: []string{"clear"}, cmdFn: clear, helpMsg: "Deletes breakpoint."},
|
||||||
{aliases: []string{"clearall"}, cmdFn: clearAll, helpMsg: "clearall [<linespec>]. Deletes all breakpoints. If <linespec> is provided, only matching breakpoints will be deleted."},
|
{aliases: []string{"clearall"}, cmdFn: clearAll, helpMsg: "clearall [<linespec>]. Deletes all breakpoints. If <linespec> is provided, only matching breakpoints will be deleted."},
|
||||||
{aliases: []string{"goroutines"}, cmdFn: goroutines, helpMsg: "goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)] Print out info for every goroutine."},
|
{aliases: []string{"goroutines"}, cmdFn: goroutines, helpMsg: "goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)] Print out info for every goroutine."},
|
||||||
{aliases: []string{"goroutine"}, cmdFn: goroutine, helpMsg: "Sets current goroutine."},
|
{aliases: []string{"goroutine"}, allowedPrefixes: onPrefix | scopePrefix, cmdFn: c.goroutine, helpMsg: "Sets current goroutine."},
|
||||||
{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
|
{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
|
||||||
{aliases: []string{"print", "p"}, cmdFn: g0f0(printVar), helpMsg: "Evaluate a variable."},
|
{aliases: []string{"print", "p"}, allowedPrefixes: onPrefix | scopePrefix, cmdFn: printVar, helpMsg: "Evaluate a variable."},
|
||||||
{aliases: []string{"set"}, cmdFn: g0f0(setVar), helpMsg: "Changes the value of a variable."},
|
{aliases: []string{"set"}, allowedPrefixes: scopePrefix, cmdFn: setVar, helpMsg: "Changes the value of a variable."},
|
||||||
{aliases: []string{"sources"}, cmdFn: filterSortAndOutput(sources), helpMsg: "Print list of source files, optionally filtered by a regexp."},
|
{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."},
|
{aliases: []string{"funcs"}, cmdFn: filterSortAndOutput(funcs), helpMsg: "Print list of functions, optionally filtered by a regexp."},
|
||||||
{aliases: []string{"types"}, cmdFn: filterSortAndOutput(types), helpMsg: "Print list of types, optionally filtered by a regexp."},
|
{aliases: []string{"types"}, cmdFn: filterSortAndOutput(types), helpMsg: "Print list of types, optionally filtered by a regexp."},
|
||||||
{aliases: []string{"args"}, cmdFn: filterSortAndOutput(g0f0filter(args)), helpMsg: "Print function arguments, optionally filtered by a regexp."},
|
{aliases: []string{"args"}, allowedPrefixes: scopePrefix, cmdFn: filterSortAndOutput(args), helpMsg: "Print function arguments, optionally filtered by a regexp."},
|
||||||
{aliases: []string{"locals"}, cmdFn: filterSortAndOutput(g0f0filter(locals)), helpMsg: "Print function locals, optionally filtered by a regexp."},
|
{aliases: []string{"locals"}, allowedPrefixes: scopePrefix, cmdFn: filterSortAndOutput(locals), helpMsg: "Print function locals, optionally filtered by a regexp."},
|
||||||
{aliases: []string{"vars"}, cmdFn: filterSortAndOutput(vars), helpMsg: "Print package variables, optionally filtered by a regexp."},
|
{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."},
|
{aliases: []string{"regs"}, cmdFn: regs, helpMsg: "Print contents of CPU registers."},
|
||||||
{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."},
|
{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."},
|
||||||
{aliases: []string{"list", "ls"}, cmdFn: listCommand, helpMsg: "list <linespec>. Show source around current point or provided linespec."},
|
{aliases: []string{"list", "ls"}, allowedPrefixes: scopePrefix, cmdFn: listCommand, helpMsg: "list <linespec>. Show source around current point or provided linespec."},
|
||||||
{aliases: []string{"stack", "bt"}, cmdFn: stackCommand, helpMsg: "stack [<depth>] [-full]. Prints stack."},
|
{aliases: []string{"stack", "bt"}, allowedPrefixes: scopePrefix | onPrefix, cmdFn: stackCommand, helpMsg: "stack [<depth>] [-full]. Prints stack."},
|
||||||
{aliases: []string{"frame"}, cmdFn: frame, helpMsg: "Sets current stack frame (0 is the top of the stack)"},
|
{aliases: []string{"frame"}, allowedPrefixes: scopePrefix, cmdFn: c.frame, helpMsg: "Sets current stack frame (0 is the top of the stack)"},
|
||||||
{aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: "Executes a file containing a list of delve commands"},
|
{aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: "Executes a file containing a list of delve commands"},
|
||||||
{aliases: []string{"disassemble", "disass"}, cmdFn: g0f0(disassCommand), helpMsg: "Displays disassembly of specific function or address range: disassemble [-a <start> <end>] [-l <locspec>]"},
|
{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: 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)"},
|
{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)"},
|
||||||
{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."},
|
{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."},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +121,7 @@ func (c *Commands) Register(cmdstr string, cf cmdfunc, helpMsg string) {
|
|||||||
// Find will look up the command function for the given command input.
|
// Find will look up the command function for the given command input.
|
||||||
// If it cannot find the command it will default to noCmdAvailable().
|
// If it cannot find the command it will default to noCmdAvailable().
|
||||||
// If the command is an empty string it will replay the last command.
|
// If the command is an empty string it will replay the last command.
|
||||||
func (c *Commands) Find(cmdstr string) cmdfunc {
|
func (c *Commands) Find(cmdstr string, prefix cmdPrefix) cmdfunc {
|
||||||
// If <enter> use last command, if there was one.
|
// If <enter> use last command, if there was one.
|
||||||
if cmdstr == "" {
|
if cmdstr == "" {
|
||||||
if c.lastCmd != nil {
|
if c.lastCmd != nil {
|
||||||
@ -120,6 +132,9 @@ func (c *Commands) Find(cmdstr string) cmdfunc {
|
|||||||
|
|
||||||
for _, v := range c.cmds {
|
for _, v := range c.cmds {
|
||||||
if v.match(cmdstr) {
|
if v.match(cmdstr) {
|
||||||
|
if prefix != noPrefix && v.allowedPrefixes&prefix == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
c.lastCmd = v.cmdFn
|
c.lastCmd = v.cmdFn
|
||||||
return v.cmdFn
|
return v.cmdFn
|
||||||
}
|
}
|
||||||
@ -128,6 +143,15 @@ func (c *Commands) Find(cmdstr string) cmdfunc {
|
|||||||
return noCmdAvailable
|
return noCmdAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
// Merge takes aliases defined in the config struct and merges them with the default aliases.
|
// Merge takes aliases defined in the config struct and merges them with the default aliases.
|
||||||
func (c *Commands) Merge(allAliases map[string][]string) {
|
func (c *Commands) Merge(allAliases map[string][]string) {
|
||||||
for i := range c.cmds {
|
for i := range c.cmds {
|
||||||
@ -137,15 +161,15 @@ func (c *Commands) Merge(allAliases map[string][]string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func noCmdAvailable(t *Term, args string) error {
|
func noCmdAvailable(t *Term, ctx callContext, args string) error {
|
||||||
return fmt.Errorf("command not available")
|
return fmt.Errorf("command not available")
|
||||||
}
|
}
|
||||||
|
|
||||||
func nullCommand(t *Term, args string) error {
|
func nullCommand(t *Term, ctx callContext, args string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) help(t *Term, args string) error {
|
func (c *Commands) help(t *Term, ctx callContext, args string) error {
|
||||||
fmt.Println("The following commands are available:")
|
fmt.Println("The following commands are available:")
|
||||||
w := new(tabwriter.Writer)
|
w := new(tabwriter.Writer)
|
||||||
w.Init(os.Stdout, 0, 8, 0, '-', 0)
|
w.Init(os.Stdout, 0, 8, 0, '-', 0)
|
||||||
@ -165,7 +189,7 @@ 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) 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 }
|
func (a byThreadID) Less(i, j int) bool { return a[i].ID < a[j].ID }
|
||||||
|
|
||||||
func threads(t *Term, args string) error {
|
func threads(t *Term, ctx callContext, args string) error {
|
||||||
threads, err := t.client.ListThreads()
|
threads, err := t.client.ListThreads()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -191,7 +215,7 @@ func threads(t *Term, args string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func thread(t *Term, args string) error {
|
func thread(t *Term, ctx callContext, args string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("you must specify a thread")
|
return fmt.Errorf("you must specify a thread")
|
||||||
}
|
}
|
||||||
@ -226,7 +250,7 @@ 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) 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 }
|
func (a byGoroutineID) Less(i, j int) bool { return a[i].ID < a[j].ID }
|
||||||
|
|
||||||
func goroutines(t *Term, argstr string) error {
|
func goroutines(t *Term, ctx callContext, argstr string) error {
|
||||||
args := strings.Split(argstr, " ")
|
args := strings.Split(argstr, " ")
|
||||||
var fgl = fglUserCurrent
|
var fgl = fglUserCurrent
|
||||||
|
|
||||||
@ -269,122 +293,73 @@ func goroutines(t *Term, argstr string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func goroutine(t *Term, argstr string) error {
|
func (c *Commands) goroutine(t *Term, ctx callContext, argstr string) error {
|
||||||
if argstr == "" {
|
args := strings.SplitN(argstr, " ", 3)
|
||||||
return printscope(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Index(argstr, " ") < 0 {
|
if ctx.Prefix == onPrefix {
|
||||||
gid, err := strconv.Atoi(argstr)
|
if len(args) != 1 || args[0] != "" {
|
||||||
if err != nil {
|
return errors.New("too many arguments to goroutine")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
ctx.Breakpoint.Goroutine = true
|
||||||
oldState, err := t.client.GetState()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newState, err := t.client.SwitchGoroutine(gid)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Switched from %d to %d (thread %d)\n", oldState.SelectedGoroutine.ID, gid, newState.CurrentThread.ID)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return scopePrefix(t, "goroutine "+argstr)
|
switch len(args) {
|
||||||
}
|
case 1:
|
||||||
|
if ctx.Prefix == scopePrefix {
|
||||||
func frame(t *Term, args string) error {
|
return errors.New("no command passed to goroutine")
|
||||||
return scopePrefix(t, "frame "+args)
|
}
|
||||||
}
|
if args[0] == "" {
|
||||||
|
return printscope(t)
|
||||||
func scopePrefix(t *Term, cmdstr string) error {
|
|
||||||
scope := api.EvalScope{GoroutineID: -1, Frame: 0}
|
|
||||||
lastcmd := ""
|
|
||||||
rest := cmdstr
|
|
||||||
|
|
||||||
nexttok := func() string {
|
|
||||||
v := strings.SplitN(rest, " ", 2)
|
|
||||||
if len(v) > 1 {
|
|
||||||
rest = v[1]
|
|
||||||
} else {
|
} else {
|
||||||
rest = ""
|
gid, err := strconv.Atoi(argstr)
|
||||||
}
|
if err != nil {
|
||||||
return v[0]
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
callFilterSortAndOutput := func(fn scopedFilteringFunc, fnargs string) error {
|
oldState, err := t.client.GetState()
|
||||||
outfn := filterSortAndOutput(func(t *Term, filter string) ([]string, error) {
|
if err != nil {
|
||||||
return fn(t, scope, filter)
|
return err
|
||||||
})
|
}
|
||||||
return outfn(t, fnargs)
|
newState, err := t.client.SwitchGoroutine(gid)
|
||||||
}
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
fmt.Printf("Switched from %d to %d (thread %d)\n", oldState.SelectedGoroutine.ID, gid, newState.CurrentThread.ID)
|
||||||
cmd := nexttok()
|
|
||||||
if cmd == "" && rest == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch cmd {
|
|
||||||
case "goroutine":
|
|
||||||
if rest == "" {
|
|
||||||
return fmt.Errorf("goroutine command needs an argument")
|
|
||||||
}
|
|
||||||
n, err := strconv.Atoi(nexttok())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid argument to goroutine, expected integer")
|
|
||||||
}
|
|
||||||
scope.GoroutineID = int(n)
|
|
||||||
case "frame":
|
|
||||||
if rest == "" {
|
|
||||||
return fmt.Errorf("frame command needs an argument")
|
|
||||||
}
|
|
||||||
n, err := strconv.Atoi(nexttok())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid argument to frame, expected integer")
|
|
||||||
}
|
|
||||||
scope.Frame = int(n)
|
|
||||||
case "list", "ls":
|
|
||||||
frame, gid := scope.Frame, scope.GoroutineID
|
|
||||||
locs, err := t.client.Stacktrace(gid, frame, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if frame >= len(locs) {
|
|
||||||
return fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
|
|
||||||
}
|
|
||||||
loc := locs[frame]
|
|
||||||
return printfile(t, loc.File, loc.Line, true)
|
|
||||||
case "stack", "bt":
|
|
||||||
depth, full, err := parseStackArgs(rest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
stack, err := t.client.Stacktrace(scope.GoroutineID, depth, full)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
printStack(stack, "")
|
|
||||||
return nil
|
return nil
|
||||||
case "locals":
|
|
||||||
return callFilterSortAndOutput(locals, rest)
|
|
||||||
case "args":
|
|
||||||
return callFilterSortAndOutput(args, rest)
|
|
||||||
case "print", "p":
|
|
||||||
return printVar(t, scope, rest)
|
|
||||||
case "set":
|
|
||||||
return setVar(t, scope, rest)
|
|
||||||
case "disassemble", "disasm":
|
|
||||||
return disassCommand(t, scope, rest)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown command %s", cmd)
|
|
||||||
}
|
}
|
||||||
lastcmd = cmd
|
case 2:
|
||||||
|
args = append(args, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("no command passed to %s", lastcmd)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printscope(t *Term) error {
|
func printscope(t *Term) error {
|
||||||
@ -451,7 +426,7 @@ func writeGoroutineLong(w io.Writer, g *api.Goroutine, prefix string) {
|
|||||||
prefix, formatLocation(g.GoStatementLoc))
|
prefix, formatLocation(g.GoStatementLoc))
|
||||||
}
|
}
|
||||||
|
|
||||||
func restart(t *Term, args string) error {
|
func restart(t *Term, ctx callContext, args string) error {
|
||||||
if err := t.client.Restart(); err != nil {
|
if err := t.client.Restart(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -459,7 +434,7 @@ func restart(t *Term, args string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cont(t *Term, args string) error {
|
func cont(t *Term, ctx callContext, args string) error {
|
||||||
stateChan := t.client.Continue()
|
stateChan := t.client.Continue()
|
||||||
for state := range stateChan {
|
for state := range stateChan {
|
||||||
if state.Err != nil {
|
if state.Err != nil {
|
||||||
@ -470,7 +445,7 @@ func cont(t *Term, args string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func step(t *Term, args string) error {
|
func step(t *Term, ctx callContext, args string) error {
|
||||||
state, err := t.client.Step()
|
state, err := t.client.Step()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -479,7 +454,7 @@ func step(t *Term, args string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func stepInstruction(t *Term, args string) error {
|
func stepInstruction(t *Term, ctx callContext, args string) error {
|
||||||
state, err := t.client.StepInstruction()
|
state, err := t.client.StepInstruction()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -488,7 +463,7 @@ func stepInstruction(t *Term, args string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func next(t *Term, args string) error {
|
func next(t *Term, ctx callContext, args string) error {
|
||||||
state, err := t.client.Next()
|
state, err := t.client.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -497,7 +472,7 @@ func next(t *Term, args string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func clear(t *Term, args string) error {
|
func clear(t *Term, ctx callContext, args string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("not enough arguments")
|
return fmt.Errorf("not enough arguments")
|
||||||
}
|
}
|
||||||
@ -515,7 +490,7 @@ func clear(t *Term, args string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearAll(t *Term, args string) error {
|
func clearAll(t *Term, ctx callContext, args string) error {
|
||||||
breakPoints, err := t.client.ListBreakpoints()
|
breakPoints, err := t.client.ListBreakpoints()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -556,7 +531,7 @@ 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) 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 (a ByID) Less(i, j int) bool { return a[i].ID < a[j].ID }
|
||||||
|
|
||||||
func breakpoints(t *Term, args string) error {
|
func breakpoints(t *Term, ctx callContext, args string) error {
|
||||||
breakPoints, err := t.client.ListBreakpoints()
|
breakPoints, err := t.client.ListBreakpoints()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -631,31 +606,23 @@ func setBreakpoint(t *Term, tracepoint bool, argstr string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func breakpoint(t *Term, args string) error {
|
func breakpoint(t *Term, ctx callContext, args string) error {
|
||||||
return setBreakpoint(t, false, args)
|
return setBreakpoint(t, false, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tracepoint(t *Term, args string) error {
|
func tracepoint(t *Term, ctx callContext, args string) error {
|
||||||
return setBreakpoint(t, true, args)
|
return setBreakpoint(t, true, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func g0f0(fn scopedCmdfunc) cmdfunc {
|
func printVar(t *Term, ctx callContext, args string) error {
|
||||||
return func(t *Term, args string) error {
|
|
||||||
return fn(t, api.EvalScope{GoroutineID: -1, Frame: 0}, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func g0f0filter(fn scopedFilteringFunc) filteringFunc {
|
|
||||||
return func(t *Term, filter string) ([]string, error) {
|
|
||||||
return fn(t, api.EvalScope{GoroutineID: -1, Frame: 0}, filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printVar(t *Term, scope api.EvalScope, args string) error {
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("not enough arguments")
|
return fmt.Errorf("not enough arguments")
|
||||||
}
|
}
|
||||||
val, err := t.client.EvalVariable(scope, args)
|
if ctx.Prefix == onPrefix {
|
||||||
|
ctx.Breakpoint.Variables = append(ctx.Breakpoint.Variables, args)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
val, err := t.client.EvalVariable(ctx.Scope, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -664,7 +631,7 @@ func printVar(t *Term, scope api.EvalScope, args string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setVar(t *Term, scope api.EvalScope, args string) error {
|
func setVar(t *Term, ctx callContext, args string) error {
|
||||||
// HACK: in go '=' is not an operator, we detect the error and try to recover from it by splitting the input string
|
// 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)
|
_, err := parser.ParseExpr(args)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -678,7 +645,7 @@ func setVar(t *Term, scope api.EvalScope, args string) error {
|
|||||||
|
|
||||||
lexpr := args[:el[0].Pos.Offset]
|
lexpr := args[:el[0].Pos.Offset]
|
||||||
rexpr := args[el[0].Pos.Offset+1:]
|
rexpr := args[el[0].Pos.Offset+1:]
|
||||||
return t.client.SetVariable(scope, lexpr, rexpr)
|
return t.client.SetVariable(ctx.Scope, lexpr, rexpr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterVariables(vars []api.Variable, filter string) []string {
|
func filterVariables(vars []api.Variable, filter string) []string {
|
||||||
@ -696,35 +663,35 @@ func filterVariables(vars []api.Variable, filter string) []string {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func sources(t *Term, filter string) ([]string, error) {
|
func sources(t *Term, ctx callContext, filter string) ([]string, error) {
|
||||||
return t.client.ListSources(filter)
|
return t.client.ListSources(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func funcs(t *Term, filter string) ([]string, error) {
|
func funcs(t *Term, ctx callContext, filter string) ([]string, error) {
|
||||||
return t.client.ListFunctions(filter)
|
return t.client.ListFunctions(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func types(t *Term, filter string) ([]string, error) {
|
func types(t *Term, ctx callContext, filter string) ([]string, error) {
|
||||||
return t.client.ListTypes(filter)
|
return t.client.ListTypes(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func args(t *Term, scope api.EvalScope, filter string) ([]string, error) {
|
func args(t *Term, ctx callContext, filter string) ([]string, error) {
|
||||||
vars, err := t.client.ListFunctionArgs(scope)
|
vars, err := t.client.ListFunctionArgs(ctx.Scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return filterVariables(vars, filter), nil
|
return filterVariables(vars, filter), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func locals(t *Term, scope api.EvalScope, filter string) ([]string, error) {
|
func locals(t *Term, ctx callContext, filter string) ([]string, error) {
|
||||||
locals, err := t.client.ListLocalVariables(scope)
|
locals, err := t.client.ListLocalVariables(ctx.Scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return filterVariables(locals, filter), nil
|
return filterVariables(locals, filter), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vars(t *Term, filter string) ([]string, error) {
|
func vars(t *Term, ctx callContext, filter string) ([]string, error) {
|
||||||
vars, err := t.client.ListPackageVariables(filter)
|
vars, err := t.client.ListPackageVariables(filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -732,7 +699,7 @@ func vars(t *Term, filter string) ([]string, error) {
|
|||||||
return filterVariables(vars, filter), nil
|
return filterVariables(vars, filter), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func regs(t *Term, args string) error {
|
func regs(t *Term, ctx callContext, args string) error {
|
||||||
regs, err := t.client.ListRegisters()
|
regs, err := t.client.ListRegisters()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -742,7 +709,7 @@ func regs(t *Term, args string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func filterSortAndOutput(fn filteringFunc) cmdfunc {
|
func filterSortAndOutput(fn filteringFunc) cmdfunc {
|
||||||
return func(t *Term, args string) error {
|
return func(t *Term, ctx callContext, args string) error {
|
||||||
var filter string
|
var filter string
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
if _, err := regexp.Compile(args); err != nil {
|
if _, err := regexp.Compile(args); err != nil {
|
||||||
@ -750,7 +717,7 @@ func filterSortAndOutput(fn filteringFunc) cmdfunc {
|
|||||||
}
|
}
|
||||||
filter = args
|
filter = args
|
||||||
}
|
}
|
||||||
data, err := fn(t, filter)
|
data, err := fn(t, ctx, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -762,16 +729,16 @@ func filterSortAndOutput(fn filteringFunc) cmdfunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stackCommand(t *Term, args string) error {
|
func stackCommand(t *Term, ctx callContext, args string) error {
|
||||||
var (
|
|
||||||
err error
|
|
||||||
goroutineid = -1
|
|
||||||
)
|
|
||||||
depth, full, err := parseStackArgs(args)
|
depth, full, err := parseStackArgs(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
stack, err := t.client.Stacktrace(goroutineid, depth, full)
|
if ctx.Prefix == onPrefix {
|
||||||
|
ctx.Breakpoint.Stacktrace = depth
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, depth, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -801,7 +768,19 @@ func parseStackArgs(argstr string) (int, bool, error) {
|
|||||||
return depth, full, nil
|
return depth, full, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func listCommand(t *Term, args string) error {
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
state, err := t.client.GetState()
|
state, err := t.client.GetState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -822,7 +801,7 @@ func listCommand(t *Term, args string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) sourceCommand(t *Term, args string) error {
|
func (c *Commands) sourceCommand(t *Term, ctx callContext, args string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("wrong number of arguments: source <filename>")
|
return fmt.Errorf("wrong number of arguments: source <filename>")
|
||||||
}
|
}
|
||||||
@ -832,7 +811,7 @@ func (c *Commands) sourceCommand(t *Term, args string) error {
|
|||||||
|
|
||||||
var disasmUsageError = errors.New("wrong number of arguments: disassemble [-a <start> <end>] [-l <locspec>]")
|
var disasmUsageError = errors.New("wrong number of arguments: disassemble [-a <start> <end>] [-l <locspec>]")
|
||||||
|
|
||||||
func disassCommand(t *Term, scope api.EvalScope, args string) error {
|
func disassCommand(t *Term, ctx callContext, args string) error {
|
||||||
var cmd, rest string
|
var cmd, rest string
|
||||||
|
|
||||||
if args != "" {
|
if args != "" {
|
||||||
@ -849,11 +828,11 @@ func disassCommand(t *Term, scope api.EvalScope, args string) error {
|
|||||||
|
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case "":
|
case "":
|
||||||
locs, err := t.client.FindLocation(scope, "+0")
|
locs, err := t.client.FindLocation(ctx.Scope, "+0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
disasm, disasmErr = t.client.DisassemblePC(scope, locs[0].PC, api.IntelFlavour)
|
disasm, disasmErr = t.client.DisassemblePC(ctx.Scope, locs[0].PC, api.IntelFlavour)
|
||||||
case "-a":
|
case "-a":
|
||||||
v := strings.SplitN(rest, " ", 2)
|
v := strings.SplitN(rest, " ", 2)
|
||||||
if len(v) != 2 {
|
if len(v) != 2 {
|
||||||
@ -867,16 +846,16 @@ func disassCommand(t *Term, scope api.EvalScope, args string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wrong argument: %s is not a number", v[1])
|
return fmt.Errorf("wrong argument: %s is not a number", v[1])
|
||||||
}
|
}
|
||||||
disasm, disasmErr = t.client.DisassembleRange(scope, uint64(startpc), uint64(endpc), api.IntelFlavour)
|
disasm, disasmErr = t.client.DisassembleRange(ctx.Scope, uint64(startpc), uint64(endpc), api.IntelFlavour)
|
||||||
case "-l":
|
case "-l":
|
||||||
locs, err := t.client.FindLocation(scope, rest)
|
locs, err := t.client.FindLocation(ctx.Scope, rest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(locs) != 1 {
|
if len(locs) != 1 {
|
||||||
return errors.New("expression specifies multiple locations")
|
return errors.New("expression specifies multiple locations")
|
||||||
}
|
}
|
||||||
disasm, disasmErr = t.client.DisassemblePC(scope, locs[0].PC, api.IntelFlavour)
|
disasm, disasmErr = t.client.DisassemblePC(ctx.Scope, locs[0].PC, api.IntelFlavour)
|
||||||
default:
|
default:
|
||||||
return disasmUsageError
|
return disasmUsageError
|
||||||
}
|
}
|
||||||
@ -1064,7 +1043,7 @@ func (ere ExitRequestError) Error() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func exitCommand(t *Term, args string) error {
|
func exitCommand(t *Term, ctx callContext, args string) error {
|
||||||
return ExitRequestError{}
|
return ExitRequestError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1077,11 +1056,15 @@ func getBreakpointByIDOrName(t *Term, arg string) (bp *api.Breakpoint, err error
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func onCmd(t *Term, argstr string) error {
|
func (c *Commands) onCmd(t *Term, ctx callContext, argstr string) error {
|
||||||
args := strings.SplitN(argstr, " ", 3)
|
args := strings.SplitN(argstr, " ", 3)
|
||||||
|
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return fmt.Errorf("not enough arguments")
|
return errors.New("not enough arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) < 3 {
|
||||||
|
args = append(args, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
bp, err := getBreakpointByIDOrName(t, args[0])
|
bp, err := getBreakpointByIDOrName(t, args[0])
|
||||||
@ -1089,29 +1072,16 @@ func onCmd(t *Term, argstr string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch args[1] {
|
ctx.Prefix = onPrefix
|
||||||
case "p", "print":
|
ctx.Breakpoint = bp
|
||||||
if len(args) < 3 {
|
err = c.CallWithContext(args[1], args[2], t, ctx)
|
||||||
return fmt.Errorf("not enough arguments")
|
if err != nil {
|
||||||
}
|
return err
|
||||||
bp.Variables = append(bp.Variables, args[2])
|
|
||||||
case "stack", "bt":
|
|
||||||
depth, _, err := parseStackArgs(args[2])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
bp.Stacktrace = depth
|
|
||||||
case "goroutine":
|
|
||||||
if len(args) != 2 {
|
|
||||||
return fmt.Errorf("too many arguments")
|
|
||||||
}
|
|
||||||
bp.Goroutine = true
|
|
||||||
}
|
}
|
||||||
|
return t.client.AmendBreakpoint(ctx.Breakpoint)
|
||||||
return t.client.AmendBreakpoint(bp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func conditionCmd(t *Term, argstr string) error {
|
func conditionCmd(t *Term, ctx callContext, argstr string) error {
|
||||||
args := strings.SplitN(argstr, " ", 2)
|
args := strings.SplitN(argstr, " ", 2)
|
||||||
|
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
@ -1152,10 +1122,8 @@ func (c *Commands) executeFile(t *Term, name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmdstr, args := parseCommand(line)
|
cmdstr, args := parseCommand(line)
|
||||||
cmd := c.Find(cmdstr)
|
|
||||||
err := cmd(t, args)
|
|
||||||
|
|
||||||
if err != nil {
|
if err := c.Call(cmdstr, args, t); err != nil {
|
||||||
fmt.Printf("%s:%d: %v\n", name, lineno, err)
|
fmt.Printf("%s:%d: %v\n", name, lineno, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -17,12 +18,13 @@ import (
|
|||||||
|
|
||||||
type FakeTerminal struct {
|
type FakeTerminal struct {
|
||||||
*Term
|
*Term
|
||||||
t testing.TB
|
t testing.TB
|
||||||
|
client service.Client
|
||||||
|
cmds *Commands
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ft *FakeTerminal) Exec(cmdstr string) (outstr string, err error) {
|
func (ft *FakeTerminal) Exec(cmdstr string) (outstr string, err error) {
|
||||||
cmdstr, args := parseCommand(cmdstr)
|
cmdstr, args := parseCommand(cmdstr)
|
||||||
cmd := ft.cmds.Find(cmdstr)
|
|
||||||
|
|
||||||
outfh, err := ioutil.TempFile("", "cmdtestout")
|
outfh, err := ioutil.TempFile("", "cmdtestout")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -41,7 +43,7 @@ func (ft *FakeTerminal) Exec(cmdstr string) (outstr string, err error) {
|
|||||||
outstr = string(outbs)
|
outstr = string(outbs)
|
||||||
os.Remove(outfh.Name())
|
os.Remove(outfh.Name())
|
||||||
}()
|
}()
|
||||||
err = cmd(ft.Term, args)
|
err = ft.cmds.Call(cmdstr, args, ft.Term)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +55,23 @@ func (ft *FakeTerminal) MustExec(cmdstr string) string {
|
|||||||
return outstr
|
return outstr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ft *FakeTerminal) AssertExec(cmdstr, tgt string) {
|
||||||
|
out := ft.MustExec(cmdstr)
|
||||||
|
if out != tgt {
|
||||||
|
ft.t.Fatalf("Error executing %q, expected %q got %q", cmdstr, tgt, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ft *FakeTerminal) AssertExecError(cmdstr, tgterr string) {
|
||||||
|
_, err := ft.Exec(cmdstr)
|
||||||
|
if err == nil {
|
||||||
|
ft.t.Fatalf("Expected error executing %q")
|
||||||
|
}
|
||||||
|
if err.Error() != tgterr {
|
||||||
|
ft.t.Fatalf("Expected error %q executing %q, got error %q", tgterr, cmdstr, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) {
|
func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) {
|
||||||
listener, err := net.Listen("tcp", "localhost:0")
|
listener, err := net.Listen("tcp", "localhost:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -70,9 +89,12 @@ func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
client.Detach(true)
|
client.Detach(true)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ft := &FakeTerminal{
|
ft := &FakeTerminal{
|
||||||
t: t,
|
t: t,
|
||||||
Term: New(client, nil),
|
client: client,
|
||||||
|
cmds: DebugCommands(client),
|
||||||
|
Term: New(client, nil),
|
||||||
}
|
}
|
||||||
fn(ft)
|
fn(ft)
|
||||||
}
|
}
|
||||||
@ -80,10 +102,10 @@ func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) {
|
|||||||
func TestCommandDefault(t *testing.T) {
|
func TestCommandDefault(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
cmds = Commands{}
|
cmds = Commands{}
|
||||||
cmd = cmds.Find("non-existant-command")
|
cmd = cmds.Find("non-existant-command", noPrefix)
|
||||||
)
|
)
|
||||||
|
|
||||||
err := cmd(nil, "")
|
err := cmd(nil, callContext{}, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("cmd() did not default")
|
t.Fatal("cmd() did not default")
|
||||||
}
|
}
|
||||||
@ -95,16 +117,16 @@ func TestCommandDefault(t *testing.T) {
|
|||||||
|
|
||||||
func TestCommandReplay(t *testing.T) {
|
func TestCommandReplay(t *testing.T) {
|
||||||
cmds := DebugCommands(nil)
|
cmds := DebugCommands(nil)
|
||||||
cmds.Register("foo", func(t *Term, args string) error { return fmt.Errorf("registered command") }, "foo command")
|
cmds.Register("foo", func(t *Term, ctx callContext, args string) error { return fmt.Errorf("registered command") }, "foo command")
|
||||||
cmd := cmds.Find("foo")
|
cmd := cmds.Find("foo", noPrefix)
|
||||||
|
|
||||||
err := cmd(nil, "")
|
err := cmd(nil, callContext{}, "")
|
||||||
if err.Error() != "registered command" {
|
if err.Error() != "registered command" {
|
||||||
t.Fatal("wrong command output")
|
t.Fatal("wrong command output")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = cmds.Find("")
|
cmd = cmds.Find("", noPrefix)
|
||||||
err = cmd(nil, "")
|
err = cmd(nil, callContext{}, "")
|
||||||
if err.Error() != "registered command" {
|
if err.Error() != "registered command" {
|
||||||
t.Fatal("wrong command output")
|
t.Fatal("wrong command output")
|
||||||
}
|
}
|
||||||
@ -113,8 +135,8 @@ func TestCommandReplay(t *testing.T) {
|
|||||||
func TestCommandReplayWithoutPreviousCommand(t *testing.T) {
|
func TestCommandReplayWithoutPreviousCommand(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
cmds = DebugCommands(nil)
|
cmds = DebugCommands(nil)
|
||||||
cmd = cmds.Find("")
|
cmd = cmds.Find("", noPrefix)
|
||||||
err = cmd(nil, "")
|
err = cmd(nil, callContext{}, "")
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -125,10 +147,10 @@ func TestCommandReplayWithoutPreviousCommand(t *testing.T) {
|
|||||||
func TestCommandThread(t *testing.T) {
|
func TestCommandThread(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
cmds = DebugCommands(nil)
|
cmds = DebugCommands(nil)
|
||||||
cmd = cmds.Find("thread")
|
cmd = cmds.Find("thread", noPrefix)
|
||||||
)
|
)
|
||||||
|
|
||||||
err := cmd(nil, "")
|
err := cmd(nil, callContext{}, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("thread terminal command did not default")
|
t.Fatal("thread terminal command did not default")
|
||||||
}
|
}
|
||||||
@ -144,11 +166,11 @@ func TestExecuteFile(t *testing.T) {
|
|||||||
c := &Commands{
|
c := &Commands{
|
||||||
client: nil,
|
client: nil,
|
||||||
cmds: []command{
|
cmds: []command{
|
||||||
{aliases: []string{"trace"}, cmdFn: func(t *Term, args string) error {
|
{aliases: []string{"trace"}, cmdFn: func(t *Term, ctx callContext, args string) error {
|
||||||
traceCount++
|
traceCount++
|
||||||
return nil
|
return nil
|
||||||
}},
|
}},
|
||||||
{aliases: []string{"break"}, cmdFn: func(t *Term, args string) error {
|
{aliases: []string{"break"}, cmdFn: func(t *Term, ctx callContext, args string) error {
|
||||||
breakCount++
|
breakCount++
|
||||||
return nil
|
return nil
|
||||||
}},
|
}},
|
||||||
@ -184,3 +206,153 @@ func TestIssue411(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestScopePrefix(t *testing.T) {
|
||||||
|
const goroutinesLinePrefix = " Goroutine "
|
||||||
|
const goroutinesCurLinePrefix = "* Goroutine "
|
||||||
|
withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
|
||||||
|
term.MustExec("b stacktraceme")
|
||||||
|
term.MustExec("continue")
|
||||||
|
|
||||||
|
goroutinesOut := strings.Split(term.MustExec("goroutines"), "\n")
|
||||||
|
|
||||||
|
agoroutines := []int{}
|
||||||
|
curgid := -1
|
||||||
|
|
||||||
|
for _, line := range goroutinesOut {
|
||||||
|
iscur := strings.HasPrefix(line, goroutinesCurLinePrefix)
|
||||||
|
if !iscur && !strings.HasPrefix(line, goroutinesLinePrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dash := strings.Index(line, " - ")
|
||||||
|
if dash < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
gid, err := strconv.Atoi(line[len(goroutinesLinePrefix):dash])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if iscur {
|
||||||
|
curgid = gid
|
||||||
|
}
|
||||||
|
|
||||||
|
if idx := strings.Index(line, " main.agoroutine "); idx < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
agoroutines = append(agoroutines, gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(agoroutines) != 10 {
|
||||||
|
t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine: %q", goroutinesOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
if curgid < 0 {
|
||||||
|
t.Fatalf("Could not find current goroutine in output of goroutines: %q", goroutinesOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make([]bool, 10)
|
||||||
|
_ = seen
|
||||||
|
|
||||||
|
for _, gid := range agoroutines {
|
||||||
|
stackOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d stack", gid)), "\n")
|
||||||
|
fid := -1
|
||||||
|
for _, line := range stackOut {
|
||||||
|
space := strings.Index(line, " ")
|
||||||
|
if space < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
curfid, err := strconv.Atoi(line[:space])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if idx := strings.Index(line, " main.agoroutine"); idx >= 0 {
|
||||||
|
fid = curfid
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fid < 0 {
|
||||||
|
t.Fatalf("Could not find frame for goroutine %d: %v", gid, stackOut)
|
||||||
|
}
|
||||||
|
term.AssertExec(fmt.Sprintf("goroutine %d frame %d locals", gid, fid), "")
|
||||||
|
argsOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d frame %d args", gid, fid)), "\n")
|
||||||
|
if len(argsOut) != 4 || argsOut[3] != "" {
|
||||||
|
t.Fatalf("Wrong number of arguments in goroutine %d frame %d: %v", gid, fid, argsOut)
|
||||||
|
}
|
||||||
|
out := term.MustExec(fmt.Sprintf("goroutine %d frame %d p i", gid, fid))
|
||||||
|
ival, err := strconv.Atoi(out[:len(out)-1])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not parse value %q of i for goroutine %d frame %d: %v", out, gid, fid, err)
|
||||||
|
}
|
||||||
|
seen[ival] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range seen {
|
||||||
|
if !seen[i] {
|
||||||
|
t.Fatalf("goroutine %d not found", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
term.MustExec("c")
|
||||||
|
|
||||||
|
term.AssertExecError("frame", "not enough arguments")
|
||||||
|
term.AssertExecError("frame 1", "not enough arguments")
|
||||||
|
term.AssertExecError("frame 1 goroutines", "command not available")
|
||||||
|
term.AssertExecError("frame 1 goroutine", "no command passed to goroutine")
|
||||||
|
term.AssertExecError(fmt.Sprintf("frame 1 goroutine %d", curgid), "no command passed to goroutine")
|
||||||
|
term.AssertExecError(fmt.Sprintf("goroutine %d frame 10 locals", curgid), fmt.Sprintf("Frame 10 does not exist in goroutine %d", curgid))
|
||||||
|
term.AssertExecError("goroutine 9000 locals", "Unknown goroutine 9000")
|
||||||
|
|
||||||
|
term.AssertExecError("print n", "could not find symbol value for n")
|
||||||
|
term.AssertExec("frame 1 print n", "3\n")
|
||||||
|
term.AssertExec("frame 2 print n", "2\n")
|
||||||
|
term.AssertExec("frame 3 print n", "1\n")
|
||||||
|
term.AssertExec("frame 4 print n", "0\n")
|
||||||
|
term.AssertExecError("frame 5 print n", "could not find symbol value for n")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnPrefix(t *testing.T) {
|
||||||
|
const prefix = "\ti: "
|
||||||
|
withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
|
||||||
|
term.MustExec("b agobp main.agoroutine")
|
||||||
|
term.MustExec("on agobp print i")
|
||||||
|
|
||||||
|
seen := make([]bool, 10)
|
||||||
|
|
||||||
|
for {
|
||||||
|
outstr, err := term.Exec("continue")
|
||||||
|
if err != nil {
|
||||||
|
if strings.Index(err.Error(), "exited") < 0 {
|
||||||
|
t.Fatalf("Unexpected error executing 'continue': %v", err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out := strings.Split(outstr, "\n")
|
||||||
|
|
||||||
|
for i := range out {
|
||||||
|
if !strings.HasPrefix(out[i], "\ti: ") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id, err := strconv.Atoi(out[i][len(prefix):])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if seen[id] {
|
||||||
|
t.Fatalf("Goroutine %d seen twice\n", id)
|
||||||
|
}
|
||||||
|
seen[id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range seen {
|
||||||
|
if !seen[i] {
|
||||||
|
t.Fatalf("Goroutine %d not seen\n", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -115,8 +115,7 @@ func (t *Term) Run() (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmdstr, args := parseCommand(cmdstr)
|
cmdstr, args := parseCommand(cmdstr)
|
||||||
cmd := t.cmds.Find(cmdstr)
|
if err := t.cmds.Call(cmdstr, args, t); err != nil {
|
||||||
if err := cmd(t, args); err != nil {
|
|
||||||
if _, ok := err.(ExitRequestError); ok {
|
if _, ok := err.(ExitRequestError); ok {
|
||||||
return t.handleExit()
|
return t.handleExit()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user