main.go 10.0 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"
L
Luke Hoban 已提交
13
	"syscall"
14

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

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

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

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

29
var (
D
Derek Parker 已提交
30 31 32 33 34 35 36 37 38
	// 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.
39
	BuildFlags string
40
)
41

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

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

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

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

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

81
	// Deprecated 'run' subcommand.
82 83
	runCommand := &cobra.Command{
		Use:   "run",
84 85 86 87 88 89 90 91 92 93 94
		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",
95
		Short: "Compile and begin debugging program.",
96
		Long: `Compiles your program with optimizations disabled,
D
Derek Parker 已提交
97
starts and attaches to it, and enables you to immediately begin debugging your program.`,
98
		Run: func(cmd *cobra.Command, args []string) {
99 100
			status := func() int {
				const debugname = "debug"
101
				goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l", BuildFlags)
102 103 104 105 106 107 108 109 110 111 112 113 114
				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...)
115
				return execute(0, processArgs, conf)
116
			}()
117 118
			os.Exit(status)
		},
M
Matt Butcher 已提交
119
	}
120
	rootCommand.AddCommand(debugCommand)
121

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

D
Derek Parker 已提交
138
	// 'trace' subcommand.
139
	var traceAttachPid, traceStackDepth int
D
Derek Parker 已提交
140
	traceCommand := &cobra.Command{
141
		Use:   "trace [regexp]",
D
Derek Parker 已提交
142 143 144 145
		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 {
146 147 148 149
				if len(args) == 0 {
					fmt.Fprintln(os.Stderr, "You must provide a function to trace.")
					return 1
				}
D
Derek Parker 已提交
150
				const debugname = "debug"
D
Derek Parker 已提交
151 152
				var processArgs []string
				if traceAttachPid == 0 {
153
					goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l", BuildFlags)
D
Derek Parker 已提交
154 155 156 157 158 159 160 161 162 163 164
					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 已提交
165

D
Derek Parker 已提交
166 167
					processArgs = append([]string{"./" + debugname}, args...)
				}
D
Derek Parker 已提交
168 169 170 171 172 173 174 175 176 177 178 179
				// 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 已提交
180
					AttachPid:   traceAttachPid,
D
Derek Parker 已提交
181 182 183 184 185 186
				}, Log)
				if err := server.Run(); err != nil {
					fmt.Fprintln(os.Stderr, err)
					return 1
				}
				sigChan := make(chan os.Signal)
L
Luke Hoban 已提交
187
				signal.Notify(sigChan, syscall.SIGINT)
D
Derek Parker 已提交
188 189 190 191 192 193 194
				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 {
195
					_, err := client.CreateBreakpoint(&api.Breakpoint{FunctionName: funcs[i], Line: -1, Tracepoint: true, Stacktrace: traceStackDepth})
D
Derek Parker 已提交
196 197 198 199 200
					if err != nil {
						fmt.Fprintln(os.Stderr, err)
						return 1
					}
				}
201 202 203 204 205 206
				cmds := terminal.DebugCommands(client)
				cmd := cmds.Find("continue")
				err = cmd(terminal.New(client, nil), "")
				if err != nil {
					fmt.Fprintln(os.Stderr, err)
					return 1
D
Derek Parker 已提交
207
				}
208
				return 0
D
Derek Parker 已提交
209 210 211 212
			}()
			os.Exit(status)
		},
	}
D
Derek Parker 已提交
213
	traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
214
	traceCommand.Flags().IntVarP(&traceStackDepth, "stack", "s", 0, "Show stack trace with given depth.")
D
Derek Parker 已提交
215 216
	rootCommand.AddCommand(traceCommand)

217 218 219 220
	// 'test' subcommand.
	testCommand := &cobra.Command{
		Use:   "test",
		Short: "Compile test binary and begin debugging program.",
221
		Long: `Compiles a test binary with optimizations disabled,
222 223
starts and attaches to it, and enable you to immediately begin debugging your program.`,
		Run: func(cmd *cobra.Command, args []string) {
224 225 226 227 228 229 230
			status := func() int {
				wd, err := os.Getwd()
				if err != nil {
					fmt.Fprintf(os.Stderr, err.Error())
					return 1
				}
				base := filepath.Base(wd)
231
				goTest := exec.Command("go", "test", "-c", "-gcflags", "-N -l", BuildFlags)
232 233 234 235 236 237
				goTest.Stderr = os.Stderr
				err = goTest.Run()
				if err != nil {
					return 1
				}
				debugname := "./" + base + ".test"
238 239 240
				// On Windows, "go test" generates an executable with the ".exe" extension
				if runtime.GOOS == "windows" {
					debugname += ".exe"
D
Derek Parker 已提交
241
				}
242 243 244
				defer os.Remove(debugname)
				processArgs := append([]string{debugname}, args...)

245
				return execute(0, processArgs, conf)
246
			}()
247 248 249 250 251 252 253 254 255 256
			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.",
257 258 259 260 261 262
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			if len(args) == 0 {
				return errors.New("you must provide a PID")
			}
			return nil
		},
263 264 265
		Run: func(cmd *cobra.Command, args []string) {
			pid, err := strconv.Atoi(args[0])
			if err != nil {
M
moshee 已提交
266
				fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0])
267 268
				os.Exit(1)
			}
269
			os.Exit(execute(pid, nil, conf))
270 271 272
		},
	}
	rootCommand.AddCommand(attachCommand)
M
Matt Butcher 已提交
273

274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
	// '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)
			}
289
			os.Exit(connect(addr, conf))
290 291 292 293
		},
	}
	rootCommand.AddCommand(connectCommand)

294
	rootCommand.Execute()
D
Derek Parker 已提交
295 296
}

297
func connect(addr string, conf *config.Config) int {
298 299 300
	// Create and start a terminal - attach to running instance
	var client service.Client
	client = rpc.NewClient(addr)
301
	term := terminal.New(client, conf)
D
Derek Parker 已提交
302
	status, err := term.Run()
303 304 305 306 307 308
	if err != nil {
		fmt.Println(err)
	}
	return status
}

309
func execute(attachPid int, processArgs []string, conf *config.Config) int {
D
Dan Mace 已提交
310
	// Make a TCP listener
311
	listener, err := net.Listen("tcp", Addr)
D
Dan Mace 已提交
312 313
	if err != nil {
		fmt.Printf("couldn't start listener: %s\n", err)
D
Derek Parker 已提交
314
		return 1
D
Dan Mace 已提交
315
	}
316
	defer listener.Close()
D
Dan Mace 已提交
317

318 319 320 321
	if Headless && (InitFile != "") {
		fmt.Fprintf(os.Stderr, "Warning: init file ignored\n")
	}

D
Derek Parker 已提交
322
	// Create and start a debugger server
323
	server := rpc.NewServer(&service.Config{
324 325 326
		Listener:    listener,
		ProcessArgs: processArgs,
		AttachPid:   attachPid,
327
	}, Log)
328 329 330 331
	if err := server.Run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1
	}
D
Dan Mace 已提交
332

D
Derek Parker 已提交
333
	var status int
334
	if !Headless {
D
Dan Mace 已提交
335
		// Create and start a terminal
D
Derek Parker 已提交
336
		var client service.Client
337
		client = rpc.NewClient(listener.Addr().String())
338
		term := terminal.New(client, conf)
339
		term.InitFile = InitFile
D
Derek Parker 已提交
340
		status, err = term.Run()
D
Dan Mace 已提交
341 342
	} else {
		ch := make(chan os.Signal)
L
Luke Hoban 已提交
343
		signal.Notify(ch, syscall.SIGINT)
D
Dan Mace 已提交
344 345 346 347
		<-ch
		err = server.Stop(true)
	}

D
Dan Mace 已提交
348 349 350
	if err != nil {
		fmt.Println(err)
	}
D
Derek Parker 已提交
351 352

	return status
D
Derek Parker 已提交
353
}