使用VSCode的golang插件debug go程序是非常方便的,可以比较容易地实现查看变量名、断点调试、单步跟踪等功能。但是笔者最近在使用这个插件debug 需要用到go plugin的程序 (MIT 6.824 mapreduce lab)时,遇到了问题:无论怎么办,从plugin中加载函数都会失败,这让我百思不得其解。在翻阅无数stackoverflow帖子和github issues之后,终于找到了解决办法,便有了这篇博文。

1. 问题复现

此处需要进行debug的go程序的文件名为mrworker.go,go插件在launch.json中给出的默认配置如下:

1
2
3
4
5
6
7
{
"name": "Launch file",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${file}"
}

对其稍加修改,得到的配置文件如下:

1
2
3
4
5
6
7
8
9
10
{
"name": "Launch test",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/src/main/mrworker.go",
"args": [
"${workspaceFolder}/src/main/wc.so"
],
},

程序mrworker.go需要接受一个命令行参数指定plugin,这里我选用wc.so

在加载wc.so时,会发现程序调用plugin.Open打开该插件时失败了:

使用调试器打印错误信息如下:

1
plugin was built with a different version of package runtime/internal/atomic

2. 错误原因

逛了很长时间的stackoverflow和github之后,才知道这个错误出现的原因:编译可执行文件和plugin的build flags不同。plugin的编译是用的MIT 6.824中给出的命令:

1
go build -race -buildmode=plugin ../mrapps/wc.go

但是dlv 编译可执行文件时的build flags无从知晓,在网上翻阅很多资料都没有找到。

build flags的不一致导致上述错误的发生。

3. 解决办法

我们无法获知dlv编译可执行文件时的build flags,那么我们可以自行编译,然后调试编译后的可执行文件。这里需要将dlv的模式从debug转成exec,表示debug可执行文件,同时在launch.json文件中添加preLaunchTask属性,表示在debug之前首先执行指定的任务,在这里就是用我们自己的flags编译插件和可执行文件了。修改之后的配置如下:

launch.json:

1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "Debug Worker",
"type": "go",
"request": "launch",
"mode": "exec",
"program": "${workspaceFolder}/src/main/mrworker",
"args": [
"${workspaceFolder}/src/main/wc.so"
],
"preLaunchTask": "build mrworker and plugin binary",
"cwd": "${workspaceFolder}/src/main"
}

tasks.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"tasks": [
{
"type": "shell",
"label": "build mrworker and plugin binary",
"command": "/usr/bin/zsh",
"args": [
"./build_mrworker_plugin.sh"
],

}
],

"version": "2.0.0"
}

tasks里面便是编译可执行文件和plugin的命令:

build_mrworker_plugin.sh

1
2
3
4
#!/usr/bin/zsh
cd ./src/main
go build -race -buildmode=plugin ../mrapps/wc.go
go build -race mrworker.go

这样可以看到我们的程序可以正常加载plugin了:

尽管我们现在可以正常调试了,但是还是会遇到下面的问题:

在调试的过程中会看到左边侧栏会提示optimized function,这样的话有时候查看一些重要变量的值或者属性时会失败,解决这个问题只需要在编译可执行文件和plugin时加上编译选项-gcflags="all=-N -l" 即可。