delve
Last updated
Was this helpful?
Last updated
Was this helpful?
日常常用的是用 goland 打断点以 debug 模式运行调试,goland 实际上也是调用 delve 来做的调试功能。
delve 可以 attach 到一个正在运行中的 golang 程序,可以用来调试线上执行中的程序,非常强大。
先对你感兴趣的代码行的左边侧边栏鼠标左击打断点,然后在main
函数左边侧边栏点击启动程序的按钮,选择绿色小虫子按钮,就是以debug模式启动了程序,当运行到断点代码行时,程序就会阻塞在此处,你就可以对程序做调试了。
如果你的程序需要设置启动参数才能启动,Goland右上角有Add Configuration
按钮,可以对运行的Golang程序做配置,Program arguments
一栏就是设置的启动参数。
Show Execution Point
:把输入光标跳转到打断点的那行代码上;
Step Over
:逐行执行,如果有函数调用,不会进入,只在当前代码处逐行执行;
Step Into
:逐行执行,如果有函数调用,会进入到函数里;
Step Out
:跳出进入的函数;
Run To Cursor
:跳转到下个断点。
点击向下的箭头按钮,可以选择切换到你感兴趣的goroutine来进行调试。
在红色断点上右击,Condition:
即为用户为该断点设置的触发条件,满足指定的表达式时才触发断点。 点击More
有更丰富的功能,可以选择触发该断点时打印日志,还支持Evaluate and log
,计算指定表达式输出日志; disable until hitting the following breakpoint
,指定的断点触发时就禁用该断点。
这段代码在 Go1.13 以下会出现一个经典 bug:递增数字输出到一半阻塞在那不动了。
可以看到,虽然程序卡在那不输出了,但是仍然是吃满了一核CPU,PID=4370。
dlv attach 4370
进入 delve 交互式命令行,执行grs
查看当前所有 goroutine,第13行是空的for
循环,剩下的 goroutine 中只有第 18 号是我创建的,当前停在了第 10 行是fmt.Println(i)
。
1 号协程前面有一个小星星,代表当前 delve 调试工具绑定到了 1 号协程,执行gr 18
切换到 18 号协程,执行bt
查看当前协程的函数调用栈回溯:
可以看到程序是阻塞在了runtime.gcStart
,查看runtime/mgc.go
的1287行:
可以看到程序是在执行 GC 的 STW 时发生了阻塞。
分析原因:GC 在开始前需要 STW 抢占所有的 P 来开启写屏障,执行输出的 goroutine18 已经被 GC 抢占所以程序不输出了,而 goroutine1 的 for 循环没能被抢占,一直在执行,所以仍然是吃满了一核CPU,而 STW 一直在等待它让出 P,这样就陷入了僵局。
在 Go1.13 中不是真正的抢占,编译器会给每个函数注入一个栈增长检测函数runtime.morestack
,会在这个函数里检查抢占标识,让出P,但是这个设计在遇到没有函数调用的时候会有问题,比如本例的空转 for 循环,没有机会执行栈增长检测函数,也就不会让出P了。所以这是 Go1.13 中的一个bug,在 Go1.14 以后实现了基于signal信号的真正的抢占式调度,就没有这个 bug 了。
在本地交叉编译好用于在服务器上运行的Go
可执行文件,然后把文件传输到服务器上,在服务器上运行Go
程序,并查到该进程的PID
;
在服务器上执行dlv attach $PID --headless --api-version=2 --log --listen=:2345
;
打开Goland
的Run/Debug Configurations
标签,点击+
,选择Go Remote
选项卡,Host
、Port
填上一步启动的delve
服务端,接下来就可以跟本地调试一样的调试远程程序了。
注:由于本地和服务器上的操作系统不同,不同的操作系统之间可能存在一些细微的差异,这有可能导致无法按照预期进行调试,所以需要在本地做交叉编译生成可执行文件。
编写用于在远程服务器上执行的脚本文件,命名为debug.sh
:
若只需attach
进已启动的Go
程序,则debug.sh
脚本如下:
注:
grep -v grep
是列出除开grep命令本身的进程;
$2
表示第2列,即进程号PID;
xargs
使用上一个操作的结果作为下一个命令的参数使用;
将一条命令的执行结果重定向到其他设备,>dlv.out
和>>dlv.out
的区别:>>
是追加内容,>
是覆盖原有内容
在本地编译好,传输到远程服务器上,启动新的Go
程序,这些操作只需要每次在本地执行如下脚本即可:
注:如果没有-gcflags="all=-N -l"
,编译优化会使实际运行的代码与源代码不同,进而导致打的断点无效。
随后即可在本地Goland
配置Go Remote
远程调试新启动的程序了。
在右下角的调试界面会显示程序执行运行时变量的值,上方有一个输入框,可以输入一个表达式,按回车键查看表达式的值,或者按Shift+Command+回车键
监听表达式的值,这对观察复杂的表达式或查看 slice/map/struct 中的特定值是非常有用的。
在服务器上安装Go
和,vim .bash_profile
,在文件最后写入如下内容,然后执行source .bash_profile
使之生效;
先配好