Golang的Dlv调试

  • Golang
  • 2,624 clicked

1. dlv的安装

delve,简称dlv是go语言的最常用的调试器,获取dlv的操作命令如下:

#下载dlv
go get -u github.com/derekparker/delve/cmd/dlv
#安装dlv
go install github.com/go-delve/delve/cmd/dlv
#验证dlv安装
dlv version

安装后直接运行dlv将会看到如下信息:

[root@localhost ~]# dlv

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.

Pass flags to the program you are debugging using `--`, for example:

`dlv exec ./hello -- server --config conf/config.toml`

Usage:
  dlv [command]

Available Commands:
  attach      Attach to running process and begin debugging.
  connect     Connect to a headless debug server.
  core        Examine a core dump.
  dap         [EXPERIMENTAL] Starts a headless TCP server communicating via Debug Adaptor Protocol (DAP).
  debug       Compile and begin debugging main package in current directory, or the package specified.
  exec        Execute a precompiled binary, and begin a debug session.
  help        Help about any command
  run         Deprecated command. Use 'debug' instead.
  test        Compile test binary and begin debugging program.
  trace       Compile and begin tracing program.
  version     Prints version.

Flags:
      --accept-multiclient               Allows a headless server to accept multiple client connections.
      --allow-non-terminal-interactive   Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
      --api-version int                  Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
      --backend string                   Backend selection (see 'dlv help backend'). (default "default")
      --build-flags string               Build flags, to be passed to the compiler. For example: --build-flags="-tags=integration -mod=vendor -cover -v"
      --check-go-version                 Exits if the version of Go in use is not compatible (too old or too new) with the version of Delve. (default true)
      --disable-aslr                     Disables address space randomization
      --headless                         Run debug server only, in headless mode.
  -h, --help                             help for dlv
      --init string                      Init file, executed by the terminal client.
  -l, --listen string                    Debugging server listen address. (default "127.0.0.1:0")
      --log                              Enable debugging server logging.
      --log-dest string                  Writes logs to the specified file or file descriptor (see 'dlv help log').
      --log-output string                Comma separated list of components that should produce debug output (see 'dlv help log')
      --only-same-user                   Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
  -r, --redirect stringArray             Specifies redirect rules for target process (see 'dlv help redirect')
      --wd string                        Working directory for running the program.

Additional help topics:
  dlv backend  Help about the --backend flag.
  dlv log      Help about logging flags.
  dlv redirect Help about file redirection.

Use "dlv [command] --help" for more information about a command.

2. 基础命令

上面列举了dlv的一些命令,其中常用的有如help、attach、core、debug、trace、version等。

2.1. dlv help

上面的信息只是列出了命令列表,具体使用方法没有给出,这里可以运行dlv help查看具体命令的使用方法。也可以运行dlv help help查看help命令的使用说明。

2.2. dlv version

查看dlv工具版本信息

Delve Debugger
Version: 1.8.0
Build: $Id: 6a6c9c332d5354ddf1f8a2da3cc477bd18d2be53 $

2.3. dlv trace

该命令最直接的用途是可以追踪代码里函数的调用轨迹,可通过help查看其调用方式。

[root@localhost ~]# dlv help trace
Trace program execution.
The trace sub command will set a tracepoint on every function matching the
provided regular expression and output information when tracepoint is hit.  This
is useful if you do not want to begin an entire debug session, but merely want
to know what functions your process is executing.

The output of the trace sub command is printed to stderr, so if you would like to
only see the output of the trace operations you can redirect stdout.

Usage:
dlv trace [package] regexp [flags]

Flags:
    --ebpf            Trace using eBPF (experimental).
-e, --exec string     Binary file to exec and trace.
...

2.4. dlv core

查看core命令使用说明

[root@localhost ~]# dlv help core
Examine a core dump (only supports linux and windows core dumps).
The core command will open the specified core file and the associated
executable and let you examine the state of the process when the
core dump was taken.
Currently supports linux/amd64 and linux/arm64 core files, windows/amd64 minidumps and core files generated by Delve's 'dump' command.

Usage:
dlv core <executable> <core> [flags]
Global Flags:
    --accept-multiclient               Allows a headless server to accept multiple client connections.
    --allow-non-terminal-interactive   Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
    --api-version int                  Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
    --backend string                   Backend selection (see 'dlv help backend'). (default "default")
    --build-flags string               Build flags, to be passed to the compiler. For example: --build-flags="-tags=integration -mod=vendor -cover -v"
    --check-go-version                 Exits if the version of Go in use is not compatible (too old or too new) with the version of Delve. (default true)
    --disable-aslr                     Disables address space randomization
    --headless                         Run debug server only, in headless mode.
    --init string                      Init file, executed by the terminal client.
-l, --listen string                    Debugging server listen address. (default "127.0.0.1:0")
    --log                              Enable debugging server logging.
    --log-dest string                  Writes logs to the specified file or file descriptor (see 'dlv help log').
    --log-output string                Comma separated list of components that should produce debug output (see 'dlv help log')
    --only-same-user                   Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
-r, --redirect stringArray             Specifies redirect rules for target process (see 'dlv help redirect')
    --wd string                        Working directory for running the program.

2.5. dlv debug

大部分命令和gdb类似,break [name] :设置断点,当需要设置多个断点时,为了断点可识别可进行自定义命名。进入调试模式后先打断点,b test.go:14,然后执行c(类似于gdb的run)运行到断点处。

[root@localhost ~]# dlv help debug
Compiles your program with optimizations disabled, starts and attaches to it.
By default, with no arguments, Delve will compile the 'main' package in the
current directory, and begin to debug it. Alternatively you can specify a
package name and Delve will compile that package instead, and begin a new debug
session.
Usage:
dlv debug [package] [flags]
Flags:
    --continue        Continue the debugged process on start.
-h, --help            help for debug
    --output string   Output path for the binary. (default "./__debug_bin")
    --tty string      TTY to use for the target program

3. 进入调试模式的几种方法

  1. dlv attach pid:类似与gdb attach pid,可以对正在运行的进程直接进行调试(pid为进程号)。
  2. dlv debug:运行dlv debug test.go会先编译go源文件,同时执行attach命令进入调试模式,该命令会在当前目录下生成一个名为debug的可执行二进制文件,退出调试模式会自动被删除。
  3. dlv exec executable_file:直接从二进制文件启动调试模式。
  4. dlv core executable_file core_file:以core文件启动调试,通常进行dlv的目的就是为了找出可执行文件core的原因,通过core文件可直接找出具体进程异常的信息。

4. dlv trace 查看调用轨迹

源代码,main函数里每隔1秒执行一次Afunc函数,现用trace命令跟踪其调用轨迹。

func FuncA(){
    fmt.Println("call FuncA")
}

func main(){
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()
    for{
        select{
            case <-ticker.C:
                FuncA()
        }
    }
}
[root@localhost ~]# dlv trace test.go FuncA
> goroutine(1): main.FuncA()
call FuncA
 => ()
> goroutine(1): main.FuncA()
call FuncA
 => ()

5. dlv debug 调试

5.1. 调试实例

正式调试开始,为演示dlv的调试命令,这里写了一个简单的测试程序:启动两个协程对变量进行处理,然后输出处理后的结果。

//main.go
package main
import (
    "fmt"
    "sync"
)
func Func(val int, wg *sync.WaitGroup) int{
    wg.Done()
    return val*val
}

func main(){
    a := 1
    b := 2
    wg := &sync.WaitGroup{}
    wg.Add(a)
    go func(){
        a = Func(a, wg)
    }()

    wg.Add(a)
    go func(){
        b = Func(b, wg)
    }()

    wg.Wait()
    fmt.Println("A=",a,"B=",b)
}

进入调试模式后,同样可以执行help查看所有的命令

[root@localhost ~]# dlv debug main.go
help
Running the program:
    call ------------------------ Resumes process, injecting a function call (EXPERIMENTAL!!!)
    continue (alias: c) --------- Run until breakpoint or program termination.
    next (alias: n) ------------- Step over to next source line.
    rebuild --------------------- Rebuild the target executable and restarts it. It does not work if the executable was not built by delve.
    restart (alias: r) ---------- Restart process.
    step (alias: s) ------------- Single step through program.
    step-instruction (alias: si)  Single step a single cpu instruction.
    stepout (alias: so) --------- Step out of the current function.

Manipulating breakpoints:
    break (alias: b) ------- Sets a breakpoint.
    breakpoints (alias: bp)  Print out info for active breakpoints.
    clear ------------------ Deletes breakpoint.
    clearall --------------- Deletes multiple breakpoints.
    condition (alias: cond)  Set breakpoint condition.
    on --------------------- Executes a command when a breakpoint is hit.
    toggle ----------------- Toggles on or off a breakpoint.
    trace (alias: t) ------- Set tracepoint.
    watch ------------------ Set watchpoint.

Viewing program variables and memory:
    args ----------------- Print function arguments.
    display -------------- Print value of an expression every time the program stops.
    examinemem (alias: x)  Examine raw memory at the given address.
    locals --------------- Print local variables.
    print (alias: p) ----- Evaluate an expression.
    regs ----------------- Print contents of CPU registers.
    set ------------------ Changes the value of a variable.
    vars ----------------- Print package variables.
    whatis --------------- Prints type of an expression.

Listing and switching between threads and goroutines:
    goroutine (alias: gr) -- Shows or changes current goroutine
    goroutines (alias: grs)  List program goroutines.
    thread (alias: tr) ----- Switch to the specified thread.
    threads ---------------- Print out info for every traced thread.

Viewing the call stack and selecting frames:
    deferred --------- Executes command in the context of a deferred call.
    down ------------- Move the current frame down.
    frame ------------ Set the current frame, or execute command on a different frame.
    stack (alias: bt)  Print stack trace.
    up --------------- Move the current frame up.

Other commands:
    config --------------------- Changes configuration parameters.
    disassemble (alias: disass)  Disassembler.
    dump ----------------------- Creates a core dump from the current process state
    edit (alias: ed) ----------- Open where you are in $DELVE_EDITOR or $EDITOR
    exit (alias: quit | q) ----- Exit the debugger.
    funcs ---------------------- Print list of functions.
    help (alias: h) ------------ Prints the help message.
    libraries ------------------ List loaded dynamic libraries
    list (alias: ls | l) ------- Show source code.
    source --------------------- Executes a file containing a list of delve commands
    sources -------------------- Print list of source files.
    types ---------------------- Print list of types

5.2. b: 设置断点

大部分命令和gdb类似,break [name] :设置断点,当需要设置多个断点时,为了断点可识别可进行自定义命名。进入调试模式后先打断点,b test.go:14,然后执行c(类似于gdb的run)运行到断点处。

5.3. bp:查看所有断点

(dlv) bp
Breakpoint runtime-fatal-throw (enabled) at 0x4390a0 for runtime.throw() /usr/local/go/src/runtime/panic.go:1107 (0)
Breakpoint unrecovered-panic (enabled) at 0x439320 for runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1190 (0)
        print runtime.curg._panic.arg
Breakpoint 1 (enabled) at 0x4b6f93 for main.main() ./main.go:21 (0)

5.4. on:指定动作

当运行到某断点时执行相应命令,断点可以是名称(在设置断点时可命名断点)或者编号,例如on 1 p a表示运行到断点1时打印变量a。这里使用continue命令,使debug继续执行

(dlv) on 1 p a
(dlv) c  
> main.main() ./main.go:14 (hits goroutine(1):1 total:1) (PC: 0x4b6d13)
        a: 1
    9:     return val*val
    10: }
    11:
    12: func main(){
    13:     a := 1
=>  14:     b := 2
    15:     wg := &sync.WaitGroup{}
    16:     wg.Add(a)
    17:     go func(){
    18:         a = Func(a, wg)
    19:     }()

5.5. condition 有条件的中断断点

当expression为true时指定的断点将被中断,程序将继续执行。

5.6. next|step:逐行执行代码

区别和gdb类似,next遇到函数调用时不会进入该函数,step则会进入函数,如果需要查看函数的具体执行过程则用step,否则用next,调试过程中一般这两个命令会结合使用,对于用户自定义的函数可能需要进入函数内部查看每步执行情况,对于系统函数则没有必要。

5.7. step-instruction |si:单步单核执行代码

如果不希望多协程并发执行可以使用该命令,这在多协程调试时极为方便。

5.8. stepout:跳出命令

当使用s命令进入某个函数后又想退出时可用此命令。

5.9. args:查看函数参数

调试时可以用此命令查看被调用函数所传入的参数值。

5.10. locals:查看所有局部变量

locals var_name:查看具体某个变量,var_name可以是正则表达式。

5.11. clear:清除单个断点

5.12. clearall:清除所有断点。

5.13. list:打印当前断点位置的源代码

list后面加行号可以展示该行附近的源代码,如list test.go:10将会展示test.go文件第10行附近的代码,值得注意的是该行必须是代码行而不能是空行。

(dlv) list main.go:30
Showing /root/main.go:30 (PC: 0x4b72dd)
25:         a = Func(a, wg)
26:     }()
27:
28:     wg.Add(a)
29:     go func(){
30:         b = Func(b, wg)
31:     }()
32:
33:     wg.Wait()
34:     fmt.Println("A=",a,"B=",b)
35: }

5.14. bt:打印当前栈信息

5.15. frame:切换栈

5.16. regs:打印寄存器内容

go语言的优势就是它的协程,为演示多协程下如何调试go进程,对Func函数做如下改动,增加一个for死循环,每休眠一秒输出变量a。

func Func(val int, wg *sync.WaitGroup) int{
    for {
        a := 1
        fmt.Println(a)
        time.Sleep(time.Second)
    }
    wg.Done()
    return val*val
}

5.17. goroutines: 显示所有协程

编号为1的是主协程,编号为5、6的为自己启动的协程。

(dlv) c
> main.main() ./main.go:33 (hits goroutine(1):1 total:1) (PC: 0x4b709f)
    28:     wg.Add(a)
    29:     go func(){
    30:         b = Func(b, wg)
    31:     }()
    32:
=>  33:     wg.Wait()
    34:     fmt.Println("A=",a,"B=",b)
    35: }
(dlv) //一直打印,按ctrl+c退出到dlv界面
(dlv) goroutines
Goroutine 1 - User: /usr/local/go/src/runtime/sema.go:56 sync.runtime_Semacquire (0x46b945) [semacquire]
Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:337 runtime.gopark (0x43bdd5) [force gc (idle)]
Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:337 runtime.gopark (0x43bdd5) [GC sweep wait]
Goroutine 4 - User: /usr/local/go/src/runtime/proc.go:337 runtime.gopark (0x43bdd5) [GC scavenge wait]
Goroutine 17 - User: /usr/local/go/src/runtime/proc.go:337 runtime.gopark (0x43bdd5) [finalizer wait]
Goroutine 18 - User: /usr/local/go/src/runtime/time.go:193 time.Sleep (0x46beb2) [sleep]    // 我们自己的携程
Goroutine 19 - User: /usr/local/go/src/runtime/time.go:193 time.Sleep (0x46beb2) [sleep]    // 我们自己的携程
[7 goroutines]

5.18. goroutine:协程切换

dlv默认会一直在主协程上执,为打印Func函数中的临时变量a,需要进行协程切换,先执行goroutine 5表示切换到5号协程上,然后bt命令查看当前协程的栈状态,执行frame 3 locals切换到3号栈上并打印栈上的变量。

(dlv) goroutine 18
(dlv) goroutine 18
Switched from 0 to 18 (thread 3587)
(dlv) locals
mp = (*runtime.m)(0x559540)
gp = (*runtime.g)(0xc0000b4180)
status = (unreadable could not find loclist entry at 0x2b65b for address 0x43bdd5)
(dlv) bt
0  0x000000000043bdd5 in runtime.gopark
at /usr/local/go/src/runtime/proc.go:337
1  0x000000000046beb2 in time.Sleep
at /usr/local/go/src/runtime/time.go:193
2  0x00000000004b6f12 in main.Func
at ./main.go:13
3  0x00000000004b729d in main.main.func1
at ./main.go:25
4  0x000000000046ec61 in runtime.goexit
at /usr/local/go/src/runtime/asm_amd64.s:1371

5.19. sources:打印所有源代码文件路径

5.20. source:执行一个含有dlv命令的文件

source命令允许将dlv命令放在一个文件中,然后逐行执行文件内的命令。

5.21. threads:显示所有线程

5.22. thread:线程切换

这两个命令与goroutines、goroutine类似,不过在go语言中一般很少使用。

5.23. trace:类似于打断点

但程序运行到该处时不会中断而是继续执行,同时会输出一行提示信息。

6. 总结

学会使用DLV调试工具进行代码流程分析,是一种很重要的调试手段,同时在实际项目开发中最好在代码里多加一些有意义的log输出,在遇到问题时能快速定位分析。

发表评论

邮箱地址不会被公开。 必填项已用*标注