mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 02:36:18 +08:00 
			
		
		
		
	 af4798e2a9
			
		
	
	af4798e2a9
	
	
	
		
			
			New API version with better backwards compatibility plus mechanism to select the API version that a headless instance should use. Adds service/test/cmd/typecheckrpc.go to type check the RPC interface.
		
			
				
	
	
		
			434 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			434 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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/rpc1"
 | |
| 	"github.com/derekparker/delve/service/rpc2"
 | |
| 	"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
 | |
| 	// ApiVersion is the requested API version while running headless
 | |
| 	ApiVersion int
 | |
| 	// 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.")
 | |
| 	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")
 | |
| 	RootCommand.PersistentFlags().IntVar(&ApiVersion, "api-version", 1, "Selects API version when headless")
 | |
| 	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 := rpc2.NewServer(&service.Config{
 | |
| 			Listener:    listener,
 | |
| 			ProcessArgs: processArgs,
 | |
| 			AttachPid:   traceAttachPid,
 | |
| 		}, Log)
 | |
| 		if err := server.Run(); err != nil {
 | |
| 			fmt.Fprintln(os.Stderr, err)
 | |
| 			return 1
 | |
| 		}
 | |
| 		client := rpc2.NewClient(listener.Addr().String())
 | |
| 		funcs, err := client.ListFunctions(regexp)
 | |
| 		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)
 | |
| 		t := terminal.New(client, nil)
 | |
| 		defer t.Close()
 | |
| 		err = cmds.Call("continue", "", t)
 | |
| 		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 = rpc2.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")
 | |
| 	}
 | |
| 
 | |
| 	var server interface {
 | |
| 		Run() error
 | |
| 		Stop(bool) error
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	if !Headless {
 | |
| 		ApiVersion = 2
 | |
| 	}
 | |
| 
 | |
| 	// Create and start a debugger server
 | |
| 	switch ApiVersion {
 | |
| 	case 1:
 | |
| 		server = rpc1.NewServer(&service.Config{
 | |
| 			Listener:    listener,
 | |
| 			ProcessArgs: processArgs,
 | |
| 			AttachPid:   attachPid,
 | |
| 			AcceptMulti: AcceptMulti,
 | |
| 		}, Log)
 | |
| 	case 2:
 | |
| 		server = rpc2.NewServer(&service.Config{
 | |
| 			Listener:    listener,
 | |
| 			ProcessArgs: processArgs,
 | |
| 			AttachPid:   attachPid,
 | |
| 			AcceptMulti: AcceptMulti,
 | |
| 		}, Log)
 | |
| 	default:
 | |
| 		fmt.Println("Unknown API version %d", ApiVersion)
 | |
| 		return 1
 | |
| 	}
 | |
| 
 | |
| 	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 = rpc2.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()
 | |
| }
 |