2014-05-20 21:29:01 +00:00
package main
import (
2015-10-09 08:45:37 +00:00
"errors"
2014-05-20 21:29:01 +00:00
"fmt"
2015-03-20 21:11:11 +00:00
"net"
2014-05-20 21:29:01 +00:00
"os"
2015-03-20 21:11:11 +00:00
"os/exec"
2015-04-17 20:49:49 +00:00
"os/signal"
2015-03-20 21:11:11 +00:00
"path/filepath"
2016-01-26 19:11:55 +00:00
"runtime"
2015-03-20 21:11:11 +00:00
"strconv"
2016-01-15 05:26:54 +00:00
"syscall"
2015-05-08 22:26:09 +00:00
2015-08-17 03:41:25 +00:00
"github.com/derekparker/delve/config"
2015-06-21 03:47:44 +00:00
"github.com/derekparker/delve/service"
2015-07-12 20:18:14 +00:00
"github.com/derekparker/delve/service/api"
2015-06-21 03:47:44 +00:00
"github.com/derekparker/delve/service/rpc"
2015-03-20 21:11:11 +00:00
"github.com/derekparker/delve/terminal"
2015-08-17 03:41:25 +00:00
2015-07-11 14:28:08 +00:00
"github.com/spf13/cobra"
2014-05-20 21:29:01 +00:00
)
2016-02-16 04:45:06 +00:00
const (
version = "0.11.0-alpha"
debugname = "debug"
testdebugname = "debug.test"
)
2015-12-13 21:51:11 +00:00
2015-07-11 14:28:08 +00:00
var (
2016-01-10 08:57:52 +00:00
// Log is whether to log debug statements.
Log bool
// Headless is whether to run without terminal.
Headless bool
2016-02-01 10:05:26 +00:00
// Allows multiple clients to connect to the same server
AcceptMulti bool
2016-01-10 08:57:52 +00:00
// 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.
2015-10-18 17:41:34 +00:00
BuildFlags string
2016-02-16 04:45:06 +00:00
// Build is the current git hash.
Build string
2014-05-23 20:27:08 +00:00
2016-01-31 23:30:40 +00:00
traceAttachPid int
traceStackDepth int
2015-08-17 03:41:25 +00:00
2016-01-31 23:30:40 +00:00
conf * config . Config
rootCommand * cobra . Command
)
const dlvCommandLongDesc = ` Delve is a source level debugger for Go programs .
2015-07-11 15:31:15 +00:00
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 .
2016-01-31 23:30:40 +00:00
`
2016-01-26 19:11:55 +00:00
2016-01-31 23:30:40 +00:00
func init ( ) {
2016-01-15 05:26:54 +00:00
buildFlagsDefault := ""
if runtime . GOOS == "windows" {
2016-01-26 19:11:55 +00:00
// Work-around for https://github.com/golang/go/issues/13154
2016-01-15 05:26:54 +00:00
buildFlagsDefault = "-ldflags=-linkmode internal"
}
2016-01-26 19:11:55 +00:00
2016-01-31 23:30:40 +00:00
// Main dlv root command.
rootCommand = & cobra . Command {
Use : "dlv" ,
Short : "Delve is a debugger for the Go programming language." ,
Long : dlvCommandLongDesc ,
}
2015-07-20 07:45:35 +00:00
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-02-01 10:05:26 +00:00
rootCommand . PersistentFlags ( ) . BoolVarP ( & AcceptMulti , "accept-multiclient" , "" , false , "Allows a headless server to accept multiple client connection. Note that the server API is not reentrant and clients will have to coordinate" )
2015-10-18 17:41:34 +00:00
rootCommand . PersistentFlags ( ) . StringVar ( & InitFile , "init" , "" , "Init file, executed by the terminal client." )
2016-01-15 05:26:54 +00:00
rootCommand . PersistentFlags ( ) . StringVar ( & BuildFlags , "build-flags" , buildFlagsDefault , "Build flags, to be passed to the compiler." )
2015-07-11 14:28:08 +00:00
// 'version' subcommand.
versionCommand := & cobra . Command {
Use : "version" ,
Short : "Prints version." ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
2015-12-13 21:51:11 +00:00
fmt . Printf ( "Delve Debugger\nVersion: %s\nBuild: %s\n" , version , Build )
2015-07-11 14:28:08 +00:00
} ,
2014-11-10 12:53:33 +00:00
}
2015-07-11 14:28:08 +00:00
rootCommand . AddCommand ( versionCommand )
2015-08-14 19:19:09 +00:00
// Deprecated 'run' subcommand.
2015-07-11 14:28:08 +00:00
runCommand := & cobra . Command {
Use : "run" ,
2015-08-14 19:19:09 +00:00
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 {
2016-02-16 04:45:06 +00:00
Use : "debug [package]" ,
2015-07-11 14:28:08 +00:00
Short : "Compile and begin debugging program." ,
2015-08-10 21:25:57 +00:00
Long : ` Compiles your program with optimizations disabled ,
2015-07-11 14:34:08 +00:00
starts and attaches to it , and enables you to immediately begin debugging your program . ` ,
2016-01-31 23:30:40 +00:00
Run : debugCmd ,
2015-04-25 14:49:12 +00:00
}
2015-08-14 19:19:09 +00:00
rootCommand . AddCommand ( debugCommand )
2015-07-11 14:28:08 +00:00
2015-08-19 23:01:47 +00:00
// 'exec' subcommand.
execCommand := & cobra . Command {
Use : "exec [./path/to/binary]" ,
Short : "Runs precompiled binary, attaches and begins debug session." ,
2015-10-09 08:45:37 +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
} ,
2015-08-19 23:01:47 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
2015-08-17 03:41:25 +00:00
os . Exit ( execute ( 0 , args , conf ) )
2015-08-19 23:01:47 +00:00
} ,
}
rootCommand . AddCommand ( execCommand )
2015-07-12 20:18:14 +00:00
// 'trace' subcommand.
traceCommand := & cobra . Command {
2016-02-16 04:45:06 +00:00
Use : "trace [package] regexp" ,
2015-07-12 20:18:14 +00:00
Short : "Compile and begin tracing program." ,
2016-02-16 04:45:06 +00:00
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 ,
2015-07-12 20:18:14 +00:00
}
2015-07-13 21:34:51 +00:00
traceCommand . Flags ( ) . IntVarP ( & traceAttachPid , "pid" , "p" , 0 , "Pid to attach to." )
2016-01-28 07:47:04 +00:00
traceCommand . Flags ( ) . IntVarP ( & traceStackDepth , "stack" , "s" , 0 , "Show stack trace with given depth." )
2015-07-12 20:18:14 +00:00
rootCommand . AddCommand ( traceCommand )
2015-07-11 14:28:08 +00:00
// 'test' subcommand.
testCommand := & cobra . Command {
2016-02-16 04:45:06 +00:00
Use : "test [package]" ,
2015-07-11 14:28:08 +00:00
Short : "Compile test binary and begin debugging program." ,
2016-01-31 23:30:40 +00:00
Long : ` Compiles a test binary with optimizations disabled, starts and attaches to it, and enable you to immediately begin debugging your program. ` ,
Run : testCmd ,
2015-07-11 14:28:08 +00:00
}
rootCommand . AddCommand ( testCommand )
// 'attach' subcommand.
attachCommand := & cobra . Command {
2016-02-16 04:45:06 +00:00
Use : "attach pid" ,
2015-07-11 14:28:08 +00:00
Short : "Attach to running process and begin debugging." ,
Long : "Attach to running process and begin debugging." ,
2015-10-09 08:45:37 +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-01-31 23:30:40 +00:00
Run : attachCmd ,
2015-07-11 14:28:08 +00:00
}
rootCommand . AddCommand ( attachCommand )
2015-04-25 14:49:12 +00:00
2015-08-10 21:25:57 +00:00
// 'connect' subcommand.
connectCommand := & cobra . Command {
2016-02-16 04:45:06 +00:00
Use : "connect addr" ,
2015-08-10 21:25:57 +00:00
Short : "Connect to a headless debug server." ,
Long : "Connect to a headless debug server." ,
2016-01-31 23:30:40 +00:00
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
2015-08-10 21:25:57 +00:00
if len ( args ) == 0 {
2016-01-31 23:30:40 +00:00
return errors . New ( "you must provide an address as the first argument" )
2015-08-10 21:25:57 +00:00
}
2016-01-31 23:30:40 +00:00
return nil
2015-08-10 21:25:57 +00:00
} ,
2016-01-31 23:30:40 +00:00
Run : connectCmd ,
2015-08-10 21:25:57 +00:00
}
rootCommand . AddCommand ( connectCommand )
2016-01-31 23:30:40 +00:00
}
func main ( ) {
// Config setup and load.
conf = config . LoadConfig ( )
2015-07-11 14:28:08 +00:00
rootCommand . Execute ( )
2015-06-13 23:16:09 +00:00
}
2016-01-31 23:30:40 +00:00
func debugCmd ( cmd * cobra . Command , args [ ] string ) {
status := func ( ) int {
2016-02-16 04:45:06 +00:00
var pkg string
dlvArgs , targetArgs := splitArgs ( cmd , args )
if len ( dlvArgs ) > 0 {
pkg = args [ 0 ]
}
err := gobuild ( debugname , pkg )
2016-01-31 23:30:40 +00:00
if err != nil {
2016-02-16 04:45:06 +00:00
fmt . Fprintf ( os . Stderr , "%v\n" , err )
2016-01-31 23:30:40 +00:00
return 1
}
fp , err := filepath . Abs ( "./" + debugname )
if err != nil {
2016-02-16 04:45:06 +00:00
fmt . Fprintf ( os . Stderr , "%v\n" , err )
2016-01-31 23:30:40 +00:00
return 1
}
defer os . Remove ( fp )
2016-02-16 04:45:06 +00:00
processArgs := append ( [ ] string { "./" + debugname } , targetArgs ... )
2016-01-31 23:30:40 +00:00
return execute ( 0 , processArgs , conf )
} ( )
os . Exit ( status )
}
func traceCmd ( cmd * cobra . Command , args [ ] string ) {
status := func ( ) int {
2016-02-16 04:45:06 +00:00
var regexp string
2016-01-31 23:30:40 +00:00
var processArgs [ ] string
2016-02-16 04:45:06 +00:00
dlvArgs , targetArgs := splitArgs ( cmd , args )
2016-01-31 23:30:40 +00:00
if traceAttachPid == 0 {
2016-02-16 04:45:06 +00:00
var pkg string
switch len ( dlvArgs ) {
case 1 :
regexp = args [ 0 ]
case 2 :
pkg = args [ 0 ]
regexp = args [ 1 ]
2016-01-31 23:30:40 +00:00
}
2016-02-16 04:45:06 +00:00
if err := gobuild ( debugname , pkg ) ; err != nil {
2016-01-31 23:30:40 +00:00
return 1
}
2016-02-16 04:45:06 +00:00
defer os . Remove ( "./" + debugname )
2016-01-31 23:30:40 +00:00
2016-02-16 04:45:06 +00:00
processArgs = append ( [ ] string { "./" + debugname } , targetArgs ... )
2016-01-31 23:30:40 +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 ( )
// 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 ( ) )
2016-02-16 04:45:06 +00:00
funcs , err := client . ListFunctions ( regexp )
2016-01-31 23:30:40 +00:00
if err != nil {
fmt . Fprintln ( os . Stderr , err )
return 1
}
for i := range funcs {
_ , err := client . CreateBreakpoint ( & api . Breakpoint { FunctionName : funcs [ i ] , Tracepoint : true , Line : - 1 , Stacktrace : traceStackDepth } )
if err != nil {
fmt . Fprintln ( os . Stderr , err )
return 1
}
}
cmds := terminal . DebugCommands ( client )
cmd := cmds . Find ( "continue" )
2016-02-02 11:48:54 +00:00
t := terminal . New ( client , nil )
defer t . Close ( )
err = cmd ( t , "" )
2016-01-31 23:30:40 +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 {
2016-02-16 04:45:06 +00:00
var pkg string
dlvArgs , targetArgs := splitArgs ( cmd , args )
if len ( dlvArgs ) > 0 {
pkg = args [ 0 ]
2016-01-31 23:30:40 +00:00
}
2016-02-16 04:45:06 +00:00
err := gotestbuild ( pkg )
2016-01-31 23:30:40 +00:00
if err != nil {
return 1
}
2016-02-16 04:45:06 +00:00
defer os . Remove ( "./" + testdebugname )
processArgs := append ( [ ] string { "./" + testdebugname } , targetArgs ... )
2016-01-31 23:30:40 +00:00
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 ) )
}
2016-02-16 04:45:06 +00:00
func splitArgs ( cmd * cobra . Command , args [ ] string ) ( [ ] string , [ ] string ) {
if cmd . ArgsLenAtDash ( ) >= 0 {
return args [ : cmd . ArgsLenAtDash ( ) ] , args [ cmd . ArgsLenAtDash ( ) : ]
}
return args , [ ] string { }
}
2015-08-17 03:41:25 +00:00
func connect ( addr string , conf * config . Config ) int {
2015-08-10 21:25:57 +00:00
// Create and start a terminal - attach to running instance
var client service . Client
client = rpc . NewClient ( addr )
2015-08-17 03:41:25 +00:00
term := terminal . New ( client , conf )
2016-01-10 08:57:52 +00:00
status , err := term . Run ( )
2015-08-10 21:25:57 +00:00
if err != nil {
fmt . Println ( err )
}
return status
}
2015-08-17 03:41:25 +00:00
func execute ( attachPid int , processArgs [ ] string , conf * config . Config ) int {
2015-03-20 21:11:11 +00:00
// Make a TCP listener
2015-07-11 14:28:08 +00:00
listener , err := net . Listen ( "tcp" , Addr )
2015-03-20 21:11:11 +00:00
if err != nil {
fmt . Printf ( "couldn't start listener: %s\n" , err )
2015-06-13 23:16:09 +00:00
return 1
2015-03-20 21:11:11 +00:00
}
2015-06-27 04:05:15 +00:00
defer listener . Close ( )
2015-03-20 21:11:11 +00:00
2015-09-29 16:40:12 +00:00
if Headless && ( InitFile != "" ) {
fmt . Fprintf ( os . Stderr , "Warning: init file ignored\n" )
}
2015-06-21 03:47:44 +00:00
// Create and start a debugger server
2015-07-11 19:51:54 +00:00
server := rpc . NewServer ( & service . Config {
2015-06-24 13:08:48 +00:00
Listener : listener ,
ProcessArgs : processArgs ,
AttachPid : attachPid ,
2016-02-01 10:05:26 +00:00
AcceptMulti : AcceptMulti ,
2015-07-11 14:28:08 +00:00
} , Log )
2015-06-27 04:05:15 +00:00
if err := server . Run ( ) ; err != nil {
fmt . Fprintln ( os . Stderr , err )
return 1
}
2015-03-20 21:11:11 +00:00
2015-06-13 23:16:09 +00:00
var status int
2016-01-31 23:30:40 +00:00
if Headless {
ch := make ( chan os . Signal )
signal . Notify ( ch , syscall . SIGINT )
<- ch
err = server . Stop ( true )
} else {
2015-04-17 20:49:49 +00:00
// Create and start a terminal
2015-06-21 03:47:44 +00:00
var client service . Client
2015-06-24 13:08:48 +00:00
client = rpc . NewClient ( listener . Addr ( ) . String ( ) )
2015-08-17 03:41:25 +00:00
term := terminal . New ( client , conf )
2015-09-29 16:40:12 +00:00
term . InitFile = InitFile
2016-01-10 08:57:52 +00:00
status , err = term . Run ( )
2015-04-17 20:49:49 +00:00
}
2015-03-20 21:11:11 +00:00
if err != nil {
fmt . Println ( err )
}
2015-06-13 23:16:09 +00:00
return status
2014-05-20 21:29:01 +00:00
}
2016-01-31 23:30:40 +00:00
2016-02-16 04:45:06 +00:00
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 ... )
2016-01-31 23:30:40 +00:00
}
2016-02-16 04:45:06 +00:00
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 ... )
2016-01-31 23:30:40 +00:00
}
func gocommand ( command string , args ... string ) error {
2016-02-16 04:45:06 +00:00
allargs := [ ] string { command }
2016-01-31 23:30:40 +00:00
allargs = append ( allargs , args ... )
goBuild := exec . Command ( "go" , allargs ... )
goBuild . Stderr = os . Stderr
return goBuild . Run ( )
}