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

import (
	"fmt"
D
Dan Mace 已提交
5
	"net"
D
Derek Parker 已提交
6
	"os"
D
Dan Mace 已提交
7
	"os/exec"
D
Dan Mace 已提交
8
	"os/signal"
D
Dan Mace 已提交
9 10
	"path/filepath"
	"strconv"
D
Derek Parker 已提交
11
	"strings"
D
Derek Parker 已提交
12

13 14
	sys "golang.org/x/sys/unix"

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

22
const version string = "0.7.0-alpha"
D
Derek Parker 已提交
23

24 25 26 27 28
var (
	Log      bool
	Headless bool
	Addr     string
)
29

D
Derek Parker 已提交
30
func main() {
31 32 33 34
	// Main dlv root command.
	rootCommand := &cobra.Command{
		Use:   "dlv",
		Short: "Delve is a debugger for the Go programming language.",
D
Derek Parker 已提交
35 36 37 38 39 40 41
		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 已提交
42
	}
43 44 45
	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.")
46 47 48 49 50 51 52 53

	// 'version' subcommand.
	versionCommand := &cobra.Command{
		Use:   "version",
		Short: "Prints version.",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("Delve version: " + version)
		},
D
Derek Parker 已提交
54
	}
55 56
	rootCommand.AddCommand(versionCommand)

57
	// Deprecated 'run' subcommand.
58 59
	runCommand := &cobra.Command{
		Use:   "run",
60 61 62 63 64 65 66 67 68 69 70
		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",
71
		Short: "Compile and begin debugging program.",
72
		Long: `Compiles your program with optimizations disabled,
D
Derek Parker 已提交
73
starts and attaches to it, and enables you to immediately begin debugging your program.`,
74
		Run: func(cmd *cobra.Command, args []string) {
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
			status := func() int {
				const debugname = "debug"
				goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l")
				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...)
				return execute(0, processArgs)
			}()
93 94
			os.Exit(status)
		},
M
Matt Butcher 已提交
95
	}
96
	rootCommand.AddCommand(debugCommand)
97

D
Derek Parker 已提交
98
	// 'trace' subcommand.
D
Derek Parker 已提交
99
	var traceAttachPid int
D
Derek Parker 已提交
100
	traceCommand := &cobra.Command{
101
		Use:   "trace [regexp]",
D
Derek Parker 已提交
102 103 104 105 106
		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 {
				const debugname = "debug"
D
Derek Parker 已提交
107 108 109 110 111 112 113 114 115 116 117 118 119 120
				var processArgs []string
				if traceAttachPid == 0 {
					goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l")
					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 已提交
121

D
Derek Parker 已提交
122 123
					processArgs = append([]string{"./" + debugname}, args...)
				}
D
Derek Parker 已提交
124 125 126 127 128 129 130 131 132 133 134 135
				// 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 已提交
136
					AttachPid:   traceAttachPid,
D
Derek Parker 已提交
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
				}, Log)
				if err := server.Run(); err != nil {
					fmt.Fprintln(os.Stderr, err)
					return 1
				}
				sigChan := make(chan os.Signal)
				signal.Notify(sigChan, sys.SIGINT)
				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 {
					_, err := client.CreateBreakpoint(&api.Breakpoint{FunctionName: funcs[i], Tracepoint: true})
					if err != nil {
						fmt.Fprintln(os.Stderr, err)
						return 1
					}
				}
				stateChan := client.Continue()
				for {
					select {
					case state := <-stateChan:
						if state.Err != nil {
							fmt.Fprintln(os.Stderr, state.Err)
							return 0
						}
						var args []string
						var fname string
						if state.CurrentThread != nil && state.CurrentThread.Function != nil {
							fname = state.CurrentThread.Function.Name
						}
						if state.BreakpointInfo != nil {
							for _, arg := range state.BreakpointInfo.Arguments {
								args = append(args, arg.Value)
							}
						}
						fmt.Printf("%s(%s) %s:%d\n", fname, strings.Join(args, ", "), state.CurrentThread.File, state.CurrentThread.Line)
					case <-sigChan:
D
Derek Parker 已提交
177
						server.Stop(traceAttachPid == 0)
D
Derek Parker 已提交
178 179 180 181 182 183 184 185
						return 1
					}
				}
				return 0
			}()
			os.Exit(status)
		},
	}
D
Derek Parker 已提交
186
	traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
D
Derek Parker 已提交
187 188
	rootCommand.AddCommand(traceCommand)

189 190 191 192
	// 'test' subcommand.
	testCommand := &cobra.Command{
		Use:   "test",
		Short: "Compile test binary and begin debugging program.",
193
		Long: `Compiles a test binary with optimizations disabled,
194 195
starts and attaches to it, and enable you to immediately begin debugging your program.`,
		Run: func(cmd *cobra.Command, args []string) {
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
			status := func() int {
				wd, err := os.Getwd()
				if err != nil {
					fmt.Fprintf(os.Stderr, err.Error())
					return 1
				}
				base := filepath.Base(wd)
				goTest := exec.Command("go", "test", "-c", "-gcflags", "-N -l")
				goTest.Stderr = os.Stderr
				err = goTest.Run()
				if err != nil {
					return 1
				}
				debugname := "./" + base + ".test"
				defer os.Remove(debugname)
				processArgs := append([]string{debugname}, args...)

				return execute(0, processArgs)
			}()
215 216 217 218 219 220 221 222 223 224 225 226 227
			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.",
		Run: func(cmd *cobra.Command, args []string) {
			pid, err := strconv.Atoi(args[0])
			if err != nil {
M
moshee 已提交
228
				fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0])
229 230 231 232 233 234
				os.Exit(1)
			}
			os.Exit(execute(pid, nil))
		},
	}
	rootCommand.AddCommand(attachCommand)
M
Matt Butcher 已提交
235

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
	// '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)
			}
			os.Exit(connect(addr))
		},
	}
	rootCommand.AddCommand(connectCommand)

256
	rootCommand.Execute()
D
Derek Parker 已提交
257 258
}

259 260 261 262 263 264 265 266 267 268 269 270
func connect(addr string) int {
	// Create and start a terminal - attach to running instance
	var client service.Client
	client = rpc.NewClient(addr)
	term := terminal.New(client)
	err, status := term.Run()
	if err != nil {
		fmt.Println(err)
	}
	return status
}

271
func execute(attachPid int, processArgs []string) int {
D
Dan Mace 已提交
272
	// Make a TCP listener
273
	listener, err := net.Listen("tcp", Addr)
D
Dan Mace 已提交
274 275
	if err != nil {
		fmt.Printf("couldn't start listener: %s\n", err)
D
Derek Parker 已提交
276
		return 1
D
Dan Mace 已提交
277
	}
278
	defer listener.Close()
D
Dan Mace 已提交
279

D
Derek Parker 已提交
280
	// Create and start a debugger server
281
	server := rpc.NewServer(&service.Config{
282 283 284
		Listener:    listener,
		ProcessArgs: processArgs,
		AttachPid:   attachPid,
285
	}, Log)
286 287 288 289
	if err := server.Run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1
	}
D
Dan Mace 已提交
290

D
Derek Parker 已提交
291
	var status int
292
	if !Headless {
D
Dan Mace 已提交
293
		// Create and start a terminal
D
Derek Parker 已提交
294
		var client service.Client
295
		client = rpc.NewClient(listener.Addr().String())
D
Dan Mace 已提交
296 297 298 299 300 301 302 303 304
		term := terminal.New(client)
		err, status = term.Run()
	} else {
		ch := make(chan os.Signal)
		signal.Notify(ch, sys.SIGINT)
		<-ch
		err = server.Stop(true)
	}

D
Dan Mace 已提交
305 306 307
	if err != nil {
		fmt.Println(err)
	}
D
Derek Parker 已提交
308 309

	return status
D
Derek Parker 已提交
310
}