go 调试器 delve(dlv) 的使用(附实例及常用命令解释)

前言

Go 目前的调试器有如下几种:

  • GDB 最早期的调试工具,现在用的很少。
  • LLDB macOS 系统推荐的标准调试工具,单 Go 的一些专有特性支持的比较少。
  • Delve 专门为 Go 语言打造的调试工具,使用最为广泛。

本篇简单说明如何使用 Delve 工具来调试 Go 程序,使用的 delve 版本为 1.20.1

安装

  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/[email protected]
$ go install github.com/go-delve/delve/cmd/[email protected]
  1. 可以自己下载源码 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

  1. 创建 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)
}
  1. 命令行进入包所在目录,然后输入 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 服务调试即可使用这种方式。

  1. 创建 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)
	}
}
  1. go build -gcflags="all=-N -l" main.go 生成 main.exe,**注意:这里一定要加上 -gcflags="all=-N -l" 不然有可能代码被编译器优化,断点打不上。
  2. 执行 main.exe,得到程序的 PID
  3. 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 # 执行到这里之后,会卡住
  1. 访问网页 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 的命令使用最好也是要了解的,不会到时候一头雾水。

参考

猜你喜欢

转载自blog.csdn.net/DisMisPres/article/details/128866995
今日推荐