在大前年,为了说服框架组采用Nuget包的形式分发框架类库,我费了老鼻子的劲也没有取得成功,其中最致命的一个问题是,nuget包不能获得源码调试级的支持,在分发和包的管理形式上其比其他方案都优秀。最后折中的选择是采用源码直接引用项目的方式,这种方案对框架类库的新分支的开发不是很有利,在源码的保护上更是完全没有了保障,不过在当时场景下,也算是可以接受的方案之一了。而经过这几年的发展,微软在这些方面都有了长足的发展,那跟着我,来看看能否解决各位心中的疑惑?
当看到一个人光鲜亮丽,光彩照人的时候,我们都有着一股探究其老底的冲动。是啊,凭啥他就能行,而我们就不行呢?我们起起底,探究下他的小秘密,说不定从他身上能发掘出不为人知的一面,为我们的崛起提供一些参考的方向,这不香吗?
扯得有点远了,我们回归正题。
PDB全称:Program Database,由微软开发的一种调试符号文件存储格式,在windows系统中,为了调试dll或者exe文件,需要有一个符号文件(Symbols file)来支撑调试。符号文件保存多个数据,这些数据在运行二进制文件时实际上并不需要,但在调试过程中可能非常有用。通常,符号文件可能包含:
调试时,必须确保调试器能够访问与正在调试的目标关联的符号文件。 实时调试和调试崩溃转储文件都需要符号。 你必须获取要调试的代码的正确符号,并将这些符号加载到调试器中。 而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下常用的调试符号文件格式。
为了能适应跨平台的需求,微软提出了便携式PDB(Portable PDB )标准,以便和长期使用的Windows PDB相区别。 其主要支持.NET中的托管代码。从历史上看,Windows PDB用于存储本机代码和托管代码的调试信息,其用于读取和写入这些PDB的工具仅在Windows平台上得到支持。便携式PDB旨在以平台无关的格式有效地存储托管的调试信息,其在多个平台上的有着丰富的支持工具,其以可移植格式存储托管的调试信息,并会生成更小的PDB,这在考虑分发大小时也是重要的优势。
让我们来看一个例子。有时您想进入框架以查看发生了什么,特别是如果发生了意外的事情。假设您如同下面所示设置了断点。那么在按F11想进入框架内部的时候,代码直接往下面执行了,这就是您所看到的。
默认情况下,Visual Studio在调试应用程序时仅逐步执行代码。这是一个非常有用的功能,因为您通常希望理解和研究自己所编写代码的逻辑。关注自己,是人生在牙牙学语阶段就开始逐渐不断增长的意识,因此始于人性,才是最好的Feature! 启用这种体验的功能被恰当地称为仅我的代码(“ Just my Code”)。
在某天,您想调试第三方组件或平台本身的逻辑,在这之前,进行调试非常困难。主要是两个方面的困难:
相比而言,JavaScript具有与.NET几乎相反的问题。JavaScript社区(包括浏览器和node.js变体)都使用SourceMap,其提供了调试第三方精简代码的良好体验。但是,JavaScript编辑器无法提供“仅我的代码”的体验。
对于.NET Core 开发人员,我们希望能够轻松自然地在默认的“ 仅我的代码”体验以及带有第三方组件和平台源调试之间进行自由切换,这一切,并不是梦!
那我们怎么能够调试三方库或.net 框架呢?
Visual Studio 2017版中已经支持了符号调试,在VS 的 工具菜单下,选择选项/调试/常规页签,配置如下参数:
选择选项/调试/符号页签,配置如下参数:
如果使用的是VS Code,可以为每个项目配置调试器设置:launch.json
"justMyCode": false,
"symbolOptions": {
"searchMicrosoftSymbolServer": true,
"searchNuGetOrgSymbolServer": true
},
"suppressJITOptimizations": true,
"env": {
"COMPlus_ZapDisable": "1",
"COMPlus_ReadyToRun": "0"
}
注意:
启动调试,发现VS开始下载符号文件,下载完毕后,进入断点。当我们按F11后,弹出如下界面:
Source Link是开发人员的一项生产力功能,它允许在编译过程中将有关程序集原始源代码的唯一信息嵌入到PDB中的一组软件包和规范, 通过SourceLink,添加到PDB文件中的元数据,和本地源代码文件、仓库内的代码文件建立了一个映射关系。
因此Visual Studio调试时可以在需要时下载文件, 并为用户提供源代码调试, Microsoft库(例如.NET Core和Roslyn)都已启用Source Link。
大多数调试是针对开发人员计算机上本地构建的源代码完成的。在这种情况下,将二进制文件与源代码匹配并不困难。
但是,在许多调试方案中,原始源代码没法立即就可用。这方面的两个很好的例子是调试崩溃转储或第三方库。在这些情况下,对于开发人员来说,要获取为生成正在调试的二进制文件而构建的确切源代码可能非常困难(可能是特定的版本)。Source Link通过在PDB中嵌入有关源代码的唯一信息(例如git commit hash)来解决此问题。诊断工具(例如调试器)可以使用此独特信息从托管服务(例如GitHub)中检索原始源代码。
sourcelink 最初的版本是 @ctaggart 实现的,目前已归档, 现在已经加入了 .Net 团队,微软人员和ctaggart 一起做了现在的版本。
官网地址: github.com/dotnet/sour…
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 文件。
在.net core项目内,在.csproj文件内增加如下配置
netcoreapp2.1
true
true
true
snupkg
按照需要引用下面的软件包,注意,这里设置为 PrivateAssets,以避免发布为nuget包后,引用该包的项目下载sourcelink 包。
该Include属性指定域以及服务器的端口(例如server-name或server-name:8080)。
如果您的项目是由版本4.7之前的Bitbucket Server或Bitbucket Data Center托管的SourceLinkBitbucketGitHost,则除了软件包参考之外,还必须指定项目组:
项目组SourceLinkBitbucketGitHost指定Bitbucket主机的域和Bitbucket的版本。该版本非常重要,因为用于访问文件的URL格式随版本4.7更改。默认情况下,源链接采用新格式(4.7+版)。
开发人员必须选择生成源链接文件。这些文件中包含的URL可能指向私有源存储库,这些存储库可能不打算公开给有权访问符号文件的任何人,因此,开发人员应做出明智的选择。
所有应用程序资产(二进制文件,符号和源代码)都在公司防火墙内使用,因此只有有权访问这些资产的用户才能看到它们。 二进制资产是从外部运送的,但符号和源仅在公司防火墙内使用过,因此只有有权访问符号和源资产的用户才可以查看它们。
二进制和符号资产在外部共享。符号资产包含源链接文件(以及可能生成的文件资产)。 源链接文件指向需要身份验证的符号源,例如VSTS。 授权用户(数量可能很少)将可以访问源。 未经授权的用户(数量可能更多)会从他们不理解的端点接收拒绝访问的消息。
在某些情况下,将源代码嵌入符号中是有益的,这样您就可以方便地部署源代码进行调试。但是,这是在便利性和PDB大小之间进行权衡的。尽管将源压缩存储在包含许多源文件的PDB中,但可能会大大增加PDB的大小。
以下嵌入选项(适用于Windows和便携式PDB)均可用:
EmbedAllSources具有布尔值的Project属性表示所有传递给编译器的源都应嵌入到PDB中。
自动嵌入未跟踪的源文件 源链接使调试器和其他工具可以查找源控件跟踪的文件的源内容。但是,并非所有参与构建的文件都被跟踪。例如,在构建期间生成的文件通常不检入存储库。尽管可以手动识别此类文件并标记它们以将其嵌入到PDB中,但是这种过程繁琐且容易出错。
该SourceLink.Embed项目已经支持自动识别,而不是由源代码控制跟踪文件的嵌入。这些API将确定源代码控制未跟踪的文件(例如,对于git存储库,匹配.gitignore文件中条目的文件)和设置EmbedUntrackedSources,然后将指示编译器嵌入未跟踪的源代码。 下面是嵌入源代码的例子,在.csproj项目文件内增加如下配置
True
默认情况下,符号被构建为单独的文件,以最小化二进制文件的大小。这些文件需要由构建它们的系统(例如CI服务器)发布,并由需要它们的系统(例如调试器)发现和检索。
如今,符号服务器主要用于在企业环境中托管符号文件。符号服务器工具可用于此类环境,并且最近也已集成到VSTS中并作为服务公开。
对于在NuGet.org上发布其库的开发人员而言,可公开使用的符号服务器的选项受到限制,并且发布和使用符号的过程比应有的要复杂得多。因此,在NuGet.org上发布的易于调试的软件包数量很少。
符号包(snupkg) 今天,符号包用于分发符号和源。良好的调试体验依赖于调试符号的存在,因为它们提供了一些关键信息,例如已编译的代码与源代码之间的关联、局部变量的名称、堆栈跟踪等。 你可以使用符号包 (.snupkg) 来分发这些符号,并改善 NuGet 包的调试体验。
如果使用 dotnet CLI 或 MSBuild,则除 .nupkg 文件外,还需要设置 IncludeSymbols 和 SymbolPackageFormat 属性以创建 .snupkg 文件。
创建 .snupkg 文件有多种方式实现该需求。
true
snupkg
dotnet pack MyPackage.csproj -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
msbuild MyPackage.csproj /t:pack /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg
nuget pack MyPackage.nuspec -Symbols -SymbolPackageFormat snupkg
nuget pack MyPackage.csproj -Symbols -SymbolPackageFormat snupkg
在这里有着重要的使用经验,如果你想获取的话,需要访问我的CSDN博客,十分抱歉啊,没法直接粘贴过来,因为此处是付费内容专享。 如果能早点提供该方式,相信几年前的方案,我应该能胜出,是吧?
在Visual Studio或其他工具中使用符号应该很方便。它需要适用于所有.NET实现,包括.NET Core,.NET Framework,Xamarin,Unity和UWP。
使用符号进行调试的主要场景有以下三种:
此方案适用于开发阶段。如果符号已经嵌入二进制文件中,则不适用。NuGet是二进制分发的一种常见情况,它允许将符号和二进制文件一起部署。
关键特征是符号文件将直接存在于磁盘上已加载的代码文件旁边,从而使调试器可以轻松地找到给定二进制文件的匹配符号文件。根据本文档中的指导,二进制文件和符号通常会以与以下示例类似的结构并置在NuGet包中。
/ /lib /netstandard2.0 foo.dll foo.pdb .NET Core开发是以NuGet为中心的,这有助于解决此问题。在开发过程中(例如使用dotnet run),. NET Core运行时默认情况下从NuGet缓存加载库,从而使调试器可以在同一位置查找匹配的符号文件。
.NET Framework开发使用NuGet,但形式上使用较少。构建项目时,NuGet库将被复制到应用程序bin目录,而不是符号。结果,代码二进制和符号之间的链接丢失了。 构建系统应使用以下逻辑,以更好地启用带符号的调试:
如果将代码二进制文件复制到某个位置,则还要将符号文件复制到同一位置。在大多数情况下,这将是应用程序bin目录。
此方案适用于调试已部署的应用程序,崩溃转储以及调试NuGet软件包中未附带符号的第三方库。在这些情况下,您需要从符号服务器获取符号。
如上所述,我们建议为上传到NuGet.org的符号提供公共符号服务。您也可以根据需要使用其他符号服务器。
获取和消耗源文件主要是首先具有获取符号的功能。有了符号后,调试器将发现以下一种或多种情况是正确的:
注意:如果调试器无法在符号文件中或通过源链接找到源文件,它仍可以尝试使用**简单符号查询协议**在符号服务器上查找它。这将允许在二进制文件和符号构建完成之后稍后使源可用的情况。
弄懂SourceLink和pdb的关系,竟然花费了我一个元旦假期,本来计划在假期内出篇文章简单介绍下,发现在自己都没有搞清楚的情况,写这些是对大家和自己的不负责任,因此,还是静下心来,仔细理清楚各个环节,希望能对大家有所帮助。
作者:webmote33
链接:https://juejin.cn/post/6913794988304236558
来源:掘金