使用 dlv 进行 debug

1,252 阅读3分钟

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

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 目录下面

# 可以下载最新的
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 的时候部分命令不能使用,比如函数的调用。

# 存在优化的编译
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 子命令,直接调试可运行的二进制文件

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

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

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

(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 加入断点。

(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 命令

(dlv) continue
# or
(dlv) c

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

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

(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 扩大输出的字符串长度

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

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

8️⃣ More

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

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

From My Blog