一篇万字长文读懂微软PDB、SourceLink——.net core之nuget 包调试
序言
在大前年,為了說服框架組采用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 -2Symbol Options: 0x14c13 Symbol Options: 0x14c11mysymbols [1000000]: addr 102cb4e_MyFunction1@4name : _InterlockedIncrement@4addr : 102cb4esize : 0flags : 0type : 0 modbase : 1000000value : 0reg : 0scope : 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”)。
在某天,您想調試第三方組件或平臺本身的邏輯,在這之前,進行調試非常困難。主要是兩個方面的困難:
缺少第三方組件或平臺二進制文件的符號;
缺少第三方組件或平臺二進制文件匹配的符號相關聯的源文件。
相比而言,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" }注意:
并非nuget.org上的每個庫都會為其.pdb文件建立索引。如果發現調試器找不到正在使用的開源庫的PDB文件,請鼓勵作者上載其PDB;
只有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 一起做了現在的版本。
官網地址:?https://github.com/dotnet/sourcelink
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
Azure Repos (former Visual Studio Team Services)
Azure DevOps Server (former Team Foundation Server)
如果您的服務器配置有非空的IIS虛擬目錄,請在SourceLinkAzureDevOpsServerGitHost項目中指定此目錄,如下所示
該Include屬性指定域以及服務器的端口(例如server-name或server-name:8080)。
GitLab
Bitbucket
如果您的項目是由版本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)
開發人員必須選擇生成源鏈接文件。這些文件中包含的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 文件:
在命令行上指定這些屬性:
或者使用msbuild
使用nuget
發布服務包到nuget
# 為方便起見,首先使用 NuGet 保存 API 密鑰 nuget SetApiKey Your-API-Key # 將主包發布到 nuget.org 后,按如下方式推送符號包。 nuget push MyPackage.snupkg # 還可以使用以下命令同時推送主包和符號包。當前文件夾中必須同時有 .nupkg 和 .snupkg 文件。 nuget push MyPackage.nupkg4.2 不使用服務器,直接嵌入發布符號包和源文件
嵌入符號文件和源代碼更簡單,它不需要網絡或互聯網連接,也不需要任何配置即可指定源代碼存儲庫和符號服務器的位置。因為配置私有的符號包服務,我暫時并沒有找到合適的平臺,因此我高度推薦你采用這種方式,非常的便捷,您只需要在csproj內增加如下代碼,即可完成嵌入的便攜式PDB,以及源文件的發布。
<PropertyGroup><DebugSymbols>True</DebugSymbols><DebugType>Embedded</DebugType><EmbedAllSources>True</EmbedAllSources> </PropertyGroup>對于.NET應用程序來說,嵌入PDB文件不會影響編譯器的優化,所以也完全不會影響應用的性能。
4.3 使用私有gitlab保護代碼
結合第3小節的介紹,我們可以很方便的結合gitlab sourceLink,來制作嵌入符號包,保護源碼的發布方式:
<PropertyGroup><TargetFramework>netcoreapp3.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 --><DebugType>Embedded</DebugType><!--<IncludeSymbols>false</IncludeSymbols><SymbolPackageFormat>snupkg</SymbolPackageFormat>--></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.SourceLink.GitLab" Version="1.0.0" PrivateAssets="All" /></ItemGroup>如果能早點提供該方式,相信幾年前的方案,我應該能勝出,是吧?
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的關系,竟然花費了我一個元旦假期,本來計劃在假期內出篇文章簡單介紹下,發現在自己都沒有搞清楚的情況,寫這些是對大家和自己的不負責任,因此,還是靜下心來,仔細理清楚各個環節,希望能對大家有所幫助。
總結
以上是生活随笔為你收集整理的一篇万字长文读懂微软PDB、SourceLink——.net core之nuget 包调试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ABP Framework 研习社经验总
- 下一篇: 关于面试,避开这几点,成功几率更大~~~