為什麼需要 Symbolic Debugging 資訊#
雖然 debugger 可以用來除錯任何已編譯的程式,但當程式包含 debugging information 時效果最佳。這些資訊將機器指令對應到原始碼的行號與變數名稱,讓你在除錯時能看到有意義的資訊,而非只是一堆記憶體位址。
缺少除錯資訊的後果#
對於 C、C++、Go 等編譯為機器碼的語言,程式預設只包含執行時期 dynamic linking 所需的最少資訊。在這種情況下,debugger 的 call stack 中只會顯示少數幾個有名稱的函式(來自共享函式庫),其餘則以 ?? 和記憶體位址取代。
三個層次的資訊#
- Stripped binary — 用
strip指令移除所有 symbol,只保留最基本的執行資訊。可用file指令檢查是否被 strip - Non-stripped binary — 保留函式名稱與變數名稱(non-static symbols),call stack 中的
??會被替換為實際函式名稱 - Debug build — 嵌入完整除錯資訊,包括每個指令對應的檔案名稱、行號,以及所有變數的存放位置
如何啟用除錯資訊#
- Java (Eclipse) — 預設啟用,可透過 Project - Properties - Java Compiler - Classfile Generation 控制
- Oracle JDK — 使用
-g選項嵌入所有除錯資訊 - Unix C/C++ 編譯器 — 使用
-g選項 - Microsoft 編譯器 — 使用
/Zi選項 - Visual Studio — 透過 Build - Configuration manager - Active solution configuration 切換 Debug/Release 組態
編譯器最佳化的影響#
現代編譯器的最佳化會大幅改變產生的程式碼,導致 single-stepping 時出現跳行、跳過語句、或執行順序不符預期的情況。
書中展示了一個簡單的 C 程式,在開啟最大最佳化後,編譯器直接在 compile time 算出結果(常數折疊),產生的組合語言完全沒有迴圈和變數運算,只是把預算好的值推入 stack 然後呼叫 printf。
關閉最佳化#
- Unix — 使用
-O0編譯選項 - Microsoft — 使用
/Od編譯選項 - IDE — 選擇 Debug configuration(IDE 會自動處理)
- Java — Oracle 的 Java compiler 不做最佳化,由 JIT 在執行時處理
即使關閉最佳化,編譯器仍可能移除 dead code 或求值常數表達式。關閉最佳化主要適用於 debug build,因為會顯著降低執行速度。
例外情況#
有兩種情況你可能不想在 debug build 中關閉最佳化:
- 程式中有些效能關鍵的部分需要最佳化,但不是你要除錯的部分 — 可以分別設定
- 有些 bug 只在 production build 中出現 — 此時需要建立一個同時包含 debug 資訊和 production 最佳化的特殊 build
重點回顧#
- 調整 build 設定以取得所需層級的除錯資訊
- 關閉編譯器最佳化,使產生的程式碼與原始碼一致
- 必要時建立同時包含 debug 資訊與最佳化的特殊 build