Delve是一个go语言的第三方调试器,github地址是: https://github.com/go-delve/delve 。 Delve是GDB调试器的有效替代品。与GDB相比,它能更高的理解Go的运行时,数据结构以及表达式。Delve目前支持Linux,OSX以及Windows的amd64平台。
本文主要介绍使用delve调试器如何调试Go程序。内容包含如下: + 下载安装Delve + 查看源码命令组, + 添加及管理断点命令组(添加断点、查看断点、清除断点等) + 控制程序的执行流程命令组 + 查看当前状态的变量及表达式的值命令组。
阅读完本文后,你将能够使用Delve工具很容易的调试你的go程序。
下载并安装Go Delve
- 安装方式一:
$ git clone https://github.com/go-delve/delve
$ cd delve
$ go install github.com/go-delve/delve/cmd/dlv
- 安装方式二:(适用于Go 1.16及以后版本)
$ go install github.com/go-delve/delve/cmd/dlv@latest
执行完命令后,dlv命令被安装在 $GOPATH/bin目录下。如果没有设置$GOPATH,则被默认安装在$HOME/go/bin目录下。
输入dlv命令检查是否安装成功:
$ dlv version
Delve Debugger
Version: 1.6.0
Build $Id: 8cc9751909843dd55a46e8ea2a561544f70db34d $
如果在终端输入dlv命令提示找不到该命令,则将$GOPATH/bin下的dlv命令软链接到/usr/local/bin目录。即保证dlv命令在$PATH环境变量下。
调试常用命令
首先我们通过下面的斐波那契数列函数代码作为示例讲解。
package main
import "fmt"
var m = make(map[int]int, 0)
func main() {
for _, n := range []int{5, 1, 9, 98, 6} {
x := fib(n)
fmt.Println(n, "fib", x)
}
}
func fib(n int) int {
if n < 2 {
return n
}
var f int
if v, ok := m[n]; ok {
f = v
} else {
f = fib(n-2) + fib(n-1)
m[n] = f
}
return f
}
在当前目录下输入dlv debug命令,编译并启动一个调试会话。
$ dlv debug main.go
Type 'help' for list of commands.
(dlv)
Delve客户端
通过执行 dlv debug即开启了一个调试的会话。dlv编译程序并附加到二进制中,接下来我们可以开始调试我们的程序了。通过dlv debug命令,我们即开启了一个delve的解释器,我们可以称它为Delve客户端,由这个客户端发送调试的命令到delve的服务端。
在开启的delve客户端下,我们输入help命令,可以查看所有可用的子命令。如下:
Type 'help' for list of commands.
(dlv) help
The following commands are available:
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. //重启动该调试进程,会保留之前设置的所有断点。和rebuild的区别是,restart命令不会重新编译源文件,即在调试过程中,如果源文件有变更,restart命令后不会体现。
step (alias: s) ------------- Single step through program. // 单步执行。如果遇到函数调用,则进入到被调用的函数中。和next的区别是,当next遇到函数调用时,不进入函数内部,仍留在主函数中。具体例子中会讲解。
step-instruction (alias: si) Single step a single cpu instruction. //单步执行cpu指令。
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. //当遇到断点时,执行一个命令
trace (alias: t) ------- Set tracepoint. //设置trace断点
Viewing program variables and memory: //查看变量和内存的命令
args ----------------- Print function arguments.
display -------------- Print value of an expression every time the program stops.
examinemem (alias: x) Examine memory:
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.
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
Type help followed by a command for full documentation.
查看源码命令组
我们要介绍的第一个命令是 list,该命令允许我们查看给定行的源代码。我们可以通过包名+函数名、文件名+行数方式指定要查看的源文件
List
[goroutine <n>] [frame <m>] list [<linespec>]
如下所示,通过包名+函数名的方式查看源代码:
(dlv) list main.main
Show /workspace/tutorials/delve/main.go:7(PC:0x10d145b)
2:
3: import "fmt"
4:
5: var m = make(map[int]int, 0)
6:
7: func main() {
8: for _, n := range []int{5, 1, 9, 98, 6} {
9: x := fib(n)
10: fmt.Println(n, "fib", x)
11: }
12:}
(dlv)
golang (dlv) list ./main.go:14 Show /workspace/tutorials/delve/main.go:7(PC:0x10d145b) 9: x := fib(n) 10: fmt.Println(n, "fib", x) 11: } 12: } 13: 14: func fib(n int) int { 15: if n < 2 { 16: return n 17: } 18: 19: var f int (dlv)
Funcs
根据正则匹配对应的函数列表。一般用于搜索函数
funcs [<regex\>]
例如:
(dlv) funcs fib
main.fib
Exit
退出当前调试会话命令
(dlv) exit
添加及管理断点命令组
一旦你知道用list命令如何显示源代码片段后,你就可以开始在程序的相应位置增加断点来调试程序了。
假设,我们想在main.go文件中的第10行增加一个端点,那么,我们就可以使用break命令来达到设置断点的目的。
break
设置一个端点。其中name指的是给断点起一个名称,linespec用来指定在设置断点的具体位置
break [name] <linespec\>
例如,我们在main.go文件的第10行增加一个断点
(dlv) break ./main.go:10
Breakpoint 1 set at 0x10d155d for main.main() ./main.go:10
(dlv) list ./main.go:10
Showing /workspace/tutorials/delve/main.go:10 (PC: 0x10d155d)
5: var m = make(map[int]int, 0)
6:
7: func main() {
8: for _, n := range []int{5, 1, 9, 98, 6} {
9: x := fib(n)
10: fmt.Println(n, "fib", x)
11: }
12: }
13:
14: func fib(n int) int {
15: if n < 2 {
(dlv)
breakpoints
设置完断点后,接下来需要查看设置了哪些断点。则需要使用breakpoints命令可以列出当前所有的断点信息。
(dlv) breakpoints
Breakpoint runtime-fatal-throw at 0x10388c0 for runtime.fatalthrow() /usr/local/go/src/runtime/panic.go:1162 (0)
Breakpoint unrecovered-panic at 0x1038940 for runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1189 (0)
print runtime.curg._panic.arg
Breakpoint 1 at 0x10d155d for main.main() ./main.go:10 (0)
该命令一共接触3个断点。其中,前两个断点是dlv自动加的,以便当遇到错误或panics时可以查看程序的状态以及变量的信息。
第3个断点就是我们刚才手动在第10行设置的断点。
clear
删除特定的断点。指定断点名或断点标识ID
clear <breakpoint name or id\>
该命令一般用于要移除错误设置的标识,或者想移除原有标识并设置新的标识时使用。
例如,下面的例子中,删除标识ID为1的断点。标识号是使用breakpoints命令显示出来的ID。
(dlv) clear 1
Breakpoint 1 cleared at 0x10d155d for main.main() ./main.go:10
clearall
清除所有手动增加的断点
例如,在下面的例子中,我们在第8、9、10行设置3个断点。然后使用clearall
(dlv) break ./main.go:8
Breakpoint 1 set at 0x10d1472 for main.main() ./main.go:8
(dlv) break ./main.go:9
Breakpoint 2 set at 0x10d154a for main.main() ./main.go:9
(dlv) break ./main.go:10
Breakpoint 3 set at 0x10d155d for main.main() ./main.go:10
(dlv) breakpoints
Breakpoint runtime-fatal-throw at 0x10388c0 for runtime.fatalthrow() /usr/local/go/src/runtime/panic.go:1162 (0)
Breakpoint unrecovered-panic at 0x1038940 for runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1189 (0)
print runtime.curg._panic.arg
Breakpoint 1 at 0x10d1472 for main.main() ./main.go:8 (0)
Breakpoint 2 at 0x10d154a for main.main() ./main.go:9 (0)
Breakpoint 3 at 0x10d155d for main.main() ./main.go:10 (0)
(dlv) clearall
Breakpoint 1 cleared at 0x10d1472 for main.main() ./main.go:8
Breakpoint 2 cleared at 0x10d154a for main.main() ./main.go:9
Breakpoint 3 cleared at 0x10d155d for main.main() ./main.go:10
控制程序的执行流程命令组
一旦我们可以设置断点,并且能够通过list命令检查源代码,现在我们看下如何运行程序。
continue
运行程序,直到遇到下一个断点或者直到程序结束。 例如下面例子,我们在main.go文件的第10行设置一个端点,然后使用continue命令,我们的调试器将会运行程序到该断点。在这个断点这里,我们可以做一些打印变量值,设置变量值等的一些事情。
(dlv) break ./main.go:10
Breakpoint 1 set at 0x10d155d for main.main() ./main.go:10
(dlv) continue
> main.main() ./main.go:10 (hits goroutine(1):1 total:1) (PC: 0x10d155d)
5: var m = make(map[int]int, 0)
6:
7: func main() {
8: for _, n := range []int{5, 1, 9, 98, 6} {
9: x := fib(n)
=> 10: fmt.Println(n, "fib", x)
11: }
12: }
13:
14: func fib(n int) int {
15: if n < 2 {
next
运行到源代码的下一行。该命令在我们想对程序一步一步调试的时候非常有用。 例如,下面程序就是从断点开始,每次往前执行一行,无论下面有没有断点,每次都只运行一行。
(dlv) next
5 fib 5
> main.main() ./main.go:8 (PC: 0x10d1693)
3: import "fmt"
4:
5: var m = make(map[int]int, 0)
6:
7: func main() {
=> 8: for _, n := range []int{5, 1, 9, 98, 6} {
9: x := fib(n)
10: fmt.Println(n, "fib", x)
11: }
12: }
13:
### step step命令用于告诉调试器进入到函数调用的内部,和next类似,但是当遇到函数调用时,step命令会进入到被调用函数的内部,而next则将函数调用看成是一个语句。 例如,下面示例中,当执行到底9行的时候,next则会计算fib函数的值,并进入到第10行。但step则会从第9行,直接进入到第14行的函数定义,然后逐步执行。
```golang
(dlv) next
main.main() ./main.go:9 (PC: 0x10d154a) 4: 5: var m = make(map[int]int, 0) 6: 7: func main() { 8: for _, n := range []int{5, 1, 9, 98, 6} { => 9: x := fib(n) 10: fmt.Println(n, "fib", x) 11: } 12: } 13: 14: func fib(n int) int { (dlv) step main.fib() ./main.go:14 (PC: 0x10d1713) 9: x := fib(n) 10: fmt.Println(n, "fib", x) 11: } 12: } 13: => 14: func fib(n int) int { 15: if n < 2 { 16: return n 17: } 18: 19: var f int
```
### stepout 和step相对应,是step的反向操作。从被调用函数中返回调用函数。 例如,如下示例中,会返回到第9行。
```golang (dlv) stepout
main.main() ./main.go:9 (PC: 0x10d1553) Values returned: ~r1: 1 4: 5: var m = make(map[int]int, 0) 6: 7: func main() { 8: for _, n := range []int{5, 1, 9, 98, 6} { => 9: x := fib(n) 10: fmt.Println(n, "fib", x) 11: } 12: } 13: 14: func fib(n int) int { ```
restart
该命令允许我们在程序终止或重新开始调试程序的时候,重启该程序,同时保留住之前所有设置过的断点。即之前设置过的断点不会丢失。
查看当前状态的变量及表达式的值命令组
到目前为止,我们已经知道了如何添加并管理断点,如何控制程序的执行流程。现在,我们介绍如何查看、编辑程序变量和内存数据,这也是调试中最基础的部分。
Print是最简单的查看变量内容和表达式的命令。例如如下,我们在文件的第10行设置了断点,然后用continue执行到断点处,然后使用print命令打印x变量的值,如下:
(dlv) break ./main.go:10
Breakpoint 1 set at 0x10d155d for main.main() ./main.go:10
(dlv) continue
> main.main() ./main.go:10 (hits goroutine(1):1 total:1) (PC: 0x10d155d)
5: var m = make(map[int]int, 0)
6:
7: func main() {
8: for _, n := range []int{5, 1, 9, 98, 6} {
9: x := fib(n)
=> 10: fmt.Println(n, "fib", x)
11: }
12: }
13:
14: func fib(n int) int {
15: if n < 2 {
(dlv) print x
5
locals
locals命令用于打印出所有的局部变量及其值。 如下所示:
(dlv) list
> main.main() ./main.go:10 (hits goroutine(1):1 total:1) (PC: 0x10d155d)
5: var m = make(map[int]int, 0)
6:
7: func main() {
8: for _, n := range []int{5, 1, 9, 98, 6} {
9: x := fib(n)
=> 10: fmt.Println(n, "fib", x)
11: }
12: }
13:
14: func fib(n int) int {
15: if n < 2 {
(dlv) locals
n = 5
x = 5
结论
本文中,我们介绍了4组相关的命令: + 查看源代码命令:list、func以及推出exit + 添加断点以及管理断点:break、breakpoints、clear、clearall + 控制程序流程的命令:continue、next、step、stepout、restart + 查看当前变量的命令:print、locals
通过以上命令,通过查看源码,设置断点、执行到断点、输出当前的变量状态,满足了最基本的程序执行的需要。