發現我對 PDB 檔的知識有點模糊過時,特舉辦知識高裝檢。

  1. .pdb 檔的主要用途為何?
    PDB 提供編譯後程式與原始碼的對映資訊,是錯誤發生時 StackTrace 能指出所在程式位置的依據,另外在進行偵錯時,也需要靠 .pdb 的資訊才能做到檢視變數、設定中斷點、Line by Line 逐行偵錯。

  2. .NET 編譯時如何決定是否產生 .pdb 檔? .csproj 有個 DebugType 參數,共有五種選項:

    • full - 以現有平台格式產生 .pdb 檔,在 Windows 為 Windows PDB 檔,在 Linux/macOS 為 Portable PDB
    • pdbonly - .NET 2.0 之後,pdb-only 基本上等同 full 參考,但 C# 6.0 起有些差異(後面會說明)
    • portable - 產生 Portable PDB 格式 .pdb。Windows PDB 歷史悠久,過度複雜且殘留 C/C++ 專屬規格,Portable PDB 是重構過的跨平台版本
    • embedded - 產生 Portable PDB 並合併到 .dll/.exe,不另存 .pdb 檔
    • none - 不產生 .pdb (並要配合 <DebugSymbols>false</DebugSymbols>)

    C# 6.0 起,full 與 pdbonly 的區別在於 full 會加入 DebuggableAttribute,影響 JIT 編譯最佳化,故在速度與檔案大小上有些差異。

  3. 如何修改 .pdb 產生選項?
    在 Visual Studio 有設定介面(專案屬性,編譯設定的進階設定):

    若是 VSCode,可直接修改 .csproj:

     <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <RootNamespace>dotnet_publish_test</RootNamespace>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <DebugType>none</DebugType>
        <DebugSymbols>false</DebugSymbols>
      </PropertyGroup>
    </Project>
    
  4. Portable PDB 看起來是趨勢,何不一律用它取代 Windows PDB?
    有一些不支援 Portable PDB 的情境,若有遇到請繼續用 Windows PDB:

    • Visual Studio 2015 Update 2 之前的版本不支援 Portable PDB 偵錯
    • .NET Framework 4.7.1 以前的版本,用 Portable PDB 無法在 StackTrace 顯示程式行號
    • FxCop 等程式碼分析工具未支援
    • 有些 Symbol Server 不支援 (如 SymbolSource.org,但 nuget.org 已支援)
    • 不支援 CCI、CodeContracs 等工具
    • .NET 反組譯工具(ILDASM、.NET Reflector)不支援
    • WinDBG 不支援
  5. Visual Studio 的 Debug 設定才會產生 .pdb 檔,Release 不會?
    上古時代可能是如此(.NET 1.x ?),但現在 Visual Studio 專案 Release 設置的預設選項是 Pdb-Only (Debug 為 Full),Release 也會產生 pdb 檔,不用擔出錯時看不到程式行號,Release 與 Debug 的主要差別在定義 DEBUG 常數跟啟用程式碼最佳化(Optimize code)。

  6. 要產生 .pdb,程式編譯就無法最佳化?
    不會,如上圖所示,最佳化與產生 .pdb 是獨立選項,Release 預設啟用 Optimize code,Debug 則否。啟用最佳化時,編譯器會透過移除未用程式、未用變數、重複程式、優化迴圈、Inline 程式展開... 等做法讓程式更小、跑得更快。 參考

  7. 最佳化會影響偵錯嗎?
    會。啟用最佳化時,編譯器跟 Runtime 會藉由調整或加入 CPU 指令讓程式跑得更快,但這可能會讓產生的程式碼無法精準對映原始碼,影響偵錯時區域變數檢視、逐行偵錯以及中斷點設定。
    .NET 編譯分成兩段,第一段是編譯器將原始碼轉成 MSIL 存成 .dll 或 .exe,第二段則是 .NET Runtime 將 MSIL 轉成 CPU 可直接執行的機器碼(JIT 編譯),兩個階段都能進行最佳化,後者效果較明顯。
    為了避免最佳化影響偵錯,在 Visual Studio 使用 F5 啟動程式偵錯時將強制停用 JIT 編譯最佳化,另外有個「Suppress JIT optimizations on module load (Managed only)」選項可讓額外載入 .dll 時也停用 JIT 編譯最佳化。但原始碼轉 MSIL 所做的最佳化已成定局,可能影響偵錯結果,例如:對映的原始碼位置不對,中斷點未被觸發... 等等。至於 StackTrace 出現的程式行碼,依實務經驗還是準確的。
    有一點要謹記在心意,偵錯時因強制停用 JIT 最佳化,故觀察結果可能與線上環境完全最佳化的版本有可能存在差異。
    延伸閱讀:Debugging Optimized Code

  8. 為了出錯時可明確顯示程式行數,部署時要用 Debug 編譯版?
    錯! 如前面所提,Release 版預設也會產生 .pdb,錯誤時一樣可由 StackTrace 取得錯誤行數, Debug 版程式未最佳化,執行效能較差,除非要用 Debugger 線上偵錯,否則沒理由用 Debug 版部署。

Basic conecept of .NET .pdb file and how it helps debugging.


Comments

Be the first to post a comment

Post a comment