
Going forward, all documentation should be placed in the Documentation directory in the root of the project. This switch allows maintainers to approve updates to documentation before they are committed, as opposed to the pre-existing wiki which anybody could modify. Currently the Documentation directory includes docs on building, usage, and minimal docs around the API. This is just the initial commit, and documentation will continue to improve over time. Some changes have been made (and will continue to be made) to `cmd/dlv` to ensure we can auto-generate documentation for all commands from the newly provided script `scripts/gen-usage-docs.go`, which can be invoked via `go run scripts/gen-usage-docs.go`. Additionally, version has been split into its own package. This was a bit of housekeeping related to the changes made the `cmd/dlv`.
407 lines
11 KiB
Go
407 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/rpc"
|
|
"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
|
|
// 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 connection. Note that the server API is not reentrant and clients will have to coordinate")
|
|
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 := 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(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)
|
|
cmd := cmds.Find("continue")
|
|
t := terminal.New(client, nil)
|
|
defer t.Close()
|
|
err = cmd(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 = rpc.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")
|
|
}
|
|
|
|
// Create and start a debugger server
|
|
server := rpc.NewServer(&service.Config{
|
|
Listener: listener,
|
|
ProcessArgs: processArgs,
|
|
AttachPid: attachPid,
|
|
AcceptMulti: AcceptMulti,
|
|
}, Log)
|
|
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 = rpc.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()
|
|
}
|