2016-02-19 18:32:24 +00:00
package cmds
import (
2016-09-25 15:26:59 +00:00
"bytes"
2016-02-19 18:32:24 +00:00
"errors"
"fmt"
"net"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"syscall"
2016-09-25 15:26:59 +00:00
"unicode"
2016-02-19 18:32:24 +00:00
2017-02-08 16:00:44 +00:00
"github.com/derekparker/delve/pkg/config"
"github.com/derekparker/delve/pkg/terminal"
"github.com/derekparker/delve/pkg/version"
2016-02-19 18:32:24 +00:00
"github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api"
2016-04-18 19:20:20 +00:00
"github.com/derekparker/delve/service/rpc2"
2016-06-19 06:43:29 +00:00
"github.com/derekparker/delve/service/rpccommon"
2016-02-19 18:32:24 +00:00
"github.com/spf13/cobra"
)
var (
// Log is whether to log debug statements.
Log bool
// Headless is whether to run without terminal.
Headless bool
2016-06-19 06:43:29 +00:00
// APIVersion is the requested API version while running headless
APIVersion int
2016-02-19 18:32:24 +00:00
// AcceptMulti allows multiple clients to connect to the same server
AcceptMulti bool
// Addr is the debugging server listen address.
Addr string
// InitFile is the path to initialization file.
InitFile string
// BuildFlags is the flags passed during compiler invocation.
BuildFlags string
2016-11-01 19:58:43 +00:00
// WorkingDir is the working directory for running the program.
WorkingDir string
2016-02-19 18:32:24 +00:00
// RootCommand is the root of the command tree.
RootCommand * cobra . Command
traceAttachPid int
traceStackDepth int
conf * config . Config
)
const (
debugname = "debug"
testdebugname = "debug.test"
)
const dlvCommandLongDesc = ` Delve is a source level debugger for Go programs .
Delve enables you to interact with your program by controlling the execution of the process ,
evaluating variables , and providing information of thread / goroutine state , CPU register state and more .
The goal of this tool is to provide a simple yet powerful interface for debugging Go programs .
2017-02-16 19:05:48 +00:00
Pass flags to the program you are debugging using ` + " ` -- ` " + ` , for example :
` + " ` dlv exec . / hello -- server -- config conf / config . toml ` "
2016-02-19 18:32:24 +00:00
// New returns an initialized command tree.
func New ( ) * cobra . Command {
// Config setup and load.
conf = config . LoadConfig ( )
buildFlagsDefault := ""
if runtime . GOOS == "windows" {
// Work-around for https://github.com/golang/go/issues/13154
2016-09-25 15:26:59 +00:00
buildFlagsDefault = "-ldflags='-linkmode internal'"
2016-02-19 18:32:24 +00:00
}
// Main dlv root command.
RootCommand = & cobra . Command {
Use : "dlv" ,
Short : "Delve is a debugger for the Go programming language." ,
Long : dlvCommandLongDesc ,
}
RootCommand . PersistentFlags ( ) . StringVarP ( & Addr , "listen" , "l" , "localhost:0" , "Debugging server listen address." )
RootCommand . PersistentFlags ( ) . BoolVarP ( & Log , "log" , "" , false , "Enable debugging server logging." )
RootCommand . PersistentFlags ( ) . BoolVarP ( & Headless , "headless" , "" , false , "Run debug server only, in headless mode." )
2016-05-25 19:13:16 +00:00
RootCommand . PersistentFlags ( ) . BoolVarP ( & AcceptMulti , "accept-multiclient" , "" , false , "Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate." )
2016-06-19 06:43:29 +00:00
RootCommand . PersistentFlags ( ) . IntVar ( & APIVersion , "api-version" , 1 , "Selects API version when headless." )
2016-02-19 18:32:24 +00:00
RootCommand . PersistentFlags ( ) . StringVar ( & InitFile , "init" , "" , "Init file, executed by the terminal client." )
RootCommand . PersistentFlags ( ) . StringVar ( & BuildFlags , "build-flags" , buildFlagsDefault , "Build flags, to be passed to the compiler." )
2016-11-01 19:58:43 +00:00
RootCommand . PersistentFlags ( ) . StringVar ( & WorkingDir , "wd" , "." , "Working directory for running the program." )
2016-02-19 18:32:24 +00:00
2016-05-20 17:22:10 +00:00
// 'attach' subcommand.
attachCommand := & cobra . Command {
Use : "attach pid" ,
Short : "Attach to running process and begin debugging." ,
2016-05-20 17:51:23 +00:00
Long : ` Attach to an already running process and begin debugging it .
This command will cause Delve to take control of an already running process , and
begin a new debug session . When exiting the debug session you will have the
option to let the process continue or kill it .
` ,
2016-05-20 17:22:10 +00:00
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) == 0 {
return errors . New ( "you must provide a PID" )
}
return nil
2016-02-19 18:32:24 +00:00
} ,
2016-05-20 17:22:10 +00:00
Run : attachCmd ,
2016-02-19 18:32:24 +00:00
}
2016-05-20 17:22:10 +00:00
RootCommand . AddCommand ( attachCommand )
2016-02-19 18:32:24 +00:00
2016-05-20 17:22:10 +00:00
// 'connect' subcommand.
connectCommand := & cobra . Command {
Use : "connect addr" ,
Short : "Connect to a headless debug server." ,
2016-05-20 17:51:23 +00:00
Long : "Connect to a running headless debug server." ,
2016-05-20 17:22:10 +00:00
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) == 0 {
return errors . New ( "you must provide an address as the first argument" )
}
return nil
2016-02-19 18:32:24 +00:00
} ,
2016-05-20 17:22:10 +00:00
Run : connectCmd ,
2016-02-19 18:32:24 +00:00
}
2016-05-20 17:22:10 +00:00
RootCommand . AddCommand ( connectCommand )
2016-02-19 18:32:24 +00:00
// 'debug' subcommand.
debugCommand := & cobra . Command {
Use : "debug [package]" ,
2016-05-25 19:13:16 +00:00
Short : "Compile and begin debugging main package in current directory, or the package specified." ,
2016-05-20 17:51:23 +00:00
Long : ` Compiles your program with optimizations disabled , starts and attaches to it .
By default , with no arguments , Delve will compile the ' main ' package in the
current directory , and begin to debug it . Alternatively you can specify a
package name and Delve will compile that package instead , and begin a new debug
session . ` ,
2016-02-19 18:32:24 +00:00
Run : debugCmd ,
}
RootCommand . AddCommand ( debugCommand )
// 'exec' subcommand.
execCommand := & cobra . Command {
Use : "exec [./path/to/binary]" ,
2016-05-20 17:51:23 +00:00
Short : "Execute a precompiled binary, and begin a debug session." ,
Long : ` Execute a precompiled binary and begin a debug session .
This command will cause Delve to exec the binary and immediately attach to it to
begin a new debug session . Please note that if the binary was not compiled with
optimizations disabled , it may be difficult to properly debug it . Please
consider compiling debugging binaries with - gcflags = "-N -l" . ` ,
2016-02-19 18:32:24 +00:00
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) == 0 {
return errors . New ( "you must provide a path to a binary" )
}
return nil
} ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
2016-04-21 10:19:21 +00:00
os . Exit ( execute ( 0 , args , conf , executingExistingFile ) )
2016-02-19 18:32:24 +00:00
} ,
}
RootCommand . AddCommand ( execCommand )
2016-05-20 17:22:10 +00:00
// Deprecated 'run' subcommand.
runCommand := & cobra . Command {
Use : "run" ,
Short : "Deprecated command. Use 'debug' instead." ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
fmt . Println ( "This command is deprecated, please use 'debug' instead." )
os . Exit ( 0 )
} ,
2016-02-19 18:32:24 +00:00
}
2016-05-20 17:22:10 +00:00
RootCommand . AddCommand ( runCommand )
2016-02-19 18:32:24 +00:00
// 'test' subcommand.
testCommand := & cobra . Command {
Use : "test [package]" ,
Short : "Compile test binary and begin debugging program." ,
2016-05-20 17:51:23 +00:00
Long : ` Compiles a test binary with optimizations disabled and begins a new debug session .
The test command allows you to begin a new debug session in the context of your
unit tests . By default Delve will debug the tests in the current directory .
Alternatively you can specify a package name , and Delve will debug the tests in
that package instead . ` ,
Run : testCmd ,
2016-02-19 18:32:24 +00:00
}
RootCommand . AddCommand ( testCommand )
2016-05-20 17:22:10 +00:00
// 'trace' subcommand.
traceCommand := & cobra . Command {
Use : "trace [package] regexp" ,
Short : "Compile and begin tracing program." ,
2016-05-20 17:51:23 +00:00
Long : ` Trace program execution .
The trace sub command will set a tracepoint on every function matching the
provided regular expression and output information when tracepoint is hit . This
is useful if you do not want to begin an entire debug session , but merely want
to know what functions your process is executing . ` ,
Run : traceCmd ,
2016-02-19 18:32:24 +00:00
}
2016-05-20 17:22:10 +00:00
traceCommand . Flags ( ) . IntVarP ( & traceAttachPid , "pid" , "p" , 0 , "Pid to attach to." )
traceCommand . Flags ( ) . IntVarP ( & traceStackDepth , "stack" , "s" , 0 , "Show stack trace with given depth." )
RootCommand . AddCommand ( traceCommand )
2016-02-19 18:32:24 +00:00
2016-05-20 17:22:10 +00:00
// 'version' subcommand.
versionCommand := & cobra . Command {
Use : "version" ,
Short : "Prints version." ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
fmt . Printf ( "Delve Debugger\n%s\n" , version . DelveVersion )
2016-02-19 18:32:24 +00:00
} ,
}
2016-05-20 17:22:10 +00:00
RootCommand . AddCommand ( versionCommand )
2016-02-19 18:32:24 +00:00
return RootCommand
}
func debugCmd ( cmd * cobra . Command , args [ ] string ) {
status := func ( ) int {
var pkg string
dlvArgs , targetArgs := splitArgs ( cmd , args )
if len ( dlvArgs ) > 0 {
pkg = args [ 0 ]
}
err := gobuild ( debugname , pkg )
if err != nil {
fmt . Fprintf ( os . Stderr , "%v\n" , err )
return 1
}
fp , err := filepath . Abs ( "./" + debugname )
if err != nil {
fmt . Fprintf ( os . Stderr , "%v\n" , err )
return 1
}
defer os . Remove ( fp )
2016-11-01 19:58:43 +00:00
abs , err := filepath . Abs ( debugname )
if err != nil {
fmt . Fprintf ( os . Stderr , "%v\n" , err )
return 1
}
processArgs := append ( [ ] string { abs } , targetArgs ... )
2016-04-21 10:19:21 +00:00
return execute ( 0 , processArgs , conf , executingGeneratedFile )
2016-02-19 18:32:24 +00:00
} ( )
os . Exit ( status )
}
func traceCmd ( cmd * cobra . Command , args [ ] string ) {
status := func ( ) int {
var regexp string
var processArgs [ ] string
dlvArgs , targetArgs := splitArgs ( cmd , args )
if traceAttachPid == 0 {
var pkg string
switch len ( dlvArgs ) {
case 1 :
regexp = args [ 0 ]
case 2 :
pkg = args [ 0 ]
regexp = args [ 1 ]
}
if err := gobuild ( debugname , pkg ) ; err != nil {
return 1
}
defer os . Remove ( "./" + debugname )
processArgs = append ( [ ] string { "./" + debugname } , targetArgs ... )
}
// Make a TCP listener
listener , err := net . Listen ( "tcp" , Addr )
if err != nil {
fmt . Printf ( "couldn't start listener: %s\n" , err )
return 1
}
defer listener . Close ( )
// Create and start a debug server
2016-06-19 06:43:29 +00:00
server := rpccommon . NewServer ( & service . Config {
2016-02-19 18:32:24 +00:00
Listener : listener ,
ProcessArgs : processArgs ,
AttachPid : traceAttachPid ,
2016-06-19 06:43:29 +00:00
APIVersion : 2 ,
2016-11-01 19:58:43 +00:00
WorkingDir : WorkingDir ,
2016-02-19 18:32:24 +00:00
} , Log )
if err := server . Run ( ) ; err != nil {
fmt . Fprintln ( os . Stderr , err )
return 1
}
2016-04-18 19:20:20 +00:00
client := rpc2 . NewClient ( listener . Addr ( ) . String ( ) )
2016-02-19 18:32:24 +00:00
funcs , err := client . ListFunctions ( regexp )
if err != nil {
fmt . Fprintln ( os . Stderr , err )
return 1
}
for i := range funcs {
2016-04-24 17:15:39 +00:00
_ , err = client . CreateBreakpoint ( & api . Breakpoint { FunctionName : funcs [ i ] , Tracepoint : true , Line : - 1 , Stacktrace : traceStackDepth , LoadArgs : & terminal . ShortLoadConfig } )
2016-02-19 18:32:24 +00:00
if err != nil {
fmt . Fprintln ( os . Stderr , err )
return 1
}
}
cmds := terminal . DebugCommands ( client )
t := terminal . New ( client , nil )
defer t . Close ( )
2016-02-23 14:12:04 +00:00
err = cmds . Call ( "continue" , "" , t )
2016-02-19 18:32:24 +00:00
if err != nil {
fmt . Fprintln ( os . Stderr , err )
return 1
}
return 0
} ( )
os . Exit ( status )
}
func testCmd ( cmd * cobra . Command , args [ ] string ) {
status := func ( ) int {
var pkg string
dlvArgs , targetArgs := splitArgs ( cmd , args )
if len ( dlvArgs ) > 0 {
pkg = args [ 0 ]
}
err := gotestbuild ( pkg )
if err != nil {
return 1
}
defer os . Remove ( "./" + testdebugname )
processArgs := append ( [ ] string { "./" + testdebugname } , targetArgs ... )
2016-04-21 10:19:21 +00:00
return execute ( 0 , processArgs , conf , executingOther )
2016-02-19 18:32:24 +00:00
} ( )
os . Exit ( status )
}
func attachCmd ( cmd * cobra . Command , args [ ] string ) {
pid , err := strconv . Atoi ( args [ 0 ] )
if err != nil {
fmt . Fprintf ( os . Stderr , "Invalid pid: %s\n" , args [ 0 ] )
os . Exit ( 1 )
}
2016-04-21 10:19:21 +00:00
os . Exit ( execute ( pid , nil , conf , executingOther ) )
2016-02-19 18:32:24 +00:00
}
func connectCmd ( cmd * cobra . Command , args [ ] string ) {
addr := args [ 0 ]
if addr == "" {
fmt . Fprintf ( os . Stderr , "An empty address was provided. You must provide an address as the first argument.\n" )
os . Exit ( 1 )
}
os . Exit ( connect ( addr , conf ) )
}
func splitArgs ( cmd * cobra . Command , args [ ] string ) ( [ ] string , [ ] string ) {
if cmd . ArgsLenAtDash ( ) >= 0 {
return args [ : cmd . ArgsLenAtDash ( ) ] , args [ cmd . ArgsLenAtDash ( ) : ]
}
return args , [ ] string { }
}
func connect ( addr string , conf * config . Config ) int {
// Create and start a terminal - attach to running instance
var client service . Client
2016-04-18 19:20:20 +00:00
client = rpc2 . NewClient ( addr )
2016-02-19 18:32:24 +00:00
term := terminal . New ( client , conf )
status , err := term . Run ( )
if err != nil {
fmt . Println ( err )
}
return status
}
2016-04-21 10:19:21 +00:00
type executeKind int
const (
executingExistingFile = executeKind ( iota )
executingGeneratedFile
executingOther
)
func execute ( attachPid int , processArgs [ ] string , conf * config . Config , kind executeKind ) int {
2016-02-19 18:32:24 +00:00
// Make a TCP listener
listener , err := net . Listen ( "tcp" , Addr )
if err != nil {
fmt . Printf ( "couldn't start listener: %s\n" , err )
return 1
}
defer listener . Close ( )
if Headless && ( InitFile != "" ) {
fmt . Fprintf ( os . Stderr , "Warning: init file ignored\n" )
}
2016-04-18 19:20:20 +00:00
var server interface {
Run ( ) error
Stop ( bool ) error
}
2016-02-19 18:32:24 +00:00
// Create and start a debugger server
2016-06-19 06:43:29 +00:00
switch APIVersion {
case 1 , 2 :
server = rpccommon . NewServer ( & service . Config {
2016-04-18 19:20:20 +00:00
Listener : listener ,
ProcessArgs : processArgs ,
AttachPid : attachPid ,
AcceptMulti : AcceptMulti ,
2016-06-19 06:43:29 +00:00
APIVersion : APIVersion ,
2016-11-01 19:58:43 +00:00
WorkingDir : WorkingDir ,
2016-04-18 19:20:20 +00:00
} , Log )
default :
2017-04-06 22:32:22 +00:00
fmt . Printf ( "Unknown API version: %d\n" , APIVersion )
2016-04-18 19:20:20 +00:00
return 1
}
2016-02-19 18:32:24 +00:00
if err := server . Run ( ) ; err != nil {
2016-04-21 10:19:21 +00:00
if err == api . NotExecutableErr {
switch kind {
case executingGeneratedFile :
fmt . Fprintln ( os . Stderr , "Can not debug non-main package" )
return 1
case executingExistingFile :
fmt . Fprintf ( os . Stderr , "%s is not executable\n" , processArgs [ 0 ] )
return 1
default :
// fallthrough
}
}
2016-02-19 18:32:24 +00:00
fmt . Fprintln ( os . Stderr , err )
return 1
}
var status int
if Headless {
2016-04-24 17:27:02 +00:00
// Print listener address
fmt . Printf ( "API server listening at: %s\n" , listener . Addr ( ) )
2016-02-19 18:32:24 +00:00
ch := make ( chan os . Signal )
signal . Notify ( ch , syscall . SIGINT )
<- ch
err = server . Stop ( true )
} else {
// Create and start a terminal
var client service . Client
2016-04-18 19:20:20 +00:00
client = rpc2 . NewClient ( listener . Addr ( ) . String ( ) )
2016-02-19 18:32:24 +00:00
term := terminal . New ( client , conf )
term . InitFile = InitFile
status , err = term . Run ( )
}
if err != nil {
fmt . Println ( err )
}
return status
}
func gobuild ( debugname , pkg string ) error {
args := [ ] string { "-gcflags" , "-N -l" , "-o" , debugname }
if BuildFlags != "" {
2016-09-25 15:26:59 +00:00
args = append ( args , splitQuotedFields ( BuildFlags ) ... )
2016-02-19 18:32:24 +00:00
}
args = append ( args , pkg )
return gocommand ( "build" , args ... )
}
func gotestbuild ( pkg string ) error {
args := [ ] string { "-gcflags" , "-N -l" , "-c" , "-o" , testdebugname }
if BuildFlags != "" {
2016-09-25 15:26:59 +00:00
args = append ( args , splitQuotedFields ( BuildFlags ) ... )
2016-02-19 18:32:24 +00:00
}
args = append ( args , pkg )
return gocommand ( "test" , args ... )
}
func gocommand ( command string , args ... string ) error {
allargs := [ ] string { command }
allargs = append ( allargs , args ... )
goBuild := exec . Command ( "go" , allargs ... )
goBuild . Stderr = os . Stderr
return goBuild . Run ( )
}
2016-09-25 15:26:59 +00:00
// Like strings.Fields but ignores spaces inside areas surrounded
// by single quotes.
// To specify a single quote use backslash to escape it: '\''
func splitQuotedFields ( in string ) [ ] string {
type stateEnum int
const (
inSpace stateEnum = iota
inField
inQuote
inQuoteEscaped
)
state := inSpace
r := [ ] string { }
var buf bytes . Buffer
for _ , ch := range in {
switch state {
case inSpace :
if ch == '\'' {
state = inQuote
} else if ! unicode . IsSpace ( ch ) {
buf . WriteRune ( ch )
state = inField
}
case inField :
if ch == '\'' {
state = inQuote
} else if unicode . IsSpace ( ch ) {
r = append ( r , buf . String ( ) )
buf . Reset ( )
} else {
buf . WriteRune ( ch )
}
case inQuote :
if ch == '\'' {
state = inField
} else if ch == '\\' {
state = inQuoteEscaped
} else {
buf . WriteRune ( ch )
}
case inQuoteEscaped :
buf . WriteRune ( ch )
state = inQuote
}
}
if buf . Len ( ) != 0 {
r = append ( r , buf . String ( ) )
}
return r
}