使用 dlv 进行 debug

使用 dlv 可以方便的对 go 生成的二进制文件进行 debug,以下面的代码 main.go 为例讲解 dlv 的使用

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

import (
"fmt"
"net/http"
"time"
)

func Hello(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("q")
if q != "" {
fmt.Printf("search query: %s\n", q)
}
fmt.Fprintf(w, "Hello World! %s", time.Now())
}

func main() {
http.HandleFunc("/", Hello)
http.ListenAndServe(":8080", nil)
}

1️⃣ 安装 dlv

直接使用 go install 命令即可将 dlv 安装到 $GOPATH/bin 目录下面

1
2
3
4
5
# 可以下载最新的
go install github.com/go-delve/delve/cmd/dlv@latest

# 也可以下载指定的版本
go install github.com/go-delve/delve/cmd/dlv@v1.7.3

2️⃣ 编译代码,设置编译参数

直接使用 go build 编译二进制文件的时候,会对代码进行优化和内联,导致使用 dlv 的时候部分命令不能使用,比如函数的调用。

1
2
3
4
5
# 存在优化的编译
go build -o http_server main.go

# 去掉编译优化
go build -gcflags="all=-N -l" -o http_server main.go

3️⃣ dlv exec path/to/bin,进入调试模式

使用 exec 子命令,直接调试可运行的二进制文件

1
2
3
➜ dlv exec ./http_server
Type 'help' for list of commands.
(dlv)

4️⃣ 比对文件路径和行数,设置对应的断点

比如我想在 main.goHello 函数进行调试,定位这个函数的行数为第 9 行,路径为 main.go,然后可以执行 break main.go:9

1
2
(dlv) break main.go:9
Breakpoint 1 set at 0x6e1bef for main.Hello() ./main.go:9

如果提示 location xxx ambiguous,需要对路径进行明确,使用输出的长路径即可,比如 break github.com/xxx/xxx/main.go

设置好断点之后,可以通过 breakpoints命令查询所有的断点,可以发现 dlv 默认也会将 panic 加入断点。

1
2
3
4
5
(dlv) breakpoints
Breakpoint runtime-fatal-throw (enabled) at 0x438720 for runtime.throw() /usr/local/go/src/runtime/panic.go:1188 (0)
Breakpoint unrecovered-panic (enabled) at 0x438a80 for runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1271 (0)
print runtime.curg._panic.arg
Breakpoint 1 (enabled) at 0x6e1bef for main.Hello() ./main.go:9 (1)

5️⃣ 执行程序,直到运行到断点或者程序结束

这一步不能省略!!! 省略之后无法调试断点,执行 continue 命令

1
2
3
(dlv) continue
# or
(dlv) c

6️⃣ 进行请求,使其能够执行到断点处

如果请求 ok 了,continue 命令不再阻塞

1
2
3
4
5
6
7
8
9
10
11
12
13
(dlv) continue
> main.Hello() ./main.go:9 (hits goroutine(18):1 total:2) (PC: 0x6e1bef)
4: "fmt"
5: "net/http"
6: "time"
7: )
8:
=> 9: func Hello(w http.ResponseWriter, r *http.Request) {
10: q := r.URL.Query().Get("q")
11: if q != "" {
12: fmt.Printf("search query: %s\n", q)
13: }
14: fmt.Fprintf(w, "Hello World! %s", time.Now()

如果出现 warning 是因为二进制文件是经过优化的,编译的时候可以添加 -gcflags="all=-N -l" 参数去掉该提示。

7️⃣ 愉快的进行调试

使用 dlv 提供的命令,进行调试

命令 说明
args 当前函数的参数
step 进入函数/方法中
next 跳转到下一行代码
continue/c 运行代码直到下一个断点或者结束
break/b 设置断点
breakpoints/bp 所有的断点
clear xx 清理掉第 xx 个断点
clearall 清理所有的断点
locals 输出所有局部变量
print 输出指定的变量

print 如果需要输出长字符串,可以先执行 config max-string-len 1000 扩大输出的字符串长度

编译时需要禁止优化才能使用

1
go build xxxx -gcflags="all=-N -l"
命令 说明
list 输出源代码
call 调用函数

8️⃣ More

dlv 不仅使用 attach 对正在运行的程序进行调试,也可以直接对二进制文件进行调试 (dlv exec),甚至是没有编译的代码包 (dlv debug),更多更详细的文档,可以参考下面的两个链接:

多动手试试,很容易就可以熟悉 dlv 使用~


生活杂笔,学习杂记,偶尔随便写写东西。

作者

Edgar

发布于

2022-11-16

更新于

2022-11-17

许可协议

评论