為什麼需要確定性建置#
除錯的基本前提是可重現性。然而,許多因素會導致同一份原始碼在不同次建置或執行中產生不同的結果,使得問題難以穩定重現。
ASLR:記憶體位址隨機化#
Address Space Layout Randomization (ASLR) 是作業系統為防止惡意攻擊而隨機化程式記憶體佈局的機制。這對安全有利,但對除錯有害:
- 每次執行時 stack、heap、code、data 段的位址都不同
- 指標值在不同執行間無法比對
- 以位址為基礎的 hash table 行為可能改變
各平台停用 ASLR 的方式#
| 平台 | 方法 |
|---|---|
| GNU/Linux | setarch $(uname -m) -R myprogram |
| Visual Studio | 連結時加 /DYNAMICBASE:NO,或在專案屬性中設定 |
| OS X | 傳遞 -no_pie 給 linker,編譯時使用 -Wl,-no_pie |
停用 ASLR 只應在除錯環境中進行,生產環境務必保持啟用以維護安全性。
其他影響建置確定性的因素#
隨機符號名稱#
- GCC 會在每個編譯單元中產生隨機選取的符號名稱
- 使用
-frandom-seed旗標固定隨機種子
編譯輸入順序#
- 如果檔案清單來自 Makefile 的 wildcard 展開,目錄項目的順序可能不同
- 應明確指定輸入檔案,或對 wildcard 展開結果排序
嵌入的時間戳記#
- 程式碼中使用
__DATE__和__TIME__巨集會導致每次建置不同 - 改用版本控制系統的識別符(如 Git SHA)取代時間戳記
- 需要時可從 commit 記錄反查時間
Hash 與 Map 的遍歷順序#
- 某些語言為了防止 algorithmic complexity attacks,會隨機化物件的 hash 方式
- 這導致 hash/map 的遍歷順序每次不同
- 解法:對結果排序,或固定 hash seed(Perl:
PERL_HASH_SEED、Python:PYTHONHASHSEED)
加密的 Salt#
- 加密程式通常會加入隨機的 salt 來防止字典攻擊
- 測試與除錯時可停用 salting(如
openssl的-nosalt選項)
停用 salt 僅限於測試環境,生產環境使用會使系統容易遭受字典攻擊。
Reproducible Builds#
建置確定性的黃金標準是在不同機器上編譯相同原始碼,能產生 bit-identical 的套件。這需要額外處理:
- 檔案路徑
- Locale 設定
- Archive metadata
- 環境變數
- 時區
可參考 reproducible builds ↗ 網站取得實務建議。
重點回顧#
- 確定性建置與執行是穩定重現問題的基礎
- 除錯時停用 ASLR,固定記憶體佈局
- 消除建置過程中的隨機因素:符號名稱、輸入順序、時間戳記、hash seed、加密 salt
- 追求 reproducible builds 能進一步確保跨環境的一致性