Go、向下相容性與 GODEBUG
引言
Go 對於向下相容性的重視是它的主要優點之一。但有時,我們無法維持完整的相容性。如果程式碼依賴於有錯誤 (包括不安全) 的行為,那麼修正錯誤將會破壞該程式碼。新的功能可能會產生類似的影響:啟用 HTTP/2 供 HTTP 伺服器使用會破壞與實作有錯誤的 HTTP/2 的伺服器建立連線的程式。這些變更都是無法避免,且 受 Go 1 相容性規則允許。儘管如此,Go 提供一個稱為 GODEBUG 的機制,以降低這些變更對 Go 開發人員使用較新的工具鏈編譯舊程式碼所造成的影響。
一個 GODEBUG 設定是一個控制 Go 程式特定部分執行的 key=value
成對值。環境變數 GODEBUG
可以保留一個由這些設定組成的逗號分隔清單。例如,如果一個 Go 程式執行在含有
GODEBUG=http2client=0,http2server=0
的環境中,那麼這個 Go 程式預設會停用 HTTP 伺服器和 HTTP 伺服器中的 HTTP/2 使用。也可以為特定程式設定預設的 GODEBUG
(如下所述)。
在準備任何 Go 1 相容性允許的變更時,但可能會中斷一些現有程式,我們會優先設計變更,讓儘可能多現有的程式保持正常運作。對於其餘程式,我們會定義新的 GODEBUG 設定,讓個別程式能選擇復歸舊有行為。如果不可行,可能會不新增 GODEBUG 設定,但這種情況極為罕見。
因相容性而新增的 GODEBUG 設定,將至少維持兩年(四個 Go 版本)。某些設定,例如 `http2client` 和 `http2server`,將維持更久,甚至無限期。
當可行時,每個 GODEBUG 設定都有關聯的 runtime/metrics 計數器,名叫 `non-default-behavior/<name>:events`,會計數特定程式的行為,已根據該設定的非預設值進行變更的次數。例如,當 `GODEBUG=http2client=0` 已設定時,`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 版本的程式,設定會符合 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 開始,主要套件的原始檔可以包含 //go:debug
指示,並將其置於檔案最上方(位於 package
陳述之前)。前一個範例中的 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.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 Socket 或重複資料刪除檔案的重新分析點現在恆常設定 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
設定 將預設值改回來。
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
的 葉子欄位。此行為由 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` 類型,由 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
設定 回復。
當連線同時不支援 TLS 1.3 或延伸主控密碼 (於 Go 1.21 中實作) 時,Go 1.22 已停用 ConnectionState.ExportKeyingMaterial
。它可使用 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 中,新增支援 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 引進替代根的概念以用於憑證驗證期間,由 x509usefallbackroots
設定 控制。
Go 1.20 移除了標準函式庫的預先安裝 .a
檔案,改由 Go 發行版提供。安裝現在會建置並快取標準函式庫,就像其他模組中的套件一樣。 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.22) 中移除。
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
設定 控制。沒有計畫要移除此設定。