微軟PDB、SourceLink——.net core之nuget 包除錯你瞭解嗎?花10分鐘看看你就懂了!

程式設計師阿軒發表於2021-01-04

在大前年,為了說服框架組採用Nuget包的形式分發框架類庫,我費了老鼻子的勁也沒有取得成功,其中最致命的一個問題是,nuget包不能獲得原始碼除錯級的支援,在分發和包的管理形式上其比其他方案都優秀。最後折中的選擇是採用原始碼直接引用專案的方式,這種方案對框架類庫的新分支的開發不是很有利,在原始碼的保護上更是完全沒有了保障,不過在當時場景下,也算是可以接受的方案之一了。而經過這幾年的發展,微軟在這些方面都有了長足的發展,那跟著我,來看看能否解決各位心中的疑惑?

1. 歷史悠久的PDB

當看到一個人光鮮亮麗,光彩照人的時候,我們都有著一股探究其老底的衝動。是啊,憑啥他就能行,而我們就不行呢?我們起起底,探究下他的小祕密,說不定從他身上能發掘出不為人知的一面,為我們的崛起提供一些參考的方向,這不香嗎?

 

扯得有點遠了,我們迴歸正題。

1.1 PDB 和符號檔案

PDB全稱:Program Database,由微軟開發的一種除錯符號檔案儲存格式,在windows系統中,為了除錯dll或者exe檔案,需要有一個符號檔案(Symbols file)來支撐除錯。符號檔案儲存多個資料,這些資料在執行二進位制檔案時實際上並不需要,但在除錯過程中可能非常有用。通常,符號檔案可能包含:

  • 全域性變數
  • 區域性變數
  • 函式名稱和其入口點的地址
  • 幀指標省略 (FPO) 記錄
  • 源行號

除錯時,必須確保偵錯程式能夠訪問與正在除錯的目標關聯的符號檔案。 實時除錯和除錯崩潰轉儲檔案都需要符號。 你必須獲取要除錯的程式碼的正確符號,並將這些符號載入到偵錯程式中。 而PDB檔案就是windows保留符號的檔案格式。熟悉C++的朋友,應該對這個檔案非常熟悉,它伴隨著Visual Studio 和WinDbg而產生,可謂是歷史悠久的檔案格式之一。 什麼,想看看PDB檔案儲存的是啥玩意? 好吧,本尊就滿足你的好奇心,在windows的除錯工具有個dbh小工具,可以檢視pdb的檔案內容。

mysymbols [1000000]: symopt -2

Symbol Options: 0x14c13
Symbol Options: 0x14c11

mysymbols [1000000]: addr 102cb4e

_MyFunction1@4
  name : _InterlockedIncrement@4
  addr :  102cb4e
  size : 0
 flags : 0
  type : 0
modbase :  1000000
 value :        0
   reg : 0
 scope : SymTagNull (0)
   tag : SymTagPublicSymbol (a)
 index : 2ab  

當然了,符號檔案本身也有很多格式,例如大名鼎鼎的COFF是Unix下常用的除錯符號檔案格式。

1.2 PDB的變種-行動式PDB

為了能適應跨平臺的需求,微軟提出了行動式PDB(Portable PDB )標準,以便和長期使用的Windows PDB相區別。 其主要支援.NET中的託管程式碼。從歷史上看,Windows PDB用於儲存本機程式碼和託管程式碼的除錯資訊,其用於讀取和寫入這些PDB的工具僅在Windows平臺上得到支援。行動式PDB旨在以平臺無關的格式有效地儲存託管的除錯資訊,其在多個平臺上的有著豐富的支援工具,其以可移植格式儲存託管的除錯資訊,並會生成更小的PDB,這在考慮分發大小時也是重要的優勢。

2 使用符號除錯

2.1 想要除錯三方庫或.net 框架

讓我們來看一個例子。有時您想進入框架以檢視發生了什麼,特別是如果發生了意外的事情。假設您如同下面所示設定了斷點。那麼在按F11想進入框架內部的時候,程式碼直接往下面執行了,這就是您所看到的。

 

預設情況下,Visual Studio在除錯應用程式時僅逐步​執行程式碼。這是一個非常有用的功能,因為您通常希望理解和研究自己所編寫程式碼的邏輯。關注自己,是人生在牙牙學語階段就開始逐漸不斷增長的意識,因此始於人性,才是最好的Feature! 啟用這種體驗的功能被恰當地稱為僅我的程式碼(“ Just my Code”)。

 

在某天,您想除錯第三方元件或平臺本身的邏輯,在這之前,進行除錯非常困難。主要是兩個方面的困難:

  1. 缺少第三方元件或平臺二進位制檔案的符號;
  2. 缺少第三方元件或平臺二進位制檔案匹配的符號相關聯的原始檔。

相比而言,JavaScript具有與.NET幾乎相反的問題。JavaScript社群(包括瀏覽器和node.js變體)都使用SourceMap,其提供了除錯第三方精簡程式碼的良好體驗。但是,JavaScript編輯器無法提供“僅我的程式碼”的體驗。

對於.NET Core 開發人員,我們希望能夠輕鬆自然地在預設的“ 僅我的程式碼”體驗以及帶有第三方元件和平臺源除錯之間進行自由切換,這一切,並不是夢!

2.2 除錯.net core 平臺程式碼

那我們怎麼能夠除錯三方庫或.net 框架呢?

Visual Studio 2017版中已經支援了符號除錯,在VS 的 工具選單下,選擇選項/除錯/常規頁籤,配置如下引數:

  • 禁用 “啟用僅我的程式碼”
  • 啟用源連結

 

選擇選項/除錯/符號頁籤,配置如下引數:

  • 選擇 Microsoft符號伺服器;
  • 選擇 NuGet.org 符號伺服器(如果除錯的是nuget庫);
  • 在快取符號目錄中設定一個目錄,避免多次下載符號庫。

 

如果使用的是VS Code,可以為每個專案配置偵錯程式設定:launch.json

"justMyCode": false,
"symbolOptions": {
    "searchMicrosoftSymbolServer": true,
    "searchNuGetOrgSymbolServer": true
},
"suppressJITOptimizations": true,
"env": {
    "COMPlus_ZapDisable": "1",
    "COMPlus_ReadyToRun": "0"
}

注意

  1. 並非nuget.org上的每個庫都會為其.pdb檔案建立索引。如果發現偵錯程式找不到正在使用的開源庫的PDB檔案,請鼓勵作者上載其PDB;
  2. 只有Microsoft提供的庫才會在Microsoft符號伺服器上擁有其.pdb檔案,因此,如果您只對三方庫感興趣,可以禁用該選項。

啟動除錯,發現VS開始下載符號檔案,下載完畢後,進入斷點。當我們按F11後,彈出如下介面:

 

3. SourceLink

Source Link是開發人員的一項生產力功能,它允許在編譯過程中將有關程式集原始原始碼的唯一資訊嵌入到PDB中的一組軟體包和規範, 通過SourceLink,新增到PDB檔案中的後設資料,和本地原始碼檔案、倉庫內的程式碼檔案建立了一個對映關係。

因此Visual Studio除錯時可以在需要時下載檔案, 併為使用者提供原始碼除錯, Microsoft庫(例如.NET Core和Roslyn)都已啟用Source Link。

3.1 為什麼使用SourceLink呢?

大多數除錯是針對開發人員計算機上本地構建的原始碼完成的。在這種情況下,將二進位制檔案與原始碼匹配並不困難。

但是,在許多除錯方案中,原始原始碼沒法立即就可用。這方面的兩個很好的例子是除錯崩潰轉儲或第三方庫。在這些情況下,對於開發人員來說,要獲取為生成正在除錯的二進位制檔案而構建的確切原始碼可能非常困難(可能是特定的版本)。Source Link通過在PDB中嵌入有關原始碼的唯一資訊(例如git commit hash)來解決此問題。診斷工具(例如偵錯程式)可以使用此獨特資訊從託管服務(例如GitHub)中檢索原始原始碼。

sourcelink 最初的版本是 @ctaggart 實現的,目前已歸檔, 現在已經加入了 .Net 團隊,微軟人員和ctaggart 一起做了現在的版本。

官網地址: github.com/dotnet/sour…

3.2 SourceLink的檔案規範

SourceLink 是一個Json配置的檔案,其內容格式如下:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "SourceLink",
    "description": "A mapping of source file paths to URLs",
    "type": "object",
    "properties": {
        "documents": {
            "type": "object",
            "minProperties": 1,
            "additionalProperties": {
                "type": "string"
            },
            "description": "Each document is defined by a file path and a URL. Original source file paths are compared 
                             case-insensitively to documents and the resulting URL is used to download source. The document 
                             may contain an asterisk to represent a wildcard in order to match anything in the asterisk's 
                             location. The rules for the asterisk are as follows:
                             1. The only acceptable wildcard is one and only one '*', which if present will be replaced by a relative path.
                             2. If the file path does not contain a *, the URL cannot contain a * and if the file path contains a * the URL must contain a *.
                             3. If the file path contains a *, it must be the final character.
                             4. If the URL contains a *, it may be anywhere in the URL."
        }
    },
    "required": ["documents"]
}

為了減輕生成該json的工作量,微軟提供了一系列的軟體包,自動生成Source Link 檔案。

3.3 自動生成SourceLink

在.net core專案內,在.csproj檔案內增加如下配置

<Project Sdk="Microsoft.NET.Sdk">
 <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
 
    <!-- Optional: Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
 
    <!-- Optional: Embed source files that are not tracked by the source control manager in the PDB -->
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
  
    <!-- Optional: Build symbol package (.snupkg) to distribute the PDB containing Source Link -->
    <IncludeSymbols>true</IncludeSymbols>
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  </PropertyGroup>
  <ItemGroup>
    <!-- Add PackageReference specific for your source control provider (see below) --> 
  </ItemGroup>
</Project>

按照需要引用下面的軟體包,注意,這裡設定為 PrivateAssets,以避免釋出為nuget包後,引用該包的專案下載sourcelink 包。

  • github.com and GitHub Enterprise
<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
</ItemGroup>
  • Azure Repos (former Visual Studio Team Services)
<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.AzureRepos.Git" Version="1.0.0" PrivateAssets="All"/>
</ItemGroup>
  • Azure DevOps Server (former Team Foundation Server)
<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.AzureDevOpsServer.Git" Version="1.0.0" PrivateAssets="All"/>
</ItemGroup>
  • 如果您的伺服器配置有非空的IIS虛擬目錄,請在SourceLinkAzureDevOpsServerGitHost專案中指定此目錄,如下所示
<ItemGroup>
  <SourceLinkAzureDevOpsServerGitHost Include="server-name" VirtualDirectory="tfs"/>
</ItemGroup>

該Include屬性指定域以及伺服器的埠(例如server-name或server-name:8080)。

  • GitLab
<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.GitLab" Version="1.0.0" PrivateAssets="All"/>
</ItemGroup>
  • Bitbucket
<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.Bitbucket.Git" Version="1.0.0" PrivateAssets="All"/>
</ItemGroup>

如果您的專案是由版本4.7之前的Bitbucket Server或Bitbucket Data Center託管的SourceLinkBitbucketGitHost,則除了軟體包參考之外,還必須指定專案組:

<ItemGroup>
  <SourceLinkBitbucketGitHost Include="bitbucket.yourdomain.com" Version="4.5"/>
</ItemGroup>

專案組SourceLinkBitbucketGitHost指定Bitbucket主機的域和Bitbucket的版本。該版本非常重要,因為用於訪問檔案的URL格式隨版本4.7更改。預設情況下,源連結採用新格式(4.7+版)。

  • gitweb (pre-release)
<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.GitWeb" Version="1.1.0-beta-20204-02" PrivateAssets="All"/>
</ItemGroup>

開發人員必須選擇生成源連結檔案。這些檔案中包含的URL可能指向私有源儲存庫,這些儲存庫可能不打算公開給有權訪問符號檔案的任何人,因此,開發人員應做出明智的選擇。

  • 開源開發人員通常選擇不參與源連結檔案生成,因為他們通常不存在公開問題。
  • 公司開發人員還應選擇採用以下方式選擇源連結檔案生成:

所有應用程式資產(二進位制檔案,符號和原始碼)都在公司防火牆內使用,因此只有有權訪問這些資產的使用者才能看到它們。 二進位制資產是從外部運送的,但符號和源僅在公司防火牆內使用過,因此只有有權訪問符號和源資產的使用者才可以檢視它們。

  • 公司開發人員還有另一個選擇(可以說是反模式),如下所示:

二進位制和符號資產在外部共享。符號資產包含源連結檔案(以及可能生成的檔案資產)。 源連結檔案指向需要身份驗證的符號源,例如VSTS。 授權使用者(數量可能很少)將可以訪問源。 未經授權的使用者(數量可能更多)會從他們不理解的端點接收拒絕訪問的訊息。

3.4 源嵌入

在某些情況下,將原始碼嵌入符號中是有益的,這樣您就可以方便地部署原始碼進行除錯。但是,這是在便利性和PDB大小之間進行權衡的。儘管將源壓縮儲存在包含許多原始檔的PDB中,但可能會大大增加PDB的大小。

以下嵌入選項(適用於Windows和行動式PDB)均可用:

  • 僅嵌入原始碼管理未跟蹤的原始檔(例如,在生成期間生成的檔案)。其餘檔案由源連結對映。
  • 嵌入手動選擇的原始檔子集。
  • 嵌入所有原始檔

EmbedAllSources具有布林值的Project屬性表示所有傳遞給編譯器的源都應嵌入到PDB中。

自動嵌入未跟蹤的原始檔 源連結使偵錯程式和其他工具可以查詢源控制元件跟蹤的檔案的源內容。但是,並非所有參與構建的檔案都被跟蹤。例如,在構建期間生成的檔案通常不檢入儲存庫。儘管可以手動識別此類檔案並標記它們以將其嵌入到PDB中,但是這種過程繁瑣且容易出錯。

該SourceLink.Embed專案已經支援自動識別,而不是由原始碼控制跟蹤檔案的嵌入。這些API將確定原始碼控制未跟蹤的檔案(例如,對於git儲存庫,匹配.gitignore檔案中條目的檔案)和設定EmbedUntrackedSources,然後將指示編譯器嵌入未跟蹤的原始碼。 下面是嵌入原始碼的例子,在.csproj專案檔案內增加如下配置

<PropertyGroup> 
  <EmbedAllSources>True</EmbedAllSources>
  <!--
  <EmbedUntrackedSources>true</EmbedUntrackedSources>
  -->
</PropertyGroup>

4.釋出符號檔案

預設情況下,符號被構建為單獨的檔案,以最小化二進位制檔案的大小。這些檔案需要由構建它們的系統(例如CI伺服器)釋出,並由需要它們的系統(例如偵錯程式)發現和檢索。

4.1將符號釋出到符號伺服器

如今,符號伺服器主要用於在企業環境中託管符號檔案。符號伺服器工具可用於此類環境,並且最近也已整合到VSTS中並作為服務公開。

對於在NuGet.org上釋出其庫的開發人員而言,可公開使用的符號伺服器的選項受到限制,並且釋出和使用符號的過程比應有的要複雜得多。因此,在NuGet.org上釋出的易於除錯的軟體包數量很少。

符號包(snupkg) 今天,符號包用於分發符號和源。良好的除錯體驗依賴於除錯符號的存在,因為它們提供了一些關鍵資訊,例如已編譯的程式碼與原始碼之間的關聯、區域性變數的名稱、堆疊跟蹤等。 你可以使用符號包 (.snupkg) 來分發這些符號,並改善 NuGet 包的除錯體驗。

如果使用 dotnet CLI 或 MSBuild,則除 .nupkg 檔案外,還需要設定 IncludeSymbols 和 SymbolPackageFormat 屬性以建立 .snupkg 檔案。

建立 .snupkg 檔案有多種方式實現該需求

  • 將以下屬性新增到 .csproj 檔案:
<PropertyGroup>
    <IncludeSymbols>true</IncludeSymbols>
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
  • 在命令列上指定這些屬性:
dotnet pack MyPackage.csproj -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
  • 或者使用msbuild
msbuild MyPackage.csproj /t:pack /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg
  • 使用nuget
nuget pack MyPackage.nuspec -Symbols -SymbolPackageFormat snupkg
nuget pack MyPackage.csproj -Symbols -SymbolPackageFormat snupkg

重要:我的心得和經驗

在這裡有著重要的使用經驗,如果你想獲取的話,需要訪問我的CSDN部落格,十分抱歉啊,沒法直接貼上過來,因為此處是付費內容專享。 如果能早點提供該方式,相信幾年前的方案,我應該能勝出,是吧?

5.使用PDB(符號)和SourceLink

在Visual Studio或其他工具中使用符號應該很方便。它需要適用於所有.NET實現,包括.NET Core,.NET Framework,Xamarin,Unity和UWP。

使用符號進行除錯的主要場景有以下三種:

  • 在開發階段除錯應用程式。在這種情況下,需要在構建系統外部檢索第三方庫符號,同時將應用程式符號生成為構建的一部分。
  • 在部署狀態下除錯應用程式。在這種情況下,需要檢索應用程式和庫符號。該方案將附加到正在執行的應用程式。
  • 除錯應用程式的故障轉儲。在這種情況下,需要應用程式和庫符號。

5.1符號和二進位制檔案一起分發

此方案適用於開發階段。如果符號已經嵌入二進位制檔案中,則不適用。NuGet是二進位制分發的一種常見情況,它允許將符號和二進位制檔案一起部署。

關鍵特徵是符號檔案將直接存在於磁碟上已載入的程式碼檔案旁邊,從而使偵錯程式可以輕鬆地找到給定二進位制檔案的匹配符號檔案。根據本文件中的指導,二進位制檔案和符號通常會以與以下示例類似的結構並置在NuGet包中。

/ /lib /netstandard2.0 foo.dll foo.pdb .NET Core開發是以NuGet為中心的,這有助於解決此問題。在開發過程中(例如使用dotnet run),. NET Core執行時預設情況下從NuGet快取載入庫,從而使偵錯程式可以在同一位置查詢匹配的符號檔案。

.NET Framework開發使用NuGet,但形式上使用較少。構建專案時,NuGet庫將被複制到應用程式bin目錄,而不是符號。結果,程式碼二進位制和符號之間的連結丟失了。 構建系統應使用以下邏輯,以更好地啟用帶符號的除錯:

如果將程式碼二進位制檔案複製到某個位置,則還要將符號檔案複製到同一位置。在大多數情況下,這將是應用程式bin目錄。

5.2 符號和二進位制檔案分開分發

此方案適用於除錯已部署的應用程式,崩潰轉儲以及除錯NuGet軟體包中未附帶符號的第三方庫。在這些情況下,您需要從符號伺服器獲取符號。

如上所述,我們建議為上傳到NuGet.org的符號提供公共符號服務。您也可以根據需要使用其他符號伺服器。

5.3 獲取和消耗原始檔

獲取和消耗原始檔主要是首先具有獲取符號的功能。有了符號後,偵錯程式將發現以下一種或多種情況是正確的:

  • 符號檔案包含偵錯程式正在尋找的原始檔的嵌入式源,此時偵錯程式將使用該源。
  • 該符號檔案是Windows PDB,包含嵌入式源伺服器資訊。可以使用諸如pdbstr或GitLink之類的構建工具,使用源伺服器資訊來修改現有的Windows PDB。
  • 該符號檔案包含一個嵌入式源連結檔案,此時偵錯程式將解釋並執行源連結檔案中的宣告。
  • 否則,將無法通過本文件中討論的機制獲得源

注意:如果偵錯程式無法在符號檔案中或通過源連結找到原始檔,它仍可以嘗試使用**簡單符號查詢協議**在符號伺服器上查詢它。這將允許在二進位制檔案和符號構建完成之後稍後使源可用的情況。

6 小結

 

弄懂SourceLink和pdb的關係,竟然花費了我一個元旦假期,本來計劃在假期內出篇文章簡單介紹下,發現在自己都沒有搞清楚的情況,寫這些是對大家和自己的不負責任,因此,還是靜下心來,仔細理清楚各個環節,希望能對大家有所幫助。


作者:webmote33
連結:https://juejin.cn/post/6913794988304236558
來源:掘金

相關文章