mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-04 14:36:47 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			460 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			460 lines
		
	
	
		
			13 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.")
 | 
						|
 | 
						|
	// 'attach' subcommand.
 | 
						|
	attachCommand := &cobra.Command{
 | 
						|
		Use:   "attach pid",
 | 
						|
		Short: "Attach to running process and begin debugging.",
 | 
						|
		Long: `Attach to an already running process and begin debugging it.
 | 
						|
 | 
						|
This command will cause Delve to take control of an already running process, and
 | 
						|
begin a new debug session.  When exiting the debug session you will have the
 | 
						|
option to let the process continue or kill it.
 | 
						|
`,
 | 
						|
		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 running 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)
 | 
						|
 | 
						|
	// 'debug' subcommand.
 | 
						|
	debugCommand := &cobra.Command{
 | 
						|
		Use:   "debug [package]",
 | 
						|
		Short: "Compile and begin debugging main package in current directory, or the package specified.",
 | 
						|
		Long: `Compiles your program with optimizations disabled, starts and attaches to it.
 | 
						|
 | 
						|
By default, with no arguments, Delve will compile the 'main' package in the
 | 
						|
current directory, and begin to debug it. Alternatively you can specify a
 | 
						|
package name and Delve will compile that package instead, and begin a new debug
 | 
						|
session.`,
 | 
						|
		Run: debugCmd,
 | 
						|
	}
 | 
						|
	RootCommand.AddCommand(debugCommand)
 | 
						|
 | 
						|
	// 'exec' subcommand.
 | 
						|
	execCommand := &cobra.Command{
 | 
						|
		Use:   "exec [./path/to/binary]",
 | 
						|
		Short: "Execute a precompiled binary, and begin a debug session.",
 | 
						|
		Long: `Execute a precompiled binary and begin a debug session.
 | 
						|
 | 
						|
This command will cause Delve to exec the binary and immediately attach to it to
 | 
						|
begin a new debug session. Please note that if the binary was not compiled with
 | 
						|
optimizations disabled, it may be difficult to properly debug it. Please
 | 
						|
consider compiling debugging binaries with -gcflags="-N -l".`,
 | 
						|
		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)
 | 
						|
 | 
						|
	// 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)
 | 
						|
 | 
						|
	// 'test' subcommand.
 | 
						|
	testCommand := &cobra.Command{
 | 
						|
		Use:   "test [package]",
 | 
						|
		Short: "Compile test binary and begin debugging program.",
 | 
						|
		Long: `Compiles a test binary with optimizations disabled and begins a new debug session.
 | 
						|
 | 
						|
The test command allows you to begin a new debug session in the context of your
 | 
						|
unit tests. By default Delve will debug the tests in the current directory.
 | 
						|
Alternatively you can specify a package name, and Delve will debug the tests in
 | 
						|
that package instead.`,
 | 
						|
		Run: testCmd,
 | 
						|
	}
 | 
						|
	RootCommand.AddCommand(testCommand)
 | 
						|
 | 
						|
	// 'trace' subcommand.
 | 
						|
	traceCommand := &cobra.Command{
 | 
						|
		Use:   "trace [package] regexp",
 | 
						|
		Short: "Compile and begin tracing program.",
 | 
						|
		Long: `Trace program execution.
 | 
						|
 | 
						|
The trace sub command will set a tracepoint on every function matching the
 | 
						|
provided regular expression and output information when tracepoint is hit.  This
 | 
						|
is useful if you do not want to begin an entire debug session, but merely want
 | 
						|
to know what functions your process is executing.`,
 | 
						|
		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)
 | 
						|
 | 
						|
	// '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)
 | 
						|
 | 
						|
	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, LoadArgs: &terminal.ShortLoadConfig})
 | 
						|
			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 {
 | 
						|
		// Print listener address
 | 
						|
		fmt.Printf("API server listening at: %s\n", listener.Addr())
 | 
						|
		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()
 | 
						|
}
 |