前言
Go
目前的调试器有如下几种:
-
GDB
最早期的调试工具,现在用的很少。 -
LLDB
macOS
系统推荐的标准调试工具,单Go
的一些专有特性支持的比较少。 -
Delve
专门为Go
语言打造的调试工具,使用最为广泛。
本篇简单说明如何使用 Delve
工具来调试 Go
程序,使用的 delve
版本为 1.20.1
。
安装
-
Go1.16
及之后:
# 最新版本:
$ go install github.com/go-delve/delve/cmd/dlv@latest
# 指定分支:
$ go install github.com/go-delve/delve/cmd/dlv@master
# 特定版本或伪版本:
$ go install github.com/go-delve/delve/cmd/dlv@v1.7.3
$ go install github.com/go-delve/delve/cmd/dlv@v1.7.4-0.20211208103735-2f13672765fe
- 可以自己下载源码
build
$ git clone https://github.com/go-delve/delve
$ cd delve
$ go install github.com/go-delve/delve/cmd/dlv
实例
讲两个经常使用的方式:
dlv debug
dlv attach
dlv debug
- 创建
main.go
文件:
package main
import (
"fmt"
)
func main() {
nums := make([]int, 5)
for i := 0; i <len(nums); i++ {
nums[i] = i * i
}
fmt.Println(nums)
}
- 命令行进入包所在目录,然后输入
dlv debug
命令进入调试
$ dlv debug
Type 'help' for list of commands.
#设置断点:main.main
(dlv)b main.main
Breakpoint 1 set at 0xff3c0a for main.main() ./main.go:7
# 查看已设置的所有断点,我们发现除了我们自己设置的 main.main 函数断点外,Delve 内部已经为 panic 异常函数设置了一个断点。
(dlv) bp
Breakpoint runtime-fatal-throw (enabled) at 0xc97940 for runtime.throw() d:/go1.18/go/src/runtime/panic.go:982 (0)
Breakpoint unrecovered-panic (enabled) at 0xc97ce0 for runtime.fatalpanic() d:/go1.18/go/src/runtime/panic.go:1065 (0)
print runtime.curg._panic.arg
Breakpoint 1 (enabled) at 0xd03c0a for main.main() ./main.go:7 (0)
# 查看全局变量(可通过正则参数过滤)
(dlv) vars main
runtime.main_init_done = chan bool nil
runtime.mainStarted = false
# 运行到下个断点处
(dlv) c
> main.main() ./main.go:7 (hits goroutine(1):1 total:1) (PC: 0xd03c0a)
2:
3: import (
4: "fmt"
5: )
6:
=> 7: func main() {
8: nums := make([]int, 5)
9: for i := 0; i <len(nums); i++ {
10: nums[i] = i * i
11: }
12: fmt.Println(nums)
# 单步执行
(dlv) n
> main.main() ./main.go:8 (PC: 0xd03c18)
3: import (
4: "fmt"
5: )
6:
7: func main() {
=> 8: nums := make([]int, 5)
9: for i := 0; i <len(nums); i++ {
10: nums[i] = i * i
11: }
12: fmt.Println(nums)
13: }
# 查看传入函数的参数
(dlv) args
(no args) # 这里main函数没有参数
# 查看局部变量
(dlv) locals
(no locals) # 这里还没初始化nums
# 再下一步,初始化nums了,可以看到有局部变量了
(dlv) n
> main.main() ./main.go:9 (PC: 0xd03c43)
4: "fmt"
5: )
6:
7: func main() {
8: nums := make([]int, 5)
=> 9: for i := 0; i <len(nums); i++ {
10: nums[i] = i * i
11: }
12: fmt.Println(nums)
13: }
(dlv) locals
nums = []int len: 5, cap: 5, [...]
# 组和使用 break 和 condition 命令设置条件断点
(dlv) b main.go:10
Breakpoint 2 set at 0xd03c62 for main.main() ./main.go:10
(dlv) bp
Breakpoint runtime-fatal-throw (enabled) at 0xc97940 for runtime.throw() d:/go1.18/go/src/runtime/panic.go:982 (0)
Breakpoint unrecovered-panic (enabled) at 0xc97ce0 for runtime.fatalpanic() d:/go1.18/go/src/runtime/panic.go:1065 (0)
print runtime.curg._panic.arg
Breakpoint 1 (enabled) at 0xd03c0a for main.main() ./main.go:7 (1) # Breakpoint 1(这个 1 是断点的编号,设置条件断点,清理断点等时候会用到)
Breakpoint 2 (enabled) at 0xd03c62 for main.main() ./main.go:10 (0)
(dlv) cond 2 i==3 # 这里设置条件断点 i 等于 3
# 继续 continue 执行到刚设置的条件断点,输出局部变量
(dlv) c
> main.main() ./main.go:10 (hits goroutine(1):1 total:1) (PC: 0xd03c62)
5: )
6:
7: func main() {
8: nums := make([]int, 5)
9: for i := 0; i <len(nums); i++ {
=> 10: nums[i] = i * i
11: }
12: fmt.Println(nums)
13: }
(dlv) locals
nums = []int len: 5, cap: 5, [...]
i = 3
(dlv) print nums
[]int len: 5, cap: 5, [0,1,4,0,0]
# 查看栈帧信息
(dlv) stack
0 0x0000000000d03c62 in main.main
at ./main.go:10
1 0x0000000000c99f28 in runtime.main
at d:/go1.18/go/src/runtime/proc.go:250
2 0x0000000000cc18a1 in runtime.goexit
at d:/go1.18/go/src/runtime/asm_amd64.s:1571
# 退出
(dlv) q
dlv attach
常见的 http
服务调试即可使用这种方式。
- 创建
main.go
文件:
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
nums := make([]int, 5)
for i := 0; i < len(nums); i++ {
nums[i] = i * i
}
writer.Write([]byte("OK"))
})
err := http.ListenAndServe(":4781", nil)
if err != nil {
log.Fatalln(err)
}
}
-
go build -gcflags="all=-N -l" main.go
生成main.exe
,**注意:这里一定要加上-gcflags="all=-N -l"
不然有可能代码被编译器优化,断点打不上。 - 执行
main.exe
,得到程序的PID
-
dlv attach PID
进入调试
$ dlv attach 22300
Type 'help' for list of commands.
(dlv) b main.go:12
Breakpoint 1 set at 0x6f34ee for main.main.func1() ./main.go:12
(dlv) c # 执行到这里之后,会卡住
- 访问网页
http://127.0.0.1:4781/hello
,这时可以看到我们刚刚打的断点生效了
> main.main.func1() ./main.go:12 (hits goroutine(6):1 total:1) (PC: 0x6f34ee)
7:
8: func main() {
9: http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
10: nums := make([]int, 5)
11: for i := 0; i < len(nums); i++ {
=> 12: nums[i] = i * i
13: }
14: writer.Write([]byte("OK"))
15: })
16:
17: err := http.ListenAndServe(":4781", nil)
接下来的用法就同上面讲的 dlv debug
用法一致。
其他
进入 dlv
调试后,可以输入 help
查看帮助信息:
(dlv) help
The following commands are available:
Running the program:
call ------------------------ 继续进程,注入函数调用(实验!!!)
continue (alias: c) --------- 运行直到断点或程序终止。
next (alias: n) ------------- 运行至下一行。
rebuild --------------------- 重新生成可执行文件,若可执行文件不是delve生成的,则不能使用。
restart (alias: r) ---------- 重新启动进程。
step (alias: s) ------------- 单步执行程序。
step-instruction (alias: si) 单步执行单个cpu指令。
stepout (alias: so) --------- 跳出当前函数。
Manipulating breakpoints:
break (alias: b) ------- 设置断点。
breakpoints (alias: bp) 查看所有断点信息。
clear ------------------ 删除断点。
clearall --------------- 删除多个断点。
condition (alias: cond) 设置断点条件。
on --------------------- 命中断点时执行命令。
toggle ----------------- 打开或关闭断点。
trace (alias: t) ------- 设置跟踪点。
watch ------------------ 设置观察点。
Viewing program variables and memory:
args ----------------- 打印函数参数。
display -------------- 每次程序停止时打印表达式的值。
examinemem (alias: x) 检查给定地址的原始内存。
locals --------------- 打印局部变量。
print (alias: p) ----- 对表达式求值。
regs ----------------- 打印CPU寄存器的内容。
set ------------------ 更改变量的值。
vars ----------------- 打印包变量。
whatis --------------- 打印表达式的类型。
Listing and switching between threads and goroutines:
goroutine (alias: gr) -- 显示或更改当前goroutine。
goroutines (alias: grs) 列出程序所有goroutine。
thread (alias: tr) ----- 切换到指定的线程。
threads ---------------- 打印每个跟踪线程的信息。
Viewing the call stack and selecting frames:
deferred --------- 在延迟调用的上下文中执行命令。
down ------------- 向下移动当前帧。
frame ------------ 设置当前帧,或在其他帧上执行命令。
stack (alias: bt) 打印堆栈跟踪。
up --------------- 向上移动当前帧。
Other commands:
config --------------------- 更改配置参数。
disassemble (alias: disass) 反汇编程序。
dump ----------------------- 从当前进程状态创建核心转储
edit (alias: ed) ----------- 自己指定编辑器编辑,读的环境变量 $DELVE_EDITOR 或者 $EDITOR
exit (alias: quit | q) ----- 退出调试器。
funcs ---------------------- 打印函数列表。
help (alias: h) ------------ 打印帮助消息。
libraries ------------------ 列出加载的动态库
list (alias: ls | l) ------- 显示源代码。
source --------------------- 执行包含 delve 命令的文件
sources -------------------- 打印源文件列表。
transcript ----------------- 将命令输出追加到文件。
types ---------------------- 打印类型列表
Type help followed by a command for full documentation.
想要查看具体的命令使用方式,可以 help 命令
(例:help call
)。
下面做一些常见的命令的解释
dlv
指令
命令 | 解释 |
attach | 这个命令将使Delve控制一个已经运行的进程,并开始一个新的调试会话。 当退出调试会话时,你可以选择让该进程继续运行或杀死它。 |
debug | 默认情况下,没有参数,Delve将编译当前目录下的 "main "包,并开始调试。或者,你可以指定一个包的名字,Delve将编译该包,并开始一个新的调试会话。 |
exec | 使Delve执行二进制文件,并立即附加到它,开始一个新的调试会话。请注意,如果二进制文件在编译时没有关闭优化功能,可能很难正确地调试它。请考虑在Go 1.10或更高版本上用-gcflags=“all=-N -l"编译调试二进制文件,在Go的早期版本上用-gcflags=”-N -l"。 |
test | test命令允许你在单元测试的背景下开始一个新的调试会话。默认情况下,Delve将调试当前目录下的测试。另外,你可以指定一个包的名称,Delve将在该包中调试测试。双破折号–可以用来传递参数给测试程序。 |
version | 查看dlv版本 |
进入 dlv
调试后的指令
命令 | 缩写 | 解释 |
break | b | 设置断点 |
breakpoints | bp | 查看当前所有断点 |
clear | / | 删除断点 |
clearall | / | 删除多个断点 |
toggle | / | 启用或关闭断点 |
continue | c | 继续执行到一个断点或者程序结束 |
next | n | 执行下一行代码 |
restart | r | 重新执行程序 |
step | s | 执行代码的下一步 |
step-instruction | si | 执行下一行机器码 |
stepout | so | 跳出当前执行函数 |
args | / | 打印函数input |
display | / | 打印加入到display的变量的值,每次执行下一行代码或下一个断点时 |
locals | / | 打印局部变量 |
print | p | 打印表达式的结果 |
set | / | 设置某个变量的值 |
vars | / | 查看全局变量 |
whatis | / | 查看变量类型 |
disassemble | disass | 查看反编译后的代码,机器码 |
exit | quit/q | 退出 |
funcs | / | 打印程序用到的所有函数 |
help | h | 帮助信息 |
list | ls/l | 打印代码 |
examinemem | x | 检查给定地址的原始内存 |
GoLand 的断点调试
GoLand
使用的也是 delve
来进行调试的,它使用的是 --listen
参数来启动了一个服务,本篇就不做详细介绍了。
1. go build -gcflags "all=-N -l" -o main.exe
2. dlv --listen=127.0.0.1:5739 --headless=true --api-version=2 --check-go-version=false --only-same-user=false exec main.exe --
使用 GoLand
进行调试也很简单,都是图形化界面,加断点即直接点击行号,开始调试,点击右上角的小甲壳虫即可。
总结
delve
工具非常强大,本篇介绍了其基础使用,希望在遇到问题的时候可以快速的进行问题排查,如果安装了 GoLand
,也可以使用 Goland
进行调试,相对来说更加的快捷,但遇到一些需要查看汇编代码或者其他高阶应用时,我还没发现 GoLand
如何使用,所以 delve
的命令使用最好也是要了解的,不会到时候一头雾水。