Golang中Delve调试器输出不稳定是工具问题还是操作问题?
Golang中Delve调试器输出不稳定是工具问题还是操作问题? 我感觉自己像在吃疯狂药丸,似乎遗漏了什么极其明显的东西。之前我曾报告过一个Delve bug,结果却被重新归类为Go bug,这次我想确认是否有其他人遇到过这种情况,或者知道这是个已知问题(当然也可能是用户错误,这可能性最大)。
下面有更详细的版本,但简而言之:
我在调试器中看到变量被报告为nil、默认值结构体和空字符串。变量在某一行看起来正常,到下一行就变成空值或nil。有人遇到过这种情况吗?
这是Go 1.11 rc1、GoLand 2018.2.1以及2018.2.1版本自带的Delve。
我搜索过Delve的问题记录,发现一些关于局部变量丢失的问题,但似乎特定于VS Code,不确定是否与此相关。也许我漏掉了已有的相关问题?
具体情况是:我在客户端启动测试,这会启动一个新服务器。然后我附加到服务器进程并逐步执行服务器端断点。我在运行之间执行了go build和go install,所以运行的服务器二进制文件应该™与我逐步调试的源代码同步。
编辑: 需要说明的是,我也尝试过使用-gcflags '-N -l'禁用编译器优化和内联进行构建——也许我遗漏了其他必要的标志?我还检查了系统上没有多个版本的服务器(这样客户端就不应该使用过时的旧服务器二进制文件)
以下是包含代码和截图的详细版本,希望能帮助其他人发现操作上的问题:
我从*http.Request中检索自定义请求结构体。这部分工作正常。结构体定义如下:
type JarReq struct {
request
Jar domain.Jar `json:"jar"`
AuthProvider string `json:"authProvider"`
}
在函数A中,我运行jarReq, err := getJarReq(r),其实现如下:
func getJarReq(r *http.Request) (restapi.JarReq, error) {
var jar restapi.JarReq
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
if err != nil {
return jar, err
}
defer r.Body.Close()
if err := json.Unmarshal(body, &jar); err != nil {
return jar, err
}
return jar, nil
}
然后在函数A中调用这个错误处理函数:
func HandleReqErr(err error, w http.ResponseWriter) {
if err == nil {
return
}
util.HandleErr(err, "CreateUserErr")
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(422) // unprocessable entity
if err := json.NewEncoder(w).Encode(err); err != nil {
util.HandleErr(err, "CreateUserErr")
}
}
由于err为nil,它直接返回。
接着我将jarReq传递给另一个函数:jarRes, err := createJar(jarReq)
其定义如下:
func createJar(jarReq restapi.JarReq) (jarRes restapi.ExportedJarRes, err error) {
jar := jarReq.Jar
repo, err := domain.NewJarRepo(env.App.Database)
eRes := restapi.ExportedJarRes{}
res, err := handlers.CreateModel(repo, &jar, &eRes)
return *res.(*restapi.ExportedJarRes), err
}
但一旦我步入createJar,调试器中的jarReq就显示为nil。
为了调试这个问题,我在createJar()中打印了jarReq,如下所示:
func createJar(jarReq restapi.JarReq) (jarRes restapi.ExportedJarRes, err error) {
msg := fmt.Sprintf("%v", jarReq)
fmt.Println(msg)
jar := jarReq.Jar
repo, err := domain.NewJarRepo(env.App.Database)
eRes := restapi.ExportedJarRes{}
res, err := handlers.CreateModel(repo, &jar, &eRes)
return *res.(*restapi.ExportedJarRes), err
}
我可以看到当我设置msg时,值看起来是正确的。但在调试器中jarReq仍然是nil。然后当我前进一步时,调试器中出现一个新变量:r·4——也是nil,类型为JarRepo(正如你在下一行看到的,我正要获取这个值,但这出现在jar := jarReq.Jar行的断点处,在我执行到domain.NewJarRepo()之前)
接着,msg变成了空字符串——在我进行任何其他调用或有机会以任何方式修改该值之前(据我所见!)以下是几张截图。注意看起来正常的msg,好像Sprintf确实从jarReq获取了内容,但jarReq却是nil。还要注意在我给msg赋值之前,jarReq就显示为nil。

现在注意前进一步——这是在fmt.Println(msg)行中断。msg现在是空字符串,jarReq仍然是nil:

还要注意,如果我逐步执行到并经过jar := jarReq.Jar,我没有遇到nil指针解引用panic,而且jar看起来正常,所以它显然获取了值。像这样:

但如果我再前进一步,jar就变成了默认的空结构体。

由于调试器中的变量不断变成默认结构体、空字符串和nil,我沿着调用链打印了一些值,如上面的示例——这些值看起来确实正确(至少我检查过的那些),但当我逐步执行到下一行时,字符串就变成了空字符串。有人遇到过这种情况吗?我是不是遗漏了什么愚蠢的明显问题?
谢谢(抱歉篇幅过长)
更多关于Golang中Delve调试器输出不稳定是工具问题还是操作问题?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
哦是的,抱歉,我误解了你的回答。
我不知道如何提供帮助,也许值得针对 Delve 再开一个 bug。
PS:我假设上面 createJar 中忽略的错误只是为了简洁起见,对吗?
更多关于Golang中Delve调试器输出不稳定是工具问题还是操作问题?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你好Liza,
我不是delve用户,所以可能帮不上太多忙。其中一个原因可能是并发问题,或者是因为Delve在网络操作期间无法正确停止程序。
能否请你链接一下你向Delve提交的错误报告?
另外,你的程序是否存在竞态条件?
我明白了。正如你正确调试的那样,这是因为你使用了插件包(因此涉及动态SO加载)。
这对Go来说相对较新,可能会导致你遇到的这类问题。
不确定目前是否有解决方案:也许可以使用github.com/natefitch/pie和多进程来重新实现你的插件…
这个问题通过以下步骤得以解决:
- 重启我的机器
- 安装 Go 1.11(之前使用的是 rc1 版本),最重要的是:
- 使用正确的命令来禁用优化:
-gcflags='all=-N -l'
出于某些原因,仅执行步骤 c) 是不够的。我不确定其他问题的根本原因是什么,但现在调试器的行为已经恢复正常。
对于将来,这个命令有助于确认我的二进制文件是否已编译优化:
objdump --dwarf executable | grep DW_AT_producer
在完成上述步骤后,我可以在每行的末尾看到 -N -l 输出(二进制文件的大小也如预期那样变大了)。
感谢您抽时间查看这个问题 😊 我最终确实在Delve上提交了一个错误报告,正如所料,这确实是编译器优化的问题——我使用了错误的标志,只禁用了主包的优化。显然,我应该使用的是 -gcflags='all=-N -l',而不是 -gcflags '-N -l'。
不幸的是,即使在修复了上述问题后,我仍然遇到相同的情况。我仍然怀疑优化没有完全禁用。使用上述标志前后的二进制文件大小完全相同,我原本预期未优化的二进制文件会更大……也许我遗漏了其他问题。无论如何,这是Delve上的问题链接,后续讨论可能会在那里进行(其中还提到了GoLand对Delve优化函数警告的处理稍好一些,而不是仅仅向用户显示误导性的nil/零值变量):https://github.com/derekparker/delve/issues/1320
忽略错误并非为了简洁,这是一个原型,还有很多工作要做,因此存在大量错误处理和其他问题需要完成和解决。
这是一个典型的调试器优化问题,通常与Go编译器的优化机制有关。即使你使用了 -gcflags '-N -l' 禁用了部分优化,Delve在某些情况下仍然可能无法正确显示变量值。
问题分析:
- 变量在运行时实际有值(从你的打印输出可以确认)
- 调试器显示为nil或默认值
- 代码执行正常,没有panic
这是Delve在特定Go版本下的已知问题,特别是在处理函数参数和局部变量时。编译器可能会重用寄存器或栈位置,导致调试器无法正确追踪变量。
解决方案:
- 使用更完整的调试标志:
// 构建时使用更完整的调试标志
go build -gcflags="all=-N -l" your_package
- 强制变量保持在作用域内:
func createJar(jarReq restapi.JarReq) (jarRes restapi.ExportedJarRes, err error) {
// 强制将参数复制到局部变量
debugJarReq := jarReq
msg := fmt.Sprintf("%v", debugJarReq)
fmt.Println(msg)
jar := debugJarReq.Jar
repo, err := domain.NewJarRepo(env.App.Database)
eRes := restapi.ExportedJarRes{}
res, err := handlers.CreateModel(repo, &jar, &eRes)
return *res.(*restapi.ExportedJarRes), err
}
- 检查Delve版本兼容性:
# 确保使用与Go 1.11兼容的Delve版本
dlv version
- 尝试直接使用Delve命令行:
# 直接附加到进程进行调试
dlv attach <pid>
- 使用指针传递避免值复制问题:
func createJar(jarReq *restapi.JarReq) (jarRes restapi.ExportedJarRes, err error) {
if jarReq == nil {
return restapi.ExportedJarRes{}, errors.New("nil jarReq")
}
msg := fmt.Sprintf("%v", *jarReq)
fmt.Println(msg)
jar := jarReq.Jar
repo, err := domain.NewJarRepo(env.App.Database)
eRes := restapi.ExportedJarRes{}
res, err := handlers.CreateModel(repo, &jar, &eRes)
return *res.(*restapi.ExportedJarRes), err
}
这个问题在Go 1.11 + Delve的组合中确实存在,特别是在通过函数边界传递结构体值时。使用上述方法通常可以缓解调试器显示问题,同时保持代码的正常执行。


