Go、向后兼容性和 GODEBUG
引言
Go 对向后兼容性的重视是其主要优势之一。然而,有时我们无法保持完全兼容性。如果代码依赖于有 bug(包括不安全)的行为,那么修复 bug 就会破坏该代码。新特性也可能产生类似的影响:HTTP 客户端启用 HTTP/2 功能曾导致连接到存在 bug 的 HTTP/2 实现服务器的程序出现问题。这些类型的更改是不可避免的,并且Go 1 兼容性规则允许。即便如此,Go 仍然提供了一种称为 GODEBUG 的机制,以减少这些更改对使用较新工具链编译旧代码的 Go 开发者造成的影响。
GODEBUG 设置是一个 key=value
对,用于控制 Go 程序的某些部分的执行。环境变量 GODEBUG
可以包含这些设置的逗号分隔列表。例如,如果一个 Go 程序运行在包含以下内容的环境中
GODEBUG=http2client=0,http2server=0
则该 Go 程序将默认在 HTTP 客户端和 HTTP 服务器中禁用 HTTP/2 的使用。GODEBUG
环境变量中无法识别的设置将被忽略。也可以为给定程序设置默认的 GODEBUG
(下面讨论)。
在准备任何 Go 1 兼容性允许但仍可能破坏某些现有程序的更改时,我们首先设计更改以尽可能保持现有程序正常运行。对于剩余的程序,我们定义了一个新的 GODEBUG 设置,允许单个程序选择恢复旧的行为。如果不可行,可能不会添加 GODEBUG 设置,但这应该极其罕见。
为兼容性而添加的 GODEBUG 设置将至少维护两年(四个 Go 版本)。有些设置,例如 http2client
和 http2server
,将维护更长时间,甚至无限期。
在可能的情况下,每个 GODEBUG 设置都有一个关联的 runtime/metrics 计数器,命名为 /godebug/non-default-behavior/<name>:events
,用于计算特定程序行为因该设置的非默认值而改变的次数。例如,当设置 GODEBUG=http2client=0
时,/godebug/non-default-behavior/http2client:events
会计算程序配置了多少个未启用 HTTP/2 支持的 HTTP 传输。
默认 GODEBUG 值
当 GODEBUG 设置未在环境变量中列出时,其值来源于三个来源:用于构建程序的 Go 工具链的默认值,根据 go.mod
中列出的 Go 版本进行修正,然后被程序中显式的 //go:debug
行覆盖。
GODEBUG 历史提供了每个 Go 工具链版本的确切默认值。例如,Go 1.21 引入了 panicnil
设置,控制是否允许 panic(nil)
;它默认为 panicnil=0
,使 panic(nil)
成为运行时错误。使用 panicnil=1
可以恢复 Go 1.20 及更早版本的行为。
当编译声明了旧 Go 版本的工作模块或工作区时,Go 工具链会修改其默认设置以尽可能与旧 Go 版本匹配。例如,当 Go 1.21 工具链编译一个程序时,如果工作模块的 go.mod
或工作区的 go.work
指明 go
1.20
,那么程序默认使用 panicnil=1
,与 Go 1.20 而非 Go 1.21 匹配。
由于这种设置 GODEBUG 默认值的方法仅在 Go 1.21 中引入,列出早于 Go 1.20 版本的程序会被配置为与 Go 1.20 匹配,而不是更早的版本。
要覆盖这些默认设置,从 Go 1.23 开始,工作模块的 go.mod
或工作区的 go.work
可以列出一行或多行 godebug
godebug (
default=go1.21
panicnil=1
asynctimerchan=0
)
特殊键 default
指示从哪个 Go 版本获取未指定的设置。这允许将 GODEBUG 默认值与模块中的 Go 语言版本分开设置。在此示例中,程序要求 Go 1.21 语义,然后要求 Go 1.21 之前旧的 panic(nil)
行为和 Go 1.23 新的 asynctimerchan=0
行为。
只有工作模块的 go.mod
会被用来查找 godebug
指令。所需依赖模块中的任何指令都将被忽略。列出无法识别设置的 godebug
是一个错误。(早于 Go 1.23 的工具链会拒绝所有 godebug
行,因为它们完全不理解 godebug
。)
来自 go
和 godebug
行的默认设置适用于所有构建的主包。为了更精细的控制,从 Go 1.21 开始,主包的源文件可以在文件顶部(在 package
语句之前)包含一个或多个 //go:debug
指令。上例中的 godebug
行可以写成
//go:debug default=go1.21
//go:debug panicnil=1
//go:debug asynctimerchan=0
从 Go 1.21 开始,Go 工具链将带有无法识别的 GODEBUG 设置的 //go:debug
指令视为无效程序。对于给定设置,具有多行 //go:debug
的程序也被视为无效。(较旧的工具链完全忽略 //go:debug
指令。)
将编译到主包中的默认设置可以通过以下命令报告
go list -f '{{.DefaultGODEBUG}}' my/main/package
仅报告与基本 Go 工具链默认设置的不同之处。
测试包时,*_test.go
文件中的 //go:debug
行被视为测试主包的指令。在任何其他上下文中,工具链会忽略 //go:debug
行;go
vet
会将此类行报告为位置错误。
GODEBUG 历史
本节记录了出于兼容性原因在每个主要的 Go 版本中引入和移除的 GODEBUG 设置。包或程序可以出于内部调试目的定义额外的设置;例如,请参阅运行时文档和go 命令文档。
Go 1.24
Go 1.24 添加了一个新的 fips140
设置,用于控制 Go 加密模块是否在 FIPS 140-3 模式下运行。可能的值有
- “off”:不对 FIPS 140-3 模式提供特殊支持。这是默认值。
- “on”:Go 加密模块在 FIPS 140-3 模式下运行。
- “only”:类似于“on”,但未获 FIPS 140-3 批准的加密算法会返回错误或 panic。更多信息请参阅FIPS 140-3 合规性。此设置在程序启动时固定,程序启动后无法通过更改
GODEBUG
环境变量来修改。
Go 1.24 将全局的 math/rand.Seed
更改为无操作 (no-op)。此行为由 randseednop
设置控制。对于 Go 1.24,它默认为 randseednop=1
。使用 randseednop=0
会恢复到 Go 1.24 之前的行为。
Go 1.24 为 multipathtcp
设置添加了新值。现在 multipathtcp
的可能值为
- “0”:默认禁用拨号器和监听器上的 MPTCP
- “1”:默认启用拨号器和监听器上的 MPTCP
- “2”:默认仅在监听器上启用 MPTCP
- “3”:默认仅在拨号器上启用 MPTCP
对于 Go 1.24,它现在默认为 multipathtcp=“2”,因此默认在监听器上启用。使用 multipathtcp=“0” 会恢复到 Go 1.24 之前的行为。
Go 1.24 改变了 go test -json
的行为,将构建错误输出为 JSON 而非文本。这些新的 JSON 事件通过新的 Action
值区分,但仍可能导致 CI 系统出现问题,如果它们对此类事件的处理不够稳健的话。此行为可以通过 gotestjsonbuildtext
设置控制。使用 gotestjsonbuildtext=1
可以恢复 1.23 的行为。此设置将在未来的版本中移除,最早可能在 Go 1.28。
Go 1.24 更改了 crypto/rsa
,要求 RSA 密钥至少为 1024 位。此行为可以通过 rsa1024min
设置控制。使用 rsa1024min=0
可以恢复 Go 1.23 的行为。
Go 1.24 引入了一种机制,用于在 crypto/subtle
包中启用平台特定的数据无关时序 (Data Independent Timing, DIT) 模式。可以使用 dataindependenttiming
设置为整个程序启用此模式。对于 Go 1.24,它默认为 dataindependenttiming=0
。当未设置 dataindependenttiming
时,与 Go 1.23 的默认行为没有变化。使用 dataindependenttiming=1
会为整个 Go 程序启用 DIT 模式。启用后,从 Go 调用 C 时将启用 DIT。启用后,从 C 调用 Go 代码将启用 DIT,如果在进入 Go 代码时未启用 DIT,则在返回 C 之前会禁用它。这目前仅影响 arm64 程序。对于所有其他平台,它是一个无操作 (no-op)。
Go 1.24 移除了 x509sha1
设置。crypto/x509
不再支持验证使用基于 SHA-1 签名算法的证书上的签名。
Go 1.24 将 x509usepolicies
setting. 的默认值从 0
更改为 1
。默认情况下,在编组证书时,策略现在取自 Certificate.Policies
字段,而不是 Certificate.PolicyIdentifiers
字段。
Go 1.24 默认启用了后量子密钥交换机制 X25519MLKEM768。可以使用 tlsmlkem
设置来恢复默认值。Go 1.24 还移除了 X25519Kyber768Draft00 和 Go 1.23 的 tlskyber
设置。
Go 1.24 使 ParsePKCS1PrivateKey
使用并验证编码私钥中的 CRT 参数。此行为可以通过 x509rsacrt
设置控制。使用 x509rsacrt=0
可以恢复 Go 1.23 的行为。
Go 1.23
Go 1.23 将 time 包创建的通道更改为无缓冲(同步)通道,这使得正确使用 Timer.Stop
和 Timer.Reset
方法结果变得容易得多。asynctimerchan
设置可以禁用此更改。此更改没有运行时指标,此设置可能会在未来的版本中移除,最早可能在 Go 1.27。
Go 1.23 更改了 os.Lstat
和 os.Stat
报告的重解析点 (reparse points) 的模式位,这可以通过 winsymlink
设置控制。从 Go 1.23 (winsymlink=1
) 开始,挂载点 (mount points) 不再设置 os.ModeSymlink
,并且不是符号链接、Unix socket 或去重文件 (dedup files) 的重解析点现在总是设置 os.ModeIrregular
。由于这些更改,filepath.EvalSymlinks
不再评估挂载点,这是许多不一致性和 bug 的来源。在早期版本 (winsymlink=0
) 中,挂载点被视为符号链接,而其他具有非默认 os.ModeType
位(例如 os.ModeDir
)的重解析点未设置 ModeIrregular
位。
Go 1.23 更改了 os.Readlink
和 filepath.EvalSymlinks
,以避免尝试将卷 (volumes) 规范化为驱动器盘符,这并不总是可能的。此行为由 winreadlinkvolume
设置控制。对于 Go 1.23,它默认为 winreadlinkvolume=1
。早期版本默认为 winreadlinkvolume=0
。
Go 1.23 默认启用了实验性的后量子密钥交换机制 X25519Kyber768Draft00。可以使用 tlskyber
设置来恢复默认值。
Go 1.23 更改了 crypto/x509.ParseCertificate 的行为,拒绝负数的序列号。此更改可以使用 x509negativeserial
设置恢复。
Go 1.23 默认重新启用了 html/template 对 ECMAScript 6 模板字面量的支持。jstmpllitinterp
设置不再有任何效果。
Go 1.23 更改了客户端和服务器在未显式配置时使用的默认 TLS 密码套件,移除了 3DES 密码套件。可以使用 tls3des
设置来恢复默认值。
Go 1.23 更改了 tls.X509KeyPair
和 tls.LoadX509KeyPair
的行为,填充返回的 tls.Certificate
的 Leaf 字段。此行为由 x509keypairleaf
设置控制。对于 Go 1.23,它默认为 x509keypairleaf=1
。早期版本默认为 x509keypairleaf=0
。
Go 1.23 更改了 net/http.ServeContent
、net/http.ServeFile
和 net/http.ServeFS
,在提供错误时移除 Cache-Control、Content-Encoding、Etag 和 Last-Modified 头。此行为由 httpservecontentkeepheaders
设置控制。使用 httpservecontentkeepheaders=1
可以恢复 Go 1.23 之前的行为。
Go 1.22
Go 1.22 添加了一个可配置的限制,用于控制 TLS 握手过程中可接受的最大 RSA 密钥大小,由 tlsmaxrsasize
设置控制。默认值为 tlsmaxrsasize=8192,将 RSA 限制为 8192 位密钥。为避免拒绝服务攻击,此设置和默认值已向后移植到 Go 1.19.13、Go 1.20.8 和 Go 1.21.1。
Go 1.22 规定 net/http 客户端或服务器读取的请求或响应中 Content-Length 头为空是错误。此行为由 httplaxcontentlength
设置控制。
Go 1.22 更改了 ServeMux 的行为,以接受扩展模式并通过段 (segment) 对模式和请求路径进行 unescape。此行为可以通过 httpmuxgo121
设置控制。
Go 1.22 向 go/types 添加了 Alias 类型,用于显式表示类型别名。类型检查器是否生成 Alias
类型由 gotypesalias
设置控制。对于 Go 1.22,它默认为 gotypesalias=0
。对于 Go 1.23,gotypesalias=1
将成为默认值。此设置将在未来的版本中移除,最早可能在 Go 1.27。
Go 1.22 将服务器和客户端支持的默认最低 TLS 版本更改为 TLS 1.2。可以使用 tls10server
设置来恢复到 TLS 1.0。
Go 1.22 更改了客户端和服务器在未显式配置时使用的默认 TLS 密码套件,移除了使用基于 RSA 密钥交换的密码套件。可以使用 tlsrsakex
设置来恢复默认值。
Go 1.22 在连接既不支持 TLS 1.3 也不支持 Extended Master Secret(在 Go 1.21 中实现)时禁用了 ConnectionState.ExportKeyingMaterial
。可以使用 tlsunsafeekm
设置重新启用它。
Go 1.22 改变了运行时与 Linux 透明大页 (transparent huge pages) 的交互方式。特别地,一个常见的默认 Linux 内核配置可能导致显著的内存开销,而 Go 1.22 不再会规避此默认设置。为了在不调整内核设置的情况下解决此问题,可以使用 disablethp
设置为 Go 内存禁用透明大页。此行为已向后移植到 Go 1.21.1,但此设置仅在 Go 1.21.6 及更高版本中可用。此设置可能会在未来的版本中移除,受此问题影响的用户应根据GC 指南中的建议调整其 Linux 配置,或切换到完全禁用透明大页的 Linux 发行版。
Go 1.22 将运行时内部锁的争用情况添加到 mutex
profile 中。这些锁的争用总是报告在 runtime._LostContendedRuntimeLock
。可以使用 runtimecontentionstacks
设置启用运行时锁的完整堆栈跟踪。这些堆栈跟踪具有非标准语义,详见设置文档。
Go 1.22 添加了一个新的 crypto/x509.Certificate
字段 Policies
,它支持组件大于 31 位的证书策略 OID。默认情况下,此字段仅在解析时使用(此时它被填充策略 OID),而在编组时则不使用。可以使用 x509usepolicies
setting. 来编组这些较大的 OID,而不是使用现有的 PolicyIdentifiers 字段。
Go 1.21
Go 1.21 规定使用 nil 接口值调用 panic
为运行时错误,由 panicnil
设置控制。
Go 1.21 规定 html/template action 出现在 ECMAScript 6 模板字面量内部是错误,由 jstmpllitinterp
设置控制。此行为已向后移植到 Go 1.19.8+ 和 Go 1.20.3+。
Go 1.21 引入了 MIME 头部和多部分表单最大数量的限制,分别由 multipartmaxheaders
和 multipartmaxparts
设置控制。此行为已向后移植到 Go 1.19.8+ 和 Go 1.20.3+。
Go 1.21 添加了对 Multipath TCP 的支持,但只有应用程序显式请求时才使用。此行为可以通过 multipathtcp
设置控制。
没有计划移除这些设置中的任何一个。
Go 1.20
Go 1.20 引入了拒绝 tar 和 zip 归档中不安全路径的支持,由 tarinsecurepath
设置和 zipinsecurepath
设置控制。它们默认设置为 tarinsecurepath=1
和 zipinsecurepath=1
,保留了早期 Go 版本的行为。未来的 Go 版本可能会将默认值更改为 tarinsecurepath=0
和 zipinsecurepath=0
。
Go 1.20 引入了 math/rand
全局随机数生成器的自动种子设置,由 randautoseed
设置控制。
Go 1.20 引入了在证书验证期间使用的回退根 (fallback roots) 的概念,由 x509usefallbackroots
设置控制。
Go 1.20 从 Go 分发版中移除了预安装的标准库 .a
文件。现在安装程序会像处理其他模块中的包一样构建和缓存标准库。installgoroot
设置会恢复预安装 .a
文件的安装和使用。
没有计划移除这些设置中的任何一个。
Go 1.19
Go 1.19 规定路径查找解析到当前目录中的二进制文件是错误,由 execerrdot
设置控制。没有计划移除此设置。
Go 1.19 开始在 DNS 请求中发送 EDNS0 附加头。据报告这可能导致某些路由器(例如 CenturyLink Zyxel C3000Z)提供的 DNS 服务器出现问题。这可以通过 netedns0
设置更改。此设置在 Go 1.21.12、Go 1.22.5、Go 1.23 及更高版本中可用。没有计划移除此设置。
Go 1.18
Go 1.18 移除了对大多数 X.509 证书中 SHA1 的支持,由 x509sha1
设置控制。此设置已在 Go 1.24 中移除。
Go 1.10
Go 1.10 改变了构建缓存的工作方式,并添加了测试缓存,以及 gocacheverify
、gocachehash
和 gocachetest
设置。没有计划移除这些设置。
Go 1.6
Go 1.6 引入了对 HTTP/2 的透明支持,由 http2client
、http2server
和 http2debug
设置控制。没有计划移除这些设置。
Go 1.5
Go 1.5 引入了一个纯 Go DNS 解析器,由 netdns
设置控制。没有计划移除此设置。