简介
不久前,我必须建立一个符号服务器,带有源索引,那时,关于这个主题的信息不多,所以我很难让所有的东西按照我们想要的方式工作。不幸的是,仍然只有这些相同的信息,但是自从微软将源代码发布到.NET框架并自动将其符号服务器添加到Visual Studio 2010以来,似乎有更多的人意识到了这一点。大多数使用.NET框架的人现在都会意识到,他们可以通过使用Microsoft的符号服务器从Microsoft获取符号文件和源代码来调试框架,但是有多少人真正了解什么是符号服务器,或者Visual Studio如何将源代码获取到.NET框架?答案出人意料地简单,与一些人的看法相反,他们认为这项技术已经存在了似乎是永远的,而且可以供你使用。因此,本文将向您介绍符号服务器的奇迹,也许更重要的是,源索引。
什么是符号服务器?
符号服务器基本上只是一个使用文件系统的非常简单的数据库,用于存储符号文件的不同版本。WinDbg和Visual Studio都可以通过SymSrv DLL使用这些数据库,SymSrv DLL随Windows调试工具包提供,用于为正在调试的应用程序加载匹配的符号。实际上,它不是一个数据库,而是一组结构方便的文件夹和文件,因此,您可以自由地复制和粘贴您的数据库到任何您喜欢的位置,但symbol服务器仍会记录事务,并保留已添加或从数据库中删除的所有内容的记录,因此您不能只是手动添加据我所知的档案。关于symbol服务器的一个重要注意事项是它不支持同时执行多个事务,没有锁定机制来阻止其他人在事务进行时更新数据库,因此您可能需要小心,一次只有一个人在更新数据库。
使用符号服务器意味着每个人都可以轻松访问最新版本的符号,如果需要调试较旧版本的应用程序或库,则无需担心自己会挖不出符号文件,它们将自动加载。
符号服务器本身是非常方便的,因为它允许您在调试时查看调用堆栈和其他有用的信息。但是举例来说,您正在调试一个客户机通过加载一个密钥库而经历的崩溃,并且您机器上的源代码不再匹配用于构建该应用程序特定版本的源代码,您可能很难准确地找到导致崩溃的原因;这是源索引出现的地方。
什么是源索引?
源索引是将命令嵌入到符号文件中的行为,当运行时,将从源代码管理系统中提取源代码的正确版本,或者从您可能已经准备好的其他备份中获取源代码。调试器可以在需要打开文件时运行这些命令,以便获得正确的源文件。因此,当您加载客户端在愤怒的电子邮件中发送给您的小型转储文件时,您可以在Visual Studio中加载它,您将看到用于生成该生成文件的确切源代码,以及(希望)指向有问题的代码行的大箭头所遇到的错误。
如何设置符号服务器
symbol服务器的先决条件是您有一些网络位置来存储数据库,即使您是唯一的用户,它也可以只是硬盘上的一个文件夹。下一步是告诉调试器符号服务器的位置,以便它在调试时检查符号文件。
在最新版本的Visual Studio中,您需要转到“工具”->“选项”->“调试”->“符号”,然后将路径添加到符号服务器。如果符号服务器位于某个网络上,则还应为Visual Studio指定一个本地缓存,以便在下次需要这些符号时将这些符号复制到该网络,而无需从该网络下载这些符号。
在旧版本的Visual Studio中,仅仅指定符号服务器的路径是不够的,还需要告诉它它是符号服务器。您可以将SRV*放在符号服务器路径之前。
SRV*只是symsrv*symsrv.dll*的简写,所以如果您看到完整版本,它的意思完全相同。SRV*语法有几个变体:
SRV*LocalCache*SymbolServerPath SRV*LocalCache*NetworkCache*SymbolServerPath
因此,可以为每个符号服务器指定不同的缓存位置。如果你是从某个非现场位置获取你的符号,你也可以指定一个网络缓存,这样当其他用户需要符号文件时,他们只需要直接从你自己的网络下载,而不是从地球的另一边下载。
如果使用WinDbg,则要添加符号服务器,需要转到“文件”->“符号文件路径”,然后使用上面的SRV*语法添加符号服务器。
您还可以为符号服务器设置一个环境变量,以便Visual Studio和WinDbg(以及任何其他兼容的调试器)都知道您的服务器,而不必在每个应用程序中显式地设置它们。需要创建的环境变量是符号路径,可以在中作为用户或系统变量创建。它对每台服务器使用SRV*语法,如果需要指定多台服务器,则需要用分号分隔每台服务器。
_NT_SYMBOL_PATH= SRV*c:symbols*\symbolserver; SRV*c:symbols*http://www.someotherplace.co.uk/symbols
此时,您应该让调试器在服务器中查找符号,此时该符号可能为空。要向服务器添加符号,需要使用Windows调试工具提供的SymStore.exe。向服务器添加一组符号的基本命令是:
symstore add /f "c:MyProjectOutput*.*" /s "\MySymbolServerSymbols" /t "MyProject" /v "Build 1234" /c "Example Transaction"
关于每个命令的注释:
- add告诉symstore我们正在添加文件。
- /f对于我们要添加的文件(在本例中是一个或多个文件)的路径,如果您像我在这里所做的那样指定一个路径,则它将搜索要添加到服务器的任何兼容文件—这些文件包括Visual Studio生成的调试pdb文件以及二进制文件本身。请记住,如果要从小型转储进行调试,则可能还必须将二进制文件添加到符号服务器。我不知道如何指定要从中添加的多个路径,因此如果您只需要.PDB和.DLL文件(但不需要可执行文件),则必须对每个文件运行单独的命令,或者将要备份的所有文件移动到同一文件夹。
- /s要将文件添加到的符号服务器的路径。如果只是一个空文件夹,那么它将添加必要的文件和文件夹,将空文件夹转换为符号服务器。
- /t事务的名称,这是一个必需的参数,通常您只需将项目名称放在此处,或任何其他标识字符串。
- /v要添加的文件的版本号。它不是必需的,只是为了您的方便,所以如果您需要手动找到一组特定的符号,您可以。
- /c事务的注释,同样地,它不是必需的,只是用于日志文件和您的利益。
Symstore还有一些其他参数,允许您以几种不同的方式设置symbol服务器。我不在这里介绍它们,因为这里有一个很好的MSDN页面来解释它们,您可以从文章底部的链接中找到它们。
如何索引符号文件
在将符号文件添加到服务器之前,可以在其中嵌入命令,以便从版本控制系统或其他任何相关的位置提取当前源代码。Windows调试工具中包含一些脚本,这些脚本将把不同版本控制系统中的源代码索引到PDB文件中。我将给出一个如何使用脚本的快速示例,然后我将检查实际发生的情况,以便如果您有需要,可以编写自己的脚本。
要使用源索引脚本,首先需要安装Perl,因为这些实际上是Perl脚本。一旦您构建了项目,并希望用附加信息为PDB文件编制索引,调试器将需要从版本控制系统中提取源代码,您需要转到Windows安装调试工具中的srcsrv文件夹,并找到版本控制的相关脚本,例如,您正在使用Subversion,那么您需要运行svnindex.cmd。有两个参数需要传递给脚本,以便脚本可以索引文件,它们是指向项目工作目录的源路径的分号分隔列表,以及PDB文件所在文件夹的分号分隔列表。所以你的命令是:
svnindex.cmd /source="c:SharedModules;C:MyVeryImportantProject" /symbols="c:SharedModulesRelease;c:MyVeryImportantProjectRelease"
然后,脚本将插入PDB文件中列出的每个文件的命令,以从SVN中提取这些命令。在SVN的情况下,如果需要使用特定的用户名和密码,还可以传入/user=“MyUserName”/pass=“MyPassword”注意,这些参数是特定于SVN脚本的,其他脚本可能不总是接受用户名和密码,并且可能有自己的特定设置。与控制台中的大多数内容一样,您可以通过传递-?作为论据。
每个索引脚本还支持从两个环境变量加载一个变量以及一个名为SrcSrv.ini的配置文件。运行脚本时,它们将使用大多数本地设置,因此命令行参数将覆盖srcsrv.ini,后者将覆盖环境变量。还可以通过添加/ini=“Path to ini file”指定运行脚本时要使用的特定配置文件。
脚本里做了什么
基本上,索引脚本的目的是生成这样的数据块:
SRCSRV: ini ------------------------------------------------ VERSION=1 INDEXVERSION=2 VERCTRL=Test DATETIME=Mon, 04 October 2010 SRCSRV: variables ------------------------------------------ SRCSRVTRG=%targ%\%var4%\%var2%\%fnfile%(%var1%) SRCSRVCMD=cmd /c copy "%var1%" "%SRCSRVTRG%" SRCSRV: source files --------------------------------------- D:DocumentsSKProjectsAlphaFormsAlphaFormsLayeredWindow.cs*11*_*AlphaFormsAlphaForms *svn://192.168.1.5/AlphaForms/trunk/AlphaForms/LayeredWindow.cs SRCSRV: end ------------------------------------------------
上面有一些关于如何构建命令的信息,下面列出了每个文件的该命令的参数。因此,如果该命令是一个简单的副本,那么您可以将该命令设置为:
copy "%var1%" "%srcsrvtrg%
var1引用变量列表中的第一项,在本例中为D: DocumentsSKProjectsAlphaFormsAlphaFormsLayeredWindow.cs。srcsrvtrg是数据块头中指定文件复制位置的命名变量之一。在调用命令之前,调试器将检查文件是否存在,因此,如果您在此之前打开了该文件的版本,则不需要重新运行一个可能非常慢的命令。srcsrvtrg由其他几个命名变量组成。targ是文件将放入的本地缓存目录,fnfile实际上是一个函数,它从以下括号中指定的路径(在本例中是var1,文件的路径)获取文件名。
您可能已经注意到,两个模符号之间的所有内容都被视为变量,并将被它们表示的实际数据替换(如果可能的话),这种替换值的操作也是递归的,就像srcsrvtrg变量将被它表示的字符串替换一样,然后填写变量targ和var1。
事实上,将这些数据插入PDB文件是完全无用的,因为它只会将您系统中已有的文件复制到其他地方。一个更现实的例子是:
SRCSRV: ini ------------------------------------------------ VERSION=1 INDEXVERSION=2 VERCTRL=Subversion DATETIME=Mon, 04 October 2010 SRCSRV: variables ------------------------------------------ SRCSRVTRG=%targ%\%var4%\%var2%\%fnfile%(%var1%) SRCSRVCMD=cmd /c "svn cat "%var5%@%var2% --non-interactive > "%SRCSRVTRG%" SRCSRV: source files --------------------------------------- D:DocumentsSKProjectsAlphaFormsAlphaFormsLayeredWindow.cs*11*_*AlphaFormsAlphaForms *svn://192.168.1.5/AlphaForms/trunk/AlphaForms/LayeredWindow.cs D:DocumentsSKProjectsAlphaFormsAlphaFormsAlphaForm_WndProc.cs*10*_ *AlphaFormsAlphaForms*svn://192.168.1.5/AlphaForms/trunk/AlphaForms/AlphaForm_WndProc.cs D:DocumentsSKProjectsAlphaFormsAlphaFormsAlphaForm.cs*10*_ *AlphaFormsAlphaForms*svn://192.168.1.5/AlphaForms/trunk/AlphaForms/AlphaForm.cs SRCSRV: end ------------------------------------------------
然后,实际的命令将由main部分中指定的变量生成,因此对于第一个文件,它将如下所示:
cmd /c "svn cat " svn://192.168.1.5/AlphaForms/trunk/AlphaForms/LayeredWindow.cs@11 --non-interactive > "C:Documents…"
然后将执行此命令,并希望将所需的文件放在目标目录中,然后调试器将尝试打开它。
如果您确实想编写自己的脚本或程序来索引PDB文件,那么您只需要生成一个类似的数据块,然后使用pdbstr.exe将其插入PDB文件。将自己的源代码编制索引的基本步骤将是这样的:
- 收集工作目录中的文件列表
- 获取提取命令的每个文件的参数列表
- 使用srctool获取PDB中引用的文件列表
- 将数据块的头写入某个临时文件
- 对于PDB中的每个文件,将参数添加到临时文件
- 使用pdbstr将数据插入PDB
要获取PDB中引用的文件列表,请使用:
srctool.exe "path to pdb file" –r
它将把pdb中的每个文件打印到一个新的行上。要将数据块添加到PDB文件,需要使用:
pdbstr –w –p:"path to pdb file" –s:srcsrv –i:"path to temp file"
-w开关指定您正在写入文件,使用-r会将数据流(如果存在)打印到控制台。-s给出了我们要写入的数据流的名称,在本例中是srcsrv。实际上,您可以使用任何喜欢的流名称将任何需要的内容插入到PDB文件中,但是Visual Studio将在srcsrv流中查找数据。-我给出了将插入PDB文件的输入文件的路径,在您的情况下,该文件就是您将数据写入的文件。