github.com/neilgarb/delve@v1.9.2-nobreaks/cmd/dlv/cmds/commands.go (about) 1 package cmds 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net" 8 "os" 9 "os/exec" 10 "os/signal" 11 "path/filepath" 12 "reflect" 13 "runtime" 14 "strconv" 15 "strings" 16 "syscall" 17 18 "github.com/go-delve/delve/pkg/config" 19 "github.com/go-delve/delve/pkg/gobuild" 20 "github.com/go-delve/delve/pkg/goversion" 21 "github.com/go-delve/delve/pkg/logflags" 22 "github.com/go-delve/delve/pkg/terminal" 23 "github.com/go-delve/delve/pkg/version" 24 "github.com/go-delve/delve/service" 25 "github.com/go-delve/delve/service/api" 26 "github.com/go-delve/delve/service/dap" 27 "github.com/go-delve/delve/service/debugger" 28 "github.com/go-delve/delve/service/rpc2" 29 "github.com/go-delve/delve/service/rpccommon" 30 "github.com/mattn/go-isatty" 31 "github.com/spf13/cobra" 32 ) 33 34 var ( 35 // log is whether to log debug statements. 36 log bool 37 // logOutput is a comma separated list of components that should produce debug output. 38 logOutput string 39 // logDest is the file path or file descriptor where logs should go. 40 logDest string 41 // headless is whether to run without terminal. 42 headless bool 43 // continueOnStart is whether to continue the process on startup 44 continueOnStart bool 45 // apiVersion is the requested API version while running headless 46 apiVersion int 47 // acceptMulti allows multiple clients to connect to the same server 48 acceptMulti bool 49 // addr is the debugging server listen address. 50 addr string 51 // initFile is the path to initialization file. 52 initFile string 53 // buildFlags is the flags passed during compiler invocation. 54 buildFlags string 55 // workingDir is the working directory for running the program. 56 workingDir string 57 // checkLocalConnUser is true if the debugger should check that local 58 // connections come from the same user that started the headless server 59 checkLocalConnUser bool 60 // tty is used to provide an alternate TTY for the program you wish to debug. 61 tty string 62 // disableASLR is used to disable ASLR 63 disableASLR bool 64 65 // dapClientAddr is dap subcommand's flag that specifies the address of a DAP client. 66 // If it is specified, the dap server starts a debug session by dialing to the client. 67 // The dap server will serve only for the debug session. 68 dapClientAddr string 69 70 // backend selection 71 backend string 72 73 // checkGoVersion is true if the debugger should check the version of Go 74 // used to compile the executable and refuse to work on incompatible 75 // versions. 76 checkGoVersion bool 77 78 // rootCommand is the root of the command tree. 79 rootCommand *cobra.Command 80 81 traceAttachPid int 82 traceExecFile string 83 traceTestBinary bool 84 traceStackDepth int 85 traceUseEBPF bool 86 87 // redirect specifications for target process 88 redirects []string 89 90 allowNonTerminalInteractive bool 91 92 conf *config.Config 93 loadConfErr error 94 ) 95 96 const dlvCommandLongDesc = `Delve is a source level debugger for Go programs. 97 98 Delve enables you to interact with your program by controlling the execution of the process, 99 evaluating variables, and providing information of thread / goroutine state, CPU register state and more. 100 101 The goal of this tool is to provide a simple yet powerful interface for debugging Go programs. 102 103 Pass flags to the program you are debugging using ` + "`--`" + `, for example: 104 105 ` + "`dlv exec ./hello -- server --config conf/config.toml`" 106 107 // New returns an initialized command tree. 108 func New(docCall bool) *cobra.Command { 109 // Config setup and load. 110 conf, loadConfErr = config.LoadConfig() 111 // Delay reporting errors about configuration loading delayed until after the 112 // server is started so that the "server listening at" message is always 113 // the first thing emitted. Also logflags hasn't been setup yet at this point. 114 buildFlagsDefault := "" 115 if runtime.GOOS == "windows" { 116 ver, _ := goversion.Installed() 117 if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) { 118 // Work-around for https://github.com/golang/go/issues/13154 119 buildFlagsDefault = "-ldflags='-linkmode internal'" 120 } 121 } 122 123 // Main dlv root command. 124 rootCommand = &cobra.Command{ 125 Use: "dlv", 126 Short: "Delve is a debugger for the Go programming language.", 127 Long: dlvCommandLongDesc, 128 } 129 130 rootCommand.PersistentFlags().StringVarP(&addr, "listen", "l", "127.0.0.1:0", "Debugging server listen address.") 131 132 rootCommand.PersistentFlags().BoolVarP(&log, "log", "", false, "Enable debugging server logging.") 133 rootCommand.PersistentFlags().StringVarP(&logOutput, "log-output", "", "", `Comma separated list of components that should produce debug output (see 'dlv help log')`) 134 rootCommand.PersistentFlags().StringVarP(&logDest, "log-dest", "", "", "Writes logs to the specified file or file descriptor (see 'dlv help log').") 135 136 rootCommand.PersistentFlags().BoolVarP(&headless, "headless", "", false, "Run debug server only, in headless mode. Server will accept both JSON-RPC or DAP client connections.") 137 rootCommand.PersistentFlags().BoolVarP(&acceptMulti, "accept-multiclient", "", false, "Allows a headless server to accept multiple client connections via JSON-RPC or DAP.") 138 rootCommand.PersistentFlags().IntVar(&apiVersion, "api-version", 1, "Selects JSON-RPC API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md.") 139 rootCommand.PersistentFlags().StringVar(&initFile, "init", "", "Init file, executed by the terminal client.") 140 rootCommand.PersistentFlags().StringVar(&buildFlags, "build-flags", buildFlagsDefault, "Build flags, to be passed to the compiler. For example: --build-flags=\"-tags=integration -mod=vendor -cover -v\"") 141 rootCommand.PersistentFlags().StringVar(&workingDir, "wd", "", "Working directory for running the program.") 142 rootCommand.PersistentFlags().BoolVarP(&checkGoVersion, "check-go-version", "", true, "Exits if the version of Go in use is not compatible (too old or too new) with the version of Delve.") 143 rootCommand.PersistentFlags().BoolVarP(&checkLocalConnUser, "only-same-user", "", true, "Only connections from the same user that started this instance of Delve are allowed to connect.") 144 rootCommand.PersistentFlags().StringVar(&backend, "backend", "default", `Backend selection (see 'dlv help backend').`) 145 rootCommand.PersistentFlags().StringArrayVarP(&redirects, "redirect", "r", []string{}, "Specifies redirect rules for target process (see 'dlv help redirect')") 146 rootCommand.PersistentFlags().BoolVar(&allowNonTerminalInteractive, "allow-non-terminal-interactive", false, "Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr") 147 rootCommand.PersistentFlags().BoolVar(&disableASLR, "disable-aslr", false, "Disables address space randomization") 148 149 // 'attach' subcommand. 150 attachCommand := &cobra.Command{ 151 Use: "attach pid [executable]", 152 Short: "Attach to running process and begin debugging.", 153 Long: `Attach to an already running process and begin debugging it. 154 155 This command will cause Delve to take control of an already running process, and 156 begin a new debug session. When exiting the debug session you will have the 157 option to let the process continue or kill it. 158 `, 159 PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 160 if len(args) == 0 { 161 return errors.New("you must provide a PID") 162 } 163 return nil 164 }, 165 Run: attachCmd, 166 } 167 attachCommand.Flags().BoolVar(&continueOnStart, "continue", false, "Continue the debugged process on start.") 168 rootCommand.AddCommand(attachCommand) 169 170 // 'connect' subcommand. 171 connectCommand := &cobra.Command{ 172 Use: "connect addr", 173 Short: "Connect to a headless debug server with a terminal client.", 174 Long: "Connect to a running headless debug server with a terminal client.", 175 PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 176 if len(args) == 0 { 177 return errors.New("you must provide an address as the first argument") 178 } 179 return nil 180 }, 181 Run: connectCmd, 182 } 183 rootCommand.AddCommand(connectCommand) 184 185 // 'dap' subcommand. 186 dapCommand := &cobra.Command{ 187 Use: "dap", 188 Short: "Starts a headless TCP server communicating via Debug Adaptor Protocol (DAP).", 189 Long: `Starts a headless TCP server communicating via Debug Adaptor Protocol (DAP). 190 191 The server is always headless and requires a DAP client like VS Code to connect and request a binary 192 to be launched or a process to be attached to. The following modes can be specified via the client's launch config: 193 - launch + exec (executes precompiled binary, like 'dlv exec') 194 - launch + debug (builds and launches, like 'dlv debug') 195 - launch + test (builds and tests, like 'dlv test') 196 - launch + replay (replays an rr trace, like 'dlv replay') 197 - launch + core (replays a core dump file, like 'dlv core') 198 - attach + local (attaches to a running process, like 'dlv attach') 199 200 Program and output binary paths will be interpreted relative to dlv's working directory. 201 202 This server does not accept multiple client connections (--accept-multiclient). 203 Use 'dlv [command] --headless' instead and a DAP client with attach + remote config. 204 While --continue is not supported, stopOnEntry launch/attach attribute can be used to control if 205 execution is resumed at the start of the debug session. 206 207 The --client-addr flag is a special flag that makes the server initiate a debug session 208 by dialing in to the host:port where a DAP client is waiting. This server process 209 will exit when the debug session ends.`, 210 Run: dapCmd, 211 } 212 dapCommand.Flags().StringVar(&dapClientAddr, "client-addr", "", "host:port where the DAP client is waiting for the DAP server to dial in") 213 214 // TODO(polina): support --tty when dlv dap allows to launch a program from command-line 215 rootCommand.AddCommand(dapCommand) 216 217 // 'debug' subcommand. 218 debugCommand := &cobra.Command{ 219 Use: "debug [package]", 220 Short: "Compile and begin debugging main package in current directory, or the package specified.", 221 Long: `Compiles your program with optimizations disabled, starts and attaches to it. 222 223 By default, with no arguments, Delve will compile the 'main' package in the 224 current directory, and begin to debug it. Alternatively you can specify a 225 package name and Delve will compile that package instead, and begin a new debug 226 session.`, 227 Run: debugCmd, 228 } 229 debugCommand.Flags().String("output", "./__debug_bin", "Output path for the binary.") 230 debugCommand.Flags().BoolVar(&continueOnStart, "continue", false, "Continue the debugged process on start.") 231 debugCommand.Flags().StringVar(&tty, "tty", "", "TTY to use for the target program") 232 rootCommand.AddCommand(debugCommand) 233 234 // 'exec' subcommand. 235 execCommand := &cobra.Command{ 236 Use: "exec <path/to/binary>", 237 Short: "Execute a precompiled binary, and begin a debug session.", 238 Long: `Execute a precompiled binary and begin a debug session. 239 240 This command will cause Delve to exec the binary and immediately attach to it to 241 begin a new debug session. Please note that if the binary was not compiled with 242 optimizations disabled, it may be difficult to properly debug it. Please 243 consider compiling debugging binaries with -gcflags="all=-N -l" on Go 1.10 244 or later, -gcflags="-N -l" on earlier versions of Go.`, 245 PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 246 if len(args) == 0 { 247 return errors.New("you must provide a path to a binary") 248 } 249 return nil 250 }, 251 Run: func(cmd *cobra.Command, args []string) { 252 os.Exit(execute(0, args, conf, "", debugger.ExecutingExistingFile, args, buildFlags)) 253 }, 254 } 255 execCommand.Flags().StringVar(&tty, "tty", "", "TTY to use for the target program") 256 execCommand.Flags().BoolVar(&continueOnStart, "continue", false, "Continue the debugged process on start.") 257 rootCommand.AddCommand(execCommand) 258 259 // Deprecated 'run' subcommand. 260 runCommand := &cobra.Command{ 261 Use: "run", 262 Short: "Deprecated command. Use 'debug' instead.", 263 Run: func(cmd *cobra.Command, args []string) { 264 fmt.Println("This command is deprecated, please use 'debug' instead.") 265 os.Exit(0) 266 }, 267 } 268 rootCommand.AddCommand(runCommand) 269 270 // 'test' subcommand. 271 testCommand := &cobra.Command{ 272 Use: "test [package]", 273 Short: "Compile test binary and begin debugging program.", 274 Long: `Compiles a test binary with optimizations disabled and begins a new debug session. 275 276 The test command allows you to begin a new debug session in the context of your 277 unit tests. By default Delve will debug the tests in the current directory. 278 Alternatively you can specify a package name, and Delve will debug the tests in 279 that package instead. Double-dashes ` + "`--`" + ` can be used to pass arguments to the test program: 280 281 dlv test [package] -- -test.v -other-argument 282 283 See also: 'go help testflag'.`, 284 Run: testCmd, 285 } 286 testCommand.Flags().String("output", "debug.test", "Output path for the binary.") 287 rootCommand.AddCommand(testCommand) 288 289 // 'trace' subcommand. 290 traceCommand := &cobra.Command{ 291 Use: "trace [package] regexp", 292 Short: "Compile and begin tracing program.", 293 Long: `Trace program execution. 294 295 The trace sub command will set a tracepoint on every function matching the 296 provided regular expression and output information when tracepoint is hit. This 297 is useful if you do not want to begin an entire debug session, but merely want 298 to know what functions your process is executing. 299 300 The output of the trace sub command is printed to stderr, so if you would like to 301 only see the output of the trace operations you can redirect stdout.`, 302 Run: traceCmd, 303 } 304 traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.") 305 traceCommand.Flags().StringVarP(&traceExecFile, "exec", "e", "", "Binary file to exec and trace.") 306 traceCommand.Flags().BoolVarP(&traceTestBinary, "test", "t", false, "Trace a test binary.") 307 traceCommand.Flags().BoolVarP(&traceUseEBPF, "ebpf", "", false, "Trace using eBPF (experimental).") 308 traceCommand.Flags().IntVarP(&traceStackDepth, "stack", "s", 0, "Show stack trace with given depth. (Ignored with -ebpf)") 309 traceCommand.Flags().String("output", "debug", "Output path for the binary.") 310 rootCommand.AddCommand(traceCommand) 311 312 coreCommand := &cobra.Command{ 313 Use: "core <executable> <core>", 314 Short: "Examine a core dump.", 315 Long: `Examine a core dump (only supports linux and windows core dumps). 316 317 The core command will open the specified core file and the associated 318 executable and let you examine the state of the process when the 319 core dump was taken. 320 321 Currently supports linux/amd64 and linux/arm64 core files, windows/amd64 minidumps and core files generated by Delve's 'dump' command.`, 322 PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 323 if len(args) != 2 { 324 return errors.New("you must provide a core file and an executable") 325 } 326 return nil 327 }, 328 Run: coreCmd, 329 } 330 rootCommand.AddCommand(coreCommand) 331 332 // 'version' subcommand. 333 var versionVerbose = false 334 versionCommand := &cobra.Command{ 335 Use: "version", 336 Short: "Prints version.", 337 Run: func(cmd *cobra.Command, args []string) { 338 fmt.Printf("Delve Debugger\n%s\n", version.DelveVersion) 339 if versionVerbose { 340 fmt.Printf("Build Details: %s\n", version.BuildInfo()) 341 } 342 }, 343 } 344 versionCommand.Flags().BoolVarP(&versionVerbose, "verbose", "v", false, "print verbose version info") 345 rootCommand.AddCommand(versionCommand) 346 347 if path, _ := exec.LookPath("rr"); path != "" || docCall { 348 replayCommand := &cobra.Command{ 349 Use: "replay [trace directory]", 350 Short: "Replays a rr trace.", 351 Long: `Replays a rr trace. 352 353 The replay command will open a trace generated by mozilla rr. Mozilla rr must be installed: 354 https://github.com/mozilla/rr 355 `, 356 PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 357 if len(args) == 0 { 358 return errors.New("you must provide a path to a binary") 359 } 360 return nil 361 }, 362 Run: func(cmd *cobra.Command, args []string) { 363 backend = "rr" 364 os.Exit(execute(0, []string{}, conf, args[0], debugger.ExecutingOther, args, buildFlags)) 365 }, 366 } 367 rootCommand.AddCommand(replayCommand) 368 } 369 370 rootCommand.AddCommand(&cobra.Command{ 371 Use: "backend", 372 Short: "Help about the --backend flag.", 373 Long: `The --backend flag specifies which backend should be used, possible values 374 are: 375 376 default Uses lldb on macOS, native everywhere else. 377 native Native backend. 378 lldb Uses lldb-server or debugserver. 379 rr Uses mozilla rr (https://github.com/mozilla/rr). 380 381 `}) 382 383 rootCommand.AddCommand(&cobra.Command{ 384 Use: "log", 385 Short: "Help about logging flags.", 386 Long: `Logging can be enabled by specifying the --log flag and using the 387 --log-output flag to select which components should produce logs. 388 389 The argument of --log-output must be a comma separated list of component 390 names selected from this list: 391 392 393 debugger Log debugger commands 394 gdbwire Log connection to gdbserial backend 395 lldbout Copy output from debugserver/lldb to standard output 396 debuglineerr Log recoverable errors reading .debug_line 397 rpc Log all RPC messages 398 dap Log all DAP messages 399 fncall Log function call protocol 400 minidump Log minidump loading 401 402 Additionally --log-dest can be used to specify where the logs should be 403 written. 404 If the argument is a number it will be interpreted as a file descriptor, 405 otherwise as a file path. 406 This option will also redirect the "server listening at" message in headless 407 and dap modes. 408 409 `, 410 }) 411 412 rootCommand.AddCommand(&cobra.Command{ 413 Use: "redirect", 414 Short: "Help about file redirection.", 415 Long: `The standard file descriptors of the target process can be controlled using the '-r' and '--tty' arguments. 416 417 The --tty argument allows redirecting all standard descriptors to a terminal, specified as an argument to --tty. 418 419 The syntax for '-r' argument is: 420 421 -r [source:]destination 422 423 Where source is one of 'stdin', 'stdout' or 'stderr' and destination is the path to a file. If the source is omitted stdin is used implicitly. 424 425 File redirects can also be changed using the 'restart' command. 426 `, 427 }) 428 429 rootCommand.DisableAutoGenTag = true 430 431 return rootCommand 432 } 433 434 func dapCmd(cmd *cobra.Command, args []string) { 435 status := func() int { 436 if err := logflags.Setup(log, logOutput, logDest); err != nil { 437 fmt.Fprintf(os.Stderr, "%v\n", err) 438 return 1 439 } 440 defer logflags.Close() 441 442 if loadConfErr != nil { 443 logflags.DebuggerLogger().Errorf("%v", loadConfErr) 444 } 445 446 if cmd.Flag("headless").Changed { 447 fmt.Fprintf(os.Stderr, "Warning: dap mode is always headless\n") 448 } 449 if acceptMulti { 450 fmt.Fprintf(os.Stderr, "Warning: accept-multiclient mode not supported with dap\n") 451 } 452 if initFile != "" { 453 fmt.Fprint(os.Stderr, "Warning: init file ignored with dap\n") 454 } 455 if continueOnStart { 456 fmt.Fprintf(os.Stderr, "Warning: continue ignored with dap; specify via launch/attach request instead\n") 457 } 458 if backend != "default" { 459 fmt.Fprintf(os.Stderr, "Warning: backend ignored with dap; specify via launch/attach request instead\n") 460 } 461 if buildFlags != "" { 462 fmt.Fprintf(os.Stderr, "Warning: build flags ignored with dap; specify via launch/attach request instead\n") 463 } 464 if workingDir != "" { 465 fmt.Fprintf(os.Stderr, "Warning: working directory ignored with dap: specify via launch request instead\n") 466 } 467 dlvArgs, targetArgs := splitArgs(cmd, args) 468 if len(dlvArgs) > 0 { 469 fmt.Fprintf(os.Stderr, "Warning: debug arguments ignored with dap; specify via launch/attach request instead\n") 470 } 471 if len(targetArgs) > 0 { 472 fmt.Fprintf(os.Stderr, "Warning: program flags ignored with dap; specify via launch/attach request instead\n") 473 } 474 475 disconnectChan := make(chan struct{}) 476 config := &service.Config{ 477 DisconnectChan: disconnectChan, 478 Debugger: debugger.Config{ 479 Backend: backend, 480 Foreground: true, // server always runs without terminal client 481 DebugInfoDirectories: conf.DebugInfoDirectories, 482 CheckGoVersion: checkGoVersion, 483 DisableASLR: disableASLR, 484 }, 485 CheckLocalConnUser: checkLocalConnUser, 486 } 487 var conn net.Conn 488 if dapClientAddr == "" { 489 listener, err := net.Listen("tcp", addr) 490 if err != nil { 491 fmt.Printf("couldn't start listener: %s\n", err) 492 return 1 493 } 494 config.Listener = listener 495 } else { // with a predetermined client. 496 var err error 497 conn, err = net.Dial("tcp", dapClientAddr) 498 if err != nil { 499 fmt.Fprintf(os.Stderr, "Failed to connect to the DAP client: %v\n", err) 500 return 1 501 } 502 } 503 504 server := dap.NewServer(config) 505 defer server.Stop() 506 if conn == nil { 507 server.Run() 508 } else { // work with a predetermined client. 509 server.RunWithClient(conn) 510 } 511 waitForDisconnectSignal(disconnectChan) 512 return 0 513 }() 514 os.Exit(status) 515 } 516 517 func buildBinary(cmd *cobra.Command, args []string, isTest bool) (string, bool) { 518 debugname, err := filepath.Abs(cmd.Flag("output").Value.String()) 519 if err != nil { 520 fmt.Fprintf(os.Stderr, "%v\n", err) 521 return "", false 522 } 523 524 if isTest { 525 err = gobuild.GoTestBuild(debugname, args, buildFlags) 526 } else { 527 err = gobuild.GoBuild(debugname, args, buildFlags) 528 } 529 if err != nil { 530 fmt.Fprintf(os.Stderr, "%v\n", err) 531 return "", false 532 } 533 return debugname, true 534 } 535 536 func debugCmd(cmd *cobra.Command, args []string) { 537 status := func() int { 538 dlvArgs, targetArgs := splitArgs(cmd, args) 539 debugname, ok := buildBinary(cmd, dlvArgs, false) 540 if !ok { 541 return 1 542 } 543 defer gobuild.Remove(debugname) 544 processArgs := append([]string{debugname}, targetArgs...) 545 return execute(0, processArgs, conf, "", debugger.ExecutingGeneratedFile, dlvArgs, buildFlags) 546 }() 547 os.Exit(status) 548 } 549 550 func traceCmd(cmd *cobra.Command, args []string) { 551 status := func() int { 552 err := logflags.Setup(log, logOutput, logDest) 553 defer logflags.Close() 554 if err != nil { 555 fmt.Fprintf(os.Stderr, "%v\n", err) 556 return 1 557 } 558 if loadConfErr != nil { 559 logflags.DebuggerLogger().Errorf("%v", loadConfErr) 560 } 561 562 if headless { 563 fmt.Fprintf(os.Stderr, "Warning: headless mode not supported with trace\n") 564 } 565 if acceptMulti { 566 fmt.Fprintf(os.Stderr, "Warning: accept multiclient mode not supported with trace") 567 } 568 569 var regexp string 570 var processArgs []string 571 572 dlvArgs, targetArgs := splitArgs(cmd, args) 573 var dlvArgsLen = len(dlvArgs) 574 switch dlvArgsLen { 575 case 0: 576 fmt.Fprintf(os.Stderr, "you must supply a regexp for functions to trace\n") 577 return 1 578 case 1: 579 regexp = args[0] 580 dlvArgs = dlvArgs[0:0] 581 default: 582 regexp = dlvArgs[dlvArgsLen-1] 583 dlvArgs = dlvArgs[:dlvArgsLen-1] 584 } 585 586 var debugname string 587 if traceAttachPid == 0 { 588 if dlvArgsLen >= 2 && traceExecFile != "" { 589 fmt.Fprintln(os.Stderr, "Cannot specify package when using exec.") 590 return 1 591 } 592 593 debugname = traceExecFile 594 if traceExecFile == "" { 595 debugexe, ok := buildBinary(cmd, dlvArgs, traceTestBinary) 596 if !ok { 597 return 1 598 } 599 debugname = debugexe 600 defer gobuild.Remove(debugname) 601 } 602 603 processArgs = append([]string{debugname}, targetArgs...) 604 } 605 606 // Make a local in-memory connection that client and server use to communicate 607 listener, clientConn := service.ListenerPipe() 608 defer listener.Close() 609 610 if workingDir == "" { 611 workingDir = "." 612 } 613 614 // Create and start a debug server 615 server := rpccommon.NewServer(&service.Config{ 616 Listener: listener, 617 ProcessArgs: processArgs, 618 APIVersion: 2, 619 Debugger: debugger.Config{ 620 AttachPid: traceAttachPid, 621 WorkingDir: workingDir, 622 Backend: backend, 623 CheckGoVersion: checkGoVersion, 624 }, 625 }) 626 if err := server.Run(); err != nil { 627 fmt.Fprintln(os.Stderr, err) 628 return 1 629 } 630 client := rpc2.NewClientFromConn(clientConn) 631 funcs, err := client.ListFunctions(regexp) 632 if err != nil { 633 fmt.Fprintln(os.Stderr, err) 634 return 1 635 } 636 for i := range funcs { 637 if traceUseEBPF { 638 err := client.CreateEBPFTracepoint(funcs[i]) 639 if err != nil { 640 fmt.Fprintf(os.Stderr, "unable to set tracepoint on function %s: %#v\n", funcs[i], err) 641 } 642 } else { 643 // Fall back to breakpoint based tracing if we get an error. 644 _, err = client.CreateBreakpoint(&api.Breakpoint{ 645 FunctionName: funcs[i], 646 Tracepoint: true, 647 Line: -1, 648 Stacktrace: traceStackDepth, 649 LoadArgs: &terminal.ShortLoadConfig, 650 }) 651 if err != nil && !isBreakpointExistsErr(err) { 652 fmt.Fprintf(os.Stderr, "unable to set tracepoint on function %s: %#v\n", funcs[i], err) 653 } 654 addrs, err := client.FunctionReturnLocations(funcs[i]) 655 if err != nil { 656 fmt.Fprintf(os.Stderr, "unable to set tracepoint on function %s: %#v\n", funcs[i], err) 657 } 658 for i := range addrs { 659 _, err = client.CreateBreakpoint(&api.Breakpoint{ 660 Addr: addrs[i], 661 TraceReturn: true, 662 Stacktrace: traceStackDepth, 663 Line: -1, 664 LoadArgs: &terminal.ShortLoadConfig, 665 }) 666 if err != nil && !isBreakpointExistsErr(err) { 667 fmt.Fprintf(os.Stderr, "unable to set tracepoint on function %s: %#v\n", funcs[i], err) 668 } 669 } 670 } 671 } 672 cmds := terminal.DebugCommands(client) 673 t := terminal.New(client, nil) 674 t.RedirectTo(os.Stderr) 675 defer t.Close() 676 if traceUseEBPF { 677 done := make(chan struct{}) 678 defer close(done) 679 go func() { 680 gFnEntrySeen := map[int]struct{}{} 681 for { 682 select { 683 case <-done: 684 return 685 default: 686 tracepoints, err := client.GetBufferedTracepoints() 687 if err != nil { 688 panic(err) 689 } 690 for _, t := range tracepoints { 691 var params strings.Builder 692 for _, p := range t.InputParams { 693 if params.Len() > 0 { 694 params.WriteString(", ") 695 } 696 if p.Kind == reflect.String { 697 params.WriteString(fmt.Sprintf("%q", p.Value)) 698 } else { 699 params.WriteString(p.Value) 700 } 701 } 702 _, seen := gFnEntrySeen[t.GoroutineID] 703 if seen { 704 for _, p := range t.ReturnParams { 705 fmt.Fprintf(os.Stderr, "=> %#v\n", p.Value) 706 } 707 delete(gFnEntrySeen, t.GoroutineID) 708 } else { 709 gFnEntrySeen[t.GoroutineID] = struct{}{} 710 fmt.Fprintf(os.Stderr, "> (%d) %s(%s)\n", t.GoroutineID, t.FunctionName, params.String()) 711 } 712 } 713 } 714 } 715 }() 716 } 717 cmds.Call("continue", t) 718 return 0 719 }() 720 os.Exit(status) 721 } 722 723 func isBreakpointExistsErr(err error) bool { 724 return strings.Contains(err.Error(), "Breakpoint exists") 725 } 726 727 func testCmd(cmd *cobra.Command, args []string) { 728 status := func() int { 729 dlvArgs, targetArgs := splitArgs(cmd, args) 730 debugname, ok := buildBinary(cmd, dlvArgs, true) 731 if !ok { 732 return 1 733 } 734 defer gobuild.Remove(debugname) 735 processArgs := append([]string{debugname}, targetArgs...) 736 737 if workingDir == "" { 738 if len(dlvArgs) == 1 { 739 workingDir = getPackageDir(dlvArgs[0]) 740 } else { 741 workingDir = "." 742 } 743 } 744 745 return execute(0, processArgs, conf, "", debugger.ExecutingGeneratedTest, dlvArgs, buildFlags) 746 }() 747 os.Exit(status) 748 } 749 750 func getPackageDir(pkg string) string { 751 out, err := exec.Command("go", "list", "--json", pkg).CombinedOutput() 752 if err != nil { 753 return "." 754 } 755 type listOut struct { 756 Dir string `json:"Dir"` 757 } 758 var listout listOut 759 err = json.Unmarshal(out, &listout) 760 if err != nil { 761 return "." 762 } 763 return listout.Dir 764 } 765 766 func attachCmd(cmd *cobra.Command, args []string) { 767 pid, err := strconv.Atoi(args[0]) 768 if err != nil { 769 fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0]) 770 os.Exit(1) 771 } 772 os.Exit(execute(pid, args[1:], conf, "", debugger.ExecutingOther, args, buildFlags)) 773 } 774 775 func coreCmd(cmd *cobra.Command, args []string) { 776 os.Exit(execute(0, []string{args[0]}, conf, args[1], debugger.ExecutingOther, args, buildFlags)) 777 } 778 779 func connectCmd(cmd *cobra.Command, args []string) { 780 if err := logflags.Setup(log, logOutput, logDest); err != nil { 781 fmt.Fprintf(os.Stderr, "%v\n", err) 782 os.Exit(1) 783 return 784 } 785 defer logflags.Close() 786 if loadConfErr != nil { 787 logflags.DebuggerLogger().Errorf("%v", loadConfErr) 788 } 789 addr := args[0] 790 if addr == "" { 791 fmt.Fprint(os.Stderr, "An empty address was provided. You must provide an address as the first argument.\n") 792 os.Exit(1) 793 } 794 os.Exit(connect(addr, nil, conf, debugger.ExecutingOther)) 795 } 796 797 // waitForDisconnectSignal is a blocking function that waits for either 798 // a SIGINT (Ctrl-C) or SIGTERM (kill -15) OS signal or for disconnectChan 799 // to be closed by the server when the client disconnects. 800 // Note that in headless mode, the debugged process is foregrounded 801 // (to have control of the tty for debugging interactive programs), 802 // so SIGINT gets sent to the debuggee and not to delve. 803 func waitForDisconnectSignal(disconnectChan chan struct{}) { 804 ch := make(chan os.Signal, 1) 805 signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) 806 if runtime.GOOS == "windows" { 807 // On windows Ctrl-C sent to inferior process is delivered 808 // as SIGINT to delve. Ignore it instead of stopping the server 809 // in order to be able to debug signal handlers. 810 go func() { 811 for range ch { 812 } 813 }() 814 <-disconnectChan 815 } else { 816 select { 817 case <-ch: 818 case <-disconnectChan: 819 } 820 } 821 } 822 823 func splitArgs(cmd *cobra.Command, args []string) ([]string, []string) { 824 if cmd.ArgsLenAtDash() >= 0 { 825 return args[:cmd.ArgsLenAtDash()], args[cmd.ArgsLenAtDash():] 826 } 827 return args, []string{} 828 } 829 830 func connect(addr string, clientConn net.Conn, conf *config.Config, kind debugger.ExecuteKind) int { 831 // Create and start a terminal - attach to running instance 832 var client *rpc2.RPCClient 833 if clientConn != nil { 834 client = rpc2.NewClientFromConn(clientConn) 835 } else { 836 client = rpc2.NewClient(addr) 837 } 838 if client.IsMulticlient() { 839 state, _ := client.GetStateNonBlocking() 840 // The error return of GetState will usually be the ErrProcessExited, 841 // which we don't care about. If there are other errors they will show up 842 // later, here we are only concerned about stopping a running target so 843 // that we can initialize our connection. 844 if state != nil && state.Running { 845 _, err := client.Halt() 846 if err != nil { 847 fmt.Fprintf(os.Stderr, "could not halt: %v", err) 848 return 1 849 } 850 } 851 } 852 term := terminal.New(client, conf) 853 term.InitFile = initFile 854 status, err := term.Run() 855 if err != nil { 856 fmt.Println(err) 857 } 858 return status 859 } 860 861 func execute(attachPid int, processArgs []string, conf *config.Config, coreFile string, kind debugger.ExecuteKind, dlvArgs []string, buildFlags string) int { 862 if err := logflags.Setup(log, logOutput, logDest); err != nil { 863 fmt.Fprintf(os.Stderr, "%v\n", err) 864 return 1 865 } 866 defer logflags.Close() 867 if loadConfErr != nil { 868 logflags.DebuggerLogger().Errorf("%v", loadConfErr) 869 } 870 871 if headless && (initFile != "") { 872 fmt.Fprint(os.Stderr, "Warning: init file ignored with --headless\n") 873 } 874 if continueOnStart { 875 if !headless { 876 fmt.Fprint(os.Stderr, "Error: --continue only works with --headless; use an init file\n") 877 return 1 878 } 879 if !acceptMulti { 880 fmt.Fprint(os.Stderr, "Error: --continue requires --accept-multiclient\n") 881 return 1 882 } 883 } 884 885 if !headless && acceptMulti { 886 fmt.Fprint(os.Stderr, "Warning accept-multi: ignored\n") 887 // acceptMulti won't work in normal (non-headless) mode because we always 888 // call server.Stop after the terminal client exits. 889 acceptMulti = false 890 } 891 892 if !headless && !allowNonTerminalInteractive { 893 for _, f := range []struct { 894 name string 895 file *os.File 896 }{{"Stdin", os.Stdin}, {"Stdout", os.Stdout}, {"Stderr", os.Stderr}} { 897 if f.file == nil { 898 continue 899 } 900 if !isatty.IsTerminal(f.file.Fd()) { 901 fmt.Fprintf(os.Stderr, "%s is not a terminal, use '-r' to specify redirects for the target process or --allow-non-terminal-interactive=true if you really want to specify a redirect for Delve\n", f.name) 902 return 1 903 } 904 } 905 } 906 907 if len(redirects) > 0 && tty != "" { 908 fmt.Fprintf(os.Stderr, "Can not use -r and --tty together\n") 909 return 1 910 } 911 912 redirects, err := parseRedirects(redirects) 913 if err != nil { 914 fmt.Fprintf(os.Stderr, "%v\n", err) 915 return 1 916 } 917 918 var listener net.Listener 919 var clientConn net.Conn 920 921 // Make a TCP listener 922 if headless { 923 listener, err = net.Listen("tcp", addr) 924 } else { 925 listener, clientConn = service.ListenerPipe() 926 } 927 if err != nil { 928 fmt.Printf("couldn't start listener: %s\n", err) 929 return 1 930 } 931 defer listener.Close() 932 933 var server service.Server 934 935 disconnectChan := make(chan struct{}) 936 937 if workingDir == "" { 938 workingDir = "." 939 } 940 941 // Create and start a debugger server 942 switch apiVersion { 943 case 1, 2: 944 server = rpccommon.NewServer(&service.Config{ 945 Listener: listener, 946 ProcessArgs: processArgs, 947 AcceptMulti: acceptMulti, 948 APIVersion: apiVersion, 949 CheckLocalConnUser: checkLocalConnUser, 950 DisconnectChan: disconnectChan, 951 Debugger: debugger.Config{ 952 AttachPid: attachPid, 953 WorkingDir: workingDir, 954 Backend: backend, 955 CoreFile: coreFile, 956 Foreground: headless && tty == "", 957 Packages: dlvArgs, 958 BuildFlags: buildFlags, 959 ExecuteKind: kind, 960 DebugInfoDirectories: conf.DebugInfoDirectories, 961 CheckGoVersion: checkGoVersion, 962 TTY: tty, 963 Redirects: redirects, 964 DisableASLR: disableASLR, 965 }, 966 }) 967 default: 968 fmt.Printf("Unknown API version: %d\n", apiVersion) 969 return 1 970 } 971 972 if err := server.Run(); err != nil { 973 if err == api.ErrNotExecutable { 974 switch kind { 975 case debugger.ExecutingGeneratedFile: 976 fmt.Fprintln(os.Stderr, "Can not debug non-main package") 977 return 1 978 case debugger.ExecutingExistingFile: 979 fmt.Fprintf(os.Stderr, "%s is not executable\n", processArgs[0]) 980 return 1 981 default: 982 // fallthrough 983 } 984 } 985 fmt.Fprintln(os.Stderr, err) 986 return 1 987 } 988 989 var status int 990 if headless { 991 if continueOnStart { 992 client := rpc2.NewClient(listener.Addr().String()) 993 client.Disconnect(true) // true = continue after disconnect 994 } 995 waitForDisconnectSignal(disconnectChan) 996 err = server.Stop() 997 if err != nil { 998 fmt.Println(err) 999 } 1000 1001 return status 1002 } 1003 1004 return connect(listener.Addr().String(), clientConn, conf, kind) 1005 } 1006 1007 func parseRedirects(redirects []string) ([3]string, error) { 1008 r := [3]string{} 1009 names := [3]string{"stdin", "stdout", "stderr"} 1010 for _, redirect := range redirects { 1011 idx := 0 1012 for i, name := range names { 1013 pfx := name + ":" 1014 if strings.HasPrefix(redirect, pfx) { 1015 idx = i 1016 redirect = redirect[len(pfx):] 1017 break 1018 } 1019 } 1020 if r[idx] != "" { 1021 return r, fmt.Errorf("redirect error: %s redirected twice", names[idx]) 1022 } 1023 r[idx] = redirect 1024 } 1025 return r, nil 1026 }