2015-10-28 02:38:57 +00:00
// Package terminal implements functions for responding to user
2015-03-20 21:11:11 +00:00
// input and dispatching to appropriate backend commands.
package terminal
import (
"bufio"
"fmt"
2015-11-02 06:30:12 +00:00
"go/parser"
"go/scanner"
2015-10-16 06:42:02 +00:00
"io"
2015-09-17 08:42:34 +00:00
"math"
2015-03-20 21:11:11 +00:00
"os"
"regexp"
"sort"
"strconv"
"strings"
2015-07-29 22:49:23 +00:00
"text/tabwriter"
2015-03-20 21:11:11 +00:00
"github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api"
2015-07-29 14:24:52 +00:00
"github.com/derekparker/delve/service/debugger"
2015-03-20 21:11:11 +00:00
)
2015-11-02 06:30:12 +00:00
type cmdfunc func ( t * Term , args string ) error
type scopedCmdfunc func ( t * Term , scope api . EvalScope , args string ) error
2015-08-28 20:06:29 +00:00
2015-09-30 05:42:06 +00:00
type filteringFunc func ( t * Term , filter string ) ( [ ] string , error )
type scopedFilteringFunc func ( t * Term , scope api . EvalScope , filter string ) ( [ ] string , error )
2015-03-20 21:11:11 +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
}
type Commands struct {
2015-10-04 17:58:32 +00:00
cmds [ ] command
lastCmd cmdfunc
client service . Client
2015-03-20 21:11:11 +00:00
}
// Returns a Commands struct with default commands defined.
func DebugCommands ( client service . Client ) * Commands {
c := & Commands { client : client }
c . cmds = [ ] command {
{ aliases : [ ] string { "help" } , cmdFn : c . help , helpMsg : "Prints the help message." } ,
2015-08-07 16:50:14 +00:00
{ aliases : [ ] string { "break" , "b" } , cmdFn : breakpoint , helpMsg : "break <linespec> [-stack <n>|-goroutine|<variable name>]*" } ,
2015-07-29 22:49:23 +00:00
{ aliases : [ ] string { "trace" , "t" } , cmdFn : tracepoint , helpMsg : "Set tracepoint, takes the same arguments as break." } ,
2015-07-03 20:35:22 +00:00
{ aliases : [ ] string { "restart" , "r" } , cmdFn : restart , helpMsg : "Restart process." } ,
2015-03-20 21:11:11 +00:00
{ aliases : [ ] string { "continue" , "c" } , cmdFn : cont , helpMsg : "Run until breakpoint or program termination." } ,
{ aliases : [ ] string { "step" , "si" } , cmdFn : step , helpMsg : "Single step through program." } ,
{ 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." } ,
2015-07-29 13:20:33 +00:00
{ aliases : [ ] string { "thread" , "tr" } , cmdFn : thread , helpMsg : "Switch to the specified thread." } ,
2015-03-20 21:11:11 +00:00
{ aliases : [ ] string { "clear" } , cmdFn : clear , helpMsg : "Deletes breakpoint." } ,
2015-10-04 17:59:53 +00:00
{ aliases : [ ] string { "clearall" } , cmdFn : clearAll , helpMsg : "clearall [<linespec>]. Deletes all breakpoints. If <linespec> is provided, only matching breakpoints will be deleted." } ,
2015-10-18 22:02:14 +00:00
{ aliases : [ ] string { "goroutines" } , cmdFn : goroutines , helpMsg : "goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)] Print out info for every goroutine." } ,
2015-08-28 20:06:29 +00:00
{ aliases : [ ] string { "goroutine" } , cmdFn : goroutine , helpMsg : "Sets current goroutine." } ,
2015-03-20 21:11:11 +00:00
{ aliases : [ ] string { "breakpoints" , "bp" } , cmdFn : breakpoints , helpMsg : "Print out info for active breakpoints." } ,
2015-08-28 20:06:29 +00:00
{ aliases : [ ] string { "print" , "p" } , cmdFn : g0f0 ( printVar ) , helpMsg : "Evaluate a variable." } ,
2015-09-28 10:01:18 +00:00
{ aliases : [ ] string { "set" } , cmdFn : g0f0 ( setVar ) , helpMsg : "Changes the value of a variable." } ,
2015-08-11 02:31:27 +00:00
{ 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." } ,
2015-08-28 20:06:29 +00:00
{ aliases : [ ] string { "args" } , cmdFn : filterSortAndOutput ( g0f0filter ( 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." } ,
2015-08-11 02:31:27 +00:00
{ 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." } ,
2015-07-29 23:19:06 +00:00
{ aliases : [ ] string { "exit" , "quit" , "q" } , cmdFn : exitCommand , helpMsg : "Exit the debugger." } ,
2015-08-14 13:58:17 +00:00
{ aliases : [ ] string { "list" , "ls" } , cmdFn : listCommand , helpMsg : "list <linespec>. Show source around current point or provided linespec." } ,
2015-09-20 04:41:26 +00:00
{ aliases : [ ] string { "stack" , "bt" } , cmdFn : stackCommand , helpMsg : "stack [<depth>] [-full]. Prints stack." } ,
2015-08-28 20:06:29 +00:00
{ aliases : [ ] string { "frame" } , cmdFn : frame , helpMsg : "Sets current stack frame (0 is the top of the stack)" } ,
2015-09-29 16:40:12 +00:00
{ aliases : [ ] string { "source" } , cmdFn : c . sourceCommand , helpMsg : "Executes a file containing a list of delve commands" } ,
2015-03-20 21:11:11 +00:00
}
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.
2015-07-16 18:07:34 +00:00
// If it cannot find the command it will default to noCmdAvailable().
2015-03-20 21:11:11 +00:00
// If the command is an empty string it will replay the last command.
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
}
for _ , v := range c . cmds {
if v . match ( cmdstr ) {
c . lastCmd = v . cmdFn
return v . cmdFn
}
}
return noCmdAvailable
}
2015-08-17 03:41:25 +00:00
// 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 ... )
}
}
}
2015-03-20 21:11:11 +00:00
func CommandFunc ( fn func ( ) error ) cmdfunc {
2015-11-02 06:30:12 +00:00
return func ( t * Term , args string ) error {
2015-03-20 21:11:11 +00:00
return fn ( )
}
}
2015-11-02 06:30:12 +00:00
func noCmdAvailable ( t * Term , args string ) error {
2015-05-04 22:31:13 +00:00
return fmt . Errorf ( "command not available" )
2015-03-20 21:11:11 +00:00
}
2015-11-02 06:30:12 +00:00
func nullCommand ( t * Term , args string ) error {
2015-03-20 21:11:11 +00:00
return nil
}
2015-11-02 06:30:12 +00:00
func ( c * Commands ) help ( t * Term , args string ) error {
2015-03-20 21:11:11 +00:00
fmt . Println ( "The following commands are available:" )
2015-07-29 22:49:23 +00:00
w := new ( tabwriter . Writer )
w . Init ( os . Stdout , 0 , 8 , 0 , '-' , 0 )
2015-03-20 21:11:11 +00:00
for _ , cmd := range c . cmds {
2015-07-29 22:49:23 +00:00
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 )
}
2015-03-20 21:11:11 +00:00
}
2015-07-29 22:49:23 +00:00
return w . Flush ( )
2015-03-20 21:11:11 +00:00
}
2015-09-10 18:12:42 +00:00
type byThreadID [ ] * api . Thread
2015-09-10 18:04:59 +00:00
2015-09-10 18:12:42 +00:00
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 }
2015-09-10 18:04:59 +00:00
2015-11-02 06:30:12 +00:00
func threads ( t * Term , args string ) error {
2015-09-30 05:42:06 +00:00
threads , err := t . client . ListThreads ( )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-09-30 05:42:06 +00:00
state , err := t . client . GetState ( )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-09-10 18:12:42 +00:00
sort . Sort ( byThreadID ( threads ) )
2015-03-20 21:11:11 +00:00
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" ,
2015-12-28 02:58:00 +00:00
prefix , th . ID , th . PC , ShortenFilePath ( th . File ) ,
2015-03-20 21:11:11 +00:00
th . Line , th . Function . Name )
} else {
2015-08-28 20:06:29 +00:00
fmt . Printf ( "%sThread %s\n" , prefix , formatThread ( th ) )
2015-03-20 21:11:11 +00:00
}
}
return nil
}
2015-11-02 06:30:12 +00:00
func thread ( t * Term , args string ) error {
2015-05-29 17:29:02 +00:00
if len ( args ) == 0 {
return fmt . Errorf ( "you must specify a thread" )
}
2015-11-02 06:30:12 +00:00
tid , err := strconv . Atoi ( args )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-09-30 05:42:06 +00:00
oldState , err := t . client . GetState ( )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-09-30 05:42:06 +00:00
newState , err := t . client . SwitchThread ( tid )
2015-03-20 21:11:11 +00:00
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
}
2015-09-10 18:12:42 +00:00
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 }
2015-11-02 06:30:12 +00:00
func goroutines ( t * Term , argstr string ) error {
args := strings . Split ( argstr , " " )
2015-10-16 06:42:02 +00:00
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
default :
fmt . Errorf ( "wrong argument: '%s'" , args [ 0 ] )
}
default :
return fmt . Errorf ( "too many arguments" )
}
2015-09-30 05:42:06 +00:00
state , err := t . client . GetState ( )
2015-08-28 20:06:29 +00:00
if err != nil {
return err
}
2015-09-30 05:42:06 +00:00
gs , err := t . client . ListGoroutines ( )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-09-10 18:12:42 +00:00
sort . Sort ( byGoroutineID ( gs ) )
2015-03-20 21:11:11 +00:00
fmt . Printf ( "[%d goroutines]\n" , len ( gs ) )
for _ , g := range gs {
2015-08-28 20:06:29 +00:00
prefix := " "
2015-10-03 12:29:46 +00:00
if state . SelectedGoroutine != nil && g . ID == state . SelectedGoroutine . ID {
2015-08-28 20:06:29 +00:00
prefix = "* "
}
2015-10-16 06:42:02 +00:00
fmt . Printf ( "%sGoroutine %s\n" , prefix , formatGoroutine ( g , fgl ) )
2015-08-28 20:06:29 +00:00
}
return nil
}
2015-11-02 06:30:12 +00:00
func goroutine ( t * Term , argstr string ) error {
if argstr == "" {
2015-09-30 05:42:06 +00:00
return printscope ( t )
2015-11-02 06:30:12 +00:00
}
2015-08-28 20:06:29 +00:00
2015-11-02 06:30:12 +00:00
if strings . Index ( argstr , " " ) < 0 {
gid , err := strconv . Atoi ( argstr )
2015-08-28 20:06:29 +00:00
if err != nil {
return err
}
2015-09-30 05:42:06 +00:00
oldState , err := t . client . GetState ( )
2015-08-28 20:06:29 +00:00
if err != nil {
return err
}
2015-09-30 05:42:06 +00:00
newState , err := t . client . SwitchGoroutine ( gid )
2015-08-28 20:06:29 +00:00
if err != nil {
return err
}
fmt . Printf ( "Switched from %d to %d (thread %d)\n" , oldState . SelectedGoroutine . ID , gid , newState . CurrentThread . ID )
return nil
}
2015-11-02 06:30:12 +00:00
return scopePrefix ( t , "goroutine " + argstr )
2015-08-28 20:06:29 +00:00
}
2015-11-02 06:30:12 +00:00
func frame ( t * Term , args string ) error {
return scopePrefix ( t , "frame " + args )
}
2015-08-28 20:06:29 +00:00
2015-11-02 06:30:12 +00:00
func scopePrefix ( t * Term , cmdstr string ) error {
2015-08-28 20:06:29 +00:00
scope := api . EvalScope { - 1 , 0 }
lastcmd := ""
2015-11-02 06:30:12 +00:00
rest := cmdstr
nexttok := func ( ) string {
v := strings . SplitN ( rest , " " , 2 )
if len ( v ) > 1 {
rest = v [ 1 ]
} else {
rest = ""
}
return v [ 0 ]
}
2015-08-28 20:06:29 +00:00
2015-11-02 06:30:12 +00:00
callFilterSortAndOutput := func ( fn scopedFilteringFunc , fnargs string ) error {
2015-09-30 05:42:06 +00:00
outfn := filterSortAndOutput ( func ( t * Term , filter string ) ( [ ] string , error ) {
return fn ( t , scope , filter )
2015-08-28 20:06:29 +00:00
} )
2015-11-02 06:30:12 +00:00
return outfn ( t , fnargs )
2015-08-28 20:06:29 +00:00
}
2015-11-02 06:30:12 +00:00
for {
cmd := nexttok ( )
if cmd == "" && rest == "" {
break
}
switch cmd {
2015-08-28 20:06:29 +00:00
case "goroutine" :
2015-11-02 06:30:12 +00:00
if rest == "" {
2015-08-28 20:06:29 +00:00
return fmt . Errorf ( "goroutine command needs an argument" )
}
2015-11-02 06:30:12 +00:00
n , err := strconv . Atoi ( nexttok ( ) )
2015-08-28 20:06:29 +00:00
if err != nil {
return fmt . Errorf ( "invalid argument to goroutine, expected integer" )
}
scope . GoroutineID = int ( n )
case "frame" :
2015-11-02 06:30:12 +00:00
if rest == "" {
2015-08-28 20:06:29 +00:00
return fmt . Errorf ( "frame command needs an argument" )
}
2015-11-02 06:30:12 +00:00
n , err := strconv . Atoi ( nexttok ( ) )
2015-08-28 20:06:29 +00:00
if err != nil {
return fmt . Errorf ( "invalid argument to frame, expected integer" )
}
scope . Frame = int ( n )
2015-09-10 17:24:49 +00:00
case "list" , "ls" :
frame , gid := scope . Frame , scope . GoroutineID
2015-09-30 05:42:06 +00:00
locs , err := t . client . Stacktrace ( gid , frame , false )
2015-09-10 17:24:49 +00:00
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 ]
2015-09-30 05:42:06 +00:00
return printfile ( t , loc . File , loc . Line , true )
2015-09-20 04:41:26 +00:00
case "stack" , "bt" :
2015-11-02 06:30:12 +00:00
depth , full , err := parseStackArgs ( rest )
2015-09-20 04:41:26 +00:00
if err != nil {
return err
}
2015-09-30 05:42:06 +00:00
stack , err := t . client . Stacktrace ( scope . GoroutineID , depth , full )
2015-09-20 04:41:26 +00:00
if err != nil {
return err
}
printStack ( stack , "" )
return nil
2015-08-28 20:06:29 +00:00
case "locals" :
2015-11-02 06:30:12 +00:00
return callFilterSortAndOutput ( locals , rest )
2015-08-28 20:06:29 +00:00
case "args" :
2015-11-02 06:30:12 +00:00
return callFilterSortAndOutput ( args , rest )
2015-08-28 20:06:29 +00:00
case "print" , "p" :
2015-11-02 06:30:12 +00:00
return printVar ( t , scope , rest )
case "set" :
return setVar ( t , scope , rest )
2015-08-28 20:06:29 +00:00
default :
2015-11-02 06:30:12 +00:00
return fmt . Errorf ( "unknown command %s" , cmd )
2015-08-28 20:06:29 +00:00
}
2015-11-02 06:30:12 +00:00
lastcmd = cmd
2015-08-28 20:06:29 +00:00
}
return fmt . Errorf ( "no command passed to %s" , lastcmd )
}
2015-09-30 05:42:06 +00:00
func printscope ( t * Term ) error {
state , err := t . client . GetState ( )
2015-08-28 20:06:29 +00:00
if err != nil {
return err
2015-03-20 21:11:11 +00:00
}
2015-08-28 20:06:29 +00:00
2015-10-16 06:42:02 +00:00
fmt . Printf ( "Thread %s\n" , formatThread ( state . CurrentThread ) )
if state . SelectedGoroutine != nil {
writeGoroutineLong ( os . Stdout , state . SelectedGoroutine , "" )
}
2015-03-20 21:11:11 +00:00
return nil
}
2015-08-28 20:06:29 +00:00
func formatThread ( th * api . Thread ) string {
if th == nil {
return "<nil>"
}
2015-12-28 02:58:00 +00:00
return fmt . Sprintf ( "%d at %s:%d" , th . ID , ShortenFilePath ( th . File ) , th . Line )
2015-08-28 20:06:29 +00:00
}
2015-10-16 06:42:02 +00:00
type formatGoroutineLoc int
const (
fglRuntimeCurrent = formatGoroutineLoc ( iota )
fglUserCurrent
fglGo
)
func formatLocation ( loc api . Location ) string {
fname := ""
if loc . Function != nil {
fname = loc . Function . Name
}
2015-12-28 02:58:00 +00:00
return fmt . Sprintf ( "%s:%d %s (%#v)" , ShortenFilePath ( loc . File ) , loc . Line , fname , loc . PC )
2015-10-16 06:42:02 +00:00
}
func formatGoroutine ( g * api . Goroutine , fgl formatGoroutineLoc ) string {
2015-08-28 20:06:29 +00:00
if g == nil {
return "<nil>"
}
2015-10-16 06:42:02 +00:00
var locname string
var loc api . Location
switch fgl {
case fglRuntimeCurrent :
locname = "Runtime"
2015-10-18 22:02:14 +00:00
loc = g . CurrentLoc
2015-10-16 06:42:02 +00:00
case fglUserCurrent :
locname = "User"
2015-10-18 22:02:14 +00:00
loc = g . UserCurrentLoc
2015-10-16 06:42:02 +00:00
case fglGo :
locname = "Go"
2015-10-18 22:02:14 +00:00
loc = g . GoStatementLoc
2015-06-28 15:00:56 +00:00
}
2015-10-16 06:42:02 +00:00
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 ,
2015-10-18 22:02:14 +00:00
prefix , formatLocation ( g . CurrentLoc ) ,
prefix , formatLocation ( g . UserCurrentLoc ) ,
prefix , formatLocation ( g . GoStatementLoc ) )
2015-06-28 15:00:56 +00:00
}
2015-11-02 06:30:12 +00:00
func restart ( t * Term , args string ) error {
2015-09-30 05:42:06 +00:00
if err := t . client . Restart ( ) ; err != nil {
2015-07-03 20:35:22 +00:00
return err
}
2015-09-30 05:42:06 +00:00
fmt . Println ( "Process restarted with PID" , t . client . ProcessPid ( ) )
2015-07-03 20:35:22 +00:00
return nil
}
2015-11-02 06:30:12 +00:00
func cont ( t * Term , args string ) error {
2015-09-30 05:42:06 +00:00
stateChan := t . client . Continue ( )
2015-07-01 03:16:52 +00:00
for state := range stateChan {
2015-06-28 15:00:56 +00:00
if state . Err != nil {
return state . Err
}
2015-09-30 05:42:06 +00:00
printcontext ( t , state )
2015-03-20 21:11:11 +00:00
}
return nil
}
2015-11-02 06:30:12 +00:00
func step ( t * Term , args string ) error {
2015-09-30 05:42:06 +00:00
state , err := t . client . Step ( )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-09-30 05:42:06 +00:00
printcontext ( t , state )
2015-03-20 21:11:11 +00:00
return nil
}
2015-11-02 06:30:12 +00:00
func next ( t * Term , args string ) error {
2015-09-30 05:42:06 +00:00
state , err := t . client . Next ( )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-09-30 05:42:06 +00:00
printcontext ( t , state )
2015-03-20 21:11:11 +00:00
return nil
}
2015-11-02 06:30:12 +00:00
func clear ( t * Term , args string ) error {
2015-03-20 21:11:11 +00:00
if len ( args ) == 0 {
2015-05-04 22:31:13 +00:00
return fmt . Errorf ( "not enough arguments" )
2015-03-20 21:11:11 +00:00
}
2015-11-02 06:30:12 +00:00
id , err := strconv . Atoi ( args )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-09-30 05:42:06 +00:00
bp , err := t . client . ClearBreakpoint ( id )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-12-28 02:58:00 +00:00
fmt . Printf ( "Breakpoint %d cleared at %#v for %s %s:%d\n" , bp . ID , bp . Addr , bp . FunctionName , ShortenFilePath ( bp . File ) , bp . Line )
2015-03-20 21:11:11 +00:00
return nil
}
2015-11-02 06:30:12 +00:00
func clearAll ( t * Term , args string ) error {
2015-09-30 05:42:06 +00:00
breakPoints , err := t . client . ListBreakpoints ( )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-10-02 13:43:21 +00:00
var locPCs map [ uint64 ] struct { }
2015-11-02 06:30:12 +00:00
if args != "" {
locs , err := t . client . FindLocation ( api . EvalScope { - 1 , 0 } , args )
2015-10-02 13:43:21 +00:00
if err != nil {
return err
}
locPCs = make ( map [ uint64 ] struct { } )
for _ , loc := range locs {
locPCs [ loc . PC ] = struct { } { }
}
}
2015-03-20 21:11:11 +00:00
for _ , bp := range breakPoints {
2015-10-02 13:43:21 +00:00
if locPCs != nil {
if _ , ok := locPCs [ bp . Addr ] ; ! ok {
continue
}
}
2015-09-30 05:42:06 +00:00
_ , err := t . client . ClearBreakpoint ( bp . ID )
2015-03-20 21:11:11 +00:00
if err != nil {
2015-12-28 02:58:00 +00:00
fmt . Printf ( "Couldn't delete breakpoint %d at %#v %s:%d: %s\n" , bp . ID , bp . Addr , ShortenFilePath ( bp . File ) , bp . Line , err )
2015-03-20 21:11:11 +00:00
}
2015-12-28 02:58:00 +00:00
fmt . Printf ( "Breakpoint %d cleared at %#v for %s %s:%d\n" , bp . ID , bp . Addr , bp . FunctionName , ShortenFilePath ( bp . File ) , bp . Line )
2015-03-20 21:11:11 +00:00
}
return nil
}
2015-06-12 19:32:32 +00:00
type ById [ ] * api . Breakpoint
2015-03-20 21:11:11 +00:00
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 }
2015-11-02 06:30:12 +00:00
func breakpoints ( t * Term , args string ) error {
2015-09-30 05:42:06 +00:00
breakPoints , err := t . client . ListBreakpoints ( )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
sort . Sort ( ById ( breakPoints ) )
for _ , bp := range breakPoints {
2015-06-28 15:00:56 +00:00
thing := "Breakpoint"
if bp . Tracepoint {
thing = "Tracepoint"
}
2015-12-28 02:58:00 +00:00
fmt . Printf ( "%s %d at %#v %s:%d (%d)\n" , thing , bp . ID , bp . Addr , ShortenFilePath ( bp . File ) , bp . Line , bp . TotalHitCount )
2015-06-28 15:00:56 +00:00
var attrs [ ] string
if bp . Stacktrace > 0 {
attrs = append ( attrs , "-stack" )
attrs = append ( attrs , strconv . Itoa ( bp . Stacktrace ) )
}
if bp . Goroutine {
attrs = append ( attrs , "-goroutine" )
}
2015-07-01 03:16:52 +00:00
for i := range bp . Variables {
attrs = append ( attrs , bp . Variables [ i ] )
2015-06-28 15:00:56 +00:00
}
if len ( attrs ) > 0 {
fmt . Printf ( "\t%s\n" , strings . Join ( attrs , " " ) )
}
2015-03-20 21:11:11 +00:00
}
return nil
}
2015-11-02 06:30:12 +00:00
func setBreakpoint ( t * Term , tracepoint bool , argstr string ) error {
args := strings . Split ( argstr , " " )
2015-06-28 15:00:56 +00:00
if len ( args ) < 1 {
return fmt . Errorf ( "address required, specify either a function name or <file:line>" )
2015-03-20 21:11:11 +00:00
}
2015-06-12 19:32:32 +00:00
requestedBp := & api . Breakpoint { }
2015-03-20 21:11:11 +00:00
2015-06-28 15:00:56 +00:00
for i := 1 ; i < len ( args ) ; i ++ {
switch args [ i ] {
case "-stack" :
i ++
n , err := strconv . Atoi ( args [ i ] )
if err != nil {
return fmt . Errorf ( "argument of -stack must be a number" )
}
requestedBp . Stacktrace = n
case "-goroutine" :
requestedBp . Goroutine = true
default :
2015-07-01 03:16:52 +00:00
requestedBp . Variables = append ( requestedBp . Variables , args [ i ] )
2015-06-28 15:00:56 +00:00
}
}
requestedBp . Tracepoint = tracepoint
2015-09-30 05:42:06 +00:00
locs , err := t . client . FindLocation ( api . EvalScope { - 1 , 0 } , args [ 0 ] )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-06-28 15:00:56 +00:00
thing := "Breakpoint"
if tracepoint {
thing = "Tracepoint"
}
2015-08-07 16:50:14 +00:00
for _ , loc := range locs {
requestedBp . Addr = loc . PC
2015-09-30 05:42:06 +00:00
bp , err := t . client . CreateBreakpoint ( requestedBp )
2015-08-07 16:50:14 +00:00
if err != nil {
return err
}
2015-12-28 02:58:00 +00:00
fmt . Printf ( "%s %d set at %#v for %s %s:%d\n" , thing , bp . ID , bp . Addr , bp . FunctionName , ShortenFilePath ( bp . File ) , bp . Line )
2015-08-07 16:50:14 +00:00
}
2015-03-20 21:11:11 +00:00
return nil
}
2015-11-02 06:30:12 +00:00
func breakpoint ( t * Term , args string ) error {
return setBreakpoint ( t , false , args )
2015-06-28 15:00:56 +00:00
}
2015-11-02 06:30:12 +00:00
func tracepoint ( t * Term , args string ) error {
return setBreakpoint ( t , true , args )
2015-06-28 15:00:56 +00:00
}
2015-08-28 20:06:29 +00:00
func g0f0 ( fn scopedCmdfunc ) cmdfunc {
2015-11-02 06:30:12 +00:00
return func ( t * Term , args string ) error {
return fn ( t , api . EvalScope { - 1 , 0 } , args )
2015-08-28 20:06:29 +00:00
}
}
func g0f0filter ( fn scopedFilteringFunc ) filteringFunc {
2015-09-30 05:42:06 +00:00
return func ( t * Term , filter string ) ( [ ] string , error ) {
return fn ( t , api . EvalScope { - 1 , 0 } , filter )
2015-08-28 20:06:29 +00:00
}
}
2015-11-02 06:30:12 +00:00
func printVar ( t * Term , scope api . EvalScope , args string ) error {
2015-03-20 21:11:11 +00:00
if len ( args ) == 0 {
2015-05-04 22:31:13 +00:00
return fmt . Errorf ( "not enough arguments" )
2015-03-20 21:11:11 +00:00
}
2015-11-02 06:30:12 +00:00
val , err := t . client . EvalVariable ( scope , args )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-10-18 17:37:13 +00:00
fmt . Println ( val . MultilineString ( "" ) )
2015-03-20 21:11:11 +00:00
return nil
}
2015-11-02 06:30:12 +00:00
func setVar ( t * Term , scope api . EvalScope , 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
_ , err := parser . ParseExpr ( args )
if err == nil {
return fmt . Errorf ( "syntax error '=' not found" )
2015-09-28 10:01:18 +00:00
}
2015-11-02 06:30:12 +00:00
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 : ]
return t . client . SetVariable ( scope , lexpr , rexpr )
2015-09-28 10:01:18 +00:00
}
2015-08-11 02:31:27 +00:00
func filterVariables ( vars [ ] api . Variable , filter string ) [ ] string {
reg , err := regexp . Compile ( filter )
if err != nil {
fmt . Fprintf ( os . Stderr , err . Error ( ) )
return nil
}
2015-03-20 21:11:11 +00:00
data := make ( [ ] string , 0 , len ( vars ) )
for _ , v := range vars {
2015-08-11 02:31:27 +00:00
if reg == nil || reg . Match ( [ ] byte ( v . Name ) ) {
2015-10-18 17:37:13 +00:00
data = append ( data , fmt . Sprintf ( "%s = %s" , v . Name , v . SinglelineString ( ) ) )
2015-03-20 21:11:11 +00:00
}
}
return data
}
2015-09-30 05:42:06 +00:00
func sources ( t * Term , filter string ) ( [ ] string , error ) {
return t . client . ListSources ( filter )
2015-08-11 02:31:27 +00:00
}
2015-03-20 21:11:11 +00:00
2015-09-30 05:42:06 +00:00
func funcs ( t * Term , filter string ) ( [ ] string , error ) {
return t . client . ListFunctions ( filter )
2015-08-11 02:31:27 +00:00
}
2015-03-20 21:11:11 +00:00
2015-09-30 05:42:06 +00:00
func args ( t * Term , scope api . EvalScope , filter string ) ( [ ] string , error ) {
vars , err := t . client . ListFunctionArgs ( scope )
2015-08-11 02:31:27 +00:00
if err != nil {
return nil , err
}
return filterVariables ( vars , filter ) , nil
}
2015-03-20 21:11:11 +00:00
2015-09-30 05:42:06 +00:00
func locals ( t * Term , scope api . EvalScope , filter string ) ( [ ] string , error ) {
locals , err := t . client . ListLocalVariables ( scope )
2015-08-11 02:31:27 +00:00
if err != nil {
return nil , err
}
return filterVariables ( locals , filter ) , nil
}
2015-03-20 21:11:11 +00:00
2015-09-30 05:42:06 +00:00
func vars ( t * Term , filter string ) ( [ ] string , error ) {
vars , err := t . client . ListPackageVariables ( filter )
2015-08-11 02:31:27 +00:00
if err != nil {
return nil , err
}
return filterVariables ( vars , filter ) , nil
}
2015-03-20 21:11:11 +00:00
2015-11-02 06:30:12 +00:00
func regs ( t * Term , args string ) error {
2015-09-30 05:42:06 +00:00
regs , err := t . client . ListRegisters ( )
2015-08-11 02:31:27 +00:00
if err != nil {
return err
}
fmt . Println ( regs )
return nil
}
2015-06-19 07:20:10 +00:00
2015-08-28 20:06:29 +00:00
func filterSortAndOutput ( fn filteringFunc ) cmdfunc {
2015-11-02 06:30:12 +00:00
return func ( t * Term , args string ) error {
2015-08-11 02:31:27 +00:00
var filter string
2015-11-02 06:30:12 +00:00
if len ( args ) > 0 {
if _ , err := regexp . Compile ( args ) ; err != nil {
2015-08-11 02:31:27 +00:00
return fmt . Errorf ( "invalid filter argument: %s" , err . Error ( ) )
}
2015-11-02 06:30:12 +00:00
filter = args
2015-03-20 21:11:11 +00:00
}
2015-09-30 05:42:06 +00:00
data , err := fn ( t , filter )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
2015-08-11 02:31:27 +00:00
sort . Sort ( sort . StringSlice ( data ) )
for _ , d := range data {
fmt . Println ( d )
2015-03-20 21:11:11 +00:00
}
2015-08-11 02:31:27 +00:00
return nil
2015-03-20 21:11:11 +00:00
}
}
2015-11-02 06:30:12 +00:00
func stackCommand ( t * Term , args string ) error {
2015-09-20 04:41:26 +00:00
var (
err error
goroutineid = - 1
)
depth , full , err := parseStackArgs ( args )
if err != nil {
return err
}
2015-09-30 05:42:06 +00:00
stack , err := t . client . Stacktrace ( goroutineid , depth , full )
2015-09-20 04:41:26 +00:00
if err != nil {
return err
}
printStack ( stack , "" )
return nil
}
2015-06-17 17:11:57 +00:00
2015-11-02 06:30:12 +00:00
func parseStackArgs ( argstr string ) ( int , bool , error ) {
2015-09-20 04:41:26 +00:00
var (
depth = 10
full = false
)
2015-11-02 06:30:12 +00:00
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
2015-09-17 08:42:34 +00:00
}
2015-06-17 17:11:57 +00:00
}
}
2015-09-20 04:41:26 +00:00
return depth , full , nil
2015-06-28 15:00:56 +00:00
}
2015-11-02 06:30:12 +00:00
func listCommand ( t * Term , args string ) error {
2015-07-29 14:24:52 +00:00
if len ( args ) == 0 {
2015-09-30 05:42:06 +00:00
state , err := t . client . GetState ( )
2015-07-29 14:24:52 +00:00
if err != nil {
return err
}
2015-09-30 05:42:06 +00:00
printcontext ( t , state )
2015-07-29 14:24:52 +00:00
return nil
}
2015-11-02 06:30:12 +00:00
locs , err := t . client . FindLocation ( api . EvalScope { - 1 , 0 } , args )
2015-07-29 14:24:52 +00:00
if err != nil {
return err
}
if len ( locs ) > 1 {
2015-11-02 06:30:12 +00:00
return debugger . AmbiguousLocationError { Location : args , CandidatesLocation : locs }
2015-07-29 14:24:52 +00:00
}
2015-09-30 05:42:06 +00:00
printfile ( t , locs [ 0 ] . File , locs [ 0 ] . Line , false )
2015-07-29 14:24:52 +00:00
return nil
}
2015-11-02 06:30:12 +00:00
func ( cmds * Commands ) sourceCommand ( t * Term , args string ) error {
2015-12-15 14:16:17 +00:00
if len ( args ) == 0 {
2015-09-29 16:40:12 +00:00
return fmt . Errorf ( "wrong number of arguments: source <filename>" )
}
2015-11-02 06:30:12 +00:00
return cmds . executeFile ( t , args )
2015-09-29 16:40:12 +00:00
}
2015-09-17 08:42:34 +00:00
func digits ( n int ) int {
return int ( math . Floor ( math . Log10 ( float64 ( n ) ) ) ) + 1
}
func printStack ( stack [ ] api . Stackframe , ind string ) {
d := digits ( len ( stack ) - 1 )
fmtstr := "%s%" + strconv . Itoa ( d ) + "d 0x%016x in %s\n"
2015-09-20 04:58:18 +00:00
s := strings . Repeat ( " " , d + 2 + len ( ind ) )
2015-09-17 08:42:34 +00:00
2015-06-17 17:11:57 +00:00
for i := range stack {
name := "(nil)"
if stack [ i ] . Function != nil {
name = stack [ i ] . Function . Name
}
2015-09-17 08:42:34 +00:00
fmt . Printf ( fmtstr , ind , i , stack [ i ] . PC , name )
2015-12-28 02:58:00 +00:00
fmt . Printf ( "%sat %s:%d\n" , s , ShortenFilePath ( stack [ i ] . File ) , stack [ i ] . Line )
2015-09-17 08:42:34 +00:00
for j := range stack [ i ] . Arguments {
2015-10-18 17:37:13 +00:00
fmt . Printf ( "%s %s = %s\n" , s , stack [ i ] . Arguments [ j ] . Name , stack [ i ] . Arguments [ j ] . SinglelineString ( ) )
2015-09-17 08:42:34 +00:00
}
for j := range stack [ i ] . Locals {
2015-10-18 17:37:13 +00:00
fmt . Printf ( "%s %s = %s\n" , s , stack [ i ] . Locals [ j ] . Name , stack [ i ] . Locals [ j ] . SinglelineString ( ) )
2015-09-17 08:42:34 +00:00
}
2015-06-17 17:11:57 +00:00
}
}
2015-09-30 05:42:06 +00:00
func printcontext ( t * Term , state * api . DebuggerState ) error {
2015-10-29 09:59:22 +00:00
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 ] )
}
}
2015-03-20 21:11:11 +00:00
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 )
2015-09-30 05:42:06 +00:00
t . Println ( "=>" , "no source available" )
2015-03-20 21:11:11 +00:00
return nil
}
2015-10-29 09:59:22 +00:00
printcontextThread ( t , state . CurrentThread )
if state . CurrentThread . Breakpoint == nil || ! state . CurrentThread . Breakpoint . Tracepoint {
return printfile ( t , state . CurrentThread . File , state . CurrentThread . Line , true )
2015-07-12 20:18:14 +00:00
}
2015-10-29 09:59:22 +00:00
return nil
}
2015-09-26 00:04:39 +00:00
2015-10-29 09:59:22 +00:00
func printcontextThread ( t * Term , th * api . Thread ) {
fn := th . Function
if th . Breakpoint == nil {
fmt . Printf ( "> %s() %s:%d\n" , fn . Name , ShortenFilePath ( th . File ) , th . Line )
return
}
args := ""
if th . Breakpoint . Tracepoint && fn != nil {
var arg [ ] string
for _ , ar := range fn . Args {
arg = append ( arg , ar . SinglelineString ( ) )
2015-07-12 20:18:14 +00:00
}
2015-10-29 09:59:22 +00:00
args = strings . Join ( arg , ", " )
}
if hitCount , ok := th . Breakpoint . HitCount [ strconv . Itoa ( th . GoroutineID ) ] ; ok {
fmt . Printf ( "> %s(%s) %s:%d (hits goroutine(%d):%d total:%d)\n" ,
fn . Name ,
args ,
ShortenFilePath ( th . File ) ,
th . Line ,
th . GoroutineID ,
hitCount ,
th . Breakpoint . TotalHitCount )
2015-07-12 20:18:14 +00:00
} else {
2015-10-29 09:59:22 +00:00
fmt . Printf ( "> %s(%s) %s:%d (hits total:%d)\n" ,
fn . Name ,
args ,
ShortenFilePath ( th . File ) ,
th . Line ,
th . Breakpoint . TotalHitCount )
2015-03-20 21:11:11 +00:00
}
2015-10-29 09:59:22 +00:00
if th . BreakpointInfo != nil {
bpi := th . BreakpointInfo
2015-06-28 15:00:56 +00:00
if bpi . Goroutine != nil {
2015-10-16 06:42:02 +00:00
writeGoroutineLong ( os . Stdout , bpi . Goroutine , "\t" )
2015-06-28 15:00:56 +00:00
}
2015-10-29 09:59:22 +00:00
if len ( bpi . Variables ) > 0 {
ss := make ( [ ] string , len ( bpi . Variables ) )
for i , v := range bpi . Variables {
ss [ i ] = fmt . Sprintf ( "%s: %s" , v . Name , v . MultilineString ( "" ) )
}
fmt . Printf ( "\t%s\n" , strings . Join ( ss , ", " ) )
2015-06-28 15:00:56 +00:00
}
if bpi . Stacktrace != nil {
fmt . Printf ( "\tStack:\n" )
printStack ( bpi . Stacktrace , "\t\t" )
}
}
2015-07-29 14:24:52 +00:00
}
2015-09-30 05:42:06 +00:00
func printfile ( t * Term , filename string , line int , showArrow bool ) error {
2015-07-29 14:24:52 +00:00
file , err := os . Open ( filename )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
defer file . Close ( )
2015-09-30 05:42:06 +00:00
buf := bufio . NewScanner ( file )
2015-07-29 14:24:52 +00:00
l := line
2015-03-20 21:11:11 +00:00
for i := 1 ; i < l - 5 ; i ++ {
2015-09-30 05:42:06 +00:00
if ! buf . Scan ( ) {
return nil
2015-03-20 21:11:11 +00:00
}
}
2015-07-29 14:24:52 +00:00
s := l - 5
if s < 1 {
s = 1
}
for i := s ; i <= l + 5 ; i ++ {
2015-09-30 05:42:06 +00:00
if ! buf . Scan ( ) {
return nil
2015-03-20 21:11:11 +00:00
}
2015-09-30 15:37:14 +00:00
var prefix string
2015-07-29 14:24:52 +00:00
if showArrow {
2015-09-30 15:37:14 +00:00
prefix = " "
2015-07-29 14:24:52 +00:00
if i == l {
2015-09-30 15:37:14 +00:00
prefix = "=>"
2015-07-29 14:24:52 +00:00
}
2015-03-20 21:11:11 +00:00
}
2015-09-30 15:37:14 +00:00
prefix = fmt . Sprintf ( "%s%4d:\t" , prefix , i )
t . Println ( prefix , buf . Text ( ) )
2015-03-20 21:11:11 +00:00
}
return nil
}
2015-07-29 23:19:06 +00:00
type ExitRequestError struct { }
func ( ere ExitRequestError ) Error ( ) string {
return ""
}
2015-11-02 06:30:12 +00:00
func exitCommand ( t * Term , args string ) error {
2015-07-29 23:19:06 +00:00
return ExitRequestError { }
}
2015-08-19 22:38:53 +00:00
2015-12-28 02:58:00 +00:00
func ShortenFilePath ( fullPath string ) string {
2015-08-19 22:38:53 +00:00
workingDir , _ := os . Getwd ( )
return strings . Replace ( fullPath , workingDir , "." , 1 )
}
2015-09-29 16:40:12 +00:00
func ( cmds * Commands ) executeFile ( t * Term , name string ) error {
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 )
cmd := cmds . Find ( cmdstr )
2015-11-02 06:30:12 +00:00
err := cmd ( t , args )
2015-09-29 16:40:12 +00:00
if err != nil {
fmt . Printf ( "%s:%d: %v\n" , name , lineno , err )
}
}
return scanner . Err ( )
}