dlv: Misc refactors

Mainly just cleans up the code in cmd/dlv/main.go
This commit is contained in:
Derek Parker 2016-02-01 00:30:40 +01:00 committed by aarzilli
parent 8a1f36a1ce
commit 94a265f098
2 changed files with 183 additions and 151 deletions

@ -37,31 +37,37 @@ var (
InitFile string InitFile string
// BuildFlags is the flags passed during compiler invocation. // BuildFlags is the flags passed during compiler invocation.
BuildFlags string BuildFlags string
traceAttachPid int
traceStackDepth int
conf *config.Config
rootCommand *cobra.Command
) )
func main() { const dlvCommandLongDesc = `Delve is a source level debugger for Go programs.
// 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.
Delve enables you to interact with your program by controlling the execution of the process, 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. 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. The goal of this tool is to provide a simple yet powerful interface for debugging Go programs.
`, `
}
func init() {
buildFlagsDefault := "" buildFlagsDefault := ""
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// Work-around for https://github.com/golang/go/issues/13154 // Work-around for https://github.com/golang/go/issues/13154
buildFlagsDefault = "-ldflags=-linkmode internal" 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().StringVarP(&Addr, "listen", "l", "localhost:0", "Debugging server listen address.")
rootCommand.PersistentFlags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.") rootCommand.PersistentFlags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.")
rootCommand.PersistentFlags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.") 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.", Short: "Compile and begin debugging program.",
Long: `Compiles your program with optimizations disabled, Long: `Compiles your program with optimizations disabled,
starts and attaches to it, and enables you to immediately begin debugging your program.`, starts and attaches to it, and enables you to immediately begin debugging your program.`,
Run: func(cmd *cobra.Command, args []string) { Run: debugCmd,
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)
},
} }
rootCommand.AddCommand(debugCommand) rootCommand.AddCommand(debugCommand)
@ -136,79 +122,17 @@ starts and attaches to it, and enables you to immediately begin debugging your p
rootCommand.AddCommand(execCommand) rootCommand.AddCommand(execCommand)
// 'trace' subcommand. // 'trace' subcommand.
var traceAttachPid, traceStackDepth int
traceCommand := &cobra.Command{ traceCommand := &cobra.Command{
Use: "trace [regexp]", Use: "trace [regexp]",
Short: "Compile and begin tracing program.", 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.", 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) { PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
status := func() int { if len(args) == 0 {
if len(args) == 0 { return errors.New("you must provide a function to trace")
fmt.Fprintln(os.Stderr, "You must provide a function to trace.") }
return 1 return nil
}
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)
}, },
Run: traceCmd,
} }
traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.") traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
traceCommand.Flags().IntVarP(&traceStackDepth, "stack", "s", 0, "Show stack trace with given depth.") 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{ testCommand := &cobra.Command{
Use: "test", Use: "test",
Short: "Compile test binary and begin debugging program.", Short: "Compile test binary and begin debugging program.",
Long: `Compiles a test binary with optimizations disabled, Long: `Compiles a test binary with optimizations disabled, starts and attaches to it, and enable you to immediately begin debugging your program.`,
starts and attaches to it, and enable you to immediately begin debugging your program.`, Run: testCmd,
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)
},
} }
rootCommand.AddCommand(testCommand) rootCommand.AddCommand(testCommand)
@ -260,14 +158,7 @@ starts and attaches to it, and enable you to immediately begin debugging your pr
} }
return nil return nil
}, },
Run: func(cmd *cobra.Command, args []string) { Run: attachCmd,
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))
},
} }
rootCommand.AddCommand(attachCommand) rootCommand.AddCommand(attachCommand)
@ -276,24 +167,148 @@ starts and attaches to it, and enable you to immediately begin debugging your pr
Use: "connect [addr]", Use: "connect [addr]",
Short: "Connect to a headless debug server.", Short: "Connect to a headless debug server.",
Long: "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 { if len(args) == 0 {
fmt.Fprintf(os.Stderr, "An address was not provided. You must provide an address as the first argument.\n") return errors.New("you must provide an address as the first argument")
os.Exit(1)
} }
addr := args[0] return nil
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))
}, },
Run: connectCmd,
} }
rootCommand.AddCommand(connectCommand) rootCommand.AddCommand(connectCommand)
}
func main() {
// Config setup and load.
conf = config.LoadConfig()
rootCommand.Execute() 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 { func connect(addr string, conf *config.Config) int {
// Create and start a terminal - attach to running instance // Create and start a terminal - attach to running instance
var client service.Client var client service.Client
@ -331,18 +346,18 @@ func execute(attachPid int, processArgs []string, conf *config.Config) int {
} }
var status 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 // Create and start a terminal
var client service.Client var client service.Client
client = rpc.NewClient(listener.Addr().String()) client = rpc.NewClient(listener.Addr().String())
term := terminal.New(client, conf) term := terminal.New(client, conf)
term.InitFile = InitFile term.InitFile = InitFile
status, err = term.Run() status, err = term.Run()
} else {
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT)
<-ch
err = server.Stop(true)
} }
if err != nil { if err != nil {
@ -351,3 +366,19 @@ func execute(attachPid int, processArgs []string, conf *config.Config) int {
return status 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()
}

@ -65,6 +65,7 @@ func (s *ServerImpl) Run() error {
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer s.s.listener.Close()
rpcs := grpc.NewServer() rpcs := grpc.NewServer()
rpcs.Register(s.s) rpcs.Register(s.s)