2016-02-19 18:32:24 +00:00
package cmds
import (
"errors"
"fmt"
"net"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"syscall"
"github.com/derekparker/delve/config"
"github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api"
"github.com/derekparker/delve/service/rpc"
"github.com/derekparker/delve/terminal"
"github.com/derekparker/delve/version"
"github.com/spf13/cobra"
)
var (
// Log is whether to log debug statements.
Log bool
// Headless is whether to run without terminal.
Headless bool
// 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
// 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 .
`
// 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
buildFlagsDefault = "-ldflags=-linkmode internal"
}
// 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-04-08 21:12:38 +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-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." )
// '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 )
} ,
}
RootCommand . AddCommand ( versionCommand )
// 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 )
} ,
}
RootCommand . AddCommand ( runCommand )
// 'debug' subcommand.
debugCommand := & cobra . Command {
Use : "debug [package]" ,
Short : "Compile and begin debugging program." ,
Long : ` Compiles your program with optimizations disabled ,
starts and attaches to it , and enables you to immediately begin debugging your program . ` ,
Run : debugCmd ,
}
RootCommand . AddCommand ( debugCommand )
// 'exec' subcommand.
execCommand := & cobra . Command {
Use : "exec [./path/to/binary]" ,
Short : "Runs precompiled binary, attaches and begins debug session." ,
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 ) {
os . Exit ( execute ( 0 , args , conf ) )
} ,
}
RootCommand . AddCommand ( execCommand )
// 'trace' subcommand.
traceCommand := & cobra . Command {
Use : "trace [package] regexp" ,
Short : "Compile and begin tracing program." ,
Long : "Trace program execution. Will set a tracepoint on every function matching the provided regular expression and output information when tracepoint is hit." ,
Run : traceCmd ,
}
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 )
// 'test' subcommand.
testCommand := & cobra . Command {
Use : "test [package]" ,
Short : "Compile test binary and begin debugging program." ,
Long : ` Compiles a test binary with optimizations disabled, starts and attaches to it, and enable you to immediately begin debugging your program. ` ,
Run : testCmd ,
}
RootCommand . AddCommand ( testCommand )
// 'attach' subcommand.
attachCommand := & cobra . Command {
Use : "attach pid" ,
Short : "Attach to running process and begin debugging." ,
Long : "Attach to running process and begin debugging." ,
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) == 0 {
return errors . New ( "you must provide a PID" )
}
return nil
} ,
Run : attachCmd ,
}
RootCommand . AddCommand ( attachCommand )
// 'connect' subcommand.
connectCommand := & cobra . Command {
Use : "connect addr" ,
Short : "Connect to a headless debug server." ,
Long : "Connect to a headless debug server." ,
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
} ,
Run : connectCmd ,
}
RootCommand . AddCommand ( connectCommand )
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 )
processArgs := append ( [ ] string { "./" + debugname } , targetArgs ... )
return execute ( 0 , processArgs , conf )
} ( )
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
server := rpc . NewServer ( & service . Config {
Listener : listener ,
ProcessArgs : processArgs ,
AttachPid : traceAttachPid ,
} , Log )
if err := server . Run ( ) ; err != nil {
fmt . Fprintln ( os . Stderr , err )
return 1
}
client := rpc . NewClient ( listener . Addr ( ) . String ( ) )
funcs , err := client . ListFunctions ( regexp )
if err != nil {
fmt . Fprintln ( os . Stderr , err )
return 1
}
for i := range funcs {
2016-03-09 15:11:58 +00:00
_ , err = client . CreateBreakpoint ( & api . Breakpoint { FunctionName : funcs [ i ] , Tracepoint : true , Line : - 1 , Stacktrace : traceStackDepth } )
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 ... )
return execute ( 0 , processArgs , conf )
} ( )
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 )
}
os . Exit ( execute ( pid , nil , conf ) )
}
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
client = rpc . NewClient ( addr )
term := terminal . New ( client , conf )
status , err := term . Run ( )
if err != nil {
fmt . Println ( err )
}
return status
}
func execute ( attachPid int , processArgs [ ] string , conf * config . Config ) int {
// 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" )
}
// Create and start a debugger server
server := rpc . NewServer ( & service . Config {
Listener : listener ,
ProcessArgs : processArgs ,
AttachPid : attachPid ,
AcceptMulti : AcceptMulti ,
} , Log )
if err := server . Run ( ) ; err != nil {
fmt . Fprintln ( os . Stderr , err )
return 1
}
var status int
if Headless {
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
client = rpc . NewClient ( listener . Addr ( ) . String ( ) )
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 != "" {
args = append ( args , BuildFlags )
}
args = append ( args , pkg )
return gocommand ( "build" , args ... )
}
func gotestbuild ( pkg string ) error {
args := [ ] string { "-gcflags" , "-N -l" , "-c" , "-o" , testdebugname }
if BuildFlags != "" {
args = append ( args , BuildFlags )
}
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 ( )
}