Go、向后兼容性与 GODEBUG
引言
Go 对向后兼容性的重视是其关键优势之一。然而,有时我们无法保持完全兼容。如果代码依赖于有缺陷(包括不安全)的行为,那么修复缺陷将破坏该代码。新功能也可能产生类似的影响:HTTP 客户端启用 HTTP/2 导致连接到具有缺陷 HTTP/2 实现的服务器的程序中断。这些类型的更改是不可避免的,并且Go 1 兼容性规则允许。即便如此,Go 仍然提供了一种称为 GODEBUG 的机制,以减少此类更改对使用较新工具链编译旧代码的 Go 开发者造成的影响。
GODEBUG 设置是控制 Go 程序某些部分执行的key=value
对。环境变量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.mod
文件中的godebug
指令将被忽略,而是会查阅go.work
中的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.25
Go 1.25 添加了一个新的decoratemappings
设置,用于控制 Go 运行时是否用其目的的上下文来标注操作系统匿名内存映射。这些标注以“[anon: Go: …]”的形式出现在 /proc/self/maps 和 /proc/self/smaps 中。此设置仅在 Linux 上使用。对于 Go 1.25,它默认为decoratemappings=1
,启用标注。使用decoratemappings=0
恢复到 Go 1.25 之前的行为。此设置在程序启动时固定,并且不能在程序启动后通过更改GODEBUG
环境变量进行修改。
Go 1.25 添加了一个新的embedfollowsymlinks
设置,用于控制 Go 命令是否会跟随符号链接到嵌入文件的常规文件。默认值embedfollowsymlinks=0
不允许跟随符号链接。embedfollowsymlinks=1
将允许跟随符号链接。
Go 1.25 添加了一个新的containermaxprocs
设置,用于控制 Go 运行时在设置默认 GOMAXPROCS 时是否会考虑 cgroup CPU 限制。默认值containermaxprocs=1
将除了总逻辑 CPU 计数和 CPU 亲和性之外,还会使用 cgroup 限制。containermaxprocs=0
将禁用 cgroup 限制的考虑。此设置仅影响 Linux。
Go 1.25 添加了一个新的updatemaxprocs
设置,用于控制 Go 运行时是否会定期更新 GOMAXPROCS 以适应新的 CPU 亲和性或 cgroup 限制。默认值updatemaxprocs=1
将启用定期更新。updatemaxprocs=0
将禁用定期更新。
Go 1.25 根据 RFC 9155 禁用了 TLS 1.2 中的 SHA-1 签名算法。可以使用tlssha1=1
设置恢复默认值。
Go 1.25 切换到 SHA-256 以填补 crypto/x509.CreateCertificate 中缺失的 SubjectKeyId。设置x509sha256skid=0
可恢复到 SHA-1。
Go 1.25 纠正了运行时内部锁的争用报告语义,因此删除了runtimecontentionstacks
设置。
Go 1.25(从 Go 1.25 RC 2 开始)由于对 VCS 注入攻击的担忧,在检测到多个 VCS 时禁用了构建信息盖章。此行为和设置已回溯到 Go 1.24.5 和 Go 1.23.11。可以通过设置allowmultiplevcs=1
重新启用此行为。
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
更改为无操作。此行为由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
包中启用平台特定数据独立计时(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 程序。对于所有其他平台,它是一个无操作。
Go 1.24 移除了x509sha1
设置。crypto/x509
不再支持验证使用基于 SHA-1 签名算法的证书上的签名。
Go 1.24 将x509usepolicies
设置的默认值从0
更改为1
。在封送证书时,策略现在默认从Certificate.Policies
字段而不是Certificate.PolicyIdentifiers
字段获取。
Go 1.24 默认启用了后量子密钥交换机制 X25519MLKEM768。可以使用tlsmlkem
设置恢复默认值。这在处理无法正确处理大型记录的 buggy TLS 服务器时非常有用,这些服务器会导致握手期间超时(参见TLS 后量子 TL;DR 失败)。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
为重解析点报告的模式位,这可以通过winsymlink
设置进行控制。从 Go 1.23 (winsymlink=1
) 开始,挂载点不再设置os.ModeSymlink
,并且不是符号链接、Unix 套接字或重复数据删除文件的重解析点现在总是设置os.ModeIrregular
。由于这些更改,filepath.EvalSymlinks
不再评估挂载点,这曾是许多不一致和错误的来源。在之前的版本 (winsymlink=0
) 中,挂载点被视为符号链接,并且其他具有非默认os.ModeType
位的重解析点(例如os.ModeDir
)没有设置ModeIrregular
位。
Go 1.23 更改了os.Readlink
和filepath.EvalSymlinks
,以避免尝试将卷标准化为驱动器号,这有时甚至不可能。此行为由winreadlinkvolume
设置控制。对于 Go 1.23,它默认为winreadlinkvolume=1
。之前的版本默认为winreadlinkvolume=0
。
Go 1.23 默认启用了实验性后量子密钥交换机制 X25519Kyber768Draft00。可以使用tlskyber
设置恢复默认值。这在处理无法正确处理大型记录的 buggy TLS 服务器时非常有用,这些服务器会导致握手期间超时(参见TLS 后量子 TL;DR 失败)。
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 的行为,以接受扩展模式并按段解逸出模式和请求路径。此行为可以通过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 禁用了ConnectionState.ExportKeyingMaterial
,当连接不支持 TLS 1.3 也不支持扩展主密钥(在 Go 1.21 中实现)时。可以使用tlsunsafeekm
设置重新启用它。
Go 1.22 更改了运行时与 Linux 上透明大页的交互方式。特别是,常见的默认 Linux 内核配置可能会导致显着的内存开销,Go 1.22 不再解决此默认问题。为了在不调整内核设置的情况下解决此问题,可以使用disablethp
设置禁用 Go 内存的透明大页。此行为已回溯到 Go 1.21.1,但此设置仅在 Go 1.21.6 及更高版本中可用。此设置可能会在未来的版本中移除,受此问题影响的用户应根据GC 指南中的建议调整其 Linux 配置,或切换到完全禁用透明大页的 Linux 发行版。
Go 1.22 将运行时内部锁的争用添加到mutex
配置文件中。这些锁的争用总是报告为runtime._LostContendedRuntimeLock
。可以使用runtimecontentionstacks
设置启用运行时锁的完整堆栈跟踪。这些堆栈跟踪具有非标准语义,详见设置文档。
Go 1.22 添加了一个新的crypto/x509.Certificate
字段,Policies
,它支持组件大于 31 位的证书策略 OID。默认情况下,此字段仅在解析时使用,此时它会填充策略 OID,但在封送时不会使用。通过使用x509usepolicies
设置,它可以用于封送这些更大的 OID,而不是现有的 PolicyIdentifiers 字段。
Go 1.21
Go 1.21 将使用 nil 接口值调用panic
变为运行时错误,由panicnil
设置控制。
Go 1.21 将 html/template 操作出现在 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 添加了对多路径 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 引入了用于证书验证期间的回退根的概念,由x509usefallbackroots
设置控制。
Go 1.20 从 Go 分发版中移除了标准库的预安装.a
文件。现在安装像其他模块中的包一样构建和缓存标准库。installgoroot
设置恢复了预安装.a
文件的安装和使用。
目前没有计划移除这些设置。
Go 1.19
Go 1.19 将路径查找解析到当前目录中的二进制文件视为错误,由execerrdot
设置控制。目前没有计划移除此设置。
Go 1.19 开始在 DNS 请求上发送 EDNS0 附加头。据报道,这可能会破坏某些路由器上提供的 DNS 服务器,例如 CenturyLink Zyxel C3000Z。这可以通过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
设置控制。目前没有计划移除此设置。