為什麼需要 Symbolic Debugging 資訊#

雖然 debugger 可以用來除錯任何已編譯的程式,但當程式包含 debugging information 時效果最佳。這些資訊將機器指令對應到原始碼的行號與變數名稱,讓你在除錯時能看到有意義的資訊,而非只是一堆記憶體位址。

缺少除錯資訊的後果#

對於 C、C++、Go 等編譯為機器碼的語言,程式預設只包含執行時期 dynamic linking 所需的最少資訊。在這種情況下,debugger 的 call stack 中只會顯示少數幾個有名稱的函式(來自共享函式庫),其餘則以 ?? 和記憶體位址取代。

三個層次的資訊#

  1. Stripped binary — 用 strip 指令移除所有 symbol,只保留最基本的執行資訊。可用 file 指令檢查是否被 strip
  2. Non-stripped binary — 保留函式名稱與變數名稱(non-static symbols),call stack 中的 ?? 會被替換為實際函式名稱
  3. 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