diff --git a/cmd/dlv/main.go b/cmd/dlv/main.go index ce405b02..82bd7417 100644 --- a/cmd/dlv/main.go +++ b/cmd/dlv/main.go @@ -37,31 +37,37 @@ var ( InitFile string // BuildFlags is the flags passed during compiler invocation. BuildFlags string + + traceAttachPid int + traceStackDepth int + + conf *config.Config + + rootCommand *cobra.Command ) -func main() { - // Config setup and load. - conf := config.LoadConfig() - - // Main dlv root command. - rootCommand := &cobra.Command{ - Use: "dlv", - Short: "Delve is a debugger for the Go programming language.", - Long: `Delve is a source level debugger for Go programs. +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. -`, - } +` +func init() { 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.") @@ -95,27 +101,7 @@ The goal of this tool is to provide a simple yet powerful interface for debuggin 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: func(cmd *cobra.Command, args []string) { - status := func() int { - const debugname = "debug" - goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l", BuildFlags) - goBuild.Stderr = os.Stderr - err := goBuild.Run() - if err != nil { - return 1 - } - fp, err := filepath.Abs("./" + debugname) - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - return 1 - } - defer os.Remove(fp) - - processArgs := append([]string{"./" + debugname}, args...) - return execute(0, processArgs, conf) - }() - os.Exit(status) - }, + Run: debugCmd, } rootCommand.AddCommand(debugCommand) @@ -136,79 +122,17 @@ starts and attaches to it, and enables you to immediately begin debugging your p rootCommand.AddCommand(execCommand) // 'trace' subcommand. - var traceAttachPid, traceStackDepth int traceCommand := &cobra.Command{ Use: "trace [regexp]", Short: "Compile and begin tracing program.", Long: "Trace program execution. Will set a tracepoint on every function matching [regexp] and output information when tracepoint is hit.", - Run: func(cmd *cobra.Command, args []string) { - status := func() int { - if len(args) == 0 { - fmt.Fprintln(os.Stderr, "You must provide a function to trace.") - return 1 - } - const debugname = "debug" - var processArgs []string - if traceAttachPid == 0 { - goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l", BuildFlags) - goBuild.Stderr = os.Stderr - err := goBuild.Run() - if err != nil { - return 1 - } - fp, err := filepath.Abs("./" + debugname) - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - return 1 - } - defer os.Remove(fp) - - processArgs = append([]string{"./" + debugname}, args...) - } - // 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 debugger 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 - } - sigChan := make(chan os.Signal) - signal.Notify(sigChan, syscall.SIGINT) - client := rpc.NewClient(listener.Addr().String()) - funcs, err := client.ListFunctions(args[0]) - if err != nil { - fmt.Fprintln(os.Stderr, err) - return 1 - } - for i := range funcs { - _, err := client.CreateBreakpoint(&api.Breakpoint{FunctionName: funcs[i], Line: -1, Tracepoint: true, Stacktrace: traceStackDepth}) - if err != nil { - fmt.Fprintln(os.Stderr, err) - return 1 - } - } - cmds := terminal.DebugCommands(client) - cmd := cmds.Find("continue") - err = cmd(terminal.New(client, nil), "") - if err != nil { - fmt.Fprintln(os.Stderr, err) - return 1 - } - return 0 - }() - os.Exit(status) + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("you must provide a function to trace") + } + return nil }, + 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.") @@ -218,34 +142,8 @@ starts and attaches to it, and enables you to immediately begin debugging your p testCommand := &cobra.Command{ Use: "test", 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: func(cmd *cobra.Command, args []string) { - status := func() int { - wd, err := os.Getwd() - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - return 1 - } - base := filepath.Base(wd) - goTest := exec.Command("go", "test", "-c", "-gcflags", "-N -l", BuildFlags) - goTest.Stderr = os.Stderr - err = goTest.Run() - if err != nil { - return 1 - } - debugname := "./" + base + ".test" - // On Windows, "go test" generates an executable with the ".exe" extension - if runtime.GOOS == "windows" { - debugname += ".exe" - } - defer os.Remove(debugname) - processArgs := append([]string{debugname}, args...) - - return execute(0, processArgs, conf) - }() - os.Exit(status) - }, + 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) @@ -260,14 +158,7 @@ starts and attaches to it, and enable you to immediately begin debugging your pr } return nil }, - Run: func(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)) - }, + Run: attachCmd, } rootCommand.AddCommand(attachCommand) @@ -276,24 +167,148 @@ starts and attaches to it, and enable you to immediately begin debugging your pr Use: "connect [addr]", Short: "Connect to a headless debug server.", Long: "Connect to a headless debug server.", - Run: func(cmd *cobra.Command, args []string) { + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - fmt.Fprintf(os.Stderr, "An address was not provided. You must provide an address as the first argument.\n") - os.Exit(1) + return errors.New("you must provide an address as the first argument") } - 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)) + return nil }, + Run: connectCmd, } rootCommand.AddCommand(connectCommand) +} + +func main() { + // Config setup and load. + conf = config.LoadConfig() + rootCommand.Execute() } +func debugCmd(cmd *cobra.Command, args []string) { + status := func() int { + const debugname = "debug" + err := gobuild(debugname) + if err != nil { + return 1 + } + fp, err := filepath.Abs("./" + debugname) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + return 1 + } + defer os.Remove(fp) + + processArgs := append([]string{"./" + debugname}, args...) + return execute(0, processArgs, conf) + }() + os.Exit(status) +} + +func traceCmd(cmd *cobra.Command, args []string) { + status := func() int { + const debugname = "debug" + var processArgs []string + if traceAttachPid == 0 { + if err := gobuild(debugname); err != nil { + return 1 + } + fp, err := filepath.Abs("./" + debugname) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + return 1 + } + defer os.Remove(fp) + + processArgs = append([]string{"./" + debugname}, args...) + } + // 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(args[0]) + 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") + err = cmd(terminal.New(client, nil), "") + 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 { + wd, err := os.Getwd() + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + return 1 + } + base := filepath.Base(wd) + err = gotestbuild() + if err != nil { + return 1 + } + debugname := "./" + base + ".test" + // On Windows, "go test" generates an executable with the ".exe" extension + if runtime.GOOS == "windows" { + debugname += ".exe" + } + defer os.Remove(debugname) + processArgs := append([]string{debugname}, args...) + + 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 connect(addr string, conf *config.Config) int { // Create and start a terminal - attach to running instance var client service.Client @@ -331,18 +346,18 @@ func execute(attachPid int, processArgs []string, conf *config.Config) int { } var status int - if !Headless { + 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() - } else { - ch := make(chan os.Signal) - signal.Notify(ch, syscall.SIGINT) - <-ch - err = server.Stop(true) } if err != nil { @@ -351,3 +366,19 @@ func execute(attachPid int, processArgs []string, conf *config.Config) int { return status } + +func gobuild(debugname string) error { + return gocommand("build", "-o", debugname, BuildFlags) +} + +func gotestbuild() error { + return gocommand("test", "-c", BuildFlags) +} + +func gocommand(command string, args ...string) error { + allargs := []string{command, "-gcflags", "-N -l"} + allargs = append(allargs, args...) + goBuild := exec.Command("go", allargs...) + goBuild.Stderr = os.Stderr + return goBuild.Run() +} diff --git a/service/rpc/server.go b/service/rpc/server.go index b6d01209..73d7c3a5 100644 --- a/service/rpc/server.go +++ b/service/rpc/server.go @@ -65,6 +65,7 @@ func (s *ServerImpl) Run() error { if err != nil { panic(err) } + defer s.s.listener.Close() rpcs := grpc.NewServer() rpcs.Register(s.s)