dlv支持debug、attach和exec三种方式。其中,exec方式类似于gdb /path/to/program,可以让调试器(dlv)去启动目标程序,然后立即挂载上去进行调试。类似Java的jwdp中suspend=true的方式,这种方式启动的程序它不会立即执行,而是hold在那里,等待调试器的进一步指令,非常适合于观察和研究启动过程和逻辑。
本文假设远端和近端都装好了dlv,而且Visual Code的debug方案也已经添加完毕。相关的操作可以参考利用delve(dlv)在Visual Code中进行go程序的远程调试-debug方式的准备部分。
环境
这部分内容和attach方式的那一篇一样
- 近端
- Win7 64bit
- Visual Code 1.32.1 x64
- Go 1.10
- dlv 1.2
- 远端
- OpenSuSE 42.1
- Go 1.10
- dlv 1.2
- 示例项目
- hello/main.go,内容如下
package main
import (
"fmt"
"time"
)
const loopTimes = 60
func main() {
for i := 1; i < loopTimes; i++ {
time.Sleep(1 * time.Second)
fmt.Printf("\rLoop %v", i)
}
fmt.Println("")
}
准备01. 编译目标程序
这部分内容和attach方式的那一篇一样,做过可以跳过
就像javac和gcc一样,go编译器默认会在编译时,对最终的可执行代码执行面向机器的开发,而使得人类进行调试时不好理解。因此,如果在编译要被调试的目标程序时,最好禁用掉这些优化。Go 1.10及之后的版本,在编译时,加入-gcflags="all=-N -l"
选项;而之前的版本,则是加入-gcflags="-N -l"
选项。
在实例项目和环境中,我们用的是go 1.10,所以编译用的命令就变成这样了。
# 进入实例项目目录
cd $GOPATH/src/hello
# 执行编译任务
go build -gcflags="all=-N -l" hello
# 将编译好的程序拷贝到没有源代码的/tmp目录下,留作后面的步骤使用
mv hello /tmp
准备02. 启动目标程序
这部分内容和attach方式的那一篇一样,做过可以跳过
这次我们要做的是对“在运行程序”的远程调试工作,所以在调试开始之前,需要先把程序运行起来——但也就是单纯地按照程序的正常启动方法就行,不需要额外的参数或者调整。
/tmp/hello
执行方法
虽然不再需要代码,但与dlv debug一样,我们需要先在远端启动dlv的服务端。
dlv exec --headless --listen ":2345" --log --api-version 2 /tmp/hello
画面显示类似以下内容则说明dlv服务端已经就绪。
API server listening at: [::]:2345
INFO[0000] launching process with args: [/tmp/hello] layer=debugger
和之前的dlv debug方式一样,回到Visual Code入debug界面,选择“Launch remote”方案后,点击启动来进行go debugger,就能启动远程调试了。
总结
exec方式的作用看起来有点土,但在开发测试阶段,应该也有不少情况会用到这个方式。和attach模式一样要留意,在Visual Code的debug控制栏里点击Stop按钮,会终结掉(可以理解为kill)你正在调试的目标程序。和attach方式不一样,想要安全地关闭调试的话,目前最好的方法那就是另开一个对话去kill掉dlv而不碰目标程序。
这里无论是dlv还是Visual Code都没有记述有更优雅的方法,关闭调试而留下目标程序继续运作。毕竟我觉得kill还是一个需要挺多人力,尤其是避免误杀而需要的注意力,的方式。但也可能是我没看到,如果找着了,我再更新吧。