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
設定控制。目前沒有計劃移除此設定。