commands.go 19.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
package cmds

import (
	"errors"
	"fmt"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"strconv"
	"syscall"

D
Derek Parker 已提交
15
	"github.com/derekparker/delve/pkg/config"
16
	"github.com/derekparker/delve/pkg/goversion"
17
	"github.com/derekparker/delve/pkg/logflags"
D
Derek Parker 已提交
18 19
	"github.com/derekparker/delve/pkg/terminal"
	"github.com/derekparker/delve/pkg/version"
20 21
	"github.com/derekparker/delve/service"
	"github.com/derekparker/delve/service/api"
22
	"github.com/derekparker/delve/service/rpc2"
A
aarzilli 已提交
23
	"github.com/derekparker/delve/service/rpccommon"
24 25 26 27 28
	"github.com/spf13/cobra"
)

var (
	// Log is whether to log debug statements.
29 30 31
	Log bool
	// LogOutput is a comma separated list of components that should produce debug output.
	LogOutput string
32 33
	// Headless is whether to run without terminal.
	Headless bool
A
aarzilli 已提交
34 35
	// APIVersion is the requested API version while running headless
	APIVersion int
36 37 38 39 40 41 42 43
	// 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
E
Evgeny L 已提交
44 45
	// WorkingDir is the working directory for running the program.
	WorkingDir string
46

47 48 49
	// Backend selection
	Backend string

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
	// RootCommand is the root of the command tree.
	RootCommand *cobra.Command

	traceAttachPid  int
	traceStackDepth int

	conf *config.Config
)

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.
65 66 67 68

Pass flags to the program you are debugging using ` + "`--`" + `, for example:

` + "`dlv exec ./hello -- server --config conf/config.toml`"
69 70

// New returns an initialized command tree.
71
func New(docCall bool) *cobra.Command {
72 73 74 75
	// Config setup and load.
	conf = config.LoadConfig()
	buildFlagsDefault := ""
	if runtime.GOOS == "windows" {
76 77 78 79 80
		ver, _ := goversion.Installed()
		if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) {
			// Work-around for https://github.com/golang/go/issues/13154
			buildFlagsDefault = "-ldflags='-linkmode internal'"
		}
81 82 83 84 85 86 87 88 89 90
	}

	// 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.")
91 92
	RootCommand.PersistentFlags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.")
	RootCommand.PersistentFlags().StringVarP(&LogOutput, "log-output", "", "", `Comma separated list of components that should produce debug output, possible values:
93 94 95
	debugger	Log debugger commands
	gdbwire		Log connection to gdbserial backend
	lldbout		Copy output from debugserver/lldb to standard output
96
	debuglineerr	Log recoverable errors reading .debug_line
97
	rpc		Log all RPC messages
98
	fncall		Log function call protocol
99
Defaults to "debugger" when logging is enabled with --log.`)
100
	RootCommand.PersistentFlags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")
101
	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.")
A
aarzilli 已提交
102
	RootCommand.PersistentFlags().IntVar(&APIVersion, "api-version", 1, "Selects API version when headless.")
103 104
	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.")
E
Evgeny L 已提交
105
	RootCommand.PersistentFlags().StringVar(&WorkingDir, "wd", ".", "Working directory for running the program.")
106 107 108
	RootCommand.PersistentFlags().StringVar(&Backend, "backend", "default", `Backend selection:
	default		Uses lldb on macOS, native everywhere else.
	native		Native backend.
109 110 111
	lldb		Uses lldb-server or debugserver.
	rr		Uses mozilla rr (https://github.com/mozilla/rr).
`)
112

113 114
	// 'attach' subcommand.
	attachCommand := &cobra.Command{
115
		Use:   "attach pid [executable]",
116
		Short: "Attach to running process and begin debugging.",
117 118 119 120 121 122
		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.
`,
123 124 125 126 127
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			if len(args) == 0 {
				return errors.New("you must provide a PID")
			}
			return nil
128
		},
129
		Run: attachCmd,
130
	}
131
	RootCommand.AddCommand(attachCommand)
132

133 134 135 136
	// 'connect' subcommand.
	connectCommand := &cobra.Command{
		Use:   "connect addr",
		Short: "Connect to a headless debug server.",
137
		Long:  "Connect to a running headless debug server.",
138 139 140 141 142
		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
143
		},
144
		Run: connectCmd,
145
	}
146
	RootCommand.AddCommand(connectCommand)
147 148 149 150

	// 'debug' subcommand.
	debugCommand := &cobra.Command{
		Use:   "debug [package]",
151
		Short: "Compile and begin debugging main package in current directory, or the package specified.",
152 153 154 155 156 157
		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.`,
158 159
		Run: debugCmd,
	}
160
	debugCommand.Flags().String("output", "debug", "Output path for the binary.")
161 162 163 164
	RootCommand.AddCommand(debugCommand)

	// 'exec' subcommand.
	execCommand := &cobra.Command{
165
		Use:   "exec <path/to/binary>",
166 167 168 169 170 171 172
		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".`,
173 174 175 176 177 178 179
		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) {
180
			os.Exit(execute(0, args, conf, "", executingExistingFile))
181 182 183 184
		},
	}
	RootCommand.AddCommand(execCommand)

185 186 187 188 189 190 191 192
	// 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)
		},
193
	}
194
	RootCommand.AddCommand(runCommand)
195 196 197 198 199

	// 'test' subcommand.
	testCommand := &cobra.Command{
		Use:   "test [package]",
		Short: "Compile test binary and begin debugging program.",
200 201 202 203 204 205 206
		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,
207
	}
208
	testCommand.Flags().String("output", "debug.test", "Output path for the binary.")
209 210
	RootCommand.AddCommand(testCommand)

211 212 213 214
	// 'trace' subcommand.
	traceCommand := &cobra.Command{
		Use:   "trace [package] regexp",
		Short: "Compile and begin tracing program.",
215 216 217 218 219 220 221
		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,
222
	}
223 224
	traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
	traceCommand.Flags().IntVarP(&traceStackDepth, "stack", "s", 0, "Show stack trace with given depth.")
225
	traceCommand.Flags().String("output", "debug", "Output path for the binary.")
226
	RootCommand.AddCommand(traceCommand)
227

228 229 230 231
	coreCommand := &cobra.Command{
		Use:   "core <executable> <core>",
		Short: "Examine a core dump.",
		Long: `Examine a core dump.
232

233 234 235 236 237 238 239 240 241 242 243 244 245
The core command will open the specified core file and the associated
executable and let you examine the state of the process when the
core dump was taken.`,
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			if len(args) != 2 {
				return errors.New("you must provide a core file and an executable")
			}
			return nil
		},
		Run: coreCmd,
	}
	RootCommand.AddCommand(coreCommand)

246 247 248 249 250 251
	// '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)
252 253
		},
	}
254
	RootCommand.AddCommand(versionCommand)
255

256
	if path, _ := exec.LookPath("rr"); path != "" || docCall {
257 258 259 260
		replayCommand := &cobra.Command{
			Use:   "replay [trace directory]",
			Short: "Replays a rr trace.",
			Long: `Replays a rr trace.
261

262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
The replay command will open a trace generated by mozilla rr. Mozilla rr must be installed:
https://github.com/mozilla/rr
			`,
			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) {
				Backend = "rr"
				os.Exit(execute(0, []string{}, conf, args[0], executingOther))
			},
		}
		RootCommand.AddCommand(replayCommand)
	}

279 280
	RootCommand.DisableAutoGenTag = true

281 282 283
	return RootCommand
}

284 285 286 287 288 289 290 291
// Remove the file at path and issue a warning to stderr if this fails.
func remove(path string) {
	err := os.Remove(path)
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not remove %v: %v\n", path, err)
	}
}

292 293
func debugCmd(cmd *cobra.Command, args []string) {
	status := func() int {
294 295 296 297 298 299
		debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return 1
		}

300
		dlvArgs, targetArgs := splitArgs(cmd, args)
301
		err = gobuild(debugname, dlvArgs)
302 303 304 305
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return 1
		}
306 307
		defer remove(debugname)
		processArgs := append([]string{debugname}, targetArgs...)
308
		return execute(0, processArgs, conf, "", executingGeneratedFile)
309 310 311 312 313 314
	}()
	os.Exit(status)
}

func traceCmd(cmd *cobra.Command, args []string) {
	status := func() int {
315 316 317 318
		if err := logflags.Setup(Log, LogOutput); err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return 1
		}
319

320 321 322 323 324 325 326
		if Headless {
			fmt.Fprintf(os.Stderr, "Warning: headless mode not supported with trace\n")
		}
		if AcceptMulti {
			fmt.Fprintf(os.Stderr, "Warning: accept multiclient mode not supported with trace")
		}

327 328 329 330 331 332
		debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return 1
		}

333 334 335 336 337 338
		var regexp string
		var processArgs []string

		dlvArgs, targetArgs := splitArgs(cmd, args)

		if traceAttachPid == 0 {
339 340
			var dlvArgsLen = len(dlvArgs)
			if dlvArgsLen == 1 {
341
				regexp = args[0]
342
				dlvArgs = dlvArgs[0:0]
343 344 345
			} else if dlvArgsLen >= 2 {
				regexp = dlvArgs[dlvArgsLen-1]
				dlvArgs = dlvArgs[:dlvArgsLen-1]
346
			}
347
			if err := gobuild(debugname, dlvArgs); err != nil {
348 349
				return 1
			}
350
			defer remove(debugname)
351

352
			processArgs = append([]string{debugname}, targetArgs...)
353
		}
354 355 356

		// Make a local in-memory connection that client and server use to communicate
		listener, clientConn := service.ListenerPipe()
357 358 359
		defer listener.Close()

		// Create and start a debug server
A
aarzilli 已提交
360
		server := rpccommon.NewServer(&service.Config{
361 362 363
			Listener:    listener,
			ProcessArgs: processArgs,
			AttachPid:   traceAttachPid,
A
aarzilli 已提交
364
			APIVersion:  2,
E
Evgeny L 已提交
365
			WorkingDir:  WorkingDir,
366
			Backend:     Backend,
D
Derek Parker 已提交
367
		})
368 369 370 371
		if err := server.Run(); err != nil {
			fmt.Fprintln(os.Stderr, err)
			return 1
		}
372
		client := rpc2.NewClientFromConn(clientConn)
373 374 375 376 377 378
		funcs, err := client.ListFunctions(regexp)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			return 1
		}
		for i := range funcs {
379
			_, err = client.CreateBreakpoint(&api.Breakpoint{FunctionName: funcs[i], Tracepoint: true, Line: -1, Stacktrace: traceStackDepth, LoadArgs: &terminal.ShortLoadConfig})
380 381 382 383 384 385 386 387
			if err != nil {
				fmt.Fprintln(os.Stderr, err)
				return 1
			}
		}
		cmds := terminal.DebugCommands(client)
		t := terminal.New(client, nil)
		defer t.Close()
388
		err = cmds.Call("continue", t)
389 390 391 392 393 394 395 396 397 398 399
		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 {
400 401 402 403 404 405
		debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return 1
		}

406
		dlvArgs, targetArgs := splitArgs(cmd, args)
407
		err = gotestbuild(debugname, dlvArgs)
408 409 410
		if err != nil {
			return 1
		}
411 412
		defer remove(debugname)
		processArgs := append([]string{debugname}, targetArgs...)
413

414
		return execute(0, processArgs, conf, "", executingGeneratedTest)
415 416 417 418 419 420 421 422 423 424
	}()
	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)
	}
425
	os.Exit(execute(pid, args[1:], conf, "", executingOther))
426 427 428 429
}

func coreCmd(cmd *cobra.Command, args []string) {
	os.Exit(execute(0, []string{args[0]}, conf, args[1], executingOther))
430 431 432 433 434
}

func connectCmd(cmd *cobra.Command, args []string) {
	addr := args[0]
	if addr == "" {
435
		fmt.Fprint(os.Stderr, "An empty address was provided. You must provide an address as the first argument.\n")
436 437
		os.Exit(1)
	}
438
	os.Exit(connect(addr, nil, conf, executingOther))
439 440 441 442 443 444 445 446 447
}

func splitArgs(cmd *cobra.Command, args []string) ([]string, []string) {
	if cmd.ArgsLenAtDash() >= 0 {
		return args[:cmd.ArgsLenAtDash()], args[cmd.ArgsLenAtDash():]
	}
	return args, []string{}
}

448
func connect(addr string, clientConn net.Conn, conf *config.Config, kind executeKind) int {
449
	// Create and start a terminal - attach to running instance
450 451 452 453 454 455
	var client *rpc2.RPCClient
	if clientConn != nil {
		client = rpc2.NewClientFromConn(clientConn)
	} else {
		client = rpc2.NewClient(addr)
	}
456 457 458 459 460 461 462
	if client.Recorded() && (kind == executingGeneratedFile || kind == executingGeneratedTest) {
		// When using the rr backend remove the trace directory if we built the
		// executable
		if tracedir, err := client.TraceDirectory(); err == nil {
			defer SafeRemoveAll(tracedir)
		}
	}
463
	term := terminal.New(client, conf)
464
	term.InitFile = InitFile
465 466 467 468 469 470 471
	status, err := term.Run()
	if err != nil {
		fmt.Println(err)
	}
	return status
}

472 473 474 475 476
type executeKind int

const (
	executingExistingFile = executeKind(iota)
	executingGeneratedFile
477
	executingGeneratedTest
478 479 480
	executingOther
)

481
func execute(attachPid int, processArgs []string, conf *config.Config, coreFile string, kind executeKind) int {
482 483 484 485
	if err := logflags.Setup(Log, LogOutput); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return 1
	}
486

487
	if Headless && (InitFile != "") {
488
		fmt.Fprint(os.Stderr, "Warning: init file ignored\n")
489 490
	}

491 492 493 494 495 496 497
	if !Headless && AcceptMulti {
		fmt.Fprint(os.Stderr, "Warning accept-multi: ignored\n")
		// AcceptMulti won't work in normal (non-headless) mode because we always
		// call server.Stop after the terminal client exits.
		AcceptMulti = false
	}

498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
	var listener net.Listener
	var clientConn net.Conn
	var err error

	// Make a TCP listener
	if Headless {
		listener, err = net.Listen("tcp", Addr)
	} else {
		listener, clientConn = service.ListenerPipe()
	}
	if err != nil {
		fmt.Printf("couldn't start listener: %s\n", err)
		return 1
	}
	defer listener.Close()

514
	var server service.Server
515

516 517
	disconnectChan := make(chan struct{})

518
	// Create and start a debugger server
A
aarzilli 已提交
519 520 521
	switch APIVersion {
	case 1, 2:
		server = rpccommon.NewServer(&service.Config{
522 523 524 525
			Listener:    listener,
			ProcessArgs: processArgs,
			AttachPid:   attachPid,
			AcceptMulti: AcceptMulti,
A
aarzilli 已提交
526
			APIVersion:  APIVersion,
E
Evgeny L 已提交
527
			WorkingDir:  WorkingDir,
528
			Backend:     Backend,
529
			CoreFile:    coreFile,
530
			Foreground:  Headless,
531 532

			DisconnectChan: disconnectChan,
D
Derek Parker 已提交
533
		})
534
	default:
D
Derek Parker 已提交
535
		fmt.Printf("Unknown API version: %d\n", APIVersion)
536 537 538
		return 1
	}

539
	if err := server.Run(); err != nil {
540
		if err == api.ErrNotExecutable {
541 542 543 544 545 546 547 548 549 550 551
			switch kind {
			case executingGeneratedFile:
				fmt.Fprintln(os.Stderr, "Can not debug non-main package")
				return 1
			case executingExistingFile:
				fmt.Fprintf(os.Stderr, "%s is not executable\n", processArgs[0])
				return 1
			default:
				// fallthrough
			}
		}
552 553 554 555 556 557
		fmt.Fprintln(os.Stderr, err)
		return 1
	}

	var status int
	if Headless {
558
		ch := make(chan os.Signal, 1)
559
		signal.Notify(ch, syscall.SIGINT)
560 561 562 563
		select {
		case <-ch:
		case <-disconnectChan:
		}
564
		err = server.Stop()
565 566
		if err != nil {
			fmt.Println(err)
567
		}
568

569
		return status
570 571
	}

572
	return connect(listener.Addr().String(), clientConn, conf, kind)
573 574
}

A
aarzilli 已提交
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
func optflags(args []string) []string {
	// after go1.9 building with -gcflags='-N -l' and -a simultaneously works.
	// after go1.10 specifying -a is unnecessary because of the new caching strategy, but we should pass -gcflags=all=-N -l to have it applied to all packages
	// see https://github.com/golang/go/commit/5993251c015dfa1e905bdf44bdb41572387edf90

	ver, _ := goversion.Installed()
	switch {
	case ver.Major < 0 || ver.AfterOrEqual(goversion.GoVersion{1, 10, -1, 0, 0, ""}):
		args = append(args, "-gcflags", "all=-N -l")
	case ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}):
		args = append(args, "-gcflags", "-N -l", "-a")
	default:
		args = append(args, "-gcflags", "-N -l")
	}
	return args
}

592
func gobuild(debugname string, pkgs []string) error {
A
aarzilli 已提交
593 594
	args := []string{"-o", debugname}
	args = optflags(args)
595
	if BuildFlags != "" {
A
aarzilli 已提交
596
		args = append(args, config.SplitQuotedFields(BuildFlags, '\'')...)
597
	}
598
	args = append(args, pkgs...)
599 600 601
	return gocommand("build", args...)
}

602
func gotestbuild(debugname string, pkgs []string) error {
603
	args := []string{"-c", "-o", debugname}
A
aarzilli 已提交
604
	args = optflags(args)
605
	if BuildFlags != "" {
A
aarzilli 已提交
606
		args = append(args, config.SplitQuotedFields(BuildFlags, '\'')...)
607
	}
608
	args = append(args, pkgs...)
609 610 611 612 613 614 615 616 617 618
	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()
}
619

620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
// SafeRemoveAll removes dir and its contents but only as long as dir does
// not contain directories.
func SafeRemoveAll(dir string) {
	dh, err := os.Open(dir)
	if err != nil {
		return
	}
	defer dh.Close()
	fis, err := dh.Readdir(-1)
	if err != nil {
		return
	}
	for _, fi := range fis {
		if fi.IsDir() {
			return
		}
	}
	for _, fi := range fis {
		if err := os.Remove(filepath.Join(dir, fi.Name())); err != nil {
			return
		}
	}
	os.Remove(dir)
}