Go 博客

在 Go 1.12 中调试您部署的代码

David Chase
2019 年 3 月 21 日

引言

Go 1.11 和 Go 1.12 在允许开发者调试与生产环境部署的优化二进制文件相同方面取得了显著进展。

随着 Go 编译器在生成更快的二进制文件方面变得越来越积极,我们在可调试性方面有所退步。在 Go 1.10 中,用户需要完全禁用优化才能从 Delve 等交互式工具获得良好的调试体验。但是用户不应该为了可调试性而牺牲性能,尤其是在运行生产服务时。如果问题发生在生产环境中,您需要在生产环境中进行调试,这不应该要求部署未经优化的二进制文件。

对于 Go 1.11 和 1.12,我们专注于改进优化二进制文件(Go 编译器的默认设置)的调试体验。改进包括:

  • 更准确的值检查,尤其是在函数入口处的参数;
  • 更精确地识别语句边界,从而使单步执行不那么跳跃,断点更能准确地落在程序员期望的位置;
  • 以及对 Delve 调用 Go 函数的初步支持(goroutine 和垃圾回收使其比 C 和 C++ 更棘手)。

使用 Delve 调试优化代码

Delve 是一个用于 Go 的 x86 调试器,支持 Linux 和 macOS。Delve 了解 goroutine 和其他 Go 特性,并提供最佳的 Go 调试体验之一。Delve 也是 GoLandVS CodeVim 的调试引擎。

Delve 通常使用 -gcflags "all=-N -l" 重新编译它正在调试的代码,这会禁用内联和大多数优化。要使用 delve 调试优化代码,请先构建优化二进制文件,然后使用 dlv exec your_program 进行调试。或者,如果您有一个崩溃的核心文件,您可以使用 dlv core your_program your_core 来检查它。使用 1.12 和最新的 Delve 版本,您应该能够检查许多变量,即使是在优化二进制文件中。

改进的值检查

在调试 Go 1.10 生成的优化二进制文件时,变量值通常完全不可用。相比之下,从 Go 1.11 开始,即使在优化二进制文件中,变量通常也可以检查,除非它们已被完全优化掉。在 Go 1.11 中,编译器开始发出 DWARF 位置列表,以便调试器可以跟踪变量在寄存器和堆栈中的移动,并重建跨越不同寄存器和堆栈槽的复杂对象。

改进的单步执行

这显示了一个在 1.10 版本调试器中单步执行简单函数的示例,其中缺陷(跳过和重复的行)由红色箭头突出显示。

像这样的缺陷会使您在单步执行程序时很容易失去对当前位置的跟踪,并干扰断点的命中。

Go 1.11 和 1.12 记录语句边界信息,并能更好地通过优化和内联跟踪源代码行号。因此,在 Go 1.12 中,单步执行此代码会在每一行停止,并且顺序与您期望的一致。

函数调用

Delve 中的函数调用支持仍在开发中,但简单的情况是可以工作的。例如:

(dlv) call fib(6)
> main.main() ./hello.go:15 (PC: 0x49d648)
Values returned:
    ~r1: 8

前进的方向

Go 1.12 是朝着改进优化二进制文件调试体验迈出的一步,我们还有进一步改进的计划。

可调试性和性能之间存在根本性的权衡,因此我们专注于最高优先级的调试缺陷,并努力收集自动化指标来监控我们的进展并捕获回归。

我们专注于为调试器生成关于变量位置的正确信息,因此如果变量可以打印,它就会被正确打印。我们也正在研究使变量值在更多时候可用,特别是在调用站点等关键点,尽管在许多情况下改进这一点需要降低程序执行速度。最后,我们正在努力改进单步执行:我们专注于使用 panic 进行单步执行的顺序、围绕循环进行单步执行的顺序,以及尽可能地尝试遵循源顺序。

关于 macOS 支持的说明

Go 1.11 开始压缩调试信息以减小二进制文件大小。Delve 原生支持此功能,但 macOS 上的 LLDB 和 GDB 都不支持压缩调试信息。如果您正在使用 LLDB 或 GDB,有两种解决方法:使用 -ldflags=-compressdwarf=false 构建二进制文件,或者使用 splitdwarfgo get golang.org/x/tools/cmd/splitdwarf)来解压缩现有二进制文件中的调试信息。

下一篇文章: Go 2018 调查结果
上一篇文章: 使用 Go Modules
博客索引