Delve是Go官方推荐的调试器,我们熟知的Goland、LiteIDE都集成了Delve,同时各大通用IDE如VSCode、Atom、Sublime等的Go插件也都集成了Delve调试Go代码。
除了IDE以外,还有一个使用Delve的GUI调试器——Gdlv。除了提供图形界面外,Gdlv也可以执行Delve命令。
开始
我们需要安装Delve和Gdlv。
Go1.16及以上版本:
$ go install github.com/go-delve/delve/cmd/dlv@latest
$ go install github.com/aarzilli/gdlv@latest
Go1.16以下版本
$ go get -u github.com/derekparker/delve/cmd/dlv
$ go get -u github.com/aarzilli/gdlv
确保的你Go bin目录已添加到path环境变量。
查看是否安装成功:
$ dlv version
$ gdlv version
配置存储路径
Delve会在磁盘存储一个配置文件和一个历史命令文件。默认情况下,Linux系统是放在$HOME/.config/dlv下,其他系统包括Windows是在$HOME/.dlv下,$HOME就是你的用户目录。
可以通过配置XDG_CONFIG_HOME环境变量来更改存放目录。
启动调试
源代码调式
打开命令行切换到main包目录,然后执行以下命令。
$ gdlv debug
也可以手动指定源码目录。
$ gdlv -d 源码目录 debug
可执行文件调试
Delve也可以调试编译好的可执行文件,当然也是由Delve编译的调试版可执行文件。
$ gdlv exec xxx.debug.exe
exec不支持-d选项。
调试Go测试代码
同调试源代码基本一致,只是将debug换成了test。
$ gdlv test
也可通过-d选项指定代码目录。
打开调试窗口以后默认断点在main函数处,在Command窗口底部可以输入Delve命令。

Gdlv启动调试默认会停在main函数处,但是Delve并不会停在main处,所以在直接用dlv debug命令调试时,需要先在main函数处设置断点,然后运行至断点处,具体命令见后面章节。
Gdlv界面操作
设置断点
在Gdlv的Listing窗口,右键会弹出一个菜单,一个是设置断点,一个是运行到指定行。

右键断点,可以禁用/启动断点,编辑断点,以及运行到断点处。

编辑断点可以设置断点条件。

单步
在Command窗口的顶部有运行、单步、跳入跳出按钮。

右上角的NEW WINDOW下拉框可以打开更多窗口,查看其他信息。比如选则Breakpoints可以查看所有断点。
查看变量
Variables窗口可以查看本地变量,通过Filter可以过滤变量。Full Types单选框可以显示完整类型信息,Address单选框可以显示变量地址。

Delve命令
接下来要介绍的是Devle的调试命令,我们可以在Gdlv的Command窗口输入这些命令。
断点
设置断点
设置断点的命令是break <loction>,break的别名是b,因此也可以使用b <location>。
此外断点也可以命名,格式为break 断点名 <location>,或者b 断点名 <location>。
location有五种格式。
内存地址
支持十进制,十六进制和八进制。
文件名:行号
文件名可以是相对路径,如果没有歧义,也可以只写文件名。如果省略文件名,则表示当前文件。
b main.go:8
b 8
偏移量
基于当前行上下偏移。
b +1
b -2
函数:行号
在不引起歧义的情况下,可以直接写函数名,否则应该写包名.函数名。这里的行号表示的是基于函数的偏移行数,如果省略,表示第0行,也就是func xxx那行。
b main.max
b main.max:2
正则
在所有满足正则匹配的函数处设置断点。正则表达式需用/包裹:/正则表达式/。
查看断点
breakpoints [-a]或者简写bp [-a]。通过-a选项可以查看所有物理断点,包括内部断点。
这个命令在Gdlv中不可用,可以通过点击NEW WINDOW->Breakpoints查看断点。
清除断点
单个清除
命令格式为clear <id or name>。
当我们通过break命令设置断点时,会打印出断点ID。

如果设置断点时指定了名称,那么也可以通过断点名删除。
通过这个命令删除断点不会在Gdlv中实时显示,需要做一个别的操作刷新页面,删除的断点才会消失。
批量清除
命令为clearall [<locspec>],如果省略<location>则清除所有断点,否则,删除与<location>匹配处的断点。
这个命令在Gdlv中也不可用。
断点条件
设置断点条件的命令是condition,别名是cond。这个命令在Gdlv中也不可用,所以在Gdlv中只能通过UI来设置断点条件。条件是一个布尔表达式,关于Delve表达式见表达式。
满足条件进入断点
命令格式如下:
cond <id or name> expr
进入断点次数做为条件
除了普通的布尔表达式,也可以根据进入断点的次数决定此次进入断点是否要停留。支持以下几种表达式。
cond -hitcount bp > n
cond -hitcount bp >= n
cond -hitcount bp < n
cond -hitcount bp <= n
cond -hitcount bp == n
cond -hitcount bp != n
cond -hitcount bp % n
bp是内置变量,表示进入断点次数,n是条件。
清除条件
通过-clear选项可以清除某个断点上的条件。
cond -clear <id or name>
断点回调
进入断点时执行命令。格式为:
on <id or name> <command>
支持的<command>有以下5个:
print打印表达式stack打印调用栈goroutine显示goroutinetrace将断点变成跟踪点(trace point)cond等同于cond
修改命令使用-edit选项。
on <id or name> -edit
禁用/启动断点
禁用/启用断点可以通过Gdlv界面进行操作。Delve官方文档给了一个toggle命令来禁用/启用断点,但是实测会提示命令不可用,不知道是不是版本的原因。
运行调试
单步
next [n]
Delve中,这个next命令可以指定执行几行代码。但在Gdlv中,n会被忽略,始终执行1行。
单步进入
命令为step,别名为s。
单步跳出
命令为stepout,别名为so。
在Gdlv中不支持so。
单步cpu指令
命令为step-instruction,别名为si。CPU指令级别的单步。
重新开始
命令restart,别名r。
退出调试
命令:exit。
继续执行
命令为continue,别名为c。
默认执行到下一个断点或至程序结束。也可以指定location,运行到指定位置。
c <loccation>
在Gdlv中,<location>会被忽略。
查看状态
查看函数参数
命令格式:
[goroutine <n>] [frame <m>] args [-v] [<regex>]
打印函数参数,-v选项用于显示参数详细信息,<regexp>可以过滤变量。
args
args a
goroutine 1 args
args在Gdlv中不可用,当进入函数后,参数和本地变量会自动显示在Valriables窗口。

查看本地变量
locals的使用与args一样。
[goroutine <n>] [frame <m>] locals [-v] [<regex>]
locals在Gdlv中也不可用,原因同args。
查看包变量
vars [-v] [<regex>]
只打印包级变量。
监控表达式
添加监控表达式
dispaly -a <expr>
将<expr>添加到监控列表,每当程序停下时都会打印出监控列表中表达式的值。
(dlv) display -a a+1
0: a+1 = 2
:前面的0是表达式的下标,删除时会用到。
查看监控表达式列表
display不带任何参数会打印出所有被监控的表达式以及表达式的值。
(dlv) display
0: a+1 = 3
1: b = 4
这里也会打印出表达式的编号。
删除表达式监控
display -d <index>
这里的<index>是表达式的编号。
求值表达式
命令为print,别名p。命令格式:
[goroutine <n>] [frame <m>] print <expression>
与display不同的是,print只会打印表达式的值1次。
print a
goroutine 1 print b
查看寄存器
regs [-a]
打印CPU寄存器的值,-a选项打印更多寄存器的值。
打印内存
命令examinemem,别名x。
x [-fmt <format>] [-count|-len <count>] [-size <size>] <address>
从<address>开始按<format>格式以<size>字节为单位打印<count>次。
<format>有5种:
- bin 二进制格式
- oct 八进制格式
- dec 十进制格式
- hex 十六进制格式(默认)
- addr 地址
<count>表示次数,默认1,最大值1000。
<size>表示字节数,默认1,最大值8。
<address表示内存地址。
示例:
(dlv) x 0xc00010fec8
0xc00010fec8: 0x02
(dlv) x -fmt dec 0xc00010fec8
0xc00010fec8: 002
(dlv) x -fmt dec -size 8 0xc00010fec8
0xc00010fec8: 000000000000000000000002
(dlv) x -fmt dec -count 2 -size 8 0xc00010fec8
0xc00010fec8: 000000000000000000000002 000000000000000000000004
查看表达式类型
whatis <expr>
打印表达式类型。
修改变量值
[goroutine <n>] [frame <m>] set <variable> = <value>
只能修改数字和指针类型的值。
查看文档
查看所有命令文档。
help
查看某个命令文档。
help xxx
表达式
Delve支持的表达式语法是Go语法的子集。
语法
- 除
<-、++、--外的单目和双目运算符 - 比较运算符
- 数字类型的类型转换
- 数字和指针类型相互转换
string、[]byte和[]rune相互转换- 访问结构体成员(如
a.A) - 切片和下标运算,支持数组、切片和字符串
- 访问
map - 指针解引用
- 调用内置函数:
cap、len、complex、imag、real。 - 类型断言(如
somevar.(concretetyp))
特殊变量
runtime.curg:当前goroutine的G结构体。如runtime.curg.goid就是当前goroutine的goroutine id。runtime.frameoff:当前栈帧基址距栈底的偏移量。
(dlv) print runtime.curg.goid
1
(dlv) print runtime.frameoff
-312
嵌套深度限制
Delve打印变量时默认会解引用,但是解引用最多解两层,嵌套超过两层的变量会打印出地址。
例如:
(dlv) print c1
main.cstruct {
pb: *struct main.bstruct {
a: (*main.astruct)(0xc82000a430),
},
sa: []*main.astruct len: 3, cap: 3, [
*(*main.astruct)(0xc82000a440),
*(*main.astruct)(0xc82000a450),
*(*main.astruct)(0xc82000a460),
],
}
想打印sa的第一个元素,有两种方式:
- 下标运算:
print c1.sa[0] - 直接用地址:
print *(*main.astruct)(0xc82000a440)
元素数量限制
对于数组、切片、字符串和map,Delve一次最多打印64个元素。想打印更多元素需要切片,如:
print arr
print arr[64:]
注意,在Delve中,map也是可以切片的,Delve会按照一个固定的顺序遍历map。
这个限制也可以通过config配置。
config max-string-len 100
config max-array-values 100
接口
接口打印格式如下:
接口名(具体类型) 值
例如:
(dlv) p iface1
interface {}(*struct main.astruct) *{A: 1, B: 2}
(dlv) p iface2
interface {}(*struct string) *"test"
(dlv) p err1
error(*struct main.astruct) *{A: 1, B: 2}
访问接口类型变量的字段有3种方式:
- 类型断言
(dlv) p iface1.(*main.astruct).B
2
- 使用
.(data)
(dlv) p iface1.(data).B
2
- 如果是结构体类型或指向结构体的指针,也可省略类型断言。
(dlv) p iface1.B
2
包路径
当变量名相同时,需要加以包名区分。
p "some/package".A
p "some/other/package".A
Cgo的指针
Char指针被当作字符串,可以索引和切片。其他C指针做为Go切片,可以索引和切片。
CPU寄存器
CPU寄存器名称必须全大写,如RAX表示RAX寄存器。
如果本地变量名和寄存器重名,表达式的结果是本地变量,寄存器会被隐藏。
寄存器名前可以加任意多下划线,比如_RAX、__RAX都表示RAX寄存器。
小于等于64比特的寄存器用uint64表示,大于64比特的寄存器用字符串以十六进制表示。
也可以将寄存器值表示为数组:
RegName.intN //表示为intN的数组
RegName.uintN //表示为uintN的数组
RegName.floatN //表示为floatN的数组
其中N必须是2的幂。
附录
- Delve:https://github.com/go-delve/delve
- Delve命令:https://github.com/go-delve/delve/blob/master/Documentation/cli/README.md
- Gdlv:https://github.com/aarzilli/gdlv

466

被折叠的 条评论
为什么被折叠?



