2017-03-19 22:45:54 +00:00
// Package terminal implements functions for responding to user
// input and dispatching to appropriate backend commands.
package terminal
import (
"bufio"
"errors"
"fmt"
"go/parser"
"go/scanner"
"io"
"math"
"os"
2018-10-13 13:45:53 +00:00
"os/exec"
"reflect"
2017-03-19 22:45:54 +00:00
"regexp"
"sort"
"strconv"
"strings"
"text/tabwriter"
2018-10-13 13:45:53 +00:00
"github.com/cosiner/argv"
2017-03-19 22:45:54 +00:00
"github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api"
"github.com/derekparker/delve/service/debugger"
)
2018-10-13 13:45:53 +00:00
const optimizedFunctionWarning = "Warning: debugging optimized function"
2017-03-19 22:45:54 +00:00
type cmdPrefix int
const (
2018-10-13 13:45:53 +00:00
noPrefix = cmdPrefix ( 0 )
onPrefix = cmdPrefix ( 1 << iota )
2017-03-19 22:45:54 +00:00
)
type callContext struct {
Prefix cmdPrefix
Scope api . EvalScope
Breakpoint * api . Breakpoint
}
2018-10-13 13:45:53 +00:00
func ( ctx * callContext ) scoped ( ) bool {
return ctx . Scope . GoroutineID >= 0 || ctx . Scope . Frame > 0
}
type frameDirection int
const (
frameSet frameDirection = iota
frameUp
frameDown
)
2017-03-19 22:45:54 +00:00
type cmdfunc func ( t * Term , ctx callContext , args string ) error
type command struct {
aliases [ ] string
2018-10-13 13:45:53 +00:00
builtinAliases [ ] string
2017-03-19 22:45:54 +00:00
allowedPrefixes cmdPrefix
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
}
// Commands represents the commands for Delve terminal process.
type Commands struct {
cmds [ ] command
lastCmd cmdfunc
client service . Client
2018-10-13 13:45:53 +00:00
frame int // Current frame as set by frame/up/down commands.
2017-03-19 22:45:54 +00:00
}
var (
2018-10-13 13:45:53 +00:00
// LongLoadConfig loads more information:
// * Follows pointers
// * Loads more array values
// * Does not limit struct fields
LongLoadConfig = api . LoadConfig { true , 1 , 64 , 64 , - 1 }
// ShortLoadConfig loads less information, not following pointers
// and limiting struct fields loaded to 3.
2017-03-19 22:45:54 +00:00
ShortLoadConfig = api . LoadConfig { false , 0 , 64 , 0 , 3 }
)
2018-10-13 13:45:53 +00:00
// ByFirstAlias will sort by the first
// alias of a command.
2017-03-19 22:45:54 +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 ] }
// DebugCommands returns a Commands struct with default commands defined.
func DebugCommands ( client service . Client ) * Commands {
c := & Commands { client : client }
c . cmds = [ ] command {
{ aliases : [ ] string { "help" , "h" } , cmdFn : c . help , helpMsg : ` Prints the help message .
help [ command ]
2018-10-13 13:45:53 +00:00
2017-03-19 22:45:54 +00:00
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 >
2018-10-13 13:45:53 +00:00
2017-03-19 22:45:54 +00:00
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" ` } ,
2018-10-13 13:45:53 +00:00
{ aliases : [ ] string { "restart" , "r" } , cmdFn : restart , helpMsg : ` Restart process .
restart [ checkpoint ]
restart [ - noargs ] newargv ...
For recorded processes restarts from the start or from the specified
checkpoint . For normal processes restarts the process , optionally changing
the arguments . With - noargs , the process starts with an empty commandline .
` } ,
{ aliases : [ ] string { "continue" , "c" } , cmdFn : c . cont , helpMsg : "Run until breakpoint or program termination." } ,
{ aliases : [ ] string { "step" , "s" } , cmdFn : c . step , helpMsg : "Single step through program." } ,
{ aliases : [ ] string { "step-instruction" , "si" } , cmdFn : c . stepInstruction , helpMsg : "Single step a single cpu instruction." } ,
{ aliases : [ ] string { "next" , "n" } , cmdFn : c . next , helpMsg : "Step over to next source line." } ,
{ aliases : [ ] string { "stepout" } , cmdFn : c . stepout , helpMsg : "Step out of the current function." } ,
{ aliases : [ ] string { "call" } , cmdFn : c . call , helpMsg : ` Resumes process , injecting a function call ( EXPERIMENTAL ! ! ! )
Current limitations :
- only pointers to stack - allocated objects can be passed as argument .
- only some automatic type conversions are supported .
- functions can only be called on running goroutines that are not
executing the runtime .
- the current goroutine needs to have at least 256 bytes of free space on
the stack .
- functions can only be called when the goroutine is stopped at a safe
point .
- calling a function will resume execution of all goroutines .
- only supported on linux ' s native backend .
` } ,
2017-03-19 22:45:54 +00:00
{ aliases : [ ] string { "threads" } , cmdFn : threads , helpMsg : "Print out info for every traced thread." } ,
{ 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 > ]
2018-10-13 13:45:53 +00:00
2017-03-19 22:45:54 +00:00
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 .
2018-10-13 13:45:53 +00:00
goroutines [ - u ( default : user location ) | - r ( runtime location ) | - g ( go statement location ) | - s ( start location ) ] [ - t ( stack trace ) ]
2017-03-19 22:45:54 +00:00
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
2018-10-13 13:45:53 +00:00
- s displays location of the start function
- t displays stack trace of goroutine
2017-03-19 22:45:54 +00:00
If no flag is specified the default is - u . ` } ,
2018-10-13 13:45:53 +00:00
{ aliases : [ ] string { "goroutine" } , allowedPrefixes : onPrefix , cmdFn : c . goroutine , helpMsg : ` Shows or changes current goroutine
2017-03-19 22:45:54 +00:00
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 . ` } ,
{ aliases : [ ] string { "breakpoints" , "bp" } , cmdFn : breakpoints , helpMsg : "Print out info for active breakpoints." } ,
2018-10-13 13:45:53 +00:00
{ aliases : [ ] string { "print" , "p" } , allowedPrefixes : onPrefix , cmdFn : printVar , helpMsg : ` Evaluate an expression .
2017-03-19 22:45:54 +00:00
[ goroutine < n > ] [ frame < m > ] print < expression >
See $ GOPATH / src / github . com / derekparker / delve / Documentation / cli / expr . md for a description of supported expressions . ` } ,
2018-10-13 13:45:53 +00:00
{ aliases : [ ] string { "whatis" } , cmdFn : whatisCommand , helpMsg : ` Prints type of an expression .
whatis < expression > ` } ,
{ aliases : [ ] string { "set" } , cmdFn : setVar , helpMsg : ` Changes the value of a variable .
2017-03-19 22:45:54 +00:00
[ 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 > ]
2018-10-13 13:45:53 +00:00
If regex is specified only the types matching it will be returned . ` } ,
{ aliases : [ ] string { "args" } , allowedPrefixes : onPrefix , cmdFn : args , helpMsg : ` Print function arguments .
2017-03-19 22:45:54 +00:00
[ 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 . ` } ,
2018-10-13 13:45:53 +00:00
{ aliases : [ ] string { "locals" } , allowedPrefixes : onPrefix , cmdFn : locals , helpMsg : ` Print local variables .
2017-03-19 22:45:54 +00:00
[ goroutine < n > ] [ frame < m > ] locals [ - v ] [ < regex > ]
2018-10-13 13:45:53 +00:00
The name of variables that are shadowed in the current scope will be shown in parenthesis .
2017-03-19 22:45:54 +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 . ` } ,
{ aliases : [ ] string { "regs" } , cmdFn : regs , helpMsg : ` Print contents of CPU registers .
regs [ - a ]
2018-10-13 13:45:53 +00:00
2017-03-19 22:45:54 +00:00
Argument - a shows more registers . ` } ,
2018-10-13 13:45:53 +00:00
{ aliases : [ ] string { "exit" , "quit" , "q" } , cmdFn : exitCommand , helpMsg : ` Exit the debugger .
exit [ - c ]
When connected to a headless instance started with the -- accept - multiclient , pass - c to resume the execution of the target process before disconnecting . ` } ,
{ aliases : [ ] string { "list" , "ls" , "l" } , cmdFn : listCommand , helpMsg : ` Show source code .
2017-03-19 22:45:54 +00:00
[ goroutine < n > ] [ frame < m > ] list [ < linespec > ]
Show source around current point or provided linespec . ` } ,
2018-10-13 13:45:53 +00:00
{ aliases : [ ] string { "stack" , "bt" } , allowedPrefixes : onPrefix , cmdFn : stackCommand , helpMsg : ` Print stack trace .
[ goroutine < n > ] [ frame < m > ] stack [ < depth > ] [ - full ] [ - g ] [ - s ] [ - offsets ]
- full every stackframe is decorated with the value of its local variables and arguments .
- offsets prints frame offset of each frame
` } ,
{ aliases : [ ] string { "frame" } ,
cmdFn : func ( t * Term , ctx callContext , arg string ) error {
return c . frameCommand ( t , ctx , arg , frameSet )
} ,
helpMsg : ` Set the current frame , or execute command on a different frame .
frame < m >
frame < m > < command >
The first form sets frame used by subsequent commands such as "print" or "set" .
The second form runs the command on the given frame . ` } ,
{ aliases : [ ] string { "up" } ,
cmdFn : func ( t * Term , ctx callContext , arg string ) error {
return c . frameCommand ( t , ctx , arg , frameUp )
} ,
helpMsg : ` Move the current frame up .
up [ < m > ]
up [ < m > ] < command >
Move the current frame up by < m > . The second form runs the command on the given frame . ` } ,
{ aliases : [ ] string { "down" } ,
cmdFn : func ( t * Term , ctx callContext , arg string ) error {
return c . frameCommand ( t , ctx , arg , frameDown )
} ,
helpMsg : ` Move the current frame down .
down [ < m > ]
down [ < m > ] < command >
Move the current frame down by < m > . The second form runs the command on the given frame . ` } ,
2017-03-19 22:45:54 +00:00
{ aliases : [ ] string { "source" } , cmdFn : c . sourceCommand , helpMsg : ` Executes a file containing a list of delve commands
source < path > ` } ,
2018-10-13 13:45:53 +00:00
{ aliases : [ ] string { "disassemble" , "disass" } , cmdFn : disassCommand , helpMsg : ` Disassembler .
2017-03-19 22:45:54 +00:00
[ 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 .
2018-10-13 13:45:53 +00:00
2017-03-19 22:45:54 +00:00
- 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 > .
2018-10-13 13:45:53 +00:00
2017-03-19 22:45:54 +00:00
Supported commands : print , stack and goroutine ) ` } ,
{ aliases : [ ] string { "condition" , "cond" } , cmdFn : conditionCmd , helpMsg : ` Set breakpoint condition .
condition < breakpoint name or id > < boolean expression > .
2018-10-13 13:45:53 +00:00
2017-03-19 22:45:54 +00:00
Specifies that the breakpoint or tracepoint should break only if the boolean expression is true . ` } ,
2018-10-13 13:45:53 +00:00
{ aliases : [ ] string { "config" } , cmdFn : configureCmd , helpMsg : ` Changes configuration parameters .
config - list
Show all configuration parameters .
config - save
Saves the configuration file to disk , overwriting the current configuration file .
config < parameter > < value >
Changes the value of a configuration parameter .
config substitute - path < from > < to >
config substitute - path < from >
Adds or removes a path substitution rule .
config alias < command > < alias >
config alias < alias >
Defines < alias > as an alias to < command > or removes an alias . ` } ,
{ aliases : [ ] string { "edit" , "ed" } , cmdFn : edit , helpMsg : ` Open where you are in $ DELVE_EDITOR or $ EDITOR
edit [ locspec ]
If locspec is omitted edit will open the current source file in the editor , otherwise it will open the specified location . ` } ,
}
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 .
clear - 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 ] `
}
}
2017-03-19 22:45:54 +00:00
}
sort . Sort ( ByFirstAlias ( c . cmds ) )
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.
// If it cannot find the command it will default to noCmdAvailable().
// If the command is an empty string it will replay the last command.
func ( c * Commands ) Find ( cmdstr string , prefix cmdPrefix ) 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 ) {
if prefix != noPrefix && v . allowedPrefixes & prefix == 0 {
continue
}
c . lastCmd = v . cmdFn
return v . cmdFn
}
}
return noCmdAvailable
}
2018-10-13 13:45:53 +00:00
// CallWithContext takes a command and a context that command should be executed in.
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 )
2017-03-19 22:45:54 +00:00
}
2018-10-13 13:45:53 +00:00
// Call takes a command to execute.
func ( c * Commands ) Call ( cmdstr string , t * Term ) error {
ctx := callContext { Prefix : noPrefix , Scope : api . EvalScope { GoroutineID : - 1 , Frame : c . frame } }
return c . CallWithContext ( cmdstr , t , ctx )
2017-03-19 22:45:54 +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 ) {
2018-10-13 13:45:53 +00:00
for i := range c . cmds {
if c . cmds [ i ] . builtinAliases != nil {
c . cmds [ i ] . aliases = append ( c . cmds [ i ] . aliases [ : 0 ] , c . cmds [ i ] . builtinAliases ... )
}
}
2017-03-19 22:45:54 +00:00
for i := range c . cmds {
if aliases , ok := allAliases [ c . cmds [ i ] . aliases [ 0 ] ] ; ok {
2018-10-13 13:45:53 +00:00
if c . cmds [ i ] . builtinAliases == nil {
c . cmds [ i ] . builtinAliases = make ( [ ] string , len ( c . cmds [ i ] . aliases ) )
copy ( c . cmds [ i ] . builtinAliases , c . cmds [ i ] . aliases )
}
2017-03-19 22:45:54 +00:00
c . cmds [ i ] . aliases = append ( c . cmds [ i ] . aliases , aliases ... )
}
}
}
var noCmdError = errors . New ( "command not available" )
func noCmdAvailable ( t * Term , ctx callContext , args string ) error {
return noCmdError
}
func nullCommand ( t * Term , ctx callContext , args string ) error {
return nil
}
func ( c * Commands ) help ( t * Term , ctx callContext , args string ) error {
if args != "" {
for _ , cmd := range c . cmds {
for _ , alias := range cmd . aliases {
if alias == args {
fmt . Println ( cmd . helpMsg )
return nil
}
}
}
return noCmdError
}
fmt . Println ( "The following commands are available:" )
w := new ( tabwriter . Writer )
w . Init ( os . Stdout , 0 , 8 , 0 , '-' , 0 )
for _ , cmd := range c . cmds {
h := cmd . helpMsg
if idx := strings . Index ( h , "\n" ) ; idx >= 0 {
h = h [ : idx ]
}
if len ( cmd . aliases ) > 1 {
fmt . Fprintf ( w , " %s (alias: %s) \t %s\n" , cmd . aliases [ 0 ] , strings . Join ( cmd . aliases [ 1 : ] , " | " ) , h )
} else {
fmt . Fprintf ( w , " %s \t %s\n" , cmd . aliases [ 0 ] , h )
}
}
if err := w . Flush ( ) ; err != nil {
return err
}
fmt . Println ( "Type help followed by a command for full documentation." )
return nil
}
type byThreadID [ ] * api . Thread
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 }
func threads ( t * Term , ctx callContext , args string ) error {
threads , err := t . client . ListThreads ( )
if err != nil {
return err
}
state , err := t . client . GetState ( )
if err != nil {
return err
}
sort . Sort ( byThreadID ( threads ) )
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" ,
prefix , th . ID , th . PC , ShortenFilePath ( th . File ) ,
2018-10-13 13:45:53 +00:00
th . Line , th . Function . Name ( ) )
2017-03-19 22:45:54 +00:00
} else {
fmt . Printf ( "%sThread %s\n" , prefix , formatThread ( th ) )
}
}
return nil
}
func thread ( t * Term , ctx callContext , args string ) error {
if len ( args ) == 0 {
return fmt . Errorf ( "you must specify a thread" )
}
tid , err := strconv . Atoi ( args )
if err != nil {
return err
}
oldState , err := t . client . GetState ( )
if err != nil {
return err
}
newState , err := t . client . SwitchThread ( tid )
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
}
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 }
func goroutines ( t * Term , ctx callContext , argstr string ) error {
args := strings . Split ( argstr , " " )
var fgl = fglUserCurrent
2018-10-13 13:45:53 +00:00
bPrintStack := false
2017-03-19 22:45:54 +00:00
switch len ( args ) {
case 0 :
// nothing to do
2018-10-13 13:45:53 +00:00
case 1 , 2 :
for _ , arg := range args {
switch arg {
case "-u" :
fgl = fglUserCurrent
case "-r" :
fgl = fglRuntimeCurrent
case "-g" :
fgl = fglGo
case "-s" :
fgl = fglStart
case "-t" :
bPrintStack = true
case "" :
// nothing to do
default :
return fmt . Errorf ( "wrong argument: '%s'" , arg )
}
2017-03-19 22:45:54 +00:00
}
default :
return fmt . Errorf ( "too many arguments" )
}
state , err := t . client . GetState ( )
if err != nil {
return err
}
gs , err := t . client . ListGoroutines ( )
if err != nil {
return err
}
sort . Sort ( byGoroutineID ( gs ) )
fmt . Printf ( "[%d goroutines]\n" , len ( gs ) )
for _ , g := range gs {
prefix := " "
if state . SelectedGoroutine != nil && g . ID == state . SelectedGoroutine . ID {
prefix = "* "
}
fmt . Printf ( "%sGoroutine %s\n" , prefix , formatGoroutine ( g , fgl ) )
2018-10-13 13:45:53 +00:00
if bPrintStack {
stack , err := t . client . Stacktrace ( g . ID , 10 , false , nil )
if err != nil {
return err
}
printStack ( stack , "\t" , false )
}
2017-03-19 22:45:54 +00:00
}
return nil
}
2018-10-13 13:45:53 +00:00
func selectedGID ( state * api . DebuggerState ) int {
if state . SelectedGoroutine == nil {
return 0
}
return state . SelectedGoroutine . ID
}
2017-03-19 22:45:54 +00:00
func ( c * Commands ) goroutine ( t * Term , ctx callContext , argstr string ) error {
2018-10-13 13:45:53 +00:00
args := strings . SplitN ( argstr , " " , 2 )
2017-03-19 22:45:54 +00:00
if ctx . Prefix == onPrefix {
if len ( args ) != 1 || args [ 0 ] != "" {
return errors . New ( "too many arguments to goroutine" )
}
ctx . Breakpoint . Goroutine = true
return nil
}
2018-10-13 13:45:53 +00:00
if len ( args ) == 1 {
2017-03-19 22:45:54 +00:00
if args [ 0 ] == "" {
return printscope ( t )
}
gid , err := strconv . Atoi ( argstr )
if err != nil {
return err
}
oldState , err := t . client . GetState ( )
if err != nil {
return err
}
newState , err := t . client . SwitchGoroutine ( gid )
if err != nil {
return err
}
2018-10-13 13:45:53 +00:00
c . frame = 0
fmt . Printf ( "Switched from %d to %d (thread %d)\n" , selectedGID ( oldState ) , gid , newState . CurrentThread . ID )
2017-03-19 22:45:54 +00:00
return nil
}
var err error
ctx . Scope . GoroutineID , err = strconv . Atoi ( args [ 0 ] )
if err != nil {
return err
}
2018-10-13 13:45:53 +00:00
return c . CallWithContext ( args [ 1 ] , t , ctx )
2017-03-19 22:45:54 +00:00
}
2018-10-13 13:45:53 +00:00
// Handle "frame", "up", "down" commands.
func ( c * Commands ) frameCommand ( t * Term , ctx callContext , argstr string , direction frameDirection ) error {
frame := 1
arg := ""
if len ( argstr ) == 0 {
if direction == frameSet {
return errors . New ( "not enough arguments" )
}
} else {
args := strings . SplitN ( argstr , " " , 2 )
var err error
if frame , err = strconv . Atoi ( args [ 0 ] ) ; err != nil {
return err
}
if len ( args ) > 1 {
arg = args [ 1 ]
}
2017-03-19 22:45:54 +00:00
}
2018-10-13 13:45:53 +00:00
switch direction {
case frameUp :
frame = c . frame + frame
case frameDown :
frame = c . frame - frame
}
if len ( arg ) > 0 {
ctx . Scope . Frame = frame
return c . CallWithContext ( arg , t , ctx )
}
if frame < 0 {
return fmt . Errorf ( "Invalid frame %d" , frame )
}
stack , err := t . client . Stacktrace ( ctx . Scope . GoroutineID , frame , false , nil )
2017-03-19 22:45:54 +00:00
if err != nil {
return err
}
2018-10-13 13:45:53 +00:00
if frame >= len ( stack ) {
return fmt . Errorf ( "Invalid frame %d" , frame )
}
c . frame = frame
state , err := t . client . GetState ( )
if err != nil {
return err
}
printcontext ( t , state )
th := stack [ frame ]
fmt . Printf ( "Frame %d: %s:%d (PC: %x)\n" , frame , ShortenFilePath ( th . File ) , th . Line , th . PC )
printfile ( t , th . File , th . Line , true )
return nil
2017-03-19 22:45:54 +00:00
}
func printscope ( t * Term ) error {
state , err := t . client . GetState ( )
if err != nil {
return err
}
fmt . Printf ( "Thread %s\n" , formatThread ( state . CurrentThread ) )
if state . SelectedGoroutine != nil {
writeGoroutineLong ( os . Stdout , state . SelectedGoroutine , "" )
}
return nil
}
func formatThread ( th * api . Thread ) string {
if th == nil {
return "<nil>"
}
return fmt . Sprintf ( "%d at %s:%d" , th . ID , ShortenFilePath ( th . File ) , th . Line )
}
type formatGoroutineLoc int
const (
fglRuntimeCurrent = formatGoroutineLoc ( iota )
fglUserCurrent
fglGo
2018-10-13 13:45:53 +00:00
fglStart
2017-03-19 22:45:54 +00:00
)
func formatLocation ( loc api . Location ) string {
2018-10-13 13:45:53 +00:00
return fmt . Sprintf ( "%s:%d %s (%#v)" , ShortenFilePath ( loc . File ) , loc . Line , loc . Function . Name ( ) , loc . PC )
2017-03-19 22:45:54 +00:00
}
func formatGoroutine ( g * api . Goroutine , fgl formatGoroutineLoc ) string {
if g == nil {
return "<nil>"
}
2018-10-13 13:45:53 +00:00
if g . Unreadable != "" {
return fmt . Sprintf ( "(unreadable %s)" , g . Unreadable )
}
2017-03-19 22:45:54 +00:00
var locname string
var loc api . Location
switch fgl {
case fglRuntimeCurrent :
locname = "Runtime"
loc = g . CurrentLoc
case fglUserCurrent :
locname = "User"
loc = g . UserCurrentLoc
case fglGo :
locname = "Go"
loc = g . GoStatementLoc
2018-10-13 13:45:53 +00:00
case fglStart :
locname = "Start"
loc = g . StartLoc
2017-03-19 22:45:54 +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 )
}
func writeGoroutineLong ( w io . Writer , g * api . Goroutine , prefix string ) {
2018-10-13 13:45:53 +00:00
fmt . Fprintf ( w , "%sGoroutine %d:\n%s\tRuntime: %s\n%s\tUser: %s\n%s\tGo: %s\n%s\tStart: %s\n" ,
2017-03-19 22:45:54 +00:00
prefix , g . ID ,
prefix , formatLocation ( g . CurrentLoc ) ,
prefix , formatLocation ( g . UserCurrentLoc ) ,
2018-10-13 13:45:53 +00:00
prefix , formatLocation ( g . GoStatementLoc ) ,
prefix , formatLocation ( g . StartLoc ) )
}
func parseArgs ( args string ) ( [ ] string , error ) {
if args == "" {
return nil , nil
}
v , err := argv . Argv ( [ ] rune ( args ) , argv . ParseEnv ( os . Environ ( ) ) ,
func ( s [ ] rune , _ map [ string ] string ) ( [ ] rune , error ) {
return nil , fmt . Errorf ( "Backtick not supported in '%s'" , string ( s ) )
} )
if err != nil {
return nil , err
}
if len ( v ) != 1 {
return nil , fmt . Errorf ( "Illegal commandline '%s'" , args )
}
return v [ 0 ] , nil
2017-03-19 22:45:54 +00:00
}
func restart ( t * Term , ctx callContext , args string ) error {
2018-10-13 13:45:53 +00:00
v , err := parseArgs ( args )
2017-03-19 22:45:54 +00:00
if err != nil {
return err
}
2018-10-13 13:45:53 +00:00
var restartPos string
var resetArgs bool
if t . client . Recorded ( ) {
if len ( v ) > 1 {
return fmt . Errorf ( "restart: illegal position '%v'" , v )
}
if len ( v ) == 1 {
restartPos = v [ 0 ]
v = nil
}
} else if len ( v ) > 0 {
resetArgs = true
if v [ 0 ] == "-noargs" {
if len ( v ) > 1 {
return fmt . Errorf ( "restart: -noargs does not take any arg" )
}
v = nil
}
}
discarded , err := t . client . RestartFrom ( restartPos , resetArgs , v )
if err != nil {
return err
}
if ! t . client . Recorded ( ) {
fmt . Println ( "Process restarted with PID" , t . client . ProcessPid ( ) )
}
2017-03-19 22:45:54 +00:00
for i := range discarded {
fmt . Printf ( "Discarded %s at %s: %v\n" , formatBreakpointName ( discarded [ i ] . Breakpoint , false ) , formatBreakpointLocation ( discarded [ i ] . Breakpoint ) , discarded [ i ] . Reason )
}
2018-10-13 13:45:53 +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 )
}
2017-03-19 22:45:54 +00:00
return nil
}
2018-10-13 13:45:53 +00:00
func printcontextNoState ( t * Term ) {
state , _ := t . client . GetState ( )
if state == nil || state . CurrentThread == nil {
return
}
printcontext ( t , state )
}
func ( c * Commands ) cont ( t * Term , ctx callContext , args string ) error {
c . frame = 0
2017-03-19 22:45:54 +00:00
stateChan := t . client . Continue ( )
var state * api . DebuggerState
for state = range stateChan {
if state . Err != nil {
2018-10-13 13:45:53 +00:00
printcontextNoState ( t )
2017-03-19 22:45:54 +00:00
return state . Err
}
printcontext ( t , state )
}
printfile ( t , state . CurrentThread . File , state . CurrentThread . Line , true )
return nil
}
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 {
2018-10-13 13:45:53 +00:00
fmt . Printf ( "\tbreakpoint hit during %s, continuing...\n" , op )
2017-03-19 22:45:54 +00:00
stateChan := t . client . Continue ( )
var state * api . DebuggerState
for state = range stateChan {
if state . Err != nil {
2018-10-13 13:45:53 +00:00
printcontextNoState ( t )
2017-03-19 22:45:54 +00:00
return state . Err
}
printcontext ( t , state )
}
if ! state . NextInProgress {
printfile ( t , state . CurrentThread . File , state . CurrentThread . Line , true )
return nil
}
}
}
func scopePrefixSwitch ( t * Term , ctx callContext ) error {
if ctx . Scope . GoroutineID > 0 {
_ , err := t . client . SwitchGoroutine ( ctx . Scope . GoroutineID )
if err != nil {
return err
}
}
return nil
}
2018-10-13 13:45:53 +00:00
func exitedToError ( state * api . DebuggerState , err error ) ( * api . DebuggerState , error ) {
if err == nil && state . Exited {
return nil , fmt . Errorf ( "Process has exited with status %d" , state . ExitStatus )
}
return state , err
}
func ( c * Commands ) step ( t * Term , ctx callContext , args string ) error {
2017-03-19 22:45:54 +00:00
if err := scopePrefixSwitch ( t , ctx ) ; err != nil {
return err
}
2018-10-13 13:45:53 +00:00
c . frame = 0
state , err := exitedToError ( t . client . Step ( ) )
2017-03-19 22:45:54 +00:00
if err != nil {
2018-10-13 13:45:53 +00:00
printcontextNoState ( t )
2017-03-19 22:45:54 +00:00
return err
}
printcontext ( t , state )
return continueUntilCompleteNext ( t , state , "step" )
}
2018-10-13 13:45:53 +00:00
var notOnFrameZeroErr = errors . New ( "not on topmost frame" )
func ( c * Commands ) stepInstruction ( t * Term , ctx callContext , args string ) error {
2017-03-19 22:45:54 +00:00
if err := scopePrefixSwitch ( t , ctx ) ; err != nil {
return err
}
2018-10-13 13:45:53 +00:00
if c . frame != 0 {
return notOnFrameZeroErr
}
state , err := exitedToError ( t . client . StepInstruction ( ) )
2017-03-19 22:45:54 +00:00
if err != nil {
2018-10-13 13:45:53 +00:00
printcontextNoState ( t )
2017-03-19 22:45:54 +00:00
return err
}
printcontext ( t , state )
printfile ( t , state . CurrentThread . File , state . CurrentThread . Line , true )
return nil
}
2018-10-13 13:45:53 +00:00
func ( c * Commands ) next ( t * Term , ctx callContext , args string ) error {
2017-03-19 22:45:54 +00:00
if err := scopePrefixSwitch ( t , ctx ) ; err != nil {
return err
}
2018-10-13 13:45:53 +00:00
if c . frame != 0 {
return notOnFrameZeroErr
}
state , err := exitedToError ( t . client . Next ( ) )
2017-03-19 22:45:54 +00:00
if err != nil {
2018-10-13 13:45:53 +00:00
printcontextNoState ( t )
2017-03-19 22:45:54 +00:00
return err
}
printcontext ( t , state )
return continueUntilCompleteNext ( t , state , "next" )
}
2018-10-13 13:45:53 +00:00
func ( c * Commands ) stepout ( t * Term , ctx callContext , args string ) error {
2017-03-19 22:45:54 +00:00
if err := scopePrefixSwitch ( t , ctx ) ; err != nil {
return err
}
2018-10-13 13:45:53 +00:00
if c . frame != 0 {
return notOnFrameZeroErr
}
state , err := exitedToError ( t . client . StepOut ( ) )
2017-03-19 22:45:54 +00:00
if err != nil {
2018-10-13 13:45:53 +00:00
printcontextNoState ( t )
2017-03-19 22:45:54 +00:00
return err
}
printcontext ( t , state )
return continueUntilCompleteNext ( t , state , "stepout" )
}
2018-10-13 13:45:53 +00:00
func ( c * Commands ) call ( t * Term , ctx callContext , args string ) error {
if err := scopePrefixSwitch ( t , ctx ) ; err != nil {
return err
}
state , err := exitedToError ( t . client . Call ( args ) )
c . frame = 0
if err != nil {
printcontextNoState ( t )
return err
}
printcontext ( t , state )
return continueUntilCompleteNext ( t , state , "call" )
}
2017-03-19 22:45:54 +00:00
func clear ( t * Term , ctx callContext , args string ) error {
if len ( args ) == 0 {
return fmt . Errorf ( "not enough arguments" )
}
id , err := strconv . Atoi ( args )
var bp * api . Breakpoint
if err == nil {
bp , err = t . client . ClearBreakpoint ( id )
} else {
bp , err = t . client . ClearBreakpointByName ( args )
}
if err != nil {
return err
}
fmt . Printf ( "%s cleared at %s\n" , formatBreakpointName ( bp , true ) , formatBreakpointLocation ( bp ) )
return nil
}
func clearAll ( t * Term , ctx callContext , args string ) error {
breakPoints , err := t . client . ListBreakpoints ( )
if err != nil {
return err
}
var locPCs map [ uint64 ] struct { }
if args != "" {
locs , err := t . client . FindLocation ( api . EvalScope { GoroutineID : - 1 , Frame : 0 } , args )
if err != nil {
return err
}
locPCs = make ( map [ uint64 ] struct { } )
for _ , loc := range locs {
locPCs [ loc . PC ] = struct { } { }
}
}
for _ , bp := range breakPoints {
if locPCs != nil {
if _ , ok := locPCs [ bp . Addr ] ; ! ok {
continue
}
}
if bp . ID < 0 {
continue
}
_ , err := t . client . ClearBreakpoint ( bp . ID )
if err != nil {
fmt . Printf ( "Couldn't delete %s at %s: %s\n" , formatBreakpointName ( bp , false ) , formatBreakpointLocation ( bp ) , err )
}
fmt . Printf ( "%s cleared at %s\n" , formatBreakpointName ( bp , true ) , formatBreakpointLocation ( bp ) )
}
return nil
}
// ByID sorts breakpoints by ID.
type ByID [ ] * api . Breakpoint
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 }
func breakpoints ( t * Term , ctx callContext , args string ) error {
breakPoints , err := t . client . ListBreakpoints ( )
if err != nil {
return err
}
sort . Sort ( ByID ( breakPoints ) )
for _ , bp := range breakPoints {
fmt . Printf ( "%s at %v (%d)\n" , formatBreakpointName ( bp , true ) , formatBreakpointLocation ( bp ) , bp . TotalHitCount )
var attrs [ ] string
if bp . Cond != "" {
attrs = append ( attrs , fmt . Sprintf ( "\tcond %s" , bp . Cond ) )
}
if bp . Stacktrace > 0 {
attrs = append ( attrs , fmt . Sprintf ( "\tstack %d" , bp . Stacktrace ) )
}
if bp . Goroutine {
attrs = append ( attrs , "\tgoroutine" )
}
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" )
}
}
for i := range bp . Variables {
attrs = append ( attrs , fmt . Sprintf ( "\tprint %s" , bp . Variables [ i ] ) )
}
if len ( attrs ) > 0 {
fmt . Printf ( "%s\n" , strings . Join ( attrs , "\n" ) )
}
}
return nil
}
2018-10-13 13:45:53 +00:00
func setBreakpoint ( t * Term , ctx callContext , tracepoint bool , argstr string ) error {
2017-03-19 22:45:54 +00:00
args := strings . SplitN ( argstr , " " , 2 )
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
}
default :
return fmt . Errorf ( "address required" )
}
requestedBp . Tracepoint = tracepoint
2018-10-13 13:45:53 +00:00
locs , err := t . client . FindLocation ( ctx . Scope , locspec )
2017-03-19 22:45:54 +00:00
if err != nil {
if requestedBp . Name == "" {
return err
}
requestedBp . Name = ""
locspec = argstr
var err2 error
2018-10-13 13:45:53 +00:00
locs , err2 = t . client . FindLocation ( ctx . Scope , locspec )
2017-03-19 22:45:54 +00:00
if err2 != nil {
return err
}
}
for _ , loc := range locs {
requestedBp . Addr = loc . PC
bp , err := t . client . CreateBreakpoint ( requestedBp )
if err != nil {
return err
}
fmt . Printf ( "%s set at %s\n" , formatBreakpointName ( bp , true ) , formatBreakpointLocation ( bp ) )
}
return nil
}
func breakpoint ( t * Term , ctx callContext , args string ) error {
2018-10-13 13:45:53 +00:00
return setBreakpoint ( t , ctx , false , args )
2017-03-19 22:45:54 +00:00
}
func tracepoint ( t * Term , ctx callContext , args string ) error {
2018-10-13 13:45:53 +00:00
return setBreakpoint ( t , ctx , true , args )
}
func edit ( t * Term , ctx callContext , args string ) error {
file , lineno , _ , err := getLocation ( t , ctx , args , false )
if err != nil {
return err
}
var editor string
if editor = os . Getenv ( "DELVE_EDITOR" ) ; editor == "" {
if editor = os . Getenv ( "EDITOR" ) ; editor == "" {
return fmt . Errorf ( "Neither DELVE_EDITOR or EDITOR is set" )
}
}
cmd := exec . Command ( editor , fmt . Sprintf ( "+%d" , lineno ) , file )
return cmd . Run ( )
2017-03-19 22:45:54 +00:00
}
func printVar ( t * Term , ctx callContext , args string ) error {
if len ( args ) == 0 {
return fmt . Errorf ( "not enough arguments" )
}
if ctx . Prefix == onPrefix {
ctx . Breakpoint . Variables = append ( ctx . Breakpoint . Variables , args )
return nil
}
2018-10-13 13:45:53 +00:00
val , err := t . client . EvalVariable ( ctx . Scope , args , t . loadConfig ( ) )
2017-03-19 22:45:54 +00:00
if err != nil {
return err
}
fmt . Println ( val . MultilineString ( "" ) )
return nil
}
2018-10-13 13:45:53 +00:00
func whatisCommand ( t * Term , ctx callContext , args string ) error {
if len ( args ) == 0 {
return fmt . Errorf ( "not enough arguments" )
}
val , err := t . client . EvalVariable ( ctx . Scope , args , ShortLoadConfig )
if err != nil {
return err
}
if val . Type != "" {
fmt . Println ( val . Type )
}
if val . RealType != val . Type {
fmt . Printf ( "Real type: %s\n" , val . RealType )
}
if val . Kind == reflect . Interface && len ( val . Children ) > 0 {
fmt . Printf ( "Concrete type: %s\n" , val . Children [ 0 ] . Type )
}
if t . conf . ShowLocationExpr && val . LocationExpr != "" {
fmt . Printf ( "location: %s\n" , val . LocationExpr )
}
return nil
}
2017-03-19 22:45:54 +00:00
func setVar ( t * Term , ctx callContext , args string ) error {
// HACK: in go '=' is not an operator, we detect the error and try to recover from it by splitting the input string
_ , err := parser . ParseExpr ( args )
if err == nil {
return fmt . Errorf ( "syntax error '=' not found" )
}
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 ( ctx . Scope , lexpr , rexpr )
}
func printFilteredVariables ( varType string , vars [ ] api . Variable , filter string , cfg api . LoadConfig ) error {
reg , err := regexp . Compile ( filter )
if err != nil {
return err
}
match := false
for _ , v := range vars {
if reg == nil || reg . Match ( [ ] byte ( v . Name ) ) {
match = true
2018-10-13 13:45:53 +00:00
name := v . Name
if v . Flags & api . VariableShadowed != 0 {
name = "(" + name + ")"
}
2017-03-19 22:45:54 +00:00
if cfg == ShortLoadConfig {
2018-10-13 13:45:53 +00:00
fmt . Printf ( "%s = %s\n" , name , v . SinglelineString ( ) )
2017-03-19 22:45:54 +00:00
} else {
2018-10-13 13:45:53 +00:00
fmt . Printf ( "%s = %s\n" , name , v . MultilineString ( "" ) )
2017-03-19 22:45:54 +00:00
}
}
}
if ! match {
fmt . Printf ( "(no %s)\n" , varType )
}
return nil
}
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 ) )
}
func types ( t * Term , ctx callContext , args string ) error {
return printSortedStrings ( t . client . ListTypes ( args ) )
}
2018-10-13 13:45:53 +00:00
func parseVarArguments ( args string , t * Term ) ( filter string , cfg api . LoadConfig ) {
2017-03-19 22:45:54 +00:00
if v := strings . SplitN ( args , " " , 2 ) ; len ( v ) >= 1 && v [ 0 ] == "-v" {
if len ( v ) == 2 {
2018-10-13 13:45:53 +00:00
return v [ 1 ] , t . loadConfig ( )
2017-03-19 22:45:54 +00:00
} else {
2018-10-13 13:45:53 +00:00
return "" , t . loadConfig ( )
2017-03-19 22:45:54 +00:00
}
}
return args , ShortLoadConfig
}
func args ( t * Term , ctx callContext , args string ) error {
2018-10-13 13:45:53 +00:00
filter , cfg := parseVarArguments ( args , t )
2017-03-19 22:45:54 +00:00
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 )
if err != nil {
return err
}
return printFilteredVariables ( "args" , vars , filter , cfg )
}
func locals ( t * Term , ctx callContext , args string ) error {
2018-10-13 13:45:53 +00:00
filter , cfg := parseVarArguments ( args , t )
2017-03-19 22:45:54 +00:00
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 )
if err != nil {
return err
}
return printFilteredVariables ( "locals" , locals , filter , cfg )
}
func vars ( t * Term , ctx callContext , args string ) error {
2018-10-13 13:45:53 +00:00
filter , cfg := parseVarArguments ( args , t )
2017-03-19 22:45:54 +00:00
vars , err := t . client . ListPackageVariables ( filter , cfg )
if err != nil {
return err
}
return printFilteredVariables ( "vars" , vars , filter , cfg )
}
func regs ( t * Term , ctx callContext , args string ) error {
includeFp := false
if args == "-a" {
includeFp = true
}
regs , err := t . client . ListRegisters ( 0 , includeFp )
if err != nil {
return err
}
fmt . Println ( regs )
return nil
}
func stackCommand ( t * Term , ctx callContext , args string ) error {
2018-10-13 13:45:53 +00:00
sa , err := parseStackArgs ( args )
2017-03-19 22:45:54 +00:00
if err != nil {
return err
}
if ctx . Prefix == onPrefix {
2018-10-13 13:45:53 +00:00
ctx . Breakpoint . Stacktrace = sa . depth
2017-03-19 22:45:54 +00:00
return nil
}
var cfg * api . LoadConfig
2018-10-13 13:45:53 +00:00
if sa . full {
2017-03-19 22:45:54 +00:00
cfg = & ShortLoadConfig
}
2018-10-13 13:45:53 +00:00
stack , err := t . client . Stacktrace ( ctx . Scope . GoroutineID , sa . depth , sa . readDefers , cfg )
2017-03-19 22:45:54 +00:00
if err != nil {
return err
}
2018-10-13 13:45:53 +00:00
printStack ( stack , "" , sa . offsets )
2017-03-19 22:45:54 +00:00
return nil
}
2018-10-13 13:45:53 +00:00
type stackArgs struct {
depth int
full bool
offsets bool
readDefers bool
}
func parseStackArgs ( argstr string ) ( stackArgs , error ) {
r := stackArgs {
depth : 50 ,
full : false ,
}
2017-03-19 22:45:54 +00:00
if argstr != "" {
args := strings . Split ( argstr , " " )
for i := range args {
2018-10-13 13:45:53 +00:00
switch args [ i ] {
case "-full" :
r . full = true
case "-offsets" :
r . offsets = true
case "-defer" :
r . readDefers = true
default :
2017-03-19 22:45:54 +00:00
n , err := strconv . Atoi ( args [ i ] )
if err != nil {
2018-10-13 13:45:53 +00:00
return stackArgs { } , fmt . Errorf ( "depth must be a number" )
2017-03-19 22:45:54 +00:00
}
2018-10-13 13:45:53 +00:00
r . depth = n
2017-03-19 22:45:54 +00:00
}
}
}
2018-10-13 13:45:53 +00:00
return r , nil
2017-03-19 22:45:54 +00:00
}
2018-10-13 13:45:53 +00:00
// getLocation returns the current location or the locations specified by the argument.
// getLocation is used to process the argument of list and edit commands.
func getLocation ( t * Term , ctx callContext , args string , showContext bool ) ( file string , lineno int , showarrow bool , err error ) {
switch {
case len ( args ) == 0 && ! ctx . scoped ( ) :
state , err := t . client . GetState ( )
2017-03-19 22:45:54 +00:00
if err != nil {
2018-10-13 13:45:53 +00:00
return "" , 0 , false , err
}
if showContext {
printcontext ( t , state )
}
if state . SelectedGoroutine != nil {
return state . SelectedGoroutine . CurrentLoc . File , state . SelectedGoroutine . CurrentLoc . Line , true , nil
}
return state . CurrentThread . File , state . CurrentThread . Line , true , nil
case len ( args ) == 0 && ctx . scoped ( ) :
locs , err := t . client . Stacktrace ( ctx . Scope . GoroutineID , ctx . Scope . Frame , false , nil )
if err != nil {
return "" , 0 , false , err
2017-03-19 22:45:54 +00:00
}
if ctx . Scope . Frame >= len ( locs ) {
2018-10-13 13:45:53 +00:00
return "" , 0 , false , fmt . Errorf ( "Frame %d does not exist in goroutine %d" , ctx . Scope . Frame , ctx . Scope . GoroutineID )
2017-03-19 22:45:54 +00:00
}
loc := locs [ ctx . Scope . Frame ]
2018-10-13 13:45:53 +00:00
gid := ctx . Scope . GoroutineID
if gid < 0 {
state , err := t . client . GetState ( )
if err != nil {
return "" , 0 , false , err
}
if state . SelectedGoroutine != nil {
gid = state . SelectedGoroutine . ID
}
}
if showContext {
fmt . Printf ( "Goroutine %d frame %d at %s:%d (PC: %#x)\n" , gid , ctx . Scope . Frame , loc . File , loc . Line , loc . PC )
}
return loc . File , loc . Line , true , nil
2017-03-19 22:45:54 +00:00
2018-10-13 13:45:53 +00:00
default :
locs , err := t . client . FindLocation ( ctx . Scope , args )
2017-03-19 22:45:54 +00:00
if err != nil {
2018-10-13 13:45:53 +00:00
return "" , 0 , false , err
2017-03-19 22:45:54 +00:00
}
2018-10-13 13:45:53 +00:00
if len ( locs ) > 1 {
return "" , 0 , false , debugger . AmbiguousLocationError { Location : args , CandidatesLocation : locs }
}
loc := locs [ 0 ]
if showContext {
fmt . Printf ( "Showing %s:%d (PC: %#x)\n" , loc . File , loc . Line , loc . PC )
}
return loc . File , loc . Line , false , nil
2017-03-19 22:45:54 +00:00
}
2018-10-13 13:45:53 +00:00
}
2017-03-19 22:45:54 +00:00
2018-10-13 13:45:53 +00:00
func listCommand ( t * Term , ctx callContext , args string ) error {
file , lineno , showarrow , err := getLocation ( t , ctx , args , true )
2017-03-19 22:45:54 +00:00
if err != nil {
return err
}
2018-10-13 13:45:53 +00:00
return printfile ( t , file , lineno , showarrow )
2017-03-19 22:45:54 +00:00
}
func ( c * Commands ) sourceCommand ( t * Term , ctx callContext , args string ) error {
if len ( args ) == 0 {
return fmt . Errorf ( "wrong number of arguments: source <filename>" )
}
return c . executeFile ( t , args )
}
var disasmUsageError = errors . New ( "wrong number of arguments: disassemble [-a <start> <end>] [-l <locspec>]" )
func disassCommand ( t * Term , ctx callContext , args string ) error {
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 "" :
locs , err := t . client . FindLocation ( ctx . Scope , "+0" )
if err != nil {
return err
}
disasm , disasmErr = t . client . DisassemblePC ( ctx . Scope , locs [ 0 ] . PC , api . IntelFlavour )
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 ] )
}
disasm , disasmErr = t . client . DisassembleRange ( ctx . Scope , uint64 ( startpc ) , uint64 ( endpc ) , api . IntelFlavour )
case "-l" :
locs , err := t . client . FindLocation ( ctx . Scope , rest )
if err != nil {
return err
}
if len ( locs ) != 1 {
return errors . New ( "expression specifies multiple locations" )
}
disasm , disasmErr = t . client . DisassemblePC ( ctx . Scope , locs [ 0 ] . PC , api . IntelFlavour )
default :
return disasmUsageError
}
if disasmErr != nil {
return disasmErr
}
DisasmPrint ( disasm , os . Stdout )
return nil
}
func digits ( n int ) int {
if n <= 0 {
return 1
}
return int ( math . Floor ( math . Log10 ( float64 ( n ) ) ) ) + 1
}
2018-10-13 13:45:53 +00:00
const stacktraceTruncatedMessage = "(truncated)"
func printStack ( stack [ ] api . Stackframe , ind string , offsets bool ) {
2017-03-19 22:45:54 +00:00
if len ( stack ) == 0 {
return
}
2018-10-13 13:45:53 +00:00
extranl := offsets
for i := range stack {
if extranl {
break
}
extranl = extranl || ( len ( stack [ i ] . Defers ) > 0 ) || ( len ( stack [ i ] . Arguments ) > 0 ) || ( len ( stack [ i ] . Locals ) > 0 )
}
2017-03-19 22:45:54 +00:00
d := digits ( len ( stack ) - 1 )
fmtstr := "%s%" + strconv . Itoa ( d ) + "d 0x%016x in %s\n"
s := ind + strings . Repeat ( " " , d + 2 + len ( ind ) )
for i := range stack {
2018-10-13 13:45:53 +00:00
if stack [ i ] . Err != "" {
fmt . Printf ( "%serror: %s\n" , s , stack [ i ] . Err )
continue
2017-03-19 22:45:54 +00:00
}
2018-10-13 13:45:53 +00:00
fmt . Printf ( fmtstr , ind , i , stack [ i ] . PC , stack [ i ] . Function . Name ( ) )
2017-03-19 22:45:54 +00:00
fmt . Printf ( "%sat %s:%d\n" , s , ShortenFilePath ( stack [ i ] . File ) , stack [ i ] . Line )
2018-10-13 13:45:53 +00:00
if offsets {
fmt . Printf ( "%sframe: %+#x frame pointer %+#x\n" , s , stack [ i ] . FrameOffset , stack [ i ] . FramePointerOffset )
}
for j , d := range stack [ i ] . Defers {
deferHeader := fmt . Sprintf ( "%s defer %d: " , s , j )
s2 := strings . Repeat ( " " , len ( deferHeader ) )
if d . Unreadable != "" {
fmt . Printf ( "%s(unreadable defer: %s)\n" , deferHeader , d . Unreadable )
continue
}
fmt . Printf ( "%s%#016x in %s\n" , deferHeader , d . DeferredLoc . PC , d . DeferredLoc . Function . Name ( ) )
fmt . Printf ( "%sat %s:%d\n" , s2 , d . DeferredLoc . File , d . DeferredLoc . Line )
fmt . Printf ( "%sdeferred by %s at %s:%d\n" , s2 , d . DeferLoc . Function . Name ( ) , d . DeferLoc . File , d . DeferLoc . Line )
}
2017-03-19 22:45:54 +00:00
for j := range stack [ i ] . Arguments {
fmt . Printf ( "%s %s = %s\n" , s , stack [ i ] . Arguments [ j ] . Name , stack [ i ] . Arguments [ j ] . SinglelineString ( ) )
}
for j := range stack [ i ] . Locals {
fmt . Printf ( "%s %s = %s\n" , s , stack [ i ] . Locals [ j ] . Name , stack [ i ] . Locals [ j ] . SinglelineString ( ) )
}
2018-10-13 13:45:53 +00:00
if extranl {
fmt . Println ( )
}
}
if len ( stack ) > 0 && ! stack [ len ( stack ) - 1 ] . Bottom {
fmt . Printf ( "%s" + stacktraceTruncatedMessage + "\n" , ind )
2017-03-19 22:45:54 +00:00
}
}
2018-10-13 13:45:53 +00:00
func printcontext ( t * Term , state * api . DebuggerState ) {
2017-03-19 22:45:54 +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 ] )
}
}
if state . CurrentThread == nil {
fmt . Println ( "No current thread available" )
2018-10-13 13:45:53 +00:00
return
2017-03-19 22:45:54 +00:00
}
2018-10-13 13:45:53 +00:00
var th * api . Thread
if state . SelectedGoroutine == nil {
th = state . CurrentThread
} else {
for i := range state . Threads {
if state . Threads [ i ] . ID == state . SelectedGoroutine . ThreadID {
th = state . Threads [ i ]
break
}
}
if th == nil {
printcontextLocation ( state . SelectedGoroutine . CurrentLoc )
return
}
}
if th . File == "" {
2017-03-19 22:45:54 +00:00
fmt . Printf ( "Stopped at: 0x%x\n" , state . CurrentThread . PC )
t . Println ( "=>" , "no source available" )
2018-10-13 13:45:53 +00:00
return
2017-03-19 22:45:54 +00:00
}
2018-10-13 13:45:53 +00:00
printcontextThread ( t , th )
2017-03-19 22:45:54 +00:00
2018-10-13 13:45:53 +00:00
if state . When != "" {
fmt . Println ( state . When )
}
}
func printcontextLocation ( loc api . Location ) {
fmt . Printf ( "> %s() %s:%d (PC: %#v)\n" , loc . Function . Name ( ) , ShortenFilePath ( loc . File ) , loc . Line , loc . PC )
if loc . Function != nil && loc . Function . Optimized {
fmt . Println ( optimizedFunctionWarning )
}
return
}
func printReturnValues ( th * api . Thread ) {
if th . ReturnValues == nil {
return
}
fmt . Println ( "Values returned:" )
for _ , v := range th . ReturnValues {
fmt . Printf ( "\t%s: %s\n" , v . Name , v . MultilineString ( "\t" ) )
}
fmt . Println ( )
2017-03-19 22:45:54 +00:00
}
func printcontextThread ( t * Term , th * api . Thread ) {
fn := th . Function
if th . Breakpoint == nil {
2018-10-13 13:45:53 +00:00
printcontextLocation ( api . Location { PC : th . PC , File : th . File , Line : th . Line , Function : th . Function } )
printReturnValues ( th )
2017-03-19 22:45:54 +00:00
return
}
args := ""
if th . BreakpointInfo != nil && th . Breakpoint . LoadArgs != nil && * th . Breakpoint . LoadArgs == ShortLoadConfig {
var arg [ ] string
for _ , ar := range th . BreakpointInfo . Arguments {
arg = append ( arg , ar . SinglelineString ( ) )
}
args = strings . Join ( arg , ", " )
}
bpname := ""
if th . Breakpoint . Name != "" {
bpname = fmt . Sprintf ( "[%s] " , th . Breakpoint . Name )
}
if hitCount , ok := th . Breakpoint . HitCount [ strconv . Itoa ( th . GoroutineID ) ] ; ok {
fmt . Printf ( "> %s%s(%s) %s:%d (hits goroutine(%d):%d total:%d) (PC: %#v)\n" ,
bpname ,
2018-10-13 13:45:53 +00:00
fn . Name ( ) ,
2017-03-19 22:45:54 +00:00
args ,
ShortenFilePath ( th . File ) ,
th . Line ,
th . GoroutineID ,
hitCount ,
th . Breakpoint . TotalHitCount ,
th . PC )
} else {
fmt . Printf ( "> %s%s(%s) %s:%d (hits total:%d) (PC: %#v)\n" ,
bpname ,
2018-10-13 13:45:53 +00:00
fn . Name ( ) ,
2017-03-19 22:45:54 +00:00
args ,
ShortenFilePath ( th . File ) ,
th . Line ,
th . Breakpoint . TotalHitCount ,
th . PC )
}
2018-10-13 13:45:53 +00:00
if th . Function != nil && th . Function . Optimized {
fmt . Println ( optimizedFunctionWarning )
}
printReturnValues ( th )
2017-03-19 22:45:54 +00:00
if th . BreakpointInfo != nil {
bp := th . Breakpoint
bpi := th . BreakpointInfo
if bpi . Goroutine != nil {
writeGoroutineLong ( os . Stdout , bpi . Goroutine , "\t" )
}
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" ) )
}
}
if bpi . Stacktrace != nil {
fmt . Printf ( "\tStack:\n" )
2018-10-13 13:45:53 +00:00
printStack ( bpi . Stacktrace , "\t\t" , false )
2017-03-19 22:45:54 +00:00
}
}
}
func printfile ( t * Term , filename string , line int , showArrow bool ) error {
2018-10-13 13:45:53 +00:00
if filename == "" {
return nil
}
2017-03-19 22:45:54 +00:00
file , err := os . Open ( t . substitutePath ( filename ) )
if err != nil {
return err
}
defer file . Close ( )
fi , _ := file . Stat ( )
lastModExe := t . client . LastModified ( )
if fi . ModTime ( ) . After ( lastModExe ) {
fmt . Println ( "Warning: listing may not match stale executable" )
}
buf := bufio . NewScanner ( file )
l := line
for i := 1 ; i < l - 5 ; i ++ {
if ! buf . Scan ( ) {
return nil
}
}
s := l - 5
if s < 1 {
s = 1
}
for i := s ; i <= l + 5 ; i ++ {
if ! buf . Scan ( ) {
return nil
}
var prefix string
if showArrow {
prefix = " "
if i == l {
prefix = "=>"
}
}
prefix = fmt . Sprintf ( "%s%4d:\t" , prefix , i )
t . Println ( prefix , buf . Text ( ) )
}
return nil
}
// ExitRequestError is returned when the user
// exits Delve.
type ExitRequestError struct { }
func ( ere ExitRequestError ) Error ( ) string {
return ""
}
func exitCommand ( t * Term , ctx callContext , args string ) error {
2018-10-13 13:45:53 +00:00
if args == "-c" {
if ! t . client . IsMulticlient ( ) {
return errors . New ( "not connected to an --accept-multiclient server" )
}
t . quitContinue = true
}
2017-03-19 22:45:54 +00:00
return ExitRequestError { }
}
func getBreakpointByIDOrName ( t * Term , arg string ) ( * api . Breakpoint , error ) {
if id , err := strconv . Atoi ( arg ) ; err == nil {
return t . client . GetBreakpoint ( id )
}
return t . client . GetBreakpointByName ( arg )
}
func ( c * Commands ) onCmd ( t * Term , ctx callContext , argstr string ) error {
2018-10-13 13:45:53 +00:00
args := strings . SplitN ( argstr , " " , 2 )
2017-03-19 22:45:54 +00:00
if len ( args ) < 2 {
return errors . New ( "not enough arguments" )
}
bp , err := getBreakpointByIDOrName ( t , args [ 0 ] )
if err != nil {
return err
}
ctx . Prefix = onPrefix
ctx . Breakpoint = bp
2018-10-13 13:45:53 +00:00
err = c . CallWithContext ( args [ 1 ] , t , ctx )
2017-03-19 22:45:54 +00:00
if err != nil {
return err
}
return t . client . AmendBreakpoint ( ctx . Breakpoint )
}
func conditionCmd ( t * Term , ctx callContext , argstr string ) error {
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 )
}
// ShortenFilePath take a full file path and attempts to shorten
// it by replacing the current directory to './'.
func ShortenFilePath ( fullPath string ) string {
workingDir , _ := os . Getwd ( )
return strings . Replace ( fullPath , workingDir , "." , 1 )
}
func ( c * 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
}
2018-10-13 13:45:53 +00:00
if err := c . Call ( line , t ) ; err != nil {
2017-03-19 22:45:54 +00:00
fmt . Printf ( "%s:%d: %v\n" , name , lineno , err )
}
}
return scanner . Err ( )
}
2018-10-13 13:45:53 +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
}
args = fmt . Sprintf ( "%s() %s:%d (%#x)" , loc . Function . Name ( ) , 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 )
}
2017-03-19 22:45:54 +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 )
}
return fmt . Sprintf ( "%#v for %s:%d" , bp . Addr , p , bp . Line )
}