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

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

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

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

1. 基本命令

使用如下命令安装

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退出

2. 命令使用

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

2.1 断点设置

1
2
(dlv) b main.go:6
Breakpoint 1 set at 0x4bd2f8 for main.main() F:/Gotest/main.go:6

2.2 打印断点信息

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)

2.3 运行直到断点处

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

2.4 下一步

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

2.5 查看局部变量

1
2
(dlv) locals
a = 10

2.6 查看当前调用栈

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

2.7 打印源代码

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

2.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

3. Goroutine 调试

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

4. 后记

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

支付宝
微信
0%