:::info 日期:2019 年 03 月 21 日
作者:David Chase
原文链接:https://go.dev/blog/debug-opt :::

介绍

Go 1.11 和 Go 1.12 在允许开发人员调试他们部署到生产中的相同优化二进制文件方面取得了重大进展。

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

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

  • 更准确的值检查,特别是函数入口处的参数;
  • 更精确地识别语句边界,以便步进更少,断点更频繁地落在程序员期望的位置
  • 并且初步支持 Delve 调用 Go 函数(goroutines 和垃圾收集使这比在 C 和 C++ 中更棘手)。

调试优化的代码与深入研究

Delve 是适用于 x86 上的 Go 调试器,支持 Linux 和 macOS。 Delve 了解 goroutines 和其他 Go 特性,并提供最好的 Go 调试体验之一。 Delve 还是 GoLandVS Code Vim 背后的调试引擎。

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 中的调试器中单步执行一个简单函数的示例,其中缺陷(跳过和重复的行)用红色箭头突出显示。
Screen Shot 2021-09-26 at 1.58.43 PM.png
像这样的缺陷使得在单步执行程序时很容易忘记您所处的位置并干扰命中断点。

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

函数调用

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

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

未来的方针

Go 1.12 是优化二进制文件的更好调试体验的一步,我们计划进一步改进它。

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

我们专注于为调试器生成关于变量位置的正确信息,所以如果一个变量可以打印,它就会被正确打印。我们也在考虑让变量值在更多时间可用,特别是在调用站点等关键点,尽管在许多情况下改进这需要减慢程序执行速度。最后,我们正在努力改进步进:我们专注于恐慌的步进顺序、循环步进的顺序,并且通常尽可能地遵循源顺序。

关于 macOS 支持的说明

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