Delve代码分析笔记(5)——debug命令

dlv debug命令会编译并且调试一个package,其代码如下:

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)
}

其中gobuild函数实现如下:

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...)
}

dlv debug命令其实就是在当前目录下临时编译生成一个没有代码优化的可执行文件,文件名是debug。接下来就是调用execute函数对这个debug文件进行调试。dlv程序退出后,会删除这个文件:defer os.Remove(fp)

 

Delve代码分析笔记(4)——构建command tree

Delve使用cobra来构造command tree。先看root command,也就是dlv

func New() *cobra.Command {
    ......
    // 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().IntVar(&ApiVersion, "api-version", 1, "Selects API version when headless")
    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.")
    ......
}

因为dlv command没有实现run函数,所以单独运行dlv命令只会打印cobra帮忙生成的默认输出:

# dlv
Delve is a source level debugger for Go programs.

......

Usage:
  dlv [command]

Available Commands:
  version     Prints version.
  ......
Flags:
      --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
    ......

依次为Long descriptionUsageAvailable Commands等等。

再以trace subcommand为例,看如何把subcommand加到root command里:

......
// '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)
......

Cobra提供两种flags
a)Persistent Flags:对当前命令及其子命令都有效;
b)Local Flags:只对当前命令有效。

 

Delve代码分析笔记(3)——config

Delve程序运行起来以后,首先就会加载和解析配置文件:

func New() *cobra.Command {
    // Config setup and load.
    conf = config.LoadConfig()
    ......
}

Delve的配置文件位于用户home目录下的.dlv文件夹下,文件名是config.yml。例如,如果是root用户,则配置文件的全路径是:/root/.dlv/config.yml。目前配置文件只支持为命令指定别名。

config包只包含一个config.go文件。代码也比较简单,归纳起来就是:如果用户没有创建配置文件,则替用户创建一个(里面没有实质内容),然后读取配置文件内容,并把Config结构体(定义如下)返回给调用者。

// Config defines all configuration options available to be set through the config file.
type Config struct {
    Aliases map[string][]string
}

 

Delve代码分析笔记(2)——version

Version包只包含了一个version.go

package version

import "fmt"

// Version represents the current version of Delve.
type Version struct {
    Major    string
    Minor    string
    Patch    string
    Metadata string
    Build    string
}

var (
    // DelveVersion is the current version of Delve.
    DelveVersion = Version{Major: "0", Minor: "11", Patch: "0", Metadata: "alpha"}
)

func (v Version) String() string {
    return fmt.Sprintf("Version: %s.%s.%s-%s\nBuild: %s", v.Major, v.Minor, v.Patch, v.Metadata, v.Build)
}

Version包定义了一个Version类型的变量:DelveVersion。而Version类型的String()方法就是用来构造执行dlv version命令时,输出的字符串(cmd/dlv/cmds/commands.go):

......
// '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)
        },
    }
......

执行dlv version命令:

# dlv version
Delve Debugger
Version: 0.11.0-alpha
Build:

 

Delve代码分析笔记(1)——main.go

delve的项目主页:https://github.com/derekparker/delve
main.go的代码比较简单:

package main

import (
    "github.com/derekparker/delve/cmd/dlv/cmds"
    "github.com/derekparker/delve/version"
)

// Build is the git sha of this binaries build.
var Build string

func main() {
    version.DelveVersion.Build = Build
    cmds.New().Execute()
}

main函数就干了两件事:
(1)把Build的值赋给version.DelveVersion变量中的Build成员;
(2)cmds.New()返回一个cobra.Commandtree,然后调用Execute()函数执行相应的子命令,例如dlv version