Go 博客
在 Go 1.12 中调试部署内容
简介
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 也是 GoLand、VS Code 和 Vim 背后的调试引擎。
Delve 通常使用 -gcflags "all=-N -l"
重新构建它正在调试的代码,这会禁用内联和大多数优化。要使用 delve 调试优化代码,首先构建优化二进制文件,然后使用 dlv exec your_program
调试它。或者,如果您有来自崩溃的 core 文件,您可以使用 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 是朝着为优化二进制文件提供更好的调试体验迈出的一步,我们计划进一步改进它。
在可调试性和性能之间存在根本性的权衡,因此我们专注于最高优先级的调试缺陷,并努力收集自动化指标来监控我们的进展并捕捉回归。
我们专注于为调试器生成有关变量位置的正确信息,因此如果可以打印变量,则可以正确打印它。我们还在研究如何让变量值在更多时间内可用,特别是在调用站点等关键位置,尽管在许多情况下,改进这一点需要减慢程序执行速度。最后,我们正在努力改进单步执行:我们专注于单步执行恐慌的顺序、单步执行循环周围的顺序,并尽力在可能的情况下遵循源代码顺序。
关于 macOS 支持的说明
Go 1.11 开始压缩调试信息以减小二进制文件大小。Delve 原生支持这一点,但 LLDB 或 GDB 在 macOS 上都不支持压缩的调试信息。如果您使用的是 LLDB 或 GDB,有两种解决方法:使用 -ldflags=-compressdwarf=false
构建二进制文件,或使用 splitdwarf (go get golang.org/x/tools/cmd/splitdwarf
) 解压缩现有二进制文件中的调试信息。
下一篇文章: Go 2018 年调查结果
上一篇文章: 使用 Go 模块
博客索引