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"
2016-02-06 06:00:48 +00:00
"errors"
2015-03-20 21:11:11 +00:00
"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
)
2016-02-23 14:12:04 +00:00
type cmdPrefix int
2015-08-28 20:06:29 +00:00
2016-02-23 14:12:04 +00:00
const (
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
2015-03-20 21:11:11 +00:00
type command struct {
2016-02-23 14:12:04 +00:00
aliases [ ] string
allowedPrefixes cmdPrefix
helpMsg string
cmdFn cmdfunc
2015-03-20 21:11:11 +00:00
}
// 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
}
2016-01-10 08:57:52 +00:00
// Commands represents the commands for Delve terminal process.
2015-03-20 21:11:11 +00:00
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
}
2016-04-24 17:15:39 +00:00
var (
LongLoadConfig = api . LoadConfig { true , 1 , 64 , 64 , - 1 }
ShortLoadConfig = api . LoadConfig { false , 0 , 64 , 0 , 3 }
)
2016-05-20 17:33:08 +00:00
type ByFirstAlias [ ] command
func ( a ByFirstAlias ) Len ( ) int { return len ( a ) }
func ( a ByFirstAlias ) Swap ( i , j int ) { a [ i ] , a [ j ] = a [ j ] , a [ i ] }
func ( a ByFirstAlias ) Less ( i , j int ) bool { return a [ i ] . aliases [ 0 ] < a [ j ] . aliases [ 0 ] }
2016-01-10 08:57:52 +00:00
// DebugCommands returns a Commands struct with default commands defined.
2015-03-20 21:11:11 +00:00
func DebugCommands ( client service . Client ) * Commands {
c := & Commands { client : client }
c . cmds = [ ] command {
2016-04-29 18:58:19 +00:00
{ aliases : [ ] string { "help" , "h" } , cmdFn : c . help , helpMsg : ` Prints the help message .
help [ command ]
Type "help" followed by the name of a command for more information about it . ` } ,
{ aliases : [ ] string { "break" , "b" } , cmdFn : breakpoint , helpMsg : ` Sets a breakpoint .
break [ name ] < linespec >
See $ GOPATH / src / github . com / derekparker / delve / Documentation / cli / locspec . md for the syntax of linespec .
See also : "help on" , "help cond" and "help clear" ` } ,
{ aliases : [ ] string { "trace" , "t" } , cmdFn : tracepoint , helpMsg : ` Set tracepoint .
trace [ name ] < linespec >
A tracepoint is a breakpoint that does not stop the execution of the program , instead when the tracepoint is hit a notification is displayed . See $ GOPATH / src / github . com / derekparker / delve / Documentation / cli / locspec . md for the syntax of linespec .
See also : "help on" , "help cond" and "help clear" ` } ,
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." } ,
2016-04-13 13:25:23 +00:00
{ aliases : [ ] string { "step" , "s" } , allowedPrefixes : scopePrefix , cmdFn : step , helpMsg : "Single step through program." } ,
2016-04-14 10:01:48 +00:00
{ aliases : [ ] string { "step-instruction" , "si" } , allowedPrefixes : scopePrefix , cmdFn : stepInstruction , helpMsg : "Single step a single cpu instruction." } ,
2016-04-11 11:50:01 +00:00
{ aliases : [ ] string { "next" , "n" } , allowedPrefixes : scopePrefix , cmdFn : next , helpMsg : "Step over to next source line." } ,
2016-04-14 09:58:59 +00:00
{ aliases : [ ] string { "stepout" } , allowedPrefixes : scopePrefix , cmdFn : stepout , helpMsg : "Step out of the current function." } ,
2015-03-20 21:11:11 +00:00
{ aliases : [ ] string { "threads" } , cmdFn : threads , helpMsg : "Print out info for every traced thread." } ,
2016-04-29 18:58:19 +00:00
{ aliases : [ ] string { "thread" , "tr" } , cmdFn : thread , helpMsg : ` Switch to the specified thread .
thread < id > ` } ,
{ aliases : [ ] string { "clear" } , cmdFn : clear , helpMsg : ` Deletes breakpoint .
clear < breakpoint name or id > ` } ,
{ aliases : [ ] string { "clearall" } , cmdFn : clearAll , helpMsg : ` Deletes multiple breakpoints .
clearall [ < linespec > ]
If called with the linespec argument it will delete all the breakpoints matching the linespec . If linespec is omitted all breakpoints are deleted . ` } ,
{ aliases : [ ] string { "goroutines" } , cmdFn : goroutines , helpMsg : ` List program goroutines .
goroutines [ - u ( default : user location ) | - r ( runtime location ) | - g ( go statement location ) ]
Print out info for every goroutine . The flag controls what information is shown along with each goroutine :
- u displays location of topmost stackframe in user code
- r displays location of topmost stackframe ( including frames inside private runtime functions )
- g displays location of go instruction that created the goroutine
If no flag is specified the default is - u . ` } ,
{ aliases : [ ] string { "goroutine" } , allowedPrefixes : onPrefix | scopePrefix , cmdFn : c . goroutine , helpMsg : ` Shows or changes current goroutine
goroutine
goroutine < id >
goroutine < id > < command >
Called without arguments it will show information about the current goroutine .
Called with a single argument it will switch to the specified goroutine .
Called with more arguments it will execute a command on the specified goroutine . ` } ,
2015-03-20 21:11:11 +00:00
{ aliases : [ ] string { "breakpoints" , "bp" } , cmdFn : breakpoints , helpMsg : "Print out info for active breakpoints." } ,
2016-04-29 18:58:19 +00:00
{ aliases : [ ] string { "print" , "p" } , allowedPrefixes : onPrefix | scopePrefix , cmdFn : printVar , helpMsg : ` Evaluate an expression .
[ goroutine < n > ] [ frame < m > ] print < expression >
See $ GOPATH / src / github . com / derekparker / delve / Documentation / cli / expr . md for a description of supported expressions . ` } ,
{ aliases : [ ] string { "set" } , allowedPrefixes : scopePrefix , cmdFn : setVar , helpMsg : ` Changes the value of a variable .
[ goroutine < n > ] [ frame < m > ] set < variable > = < value >
See $ GOPATH / src / github . com / derekparker / delve / Documentation / cli / expr . md for a description of supported expressions . Only numerical variables and pointers can be changed . ` } ,
{ aliases : [ ] string { "sources" } , cmdFn : sources , helpMsg : ` Print list of source files .
sources [ < regex > ]
If regex is specified only the source files matching it will be returned . ` } ,
{ aliases : [ ] string { "funcs" } , cmdFn : funcs , helpMsg : ` Print list of functions .
funcs [ < regex > ]
If regex is specified only the functions matching it will be returned . ` } ,
{ aliases : [ ] string { "types" } , cmdFn : types , helpMsg : ` Print list of types
types [ < regex > ]
2017-06-05 21:20:10 +00:00
If regex is specified only the types matching it will be returned . ` } ,
2016-04-29 18:58:19 +00:00
{ aliases : [ ] string { "args" } , allowedPrefixes : scopePrefix | onPrefix , cmdFn : args , helpMsg : ` Print function arguments .
[ goroutine < n > ] [ frame < m > ] args [ - v ] [ < regex > ]
If regex is specified only function arguments with a name matching it will be returned . If - v is specified more information about each function argument will be shown . ` } ,
{ aliases : [ ] string { "locals" } , allowedPrefixes : scopePrefix | onPrefix , cmdFn : locals , helpMsg : ` Print local variables .
[ goroutine < n > ] [ frame < m > ] locals [ - v ] [ < regex > ]
2017-05-04 14:35:31 +00:00
The name of variables that are shadowed in the current scope will be shown in parenthesis .
2016-04-29 18:58:19 +00:00
If regex is specified only local variables with a name matching it will be returned . If - v is specified more information about each local variable will be shown . ` } ,
{ aliases : [ ] string { "vars" } , cmdFn : vars , helpMsg : ` Print package variables .
vars [ - v ] [ < regex > ]
If regex is specified only package variables with a name matching it will be returned . If - v is specified more information about each package variable will be shown . ` } ,
2016-11-15 16:16:33 +00:00
{ aliases : [ ] string { "regs" } , cmdFn : regs , helpMsg : ` Print contents of CPU registers .
regs [ - a ]
Argument - a shows more registers . ` } ,
2015-07-29 23:19:06 +00:00
{ aliases : [ ] string { "exit" , "quit" , "q" } , cmdFn : exitCommand , helpMsg : "Exit the debugger." } ,
2016-04-29 18:58:19 +00:00
{ aliases : [ ] string { "list" , "ls" } , allowedPrefixes : scopePrefix , cmdFn : listCommand , helpMsg : ` Show source code .
[ goroutine < n > ] [ frame < m > ] list [ < linespec > ]
Show source around current point or provided linespec . ` } ,
{ aliases : [ ] string { "stack" , "bt" } , allowedPrefixes : scopePrefix | onPrefix , cmdFn : stackCommand , helpMsg : ` Print stack trace .
[ goroutine < n > ] [ frame < m > ] stack [ < depth > ] [ - full ]
If - full is specified every stackframe will be decorated by the value of its local variables and function arguments . ` } ,
{ aliases : [ ] string { "frame" } , allowedPrefixes : scopePrefix , cmdFn : c . frame , helpMsg : ` Executes command on a different frame .
frame < frame index > < command > . ` } ,
{ aliases : [ ] string { "source" } , cmdFn : c . sourceCommand , helpMsg : ` Executes a file containing a list of delve commands
source < path > ` } ,
{ aliases : [ ] string { "disassemble" , "disass" } , allowedPrefixes : scopePrefix , cmdFn : disassCommand , helpMsg : ` Disassembler .
[ goroutine < n > ] [ frame < m > ] disassemble [ - a < start > < end > ] [ - l < locspec > ]
If no argument is specified the function being executed in the selected stack frame will be executed .
- a < start > < end > disassembles the specified address range
- l < locspec > disassembles the specified function ` } ,
{ aliases : [ ] string { "on" } , cmdFn : c . onCmd , helpMsg : ` Executes a command when a breakpoint is hit .
on < breakpoint name or id > < command > .
Supported commands : print , stack and goroutine ) ` } ,
{ aliases : [ ] string { "condition" , "cond" } , cmdFn : conditionCmd , helpMsg : ` Set breakpoint condition .
condition < breakpoint name or id > < boolean expression > .
Specifies that the breakpoint or tracepoint should break only if the boolean expression is true . ` } ,
2015-03-20 21:11:11 +00:00
}
2017-05-05 22:17:52 +00:00
if client == nil || client . Recorded ( ) {
c . cmds = append ( c . cmds , command {
aliases : [ ] string { "rewind" , "rw" } ,
cmdFn : rewind ,
helpMsg : "Run backwards until breakpoint or program termination." ,
} )
c . cmds = append ( c . cmds , command {
aliases : [ ] string { "check" , "checkpoint" } ,
cmdFn : checkpoint ,
helpMsg : ` Creates a checkpoint at the current position .
checkpoint [ where ] ` ,
} )
c . cmds = append ( c . cmds , command {
aliases : [ ] string { "checkpoints" } ,
cmdFn : checkpoints ,
helpMsg : "Print out info for existing checkpoints." ,
} )
c . cmds = append ( c . cmds , command {
aliases : [ ] string { "clear-checkpoint" , "clearcheck" } ,
cmdFn : clearCheckpoint ,
helpMsg : ` Deletes checkpoint .
checkpoint < id > ` ,
} )
for i := range c . cmds {
v := & c . cmds [ i ]
if v . match ( "restart" ) {
v . helpMsg = ` Restart process from a checkpoint or event .
restart [ event number or checkpoint id ] `
}
}
}
2016-05-20 17:33:08 +00:00
sort . Sort ( ByFirstAlias ( c . cmds ) )
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.
2016-02-23 14:12:04 +00:00
func ( c * Commands ) Find ( cmdstr string , prefix cmdPrefix ) cmdfunc {
2015-03-20 21:11:11 +00:00
// 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 ) {
2016-02-23 14:12:04 +00:00
if prefix != noPrefix && v . allowedPrefixes & prefix == 0 {
continue
}
2015-03-20 21:11:11 +00:00
c . lastCmd = v . cmdFn
return v . cmdFn
}
}
return noCmdAvailable
}
2017-04-19 10:17:21 +00:00
func ( c * Commands ) CallWithContext ( cmdstr string , t * Term , ctx callContext ) error {
vals := strings . SplitN ( strings . TrimSpace ( cmdstr ) , " " , 2 )
cmdname := vals [ 0 ]
var args string
if len ( vals ) > 1 {
args = strings . TrimSpace ( vals [ 1 ] )
}
return c . Find ( cmdname , ctx . Prefix ) ( t , ctx , args )
2016-02-23 14:12:04 +00:00
}
2017-04-19 10:17:21 +00:00
func ( c * Commands ) Call ( cmdstr string , t * Term ) error {
2016-02-23 14:12:04 +00:00
ctx := callContext { Prefix : noPrefix , Scope : api . EvalScope { GoroutineID : - 1 , Frame : 0 } }
2017-04-19 10:17:21 +00:00
return c . CallWithContext ( cmdstr , t , ctx )
2016-02-23 14:12:04 +00:00
}
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 ... )
}
}
}
2016-04-29 18:58:19 +00:00
var noCmdError = errors . New ( "command not available" )
2016-02-23 14:12:04 +00:00
func noCmdAvailable ( t * Term , ctx callContext , args string ) error {
2016-04-29 18:58:19 +00:00
return noCmdError
2015-03-20 21:11:11 +00:00
}
2016-02-23 14:12:04 +00:00
func nullCommand ( t * Term , ctx callContext , args string ) error {
2015-03-20 21:11:11 +00:00
return nil
}
2016-02-23 14:12:04 +00:00
func ( c * Commands ) help ( t * Term , ctx callContext , args string ) error {
2016-04-29 18:58:19 +00:00
if args != "" {
for _ , cmd := range c . cmds {
for _ , alias := range cmd . aliases {
if alias == args {
fmt . Println ( cmd . helpMsg )
return nil
}
}
}
return noCmdError
}
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 {
2016-04-29 18:58:19 +00:00
h := cmd . helpMsg
if idx := strings . Index ( h , "\n" ) ; idx >= 0 {
h = h [ : idx ]
}
2015-07-29 22:49:23 +00:00
if len ( cmd . aliases ) > 1 {
2016-04-29 18:58:19 +00:00
fmt . Fprintf ( w , " %s (alias: %s) \t %s\n" , cmd . aliases [ 0 ] , strings . Join ( cmd . aliases [ 1 : ] , " | " ) , h )
2015-07-29 22:49:23 +00:00
} else {
2016-04-29 18:58:19 +00:00
fmt . Fprintf ( w , " %s \t %s\n" , cmd . aliases [ 0 ] , h )
2015-07-29 22:49:23 +00:00
}
2015-03-20 21:11:11 +00:00
}
2016-04-29 18:58:19 +00:00
if err := w . Flush ( ) ; err != nil {
return err
}
fmt . Println ( "Type help followed by a command for full documentation." )
return nil
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
2016-02-23 14:12:04 +00:00
func threads ( t * Term , ctx callContext , 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
}
2016-02-23 14:12:04 +00:00
func thread ( t * Term , ctx callContext , 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 }
2016-02-23 14:12:04 +00:00
func goroutines ( t * Term , ctx callContext , argstr string ) error {
2015-11-02 06:30:12 +00:00
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
2016-01-19 14:24:01 +00:00
case "" :
// nothing to do
2015-10-16 06:42:02 +00:00
default :
2016-01-10 08:57:52 +00:00
return fmt . Errorf ( "wrong argument: '%s'" , args [ 0 ] )
2015-10-16 06:42:02 +00:00
}
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
}
2017-05-08 18:16:14 +00:00
func selectedGID ( state * api . DebuggerState ) int {
if state . SelectedGoroutine == nil {
return 0
}
return state . SelectedGoroutine . ID
}
2016-02-23 14:12:04 +00:00
func ( c * Commands ) goroutine ( t * Term , ctx callContext , argstr string ) error {
2017-04-19 10:17:21 +00:00
args := strings . SplitN ( argstr , " " , 2 )
2015-08-28 20:06:29 +00:00
2016-02-23 14:12:04 +00:00
if ctx . Prefix == onPrefix {
if len ( args ) != 1 || args [ 0 ] != "" {
return errors . New ( "too many arguments to goroutine" )
2015-08-28 20:06:29 +00:00
}
2016-02-23 14:12:04 +00:00
ctx . Breakpoint . Goroutine = true
2015-08-28 20:06:29 +00:00
return nil
}
2017-04-19 10:17:21 +00:00
if len ( args ) == 1 {
2016-02-23 14:12:04 +00:00
if ctx . Prefix == scopePrefix {
return errors . New ( "no command passed to goroutine" )
2015-11-02 06:30:12 +00:00
}
2016-02-23 14:12:04 +00:00
if args [ 0 ] == "" {
return printscope ( t )
2016-03-09 15:11:58 +00:00
}
gid , err := strconv . Atoi ( argstr )
if err != nil {
return err
}
2016-02-23 14:12:04 +00:00
2016-03-09 15:11:58 +00:00
oldState , err := t . client . GetState ( )
if err != nil {
return err
}
newState , err := t . client . SwitchGoroutine ( gid )
if err != nil {
return err
2015-08-28 20:06:29 +00:00
}
2016-03-09 15:11:58 +00:00
2017-05-08 18:16:14 +00:00
fmt . Printf ( "Switched from %d to %d (thread %d)\n" , selectedGID ( oldState ) , gid , newState . CurrentThread . ID )
2016-03-09 15:11:58 +00:00
return nil
2015-08-28 20:06:29 +00:00
}
2016-02-23 14:12:04 +00:00
var err error
ctx . Prefix = scopePrefix
ctx . Scope . GoroutineID , err = strconv . Atoi ( args [ 0 ] )
if err != nil {
return err
}
2017-04-19 10:17:21 +00:00
return c . CallWithContext ( args [ 1 ] , t , ctx )
2016-02-23 14:12:04 +00:00
}
func ( c * Commands ) frame ( t * Term , ctx callContext , args string ) error {
2017-04-19 10:17:21 +00:00
v := strings . SplitN ( args , " " , 2 )
2016-02-23 14:12:04 +00:00
switch len ( v ) {
case 0 , 1 :
return errors . New ( "not enough arguments" )
}
2016-03-09 15:11:58 +00:00
var err error
2016-02-23 14:12:04 +00:00
ctx . Prefix = scopePrefix
ctx . Scope . Frame , err = strconv . Atoi ( v [ 0 ] )
if err != nil {
return err
}
2017-04-19 10:17:21 +00:00
return c . CallWithContext ( v [ 1 ] , t , ctx )
2015-08-28 20:06:29 +00:00
}
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
}
2016-06-20 17:20:44 +00:00
thread := ""
if g . ThreadID != 0 {
thread = fmt . Sprintf ( " (thread %d)" , g . ThreadID )
}
return fmt . Sprintf ( "%d - %s: %s%s" , g . ID , locname , formatLocation ( loc ) , thread )
2015-10-16 06:42:02 +00:00
}
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
}
2016-02-23 14:12:04 +00:00
func restart ( t * Term , ctx callContext , args string ) error {
2017-05-05 22:17:52 +00:00
discarded , err := t . client . RestartFrom ( args )
2016-12-22 16:53:34 +00:00
if err != nil {
2015-07-03 20:35:22 +00:00
return err
}
2017-05-05 22:17:52 +00:00
if ! t . client . Recorded ( ) {
fmt . Println ( "Process restarted with PID" , t . client . ProcessPid ( ) )
}
2016-12-22 16:53:34 +00:00
for i := range discarded {
2017-01-05 19:13:07 +00:00
fmt . Printf ( "Discarded %s at %s: %v\n" , formatBreakpointName ( discarded [ i ] . Breakpoint , false ) , formatBreakpointLocation ( discarded [ i ] . Breakpoint ) , discarded [ i ] . Reason )
2016-12-22 16:53:34 +00:00
}
2017-05-05 22:17:52 +00:00
if t . client . Recorded ( ) {
state , err := t . client . GetState ( )
if err != nil {
return err
}
printcontext ( t , state )
printfile ( t , state . CurrentThread . File , state . CurrentThread . Line , true )
}
2015-07-03 20:35:22 +00:00
return nil
}
2016-02-23 14:12:04 +00:00
func cont ( t * Term , ctx callContext , args string ) error {
2015-09-30 05:42:06 +00:00
stateChan := t . client . Continue ( )
2016-04-24 17:30:43 +00:00
var state * api . DebuggerState
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
}
2016-04-24 17:30:43 +00:00
printfile ( t , state . CurrentThread . File , state . CurrentThread . Line , true )
2015-03-20 21:11:11 +00:00
return nil
}
2016-04-24 23:20:02 +00:00
func continueUntilCompleteNext ( t * Term , state * api . DebuggerState , op string ) error {
if ! state . NextInProgress {
printfile ( t , state . CurrentThread . File , state . CurrentThread . Line , true )
return nil
}
for {
stateChan := t . client . Continue ( )
var state * api . DebuggerState
for state = range stateChan {
if state . Err != nil {
return state . Err
}
printcontext ( t , state )
}
if ! state . NextInProgress {
printfile ( t , state . CurrentThread . File , state . CurrentThread . Line , true )
return nil
}
fmt . Printf ( "\tbreakpoint hit during %s, continuing...\n" , op )
}
}
2016-04-13 13:25:23 +00:00
func scopePrefixSwitch ( t * Term , ctx callContext ) error {
if ctx . Prefix != scopePrefix {
return nil
}
if ctx . Scope . Frame != 0 {
return errors . New ( "frame prefix not accepted" )
}
if ctx . Scope . GoroutineID > 0 {
_ , err := t . client . SwitchGoroutine ( ctx . Scope . GoroutineID )
if err != nil {
return err
}
}
return nil
}
2016-02-23 14:12:04 +00:00
func step ( t * Term , ctx callContext , args string ) error {
2016-04-13 13:25:23 +00:00
if err := scopePrefixSwitch ( t , ctx ) ; err != nil {
return err
}
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 )
2016-04-24 23:20:02 +00:00
return continueUntilCompleteNext ( t , state , "step" )
2015-03-20 21:11:11 +00:00
}
2016-02-23 14:12:04 +00:00
func stepInstruction ( t * Term , ctx callContext , args string ) error {
2016-04-14 10:01:48 +00:00
if err := scopePrefixSwitch ( t , ctx ) ; err != nil {
return err
}
2016-01-24 05:25:46 +00:00
state , err := t . client . StepInstruction ( )
if err != nil {
return err
}
printcontext ( t , state )
2016-04-24 17:30:43 +00:00
printfile ( t , state . CurrentThread . File , state . CurrentThread . Line , true )
2016-01-24 05:25:46 +00:00
return nil
}
2016-02-23 14:12:04 +00:00
func next ( t * Term , ctx callContext , args string ) error {
2016-04-13 13:25:23 +00:00
if err := scopePrefixSwitch ( t , ctx ) ; err != nil {
return err
2016-04-11 11:50:01 +00:00
}
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 )
2016-04-24 23:20:02 +00:00
return continueUntilCompleteNext ( t , state , "next" )
2015-03-20 21:11:11 +00:00
}
2016-04-14 09:58:59 +00:00
func stepout ( t * Term , ctx callContext , args string ) error {
if err := scopePrefixSwitch ( t , ctx ) ; err != nil {
return err
}
state , err := t . client . StepOut ( )
if err != nil {
return err
}
printcontext ( t , state )
return continueUntilCompleteNext ( t , state , "stepout" )
}
2016-02-23 14:12:04 +00:00
func clear ( t * Term , ctx callContext , 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 )
2016-01-24 08:16:45 +00:00
var bp * api . Breakpoint
if err == nil {
bp , err = t . client . ClearBreakpoint ( id )
} else {
bp , err = t . client . ClearBreakpointByName ( args )
2015-03-20 21:11:11 +00:00
}
if err != nil {
return err
}
2016-01-24 08:16:45 +00:00
fmt . Printf ( "%s cleared at %s\n" , formatBreakpointName ( bp , true ) , formatBreakpointLocation ( bp ) )
2015-03-20 21:11:11 +00:00
return nil
}
2016-02-23 14:12:04 +00:00
func clearAll ( t * Term , ctx callContext , 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 != "" {
2016-01-10 08:57:52 +00:00
locs , err := t . client . FindLocation ( api . EvalScope { GoroutineID : - 1 , Frame : 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
}
}
2016-03-06 17:54:43 +00:00
if bp . ID < 0 {
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 {
2016-01-24 08:16:45 +00:00
fmt . Printf ( "Couldn't delete %s at %s: %s\n" , formatBreakpointName ( bp , false ) , formatBreakpointLocation ( bp ) , err )
2015-03-20 21:11:11 +00:00
}
2016-01-24 08:16:45 +00:00
fmt . Printf ( "%s cleared at %s\n" , formatBreakpointName ( bp , true ) , formatBreakpointLocation ( bp ) )
2015-03-20 21:11:11 +00:00
}
return nil
}
2016-01-10 08:57:52 +00:00
// ByID sorts breakpoints by ID.
type ByID [ ] * api . Breakpoint
2015-03-20 21:11:11 +00:00
2016-01-10 08:57:52 +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-03-20 21:11:11 +00:00
2016-02-23 14:12:04 +00:00
func breakpoints ( t * Term , ctx callContext , 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
}
2016-01-10 08:57:52 +00:00
sort . Sort ( ByID ( breakPoints ) )
2015-03-20 21:11:11 +00:00
for _ , bp := range breakPoints {
2016-01-24 08:16:45 +00:00
fmt . Printf ( "%s at %v (%d)\n" , formatBreakpointName ( bp , true ) , formatBreakpointLocation ( bp ) , bp . TotalHitCount )
2015-06-28 15:00:56 +00:00
var attrs [ ] string
2016-01-24 08:16:45 +00:00
if bp . Cond != "" {
attrs = append ( attrs , fmt . Sprintf ( "\tcond %s" , bp . Cond ) )
}
2015-06-28 15:00:56 +00:00
if bp . Stacktrace > 0 {
2016-01-24 08:16:45 +00:00
attrs = append ( attrs , fmt . Sprintf ( "\tstack %d" , bp . Stacktrace ) )
2015-06-28 15:00:56 +00:00
}
if bp . Goroutine {
2016-01-24 08:16:45 +00:00
attrs = append ( attrs , "\tgoroutine" )
2015-06-28 15:00:56 +00:00
}
2016-04-24 17:15:39 +00:00
if bp . LoadArgs != nil {
if * ( bp . LoadArgs ) == LongLoadConfig {
attrs = append ( attrs , "\targs -v" )
} else {
attrs = append ( attrs , "\targs" )
}
}
if bp . LoadLocals != nil {
if * ( bp . LoadLocals ) == LongLoadConfig {
attrs = append ( attrs , "\tlocals -v" )
} else {
attrs = append ( attrs , "\tlocals" )
}
}
2015-07-01 03:16:52 +00:00
for i := range bp . Variables {
2016-01-24 08:16:45 +00:00
attrs = append ( attrs , fmt . Sprintf ( "\tprint %s" , bp . Variables [ i ] ) )
2015-06-28 15:00:56 +00:00
}
if len ( attrs ) > 0 {
2016-01-24 08:16:45 +00:00
fmt . Printf ( "%s\n" , strings . Join ( attrs , "\n" ) )
2015-06-28 15:00:56 +00:00
}
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 {
2016-01-24 08:16:45 +00:00
args := strings . SplitN ( argstr , " " , 2 )
2015-03-20 21:11:11 +00:00
2016-01-24 08:16:45 +00:00
requestedBp := & api . Breakpoint { }
locspec := ""
switch len ( args ) {
case 1 :
locspec = argstr
case 2 :
if api . ValidBreakpointName ( args [ 0 ] ) == nil {
requestedBp . Name = args [ 0 ]
locspec = args [ 1 ]
} else {
locspec = argstr
2015-06-28 15:00:56 +00:00
}
2016-01-24 08:16:45 +00:00
default :
return fmt . Errorf ( "address required" )
2015-06-28 15:00:56 +00:00
}
requestedBp . Tracepoint = tracepoint
2016-01-24 08:16:45 +00:00
locs , err := t . client . FindLocation ( api . EvalScope { GoroutineID : - 1 , Frame : 0 } , locspec )
2015-03-20 21:11:11 +00:00
if err != nil {
2016-01-24 08:16:45 +00:00
if requestedBp . Name == "" {
return err
}
requestedBp . Name = ""
locspec = argstr
var err2 error
2016-03-09 15:11:58 +00:00
locs , err2 = t . client . FindLocation ( api . EvalScope { GoroutineID : - 1 , Frame : 0 } , locspec )
2016-01-24 08:16:45 +00:00
if err2 != nil {
return err
}
2015-06-28 15:00:56 +00:00
}
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
}
2016-01-24 08:16:45 +00:00
fmt . Printf ( "%s set at %s\n" , formatBreakpointName ( bp , true ) , formatBreakpointLocation ( bp ) )
2015-08-07 16:50:14 +00:00
}
2015-03-20 21:11:11 +00:00
return nil
}
2016-02-23 14:12:04 +00:00
func breakpoint ( t * Term , ctx callContext , args string ) error {
2015-11-02 06:30:12 +00:00
return setBreakpoint ( t , false , args )
2015-06-28 15:00:56 +00:00
}
2016-02-23 14:12:04 +00:00
func tracepoint ( t * Term , ctx callContext , args string ) error {
2015-11-02 06:30:12 +00:00
return setBreakpoint ( t , true , args )
2015-06-28 15:00:56 +00:00
}
2016-02-23 14:12:04 +00:00
func printVar ( t * Term , ctx callContext , 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
}
2016-02-23 14:12:04 +00:00
if ctx . Prefix == onPrefix {
ctx . Breakpoint . Variables = append ( ctx . Breakpoint . Variables , args )
return nil
}
2016-04-24 17:15:39 +00:00
val , err := t . client . EvalVariable ( ctx . Scope , args , LongLoadConfig )
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
}
2016-02-23 14:12:04 +00:00
func setVar ( t * Term , ctx callContext , args string ) error {
2015-11-02 06:30:12 +00:00
// 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 : ]
2016-02-23 14:12:04 +00:00
return t . client . SetVariable ( ctx . Scope , lexpr , rexpr )
2015-09-28 10:01:18 +00:00
}
2016-04-24 17:15:39 +00:00
func printFilteredVariables ( varType string , vars [ ] api . Variable , filter string , cfg api . LoadConfig ) error {
2015-08-11 02:31:27 +00:00
reg , err := regexp . Compile ( filter )
if err != nil {
2016-04-24 17:15:39 +00:00
return err
2015-08-11 02:31:27 +00:00
}
2016-04-24 17:15:39 +00:00
match := false
2015-03-20 21:11:11 +00:00
for _ , v := range vars {
2015-08-11 02:31:27 +00:00
if reg == nil || reg . Match ( [ ] byte ( v . Name ) ) {
2016-04-24 17:15:39 +00:00
match = true
2017-05-04 14:35:31 +00:00
name := v . Name
if v . Flags & api . VariableShadowed != 0 {
name = "(" + name + ")"
}
2016-04-24 17:15:39 +00:00
if cfg == ShortLoadConfig {
2017-05-04 14:35:31 +00:00
fmt . Printf ( "%s = %s\n" , name , v . SinglelineString ( ) )
2016-04-24 17:15:39 +00:00
} else {
2017-05-04 14:35:31 +00:00
fmt . Printf ( "%s = %s\n" , name , v . MultilineString ( "" ) )
2016-04-24 17:15:39 +00:00
}
2015-03-20 21:11:11 +00:00
}
}
2016-04-24 17:15:39 +00:00
if ! match {
fmt . Printf ( "(no %s)\n" , varType )
}
return nil
2015-03-20 21:11:11 +00:00
}
2016-04-24 17:15:39 +00:00
func printSortedStrings ( v [ ] string , err error ) error {
if err != nil {
return err
}
sort . Strings ( v )
for _ , d := range v {
fmt . Println ( d )
}
return nil
}
func sources ( t * Term , ctx callContext , args string ) error {
return printSortedStrings ( t . client . ListSources ( args ) )
}
func funcs ( t * Term , ctx callContext , args string ) error {
return printSortedStrings ( t . client . ListFunctions ( args ) )
2015-08-11 02:31:27 +00:00
}
2015-03-20 21:11:11 +00:00
2016-04-24 17:15:39 +00:00
func types ( t * Term , ctx callContext , args string ) error {
return printSortedStrings ( t . client . ListTypes ( args ) )
2015-08-11 02:31:27 +00:00
}
2015-03-20 21:11:11 +00:00
2016-04-24 17:15:39 +00:00
func parseVarArguments ( args string ) ( filter string , cfg api . LoadConfig ) {
if v := strings . SplitN ( args , " " , 2 ) ; len ( v ) >= 1 && v [ 0 ] == "-v" {
if len ( v ) == 2 {
return v [ 1 ] , LongLoadConfig
} else {
return "" , LongLoadConfig
}
}
return args , ShortLoadConfig
2016-02-22 15:08:45 +00:00
}
2016-04-24 17:15:39 +00:00
func args ( t * Term , ctx callContext , args string ) error {
filter , cfg := parseVarArguments ( args )
if ctx . Prefix == onPrefix {
if filter != "" {
return fmt . Errorf ( "filter not supported on breakpoint" )
}
ctx . Breakpoint . LoadArgs = & cfg
return nil
}
vars , err := t . client . ListFunctionArgs ( ctx . Scope , cfg )
2015-08-11 02:31:27 +00:00
if err != nil {
2016-04-24 17:15:39 +00:00
return err
2015-08-11 02:31:27 +00:00
}
2016-04-24 17:15:39 +00:00
return printFilteredVariables ( "args" , vars , filter , cfg )
2015-08-11 02:31:27 +00:00
}
2015-03-20 21:11:11 +00:00
2016-04-24 17:15:39 +00:00
func locals ( t * Term , ctx callContext , args string ) error {
filter , cfg := parseVarArguments ( args )
if ctx . Prefix == onPrefix {
if filter != "" {
return fmt . Errorf ( "filter not supported on breakpoint" )
}
ctx . Breakpoint . LoadLocals = & cfg
return nil
}
locals , err := t . client . ListLocalVariables ( ctx . Scope , cfg )
2015-08-11 02:31:27 +00:00
if err != nil {
2016-04-24 17:15:39 +00:00
return err
2015-08-11 02:31:27 +00:00
}
2016-04-24 17:15:39 +00:00
return printFilteredVariables ( "locals" , locals , filter , cfg )
2015-08-11 02:31:27 +00:00
}
2015-03-20 21:11:11 +00:00
2016-04-24 17:15:39 +00:00
func vars ( t * Term , ctx callContext , args string ) error {
filter , cfg := parseVarArguments ( args )
vars , err := t . client . ListPackageVariables ( filter , cfg )
2015-08-11 02:31:27 +00:00
if err != nil {
2016-04-24 17:15:39 +00:00
return err
2015-08-11 02:31:27 +00:00
}
2016-04-24 17:15:39 +00:00
return printFilteredVariables ( "vars" , vars , filter , cfg )
2015-08-11 02:31:27 +00:00
}
2015-03-20 21:11:11 +00:00
2016-02-23 14:12:04 +00:00
func regs ( t * Term , ctx callContext , args string ) error {
2016-11-15 16:16:33 +00:00
includeFp := false
if args == "-a" {
includeFp = true
}
regs , err := t . client . ListRegisters ( 0 , includeFp )
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
2016-02-23 14:12:04 +00:00
func stackCommand ( t * Term , ctx callContext , args string ) error {
2015-09-20 04:41:26 +00:00
depth , full , err := parseStackArgs ( args )
if err != nil {
return err
}
2016-02-23 14:12:04 +00:00
if ctx . Prefix == onPrefix {
ctx . Breakpoint . Stacktrace = depth
return nil
}
2016-04-24 17:15:39 +00:00
var cfg * api . LoadConfig
if full {
cfg = & ShortLoadConfig
}
stack , err := t . client . Stacktrace ( ctx . Scope . GoroutineID , depth , cfg )
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
}
2016-02-23 14:12:04 +00:00
func listCommand ( t * Term , ctx callContext , args string ) error {
2017-04-19 13:45:23 +00:00
switch {
case len ( args ) == 0 && ctx . Prefix != scopePrefix :
state , err := t . client . GetState ( )
if err != nil {
return err
}
printcontext ( t , state )
return printfile ( t , state . CurrentThread . File , state . CurrentThread . Line , true )
case len ( args ) == 0 && ctx . Prefix == scopePrefix :
2016-04-24 17:15:39 +00:00
locs , err := t . client . Stacktrace ( ctx . Scope . GoroutineID , ctx . Scope . Frame , nil )
2016-02-23 14:12:04 +00:00
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 ]
2017-04-19 13:45:23 +00:00
gid := ctx . Scope . GoroutineID
if gid < 0 {
state , err := t . client . GetState ( )
if err != nil {
return err
}
if state . SelectedGoroutine != nil {
gid = state . SelectedGoroutine . ID
}
}
fmt . Printf ( "Goroutine %d frame %d at %s:%d (PC: %#x)\n" , gid , ctx . Scope . Frame , loc . File , loc . Line , loc . PC )
2016-02-23 14:12:04 +00:00
return printfile ( t , loc . File , loc . Line , true )
2017-04-19 13:45:23 +00:00
default :
locs , err := t . client . FindLocation ( ctx . Scope , args )
2015-07-29 14:24:52 +00:00
if err != nil {
return err
}
2017-04-19 13:45:23 +00:00
if len ( locs ) > 1 {
return debugger . AmbiguousLocationError { Location : args , CandidatesLocation : locs }
}
loc := locs [ 0 ]
fmt . Printf ( "Showing %s:%d (PC: %#x)\n" , loc . File , loc . Line , loc . PC )
return printfile ( t , loc . File , loc . Line , false )
2015-07-29 14:24:52 +00:00
}
}
2016-02-23 14:12:04 +00:00
func ( c * Commands ) sourceCommand ( t * Term , ctx callContext , 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>" )
}
2016-01-10 08:57:52 +00:00
return c . executeFile ( t , args )
2015-09-29 16:40:12 +00:00
}
2016-02-06 06:00:48 +00:00
var disasmUsageError = errors . New ( "wrong number of arguments: disassemble [-a <start> <end>] [-l <locspec>]" )
2016-02-23 14:12:04 +00:00
func disassCommand ( t * Term , ctx callContext , args string ) error {
2016-02-06 06:00:48 +00:00
var cmd , rest string
if args != "" {
argv := strings . SplitN ( args , " " , 2 )
if len ( argv ) != 2 {
return disasmUsageError
}
cmd = argv [ 0 ]
rest = argv [ 1 ]
}
var disasm api . AsmInstructions
var disasmErr error
switch cmd {
case "" :
2016-02-23 14:12:04 +00:00
locs , err := t . client . FindLocation ( ctx . Scope , "+0" )
2016-02-06 06:00:48 +00:00
if err != nil {
return err
}
2016-02-23 14:12:04 +00:00
disasm , disasmErr = t . client . DisassemblePC ( ctx . Scope , locs [ 0 ] . PC , api . IntelFlavour )
2016-02-06 06:00:48 +00:00
case "-a" :
v := strings . SplitN ( rest , " " , 2 )
if len ( v ) != 2 {
return disasmUsageError
}
startpc , err := strconv . ParseInt ( v [ 0 ] , 0 , 64 )
if err != nil {
return fmt . Errorf ( "wrong argument: %s is not a number" , v [ 0 ] )
}
endpc , err := strconv . ParseInt ( v [ 1 ] , 0 , 64 )
if err != nil {
return fmt . Errorf ( "wrong argument: %s is not a number" , v [ 1 ] )
}
2016-02-23 14:12:04 +00:00
disasm , disasmErr = t . client . DisassembleRange ( ctx . Scope , uint64 ( startpc ) , uint64 ( endpc ) , api . IntelFlavour )
2016-02-06 06:00:48 +00:00
case "-l" :
2016-02-23 14:12:04 +00:00
locs , err := t . client . FindLocation ( ctx . Scope , rest )
2016-02-06 06:00:48 +00:00
if err != nil {
return err
}
if len ( locs ) != 1 {
return errors . New ( "expression specifies multiple locations" )
}
2016-02-23 14:12:04 +00:00
disasm , disasmErr = t . client . DisassemblePC ( ctx . Scope , locs [ 0 ] . PC , api . IntelFlavour )
2016-02-06 06:00:48 +00:00
default :
return disasmUsageError
}
if disasmErr != nil {
return disasmErr
}
fmt . Printf ( "printing\n" )
DisasmPrint ( disasm , os . Stdout )
return nil
}
2015-09-17 08:42:34 +00:00
func digits ( n int ) int {
2016-01-27 12:33:37 +00:00
if n <= 0 {
return 1
}
2015-09-17 08:42:34 +00:00
return int ( math . Floor ( math . Log10 ( float64 ( n ) ) ) ) + 1
}
func printStack ( stack [ ] api . Stackframe , ind string ) {
2016-01-28 07:47:04 +00:00
if len ( stack ) == 0 {
return
}
2015-09-17 08:42:34 +00:00
d := digits ( len ( stack ) - 1 )
fmtstr := "%s%" + strconv . Itoa ( d ) + "d 0x%016x in %s\n"
2016-04-24 17:15:39 +00:00
s := ind + 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 {
2017-06-23 11:31:05 +00:00
if stack [ i ] . Err != "" {
fmt . Printf ( "%serror: %s\n" , s , stack [ i ] . Err )
continue
}
2015-06-17 17:11:57 +00:00
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 )
2017-05-05 22:17:52 +00:00
if state . When != "" {
fmt . Println ( state . When )
}
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 {
2016-01-31 12:03:53 +00:00
fmt . Printf ( "> %s() %s:%d (PC: %#v)\n" , fn . Name , ShortenFilePath ( th . File ) , th . Line , th . PC )
2015-10-29 09:59:22 +00:00
return
}
args := ""
2016-04-24 17:15:39 +00:00
if th . BreakpointInfo != nil && th . Breakpoint . LoadArgs != nil && * th . Breakpoint . LoadArgs == ShortLoadConfig {
2015-10-29 09:59:22 +00:00
var arg [ ] string
2016-01-28 07:47:04 +00:00
for _ , ar := range th . BreakpointInfo . Arguments {
2015-10-29 09:59:22 +00:00
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 , ", " )
}
2016-01-24 08:16:45 +00:00
bpname := ""
if th . Breakpoint . Name != "" {
bpname = fmt . Sprintf ( "[%s] " , th . Breakpoint . Name )
}
2015-10-29 09:59:22 +00:00
if hitCount , ok := th . Breakpoint . HitCount [ strconv . Itoa ( th . GoroutineID ) ] ; ok {
2016-01-24 08:16:45 +00:00
fmt . Printf ( "> %s%s(%s) %s:%d (hits goroutine(%d):%d total:%d) (PC: %#v)\n" ,
bpname ,
2015-10-29 09:59:22 +00:00
fn . Name ,
args ,
ShortenFilePath ( th . File ) ,
th . Line ,
th . GoroutineID ,
hitCount ,
2016-01-31 12:03:53 +00:00
th . Breakpoint . TotalHitCount ,
th . PC )
2015-07-12 20:18:14 +00:00
} else {
2016-01-24 08:16:45 +00:00
fmt . Printf ( "> %s%s(%s) %s:%d (hits total:%d) (PC: %#v)\n" ,
bpname ,
2015-10-29 09:59:22 +00:00
fn . Name ,
args ,
ShortenFilePath ( th . File ) ,
th . Line ,
2016-01-31 12:03:53 +00:00
th . Breakpoint . TotalHitCount ,
th . PC )
2015-03-20 21:11:11 +00:00
}
2015-10-29 09:59:22 +00:00
if th . BreakpointInfo != nil {
2016-04-24 17:15:39 +00:00
bp := th . Breakpoint
2015-10-29 09:59:22 +00:00
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
}
2016-04-24 17:15:39 +00:00
for _ , v := range bpi . Variables {
fmt . Printf ( "\t%s: %s\n" , v . Name , v . MultilineString ( "\t" ) )
}
for _ , v := range bpi . Locals {
if * bp . LoadLocals == LongLoadConfig {
fmt . Printf ( "\t%s: %s\n" , v . Name , v . MultilineString ( "\t" ) )
} else {
fmt . Printf ( "\t%s: %s\n" , v . Name , v . SinglelineString ( ) )
}
}
if bp . LoadArgs != nil && * bp . LoadArgs == LongLoadConfig {
for _ , v := range bpi . Arguments {
fmt . Printf ( "\t%s: %s\n" , v . Name , v . MultilineString ( "\t" ) )
2015-10-29 09:59:22 +00:00
}
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 {
2016-10-25 17:01:38 +00:00
file , err := os . Open ( t . substitutePath ( filename ) )
2015-03-20 21:11:11 +00:00
if err != nil {
return err
}
defer file . Close ( )
2016-12-22 16:53:34 +00:00
fi , _ := file . Stat ( )
lastModExe := t . client . LastModified ( )
if fi . ModTime ( ) . After ( lastModExe ) {
fmt . Println ( "Warning: listing may not match stale executable" )
}
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
2016-01-10 08:57:52 +00:00
// ExitRequestError is returned when the user
// exits Delve.
2015-07-29 23:19:06 +00:00
type ExitRequestError struct { }
func ( ere ExitRequestError ) Error ( ) string {
return ""
}
2016-02-23 14:12:04 +00:00
func exitCommand ( t * Term , ctx callContext , args string ) error {
2015-07-29 23:19:06 +00:00
return ExitRequestError { }
}
2015-08-19 22:38:53 +00:00
2016-03-09 15:11:58 +00:00
func getBreakpointByIDOrName ( t * Term , arg string ) ( * api . Breakpoint , error ) {
2016-01-24 08:16:45 +00:00
if id , err := strconv . Atoi ( arg ) ; err == nil {
2016-03-09 15:11:58 +00:00
return t . client . GetBreakpoint ( id )
2016-01-24 08:16:45 +00:00
}
2016-03-09 15:11:58 +00:00
return t . client . GetBreakpointByName ( arg )
2016-01-24 08:16:45 +00:00
}
2016-02-23 14:12:04 +00:00
func ( c * Commands ) onCmd ( t * Term , ctx callContext , argstr string ) error {
2017-04-19 10:17:21 +00:00
args := strings . SplitN ( argstr , " " , 2 )
2016-01-24 08:16:45 +00:00
if len ( args ) < 2 {
2016-02-23 14:12:04 +00:00
return errors . New ( "not enough arguments" )
}
2016-01-24 08:16:45 +00:00
bp , err := getBreakpointByIDOrName ( t , args [ 0 ] )
if err != nil {
return err
}
2016-02-23 14:12:04 +00:00
ctx . Prefix = onPrefix
ctx . Breakpoint = bp
2017-04-19 10:17:21 +00:00
err = c . CallWithContext ( args [ 1 ] , t , ctx )
2016-02-23 14:12:04 +00:00
if err != nil {
return err
2016-01-24 08:16:45 +00:00
}
2016-02-23 14:12:04 +00:00
return t . client . AmendBreakpoint ( ctx . Breakpoint )
2016-01-24 08:16:45 +00:00
}
2016-02-23 14:12:04 +00:00
func conditionCmd ( t * Term , ctx callContext , argstr string ) error {
2016-01-24 08:16:45 +00:00
args := strings . SplitN ( argstr , " " , 2 )
if len ( args ) < 2 {
return fmt . Errorf ( "not enough arguments" )
}
bp , err := getBreakpointByIDOrName ( t , args [ 0 ] )
if err != nil {
return err
}
bp . Cond = args [ 1 ]
return t . client . AmendBreakpoint ( bp )
}
2016-01-10 08:57:52 +00:00
// ShortenFilePath take a full file path and attempts to shorten
// it by replacing the current directory to './'.
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
2016-01-10 08:57:52 +00:00
func ( c * Commands ) executeFile ( t * Term , name string ) error {
2015-09-29 16:40:12 +00:00
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
}
2017-04-19 10:17:21 +00:00
if err := c . Call ( line , t ) ; err != nil {
2015-09-29 16:40:12 +00:00
fmt . Printf ( "%s:%d: %v\n" , name , lineno , err )
}
}
return scanner . Err ( )
}
2016-01-24 08:16:45 +00:00
2017-05-05 22:17:52 +00:00
func rewind ( t * Term , ctx callContext , args string ) error {
stateChan := t . client . Rewind ( )
var state * api . DebuggerState
for state = range stateChan {
if state . Err != nil {
return state . Err
}
printcontext ( t , state )
}
printfile ( t , state . CurrentThread . File , state . CurrentThread . Line , true )
return nil
}
func checkpoint ( t * Term , ctx callContext , args string ) error {
if args == "" {
state , err := t . client . GetState ( )
if err != nil {
return err
}
var loc api . Location = api . Location { PC : state . CurrentThread . PC , File : state . CurrentThread . File , Line : state . CurrentThread . Line , Function : state . CurrentThread . Function }
if state . SelectedGoroutine != nil {
loc = state . SelectedGoroutine . CurrentLoc
}
fname := "???"
if loc . Function != nil {
fname = loc . Function . Name
}
args = fmt . Sprintf ( "%s() %s:%d (%#x)" , fname , loc . File , loc . Line , loc . PC )
}
cpid , err := t . client . Checkpoint ( args )
if err != nil {
return err
}
fmt . Printf ( "Checkpoint c%d created.\n" , cpid )
return nil
}
func checkpoints ( t * Term , ctx callContext , args string ) error {
cps , err := t . client . ListCheckpoints ( )
if err != nil {
return err
}
w := new ( tabwriter . Writer )
w . Init ( os . Stdout , 4 , 4 , 2 , ' ' , 0 )
fmt . Fprintln ( w , "ID\tWhen\tWhere" )
for _ , cp := range cps {
fmt . Fprintf ( w , "c%d\t%s\t%s\n" , cp . ID , cp . When , cp . Where )
}
w . Flush ( )
return nil
}
func clearCheckpoint ( t * Term , ctx callContext , args string ) error {
if len ( args ) < 0 {
return errors . New ( "not enough arguments to clear-checkpoint" )
}
if args [ 0 ] != 'c' {
return errors . New ( "clear-checkpoint argument must be a checkpoint ID" )
}
id , err := strconv . Atoi ( args [ 1 : ] )
if err != nil {
return errors . New ( "clear-checkpoint argument must be a checkpoint ID" )
}
return t . client . ClearCheckpoint ( id )
}
2016-01-24 08:16:45 +00:00
func formatBreakpointName ( bp * api . Breakpoint , upcase bool ) string {
thing := "breakpoint"
if bp . Tracepoint {
thing = "tracepoint"
}
if upcase {
thing = strings . Title ( thing )
}
id := bp . Name
if id == "" {
id = strconv . Itoa ( bp . ID )
}
return fmt . Sprintf ( "%s %s" , thing , id )
}
func formatBreakpointLocation ( bp * api . Breakpoint ) string {
p := ShortenFilePath ( bp . File )
if bp . FunctionName != "" {
return fmt . Sprintf ( "%#v for %s() %s:%d" , bp . Addr , bp . FunctionName , p , bp . Line )
}
2016-03-09 15:11:58 +00:00
return fmt . Sprintf ( "%#v for %s:%d" , bp . Addr , p , bp . Line )
2016-01-24 08:16:45 +00:00
}