delve/cmd/dlv/cmds/commands.go

406 lines
11 KiB
Go
Raw Normal View History

package cmds
import (
"errors"
"fmt"
"net"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"syscall"
"github.com/derekparker/delve/config"
"github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api"
"github.com/derekparker/delve/service/rpc"
"github.com/derekparker/delve/terminal"
"github.com/derekparker/delve/version"
"github.com/spf13/cobra"
)
var (
// Log is whether to log debug statements.
Log bool
// Headless is whether to run without terminal.
Headless bool
// 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
// RootCommand is the root of the command tree.
RootCommand *cobra.Command
traceAttachPid int
traceStackDepth int
conf *config.Config
)
const (
debugname = "debug"
testdebugname = "debug.test"
)
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.
`
// New returns an initialized command tree.
func New() *cobra.Command {
// Config setup and load.
conf = config.LoadConfig()
buildFlagsDefault := ""
if runtime.GOOS == "windows" {
// Work-around for https://github.com/golang/go/issues/13154
buildFlagsDefault = "-ldflags=-linkmode internal"
}
// 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.")
RootCommand.PersistentFlags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.")
RootCommand.PersistentFlags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")
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")
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.")
// '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)
},
}
RootCommand.AddCommand(versionCommand)
// 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)
},
}
RootCommand.AddCommand(runCommand)
// 'debug' subcommand.
debugCommand := &cobra.Command{
Use: "debug [package]",
Short: "Compile and begin debugging program.",
Long: `Compiles your program with optimizations disabled,
starts and attaches to it, and enables you to immediately begin debugging your program.`,
Run: debugCmd,
}
RootCommand.AddCommand(debugCommand)
// 'exec' subcommand.
execCommand := &cobra.Command{
Use: "exec [./path/to/binary]",
Short: "Runs precompiled binary, attaches and begins debug session.",
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) {
os.Exit(execute(0, args, conf))
},
}
RootCommand.AddCommand(execCommand)
// 'trace' subcommand.
traceCommand := &cobra.Command{
Use: "trace [package] regexp",
Short: "Compile and begin tracing program.",
Long: "Trace program execution. Will set a tracepoint on every function matching the provided regular expression and output information when tracepoint is hit.",
Run: traceCmd,
}
traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
traceCommand.Flags().IntVarP(&traceStackDepth, "stack", "s", 0, "Show stack trace with given depth.")
RootCommand.AddCommand(traceCommand)
// 'test' subcommand.
testCommand := &cobra.Command{
Use: "test [package]",
Short: "Compile test binary and begin debugging program.",
Long: `Compiles a test binary with optimizations disabled, starts and attaches to it, and enable you to immediately begin debugging your program.`,
Run: testCmd,
}
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.",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("you must provide a PID")
}
return nil
},
Run: attachCmd,
}
RootCommand.AddCommand(attachCommand)
// 'connect' subcommand.
connectCommand := &cobra.Command{
Use: "connect addr",
Short: "Connect to a headless debug server.",
Long: "Connect to a headless debug server.",
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
},
Run: connectCmd,
}
RootCommand.AddCommand(connectCommand)
return RootCommand
}
func debugCmd(cmd *cobra.Command, args []string) {
status := func() int {
var pkg string
dlvArgs, targetArgs := splitArgs(cmd, args)
if len(dlvArgs) > 0 {
pkg = args[0]
}
err := gobuild(debugname, pkg)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
fp, err := filepath.Abs("./" + debugname)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
defer os.Remove(fp)
processArgs := append([]string{"./" + debugname}, targetArgs...)
return execute(0, processArgs, conf)
}()
os.Exit(status)
}
func traceCmd(cmd *cobra.Command, args []string) {
status := func() int {
var regexp string
var processArgs []string
dlvArgs, targetArgs := splitArgs(cmd, args)
if traceAttachPid == 0 {
var pkg string
switch len(dlvArgs) {
case 1:
regexp = args[0]
case 2:
pkg = args[0]
regexp = args[1]
}
if err := gobuild(debugname, pkg); err != nil {
return 1
}
defer os.Remove("./" + debugname)
processArgs = append([]string{"./" + debugname}, targetArgs...)
}
// 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 debug server
server := rpc.NewServer(&service.Config{
Listener: listener,
ProcessArgs: processArgs,
AttachPid: traceAttachPid,
}, Log)
if err := server.Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
client := rpc.NewClient(listener.Addr().String())
funcs, err := client.ListFunctions(regexp)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
for i := range funcs {
2016-03-09 15:11:58 +00:00
_, err = client.CreateBreakpoint(&api.Breakpoint{FunctionName: funcs[i], Tracepoint: true, Line: -1, Stacktrace: traceStackDepth})
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
}
cmds := terminal.DebugCommands(client)
t := terminal.New(client, nil)
defer t.Close()
err = cmds.Call("continue", "", t)
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 {
var pkg string
dlvArgs, targetArgs := splitArgs(cmd, args)
if len(dlvArgs) > 0 {
pkg = args[0]
}
err := gotestbuild(pkg)
if err != nil {
return 1
}
defer os.Remove("./" + testdebugname)
processArgs := append([]string{"./" + testdebugname}, targetArgs...)
return execute(0, processArgs, conf)
}()
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)
}
os.Exit(execute(pid, nil, conf))
}
func connectCmd(cmd *cobra.Command, args []string) {
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, conf))
}
func splitArgs(cmd *cobra.Command, args []string) ([]string, []string) {
if cmd.ArgsLenAtDash() >= 0 {
return args[:cmd.ArgsLenAtDash()], args[cmd.ArgsLenAtDash():]
}
return args, []string{}
}
func connect(addr string, conf *config.Config) int {
// Create and start a terminal - attach to running instance
var client service.Client
client = rpc.NewClient(addr)
term := terminal.New(client, conf)
status, err := term.Run()
if err != nil {
fmt.Println(err)
}
return status
}
func execute(attachPid int, processArgs []string, conf *config.Config) int {
// 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()
if Headless && (InitFile != "") {
fmt.Fprintf(os.Stderr, "Warning: init file ignored\n")
}
// Create and start a debugger server
server := rpc.NewServer(&service.Config{
Listener: listener,
ProcessArgs: processArgs,
AttachPid: attachPid,
AcceptMulti: AcceptMulti,
}, Log)
if err := server.Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
var status int
if Headless {
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT)
<-ch
err = server.Stop(true)
} else {
// Create and start a terminal
var client service.Client
client = rpc.NewClient(listener.Addr().String())
term := terminal.New(client, conf)
term.InitFile = InitFile
status, err = term.Run()
}
if err != nil {
fmt.Println(err)
}
return status
}
func gobuild(debugname, pkg string) error {
args := []string{"-gcflags", "-N -l", "-o", debugname}
if BuildFlags != "" {
args = append(args, BuildFlags)
}
args = append(args, pkg)
return gocommand("build", args...)
}
func gotestbuild(pkg string) error {
args := []string{"-gcflags", "-N -l", "-c", "-o", testdebugname}
if BuildFlags != "" {
args = append(args, BuildFlags)
}
args = append(args, pkg)
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()
}