main.go 10.5 KB
Newer Older
D
Derek Parker 已提交
1 2 3
package main

import (
4
	"errors"
D
Derek Parker 已提交
5
	"fmt"
D
Dan Mace 已提交
6
	"net"
D
Derek Parker 已提交
7
	"os"
D
Dan Mace 已提交
8
	"os/exec"
D
Dan Mace 已提交
9
	"os/signal"
D
Dan Mace 已提交
10
	"path/filepath"
D
Derek Parker 已提交
11
	"runtime"
D
Dan Mace 已提交
12
	"strconv"
D
Derek Parker 已提交
13
	"strings"
L
Luke Hoban 已提交
14
	"syscall"
15

16
	"github.com/derekparker/delve/config"
D
Derek Parker 已提交
17
	"github.com/derekparker/delve/service"
D
Derek Parker 已提交
18
	"github.com/derekparker/delve/service/api"
D
Derek Parker 已提交
19
	"github.com/derekparker/delve/service/rpc"
D
Dan Mace 已提交
20
	"github.com/derekparker/delve/terminal"
21

22
	"github.com/spf13/cobra"
D
Derek Parker 已提交
23 24
)

D
Derek Parker 已提交
25
const version string = "0.11.0-alpha"
D
Derek Parker 已提交
26

D
Derek Parker 已提交
27
// Build is the current git hash.
28 29
var Build string

30
var (
D
Derek Parker 已提交
31 32 33 34 35 36 37 38 39
	// Log is whether to log debug statements.
	Log bool
	// Headless is whether to run without terminal.
	Headless 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.
40
	BuildFlags string
41
)
42

D
Derek Parker 已提交
43
func main() {
44 45 46
	// Config setup and load.
	conf := config.LoadConfig()

47 48 49 50
	// Main dlv root command.
	rootCommand := &cobra.Command{
		Use:   "dlv",
		Short: "Delve is a debugger for the Go programming language.",
D
Derek Parker 已提交
51 52 53 54 55 56 57
		Long: `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.
`,
D
Derek Parker 已提交
58
	}
D
Derek Parker 已提交
59

L
Luke Hoban 已提交
60 61
	buildFlagsDefault := ""
	if runtime.GOOS == "windows" {
D
Derek Parker 已提交
62
		// Work-around for https://github.com/golang/go/issues/13154
L
Luke Hoban 已提交
63 64
		buildFlagsDefault = "-ldflags=-linkmode internal"
	}
D
Derek Parker 已提交
65

66 67 68
	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.")
69
	rootCommand.PersistentFlags().StringVar(&InitFile, "init", "", "Init file, executed by the terminal client.")
L
Luke Hoban 已提交
70
	rootCommand.PersistentFlags().StringVar(&BuildFlags, "build-flags", buildFlagsDefault, "Build flags, to be passed to the compiler.")
71 72 73 74 75 76

	// 'version' subcommand.
	versionCommand := &cobra.Command{
		Use:   "version",
		Short: "Prints version.",
		Run: func(cmd *cobra.Command, args []string) {
77
			fmt.Printf("Delve Debugger\nVersion: %s\nBuild: %s\n", version, Build)
78
		},
D
Derek Parker 已提交
79
	}
80 81
	rootCommand.AddCommand(versionCommand)

82
	// Deprecated 'run' subcommand.
83 84
	runCommand := &cobra.Command{
		Use:   "run",
85 86 87 88 89 90 91 92 93 94 95
		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",
96
		Short: "Compile and begin debugging program.",
97
		Long: `Compiles your program with optimizations disabled,
D
Derek Parker 已提交
98
starts and attaches to it, and enables you to immediately begin debugging your program.`,
99
		Run: func(cmd *cobra.Command, args []string) {
100 101
			status := func() int {
				const debugname = "debug"
102
				goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l", BuildFlags)
103 104 105 106 107 108 109 110 111 112 113 114 115
				goBuild.Stderr = os.Stderr
				err := goBuild.Run()
				if err != nil {
					return 1
				}
				fp, err := filepath.Abs("./" + debugname)
				if err != nil {
					fmt.Fprintf(os.Stderr, err.Error())
					return 1
				}
				defer os.Remove(fp)

				processArgs := append([]string{"./" + debugname}, args...)
116
				return execute(0, processArgs, conf)
117
			}()
118 119
			os.Exit(status)
		},
M
Matt Butcher 已提交
120
	}
121
	rootCommand.AddCommand(debugCommand)
122

123 124 125 126
	// 'exec' subcommand.
	execCommand := &cobra.Command{
		Use:   "exec [./path/to/binary]",
		Short: "Runs precompiled binary, attaches and begins debug session.",
127 128 129 130 131 132
		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
		},
133
		Run: func(cmd *cobra.Command, args []string) {
134
			os.Exit(execute(0, args, conf))
135 136 137 138
		},
	}
	rootCommand.AddCommand(execCommand)

D
Derek Parker 已提交
139
	// 'trace' subcommand.
D
Derek Parker 已提交
140
	var traceAttachPid int
D
Derek Parker 已提交
141
	traceCommand := &cobra.Command{
142
		Use:   "trace [regexp]",
D
Derek Parker 已提交
143 144 145 146
		Short: "Compile and begin tracing program.",
		Long:  "Trace program execution. Will set a tracepoint on every function matching [regexp] and output information when tracepoint is hit.",
		Run: func(cmd *cobra.Command, args []string) {
			status := func() int {
147 148 149 150
				if len(args) == 0 {
					fmt.Fprintln(os.Stderr, "You must provide a function to trace.")
					return 1
				}
D
Derek Parker 已提交
151
				const debugname = "debug"
D
Derek Parker 已提交
152 153
				var processArgs []string
				if traceAttachPid == 0 {
154
					goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l", BuildFlags)
D
Derek Parker 已提交
155 156 157 158 159 160 161 162 163 164 165
					goBuild.Stderr = os.Stderr
					err := goBuild.Run()
					if err != nil {
						return 1
					}
					fp, err := filepath.Abs("./" + debugname)
					if err != nil {
						fmt.Fprintf(os.Stderr, err.Error())
						return 1
					}
					defer os.Remove(fp)
D
Derek Parker 已提交
166

D
Derek Parker 已提交
167 168
					processArgs = append([]string{"./" + debugname}, args...)
				}
D
Derek Parker 已提交
169 170 171 172 173 174 175 176 177 178 179 180
				// 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 debugger server
				server := rpc.NewServer(&service.Config{
					Listener:    listener,
					ProcessArgs: processArgs,
D
Derek Parker 已提交
181
					AttachPid:   traceAttachPid,
D
Derek Parker 已提交
182 183 184 185 186 187
				}, Log)
				if err := server.Run(); err != nil {
					fmt.Fprintln(os.Stderr, err)
					return 1
				}
				sigChan := make(chan os.Signal)
L
Luke Hoban 已提交
188
				signal.Notify(sigChan, syscall.SIGINT)
D
Derek Parker 已提交
189 190 191 192 193 194 195
				client := rpc.NewClient(listener.Addr().String())
				funcs, err := client.ListFunctions(args[0])
				if err != nil {
					fmt.Fprintln(os.Stderr, err)
					return 1
				}
				for i := range funcs {
196
					_, err := client.CreateBreakpoint(&api.Breakpoint{FunctionName: funcs[i], Line: -1, Tracepoint: true})
D
Derek Parker 已提交
197 198 199 200 201 202 203 204 205
					if err != nil {
						fmt.Fprintln(os.Stderr, err)
						return 1
					}
				}
				stateChan := client.Continue()
				for {
					select {
					case state := <-stateChan:
206 207 208
						if state == nil {
							return 0
						}
D
Derek Parker 已提交
209 210 211 212
						if state.Err != nil {
							fmt.Fprintln(os.Stderr, state.Err)
							return 0
						}
213 214 215 216 217 218 219 220 221 222 223 224 225 226
						for i := range state.Threads {
							th := state.Threads[i]
							if th.Breakpoint == nil {
								continue
							}
							var args []string
							var fname string
							if th.Function != nil {
								fname = th.Function.Name
							}
							if th.BreakpointInfo != nil {
								for _, arg := range th.BreakpointInfo.Arguments {
									args = append(args, arg.SinglelineString())
								}
D
Derek Parker 已提交
227
							}
228
							fmt.Printf("%s(%s) %s:%d\n", fname, strings.Join(args, ", "), terminal.ShortenFilePath(th.File), th.Line)
D
Derek Parker 已提交
229 230
						}
					case <-sigChan:
D
Derek Parker 已提交
231
						server.Stop(traceAttachPid == 0)
D
Derek Parker 已提交
232 233 234 235 236 237 238
						return 1
					}
				}
			}()
			os.Exit(status)
		},
	}
D
Derek Parker 已提交
239
	traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
D
Derek Parker 已提交
240 241
	rootCommand.AddCommand(traceCommand)

242 243 244 245
	// 'test' subcommand.
	testCommand := &cobra.Command{
		Use:   "test",
		Short: "Compile test binary and begin debugging program.",
246
		Long: `Compiles a test binary with optimizations disabled,
247 248
starts and attaches to it, and enable you to immediately begin debugging your program.`,
		Run: func(cmd *cobra.Command, args []string) {
249 250 251 252 253 254 255
			status := func() int {
				wd, err := os.Getwd()
				if err != nil {
					fmt.Fprintf(os.Stderr, err.Error())
					return 1
				}
				base := filepath.Base(wd)
256
				goTest := exec.Command("go", "test", "-c", "-gcflags", "-N -l", BuildFlags)
257 258 259 260 261 262
				goTest.Stderr = os.Stderr
				err = goTest.Run()
				if err != nil {
					return 1
				}
				debugname := "./" + base + ".test"
263 264 265
				// On Windows, "go test" generates an executable with the ".exe" extension
				if runtime.GOOS == "windows" {
					debugname += ".exe"
D
Derek Parker 已提交
266
				}
267 268 269
				defer os.Remove(debugname)
				processArgs := append([]string{debugname}, args...)

270
				return execute(0, processArgs, conf)
271
			}()
272 273 274 275 276 277 278 279 280 281
			os.Exit(status)
		},
	}
	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.",
282 283 284 285 286 287
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			if len(args) == 0 {
				return errors.New("you must provide a PID")
			}
			return nil
		},
288 289 290
		Run: func(cmd *cobra.Command, args []string) {
			pid, err := strconv.Atoi(args[0])
			if err != nil {
M
moshee 已提交
291
				fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0])
292 293
				os.Exit(1)
			}
294
			os.Exit(execute(pid, nil, conf))
295 296 297
		},
	}
	rootCommand.AddCommand(attachCommand)
M
Matt Butcher 已提交
298

299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
	// 'connect' subcommand.
	connectCommand := &cobra.Command{
		Use:   "connect [addr]",
		Short: "Connect to a headless debug server.",
		Long:  "Connect to a headless debug server.",
		Run: func(cmd *cobra.Command, args []string) {
			if len(args) == 0 {
				fmt.Fprintf(os.Stderr, "An address was not provided. You must provide an address as the first argument.\n")
				os.Exit(1)
			}
			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)
			}
314
			os.Exit(connect(addr, conf))
315 316 317 318
		},
	}
	rootCommand.AddCommand(connectCommand)

319
	rootCommand.Execute()
D
Derek Parker 已提交
320 321
}

322
func connect(addr string, conf *config.Config) int {
323 324 325
	// Create and start a terminal - attach to running instance
	var client service.Client
	client = rpc.NewClient(addr)
326
	term := terminal.New(client, conf)
D
Derek Parker 已提交
327
	status, err := term.Run()
328 329 330 331 332 333
	if err != nil {
		fmt.Println(err)
	}
	return status
}

334
func execute(attachPid int, processArgs []string, conf *config.Config) int {
D
Dan Mace 已提交
335
	// Make a TCP listener
336
	listener, err := net.Listen("tcp", Addr)
D
Dan Mace 已提交
337 338
	if err != nil {
		fmt.Printf("couldn't start listener: %s\n", err)
D
Derek Parker 已提交
339
		return 1
D
Dan Mace 已提交
340
	}
341
	defer listener.Close()
D
Dan Mace 已提交
342

343 344 345 346
	if Headless && (InitFile != "") {
		fmt.Fprintf(os.Stderr, "Warning: init file ignored\n")
	}

D
Derek Parker 已提交
347
	// Create and start a debugger server
348
	server := rpc.NewServer(&service.Config{
349 350 351
		Listener:    listener,
		ProcessArgs: processArgs,
		AttachPid:   attachPid,
352
	}, Log)
353 354 355 356
	if err := server.Run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1
	}
D
Dan Mace 已提交
357

D
Derek Parker 已提交
358
	var status int
359
	if !Headless {
D
Dan Mace 已提交
360
		// Create and start a terminal
D
Derek Parker 已提交
361
		var client service.Client
362
		client = rpc.NewClient(listener.Addr().String())
363
		term := terminal.New(client, conf)
364
		term.InitFile = InitFile
D
Derek Parker 已提交
365
		status, err = term.Run()
D
Dan Mace 已提交
366 367
	} else {
		ch := make(chan os.Signal)
L
Luke Hoban 已提交
368
		signal.Notify(ch, syscall.SIGINT)
D
Dan Mace 已提交
369 370 371 372
		<-ch
		err = server.Stop(true)
	}

D
Dan Mace 已提交
373 374 375
	if err != nil {
		fmt.Println(err)
	}
D
Derek Parker 已提交
376 377

	return status
D
Derek Parker 已提交
378
}