Golang 深入学习 5 - 使用 dlv 调试程序

警告
本文最后更新于 2020-07-26,文中内容可能已过时。

在 C/C++ 中,使用 GDB 调试程序,但在 Go 中,看过网上的一些文章后,发现 dlv 是个更好的选择,本文进行介绍。

实际上,delve 才是全称,dlv 只是启动命令,如果使用 VScode,默认使用的调试器就是基于 delve 的。

使用如下命令安装

1
go get github.com/go-delve/delve/cmd/dlv

安装后执行 dlv 命令查看基本信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
$ 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.
  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.
      --api-version int      Selects API version when headless. (default 1)
      --backend string       Backend selection (see 'dlv help backend'). (default "default")
      --build-flags string   Build flags, to be passed to the compiler.
      --check-go-version     Checks that the version of Go in use is compatible with Delve. (default true)
      --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)
      --wd string            Working directory for running the program. (default ".")

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

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

进入调试模式有以下几种办法

  1. dlv attach pid:对正在运行的进程直接进行调试(pid 为进程 id);
  2. dlv debug:编译源文件并开始调试,这里应和 main 函数位于同一目录,或者指定完整的 main 函数路径
  3. dlv exec filename:从二进制文件启动调试

我们以下面的程序为例进行说明,使用 dlv debug 进入调试

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
	a := 10
	fmt.Println(a)
}

进入调试,使用 help 可以查看所有可用命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
$ dlv debug main.go
Type 'help' for list of commands.
(dlv) help
The following commands are available:
    args ------------------------ Print function arguments.
    break (alias: b) ------------ Sets a breakpoint.
    breakpoints (alias: bp) ----- Print out info for active breakpoints.
    call ------------------------ Resumes process, injecting a function call (EXPERIMENTAL!!!)
    clear ----------------------- Deletes breakpoint.
    clearall -------------------- Deletes multiple breakpoints.
    condition (alias: cond) ----- Set breakpoint condition.
    config ---------------------- Changes configuration parameters.
    continue (alias: c) --------- Run until breakpoint or program termination.
    deferred -------------------- Executes command in the context of a deferred call.
    disassemble (alias: disass) - Disassembler.
    down ------------------------ Move the current frame down.
    edit (alias: ed) ------------ Open where you are in $DELVE_EDITOR or $EDITOR
    exit (alias: quit | q) ------ Exit the debugger.
    frame ----------------------- Set the current frame, or execute command on a different frame.
    funcs ----------------------- Print list of functions.
    goroutine (alias: gr) ------- Shows or changes current goroutine
    goroutines (alias: grs) ----- List program goroutines.
    help (alias: h) ------------- Prints the help message.
    libraries ------------------- List loaded dynamic libraries
    list (alias: ls | l) -------- Show source code.
    locals ---------------------- Print local variables.
    next (alias: n) ------------- Step over to next source line.
    on -------------------------- Executes a command when a breakpoint is hit.
    print (alias: p) ------------ Evaluate an expression.
    regs ------------------------ Print contents of CPU registers.
    restart (alias: r) ---------- Restart process.
    set ------------------------- Changes the value of a variable.
    source ---------------------- Executes a file containing a list of delve commands
    sources --------------------- Print list of source files.
    stack (alias: bt) ----------- Print stack trace.
    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.
    thread (alias: tr) ---------- Switch to the specified thread.
    threads --------------------- Print out info for every traced thread.
    trace (alias: t) ------------ Set tracepoint.
    types ----------------------- Print list of types
    up -------------------------- Move the current frame up.
    vars ------------------------ Print package variables.
    whatis ---------------------- Prints type of an expression.
Type help followed by a command for full documentation.

常用的命令总结如下:

命令含义
b 设置断点
bp 打印正活动的断点信息
clear 删除断点
clearall 删除所有断点
c 运行直到断点处或程序终止
n 下一步,不会进入函数
s 下一步,会进入函数
so 跳出当前函数
args 查看函数参数
locals 查看所有局部变量
list 打印当前源代码
on 运行到某断点然后执行相应的命令,比如 on 2 list
bt 打印当前调用栈
exit 退出

下面通过实践说明这些简单命令的使用

1
2
(dlv) b main.go:6
Breakpoint 1 set at 0x4bd2f8 for main.main() F:/Gotest/main.go:6
1
2
3
4
5
(dlv) bp
Breakpoint runtime-fatal-throw at 0x4377e0 for runtime.fatalthrow() c:/go/src/runtime/panic.go:1162 (0)
Breakpoint unrecovered-panic at 0x437860 for runtime.fatalpanic() c:/go/src/runtime/panic.go:1189 (0)  
        print runtime.curg._panic.arg
Breakpoint 1 at 0x4bd2f8 for main.main() F:/Gotest/main.go:6 (0)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(dlv) c
> main.main() F:/Gotest/main.go:6 (hits goroutine(1):1 total:1) (PC: 0x4bd2f8)
     1: package main
     2: 
     3: import "fmt"
     4: 
     5: func main() {
=>   6:         a := 10       
     7:         fmt.Println(a)
     8: }
1
2
3
4
5
6
7
8
9
(dlv) n
> main.main() F:/Gotest/main.go:7 (PC: 0x4bd301)
     2: 
     3: import "fmt"
     4: 
     5: func main() {
     6:         a := 10
=>   7:         fmt.Println(a)
     8: }
1
2
(dlv) locals
a = 10
1
2
3
4
5
6
7
(dlv) bt
0  0x00000000004bd301 in main.main
   at F:/Gotest/main.go:7
1  0x0000000000439cfa in runtime.main
   at c:/go/src/runtime/proc.go:203
2  0x00000000004643d1 in runtime.goexit
   at c:/go/src/runtime/asm_amd64.s:1373
1
2
3
4
5
6
7
8
9
(dlv) list
> main.main() F:/Gotest/main.go:7 (PC: 0x4bd301)
     2: 
     3: import "fmt"
     4: 
     5: func main() {
     6:         a := 10
=>   7:         fmt.Println(a)
     8: }

1 是断点 ID,p a 代表指定到断点处打印变量 a 的值

1
2
3
4
(dlv) on 1 p a
(dlv) c
10
Process 6540 has exited with status 0

Go 的优势在协程,dlv 相比 GDB 的优点也在于对协程调试的支持,我们以下面的程序为例,给出示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
	"fmt"
	"time"
)

func main() {
	a := 10
	go printA(a)

	time.Sleep(1e9)
}

func printA(a int) {
	fmt.Println(a)
}

设置断点并启动运行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
> b main.go:12
Breakpoint 1 set at 0x4bd5be for main.main() F:/Gotest/main.go:12
(dlv) c
> main.main() F:/Gotest/main.go:12 (hits goroutine(1):1 total:1) (PC: 0x4bd5be)
     7: 
     8: func main() {
     9:         a := 10
    10:         go printA(a)
    11: 
=>  12:         time.Sleep(1e9)
    13: }
    14: 
    15: func printA(a int) {
    16:         fmt.Println(a)
    17: }

查看当前启动的协程,其中 Goroutine 1 是主协程,Goroutine 6 是自行启动的子协程

1
2
3
4
5
6
7
8
(dlv) goroutines
* Goroutine 1 - User: F:/Gotest/main.go:12 main.main (0x4bd5be) (thread 11412)
  Goroutine 2 - User: c:/go/src/runtime/proc.go:305 runtime.gopark (0x43a092)
  Goroutine 3 - User: c:/go/src/runtime/proc.go:305 runtime.gopark (0x43a092)
  Goroutine 4 - User: c:/go/src/runtime/proc.go:305 runtime.gopark (0x43a092)
  Goroutine 5 - User: c:/go/src/runtime/proc.go:305 runtime.gopark (0x43a092)
  Goroutine 6 - User: F:/Gotest/main.go:15 main.printA (0x4bd5e0)
[6 goroutines]

打印主协程当前的变量

1
2
(dlv) locals
a = 10

如果要查看子协程的情况,需要先切换到子协程

1
2
3
4
5
6
7
8
9
(dlv) goroutine 6
Switched from 1 to 6 (thread 11412)
(dlv) bt
0  0x00000000004bd5e0 in main.printA
   at F:/Gotest/main.go:15
1  0x0000000000464621 in runtime.goexit
   at c:/go/src/runtime/asm_amd64.s:1373
(dlv) args
a = 10

写到这里好像没发现直接使用 dlv 有什么优势,VScode 在 delve 基础上完成的调试功能更方便,比自己使用命令逐步执行好多了。

支付宝
微信
0%