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 版本)。有些,例如http2clienthttp2server,将维护更长时间,甚至无限期。

在可能的情况下,每个 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指令。

gogodebug行中的默认值适用于所有构建的主包。为了进行更精细的控制,从 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.StopTimer.Reset方法结果变得容易得多。asynctimerchan设置禁用了此更改。此更改没有运行时指标。此设置可能会在未来的版本中移除,最早是 Go 1.27。

Go 1.23 更改了os.Lstatos.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.Readlinkfilepath.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.X509KeyPairtls.LoadX509KeyPair的行为,以填充返回的tls.Certificate的 Leaf 字段。此行为由x509keypairleaf设置控制。对于 Go 1.23,它默认为x509keypairleaf=1。之前的版本默认为x509keypairleaf=0

Go 1.23 更改了net/http.ServeContentnet/http.ServeFilenet/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 标头和多部分表单最大数量的限制,分别由multipartmaxheadersmultipartmaxparts设置控制。此行为已回溯到 Go 1.19.8+ 和 Go 1.20.3+。

Go 1.21 添加了对多路径 TCP 的支持,但仅在应用程序明确请求时才使用。此行为可以通过multipathtcp设置控制。

目前没有计划移除这些设置。

Go 1.20

Go 1.20 引入了对拒绝 tar 和 zip 归档中不安全路径的支持,由tarinsecurepath设置zipinsecurepath设置控制。这些默认设置为tarinsecurepath=1zipinsecurepath=1,保留了 Go 早期版本的行为。未来的 Go 版本可能会将默认值更改为tarinsecurepath=0zipinsecurepath=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 更改了构建缓存的工作方式,并添加了测试缓存,以及gocacheverifygocachehashgocachetest设置。目前没有计划移除这些设置。

Go 1.6

Go 1.6 引入了对 HTTP/2 的透明支持,由http2clienthttp2serverhttp2debug设置控制。目前没有计划移除这些设置。

Go 1.5

Go 1.5 引入了纯 Go DNS 解析器,由netdns设置控制。目前没有计划移除此设置。