象征微软之道

2021-01-31 05:49:49

符号服务器允许Windows上的开发人员工具自动查找符号。他们做得很好,以至于大多数开发人员都不必担心内部机制。但是,当出现问题时,了解它们的工作方式将很有帮助,事实证明,这一切都非常简单。

请注意,elfutils debuginfod最终可能会让Linux赶上Microsoft和Windows,但允许自动检索调试信息和源代码。

本文应该与获得符号的过程进行很好的比较,尤其是对于Linux上的客户计算机崩溃而言。我分四部分记录了该过程:

Linux上的符号第一部分:g ++库符号-学习如何为我的Linux机器获取符号

Linux上的符号第二部分:其他版本的符号–努力获取客户计算机的符号

我对Windows符号服务器的讨论利用了笔记本电脑上的符号服务器来实现自己的个人项目。每当我发布Fractal eXtreme的新版本(分形的64位优化,多核,快速且流畅的探索,此处为演示版本)时,我都会在符号服务器上放置符号和二进制文件,以便可以轻松地调查任何崩溃报告,我收到。对于家庭项目来说,这似乎有些矫,过正,但实际上,本地符号服务器只是文件的副本,以一种特定的方式排列以便于检索,并且设置起来很简单。对于我公开发布的可执行文件(例如UIforETW),我将PE和PDB文件发布到Google存储上的公共符号服务器上-详细信息在这里。

符号服务器不仅存储符号,还存储PE文件(DLL和EXE)。如果这些文件尚不可用(例如在查看小型转储或xperf配置文件时),则通常会在符号之前首先检索它们。对于来自64位进程的故障转储,这些PE文件可能需要进行堆栈遍历,因为它们包含必要的元数据。为了从符号服务器检索PE文件,需要三部分信息:文件名,链接时间戳和图像大小。为了手动检查FractalX.exe的最新版本是否已放入我的符号服务器中,我将从可执行文件中提取链接时间戳和图像大小,如下所示:

> dumpbin FractalX.exe /标题|找到“日期戳” 4FFD0109时间日期戳2012年7月10日星期二21:28:57> dumpbin FractalX.exe /标题|找到“图像大小” 147000图像大小

符号服务器共享中PE文件的路径格式为:

请注意,timeStamp始终使​​用大写字母“ A”到“ F”(如果符号服务器区分大小写,则使用大写字母)始终打印为八位数字(根据需要使用前导零)。根据需要(小写)。

撇开:如果您进行一对构建时它们之间的差异很小,则大小可能不会改变,并且peName当然也不会改变,因此阻止两个构建具有相同符号服务器地址的唯一事情就是timeStamp。这意味着将时间戳的精度(例如,从一秒降低到24小时)可能会导致一些非常严重的文件名冲突。您可能最终用第二个覆盖符号服务器中的第一个条目。假设地知道。

我的符号服务器位于c:\ MySyms中(通常它将位于共享服务器上,但这是我的个人笔记本电脑),因此上面检查的文件的完整路径为:

很简单。就我而言,添加文件时,我使用symstore.exe的/ compress选项(它节省了大量空间)。通过用下划线替换最后一个字符来指示压缩文件,因此实际路径是这样的:

这是一项很好的测试,可以确保将您的PE文件正确添加到符号服务器中,但这不是一个非常实际的用例,因为我们使用PE文件来获取检索PE文件所需的值。更为常见的情况是,您将拥有一个小型转储文件或xperf ETL文件,并且该文件将包含一系列模块名称,链接时间戳,图像大小三胞胎,并且这些将在分析时用于检索PE文件。对于小型转储文件,有一个包含相关数据的MINIDUMP_MODULE结构数组。请注意,符号服务器共享的布局可能要复杂得多。您应该使用API​​(稍后讨论)来检索PE文件-上面的技术仅用于故障排除。

如果您具有链接时间戳记,并且想要将其转换为日期,则可以使用以下Python一线批处理文件:

另一个怪癖是,在Microsoft链接器/调试器工具链中的某个位置,用于小写PE文件的名称。这意味着,如果对符号服务器使用区分大小写的文件系统(例如Chrome),则必须使用小写文件名上载符号。 Chrome小组发现了这一难题。 PDB名称是从PE文件中提取的,且大小写完整,因此只需匹配即可。我再也无法重现此行为– UIforETW.exe以大小写混合的形式上载到区分大小写的Google Storage,并且可以正常工作。

在分析客户故障转储以获取组装说明时,查找PE文件非常方便,但实际上比这更重要。小型转储文件不一定会记录足够的信息来检索PDB文件。在这些情况下,工具会检索PE文件,然后在PE文件中查找以获取检索PDB文件所需的信息。再一次,我们可以使用dumpbin从PE文件中提取此信息:

> dumpbin FractalX.exe /标题|找到“格式:” 4FFD0109 cv 56 000B9308 B7B08格式:RSDS,{6143E0D1-9975-4456-AC8E-F24C8777336D},1,FractalX.pdb

RSDS之后的长十六进制数字是GUID,其后的数字(32位十进制数字,但在这种情况下为“ 1”)称为“年龄”。 PDB文件名也在此处列出。这些一起唯一地标识了PDB文件的特定版本。符号服务器共享中PDB文件的路径格式为:

有趣的是–请注意,我使用%x来打印年龄,但是在上一段中,我将年龄描述为十进制数字。好吧,PDB使用期限(只是对同一个PDB重复使用了多少次的度量)只是一个32位数字,但是dumpbin用十进制打印它,符号服务器期望它以十六进制打印。力求一致!这意味着,如果您分析dumpbin输出,则需要将年龄转换为整数并将其打印为十六进制。如果您弄错了,那么直到您遇到年龄超过10岁的PDB时,该错误才会出现。精彩。

与PE文件一样,最后的下划线表示文件被symstore.exe压缩的时间。我的符号服务器上上面列出的PDB的路径如下所示:

很简单。生成GUID和使用期限的算法是,每当您进行重建时-每当生成新的PDB时-都会创建一个新的GUID并将使用期限设置为一个。每当进行部分构建时,PDB都会使用新的调试信息进行更新,并且使用期限也会增加。就是这样–使用PE名称,链接时间戳和图像大小来查找PE(如果尚未加载),然后使用GUID,年龄和PDB文件名来查找PDB文件。请注意,符号服务器共享的布局可能要复杂得多。您应该使用API​​(稍后讨论)来检索PDB文件-上面的技术仅用于故障排除。

如果您在Windows上交付软件,则应该有一个符号服务器。该符号服务器应包含您所发货的每种产品的PE文件和PDB文件。如果您不这样做,那么您自己或您的客户都会受到损害。您还应该为所有内部版本建立一个符号服务器,该公司的任何人最终都可能会运行它。如果程序可能崩溃,并且如果您希望能够调查崩溃,则将符号放在符号服务器上。如果您担心内部构建会占用过多空间,请将它们放在单独的符号服务器上,并偶尔清除旧文件。您还应确保构建机器正在运行源索引,以便在调试旧版本软件中的崩溃时,将自动获取正确的源文件。幸运的是我已经写过有关此的内容。将文件添加到符号服务器非常简单。将sourcedir设置为指向包含要添加文件的目录,并将dest设置为符号服务器目录,所有需要这些符号的人都可以访问该目录。然后运行以下命令:

symstore添加/ f%sourcedir%\ *。dll / s%dest%/ t产品名称/ compress symstore添加/ f%sourcedir%\ *。exe / s%dest%/ t产品名称/ compress symstore添加/ f%sourcedir%\ * .pdb / s%dest%/ t产品名称/ compress

如果您运行的是区分大小写的文件系统,则之后需要将小写的PE文件名小写-抱歉。

而已。如果要递归添加文件,请使用/ r,并参阅帮助以获取更多信息。如果您有未压缩的现有符号服务器,或者要与添加到符号服务器中分开进行压缩步骤,则makecab命令(Windows附带)可以解决问题。这是Chrome过去通过以下类型的命令行使用的:

这将生成一个压缩的chrome.dll.pd_文件,该文件在下载到本地符号缓存后会自动解压缩。

另一个选择是使用Windows Server 2003资源工具包工具中的compress.exe。不过要小心。 compress / help表示LZX是默认设置,但不是。因此,请确保使用compress -ZX,否则最终将得到SymSrv无法解压缩的压缩文件。

显然makecab,compress.exe和symstore都有其输入文件必须小于2 GiB的限制。这是Chrome统一的chrome.dll.pdb的问题,该文件当前(2019年10月)约为2.5 GB,因此无法压缩。哎呀。 Pigz可以处理4个GiB输入文件,但是生成CAB文件的版本尚未开源。我们最终通过使用Microsoft的压缩方法而不是使用HTTP压缩方法来解决此问题-我们在gsutil命令行中添加了-Z,以便在上传时对PDB进行gzip压缩。

如果通过HTTP使符号服务器可用,请确保使用https,以确保下载的完整性。

或者,如果您要制作可公开访问的符号服务器,只需遵循以下简单说明。

如何使您的开发工具使用符号服务器的确切细节各不相同,但是一种几乎通用的方法是将_NT_SYMBOL_PATH环境变量(此处和此处的高级用法)设置为如下所示:

这告诉工具首先查看本地缓存(c:\ symbols),然后查看符号服务器c:\ MySyms。如果在c:\ MySyms中找到符号,则将它们复制(并解压缩)到c:\ symbols。如果这些都不起作用,则对Microsoft的基于Web的符号缓存执行相同的过程(包括相同的缓存目录),然后对Chrome进行相同的处理。请注意,处理压缩符号时需要本地符号缓存。请注意,可以通过https和http来访问某些符号服务器,例如Chrome浏览器和Microsoft。如果可以选择使用https,则应始终使用它,因为当您下载和使用这些PDB时,中间人攻击可能会使用格式错误的PDB或源索引命令来执行任意代码。

Microsoft仍然在某些地方使用http列出其符号服务器,但是https可以使用,应该优先使用。

_NT_SYMBOL_PATH的SRV *部分很重要,文档记录薄,并且显然不易理解。根据对stackoverflow的讨论,我的理解是SRV *告诉symsrv.dll将以下路径或URL视为符号服务器,而不仅仅是一组散乱的文件。因此,如果_NT_SYMBOL_PATH为c:\ symbols,则dbghelp或symsrv可以递归搜索目录结构以查找您的符号,但是,如果_NT_SYMBOL_PATH为SRV * c:\ symbols,则它将以非常结构化和高效的方式进行搜索。如果_NT_SYMBOL_PATH为

然后symsrv首先使用快速高效的符号服务器算法查找c:\ symbols,然后(如果找不到符号)在Microsoft的符号服务器中进行相同的高效搜索。您应该更喜欢使用SRV *和符号服务器布局,而不是使用非结构化符号。

通常,您使用的调试器和探查器会知道如何使用符号服务器,但是有时您可能需要编写代码来下载符号-也许您正在编写调试器或探查器。以我为例,我有一个网页,其中列出了数十个Windows版本中需要符号的Microsoft DLL的GUID,年龄和PDB名称。编写代码来下载所有这些符号是微不足道的-与获取其他版本Linux的符号相比,要容易几个数量级。我需要做的简短解释是“调用SymFindFileInPath”。为了证明这是多么容易,我决定给出一个稍长的解释。该示例代码作为UIforETW的一部分在github上提供,它使用GUID,年龄和pdb名称,或日期戳,大小和pename,并从符号服务器下载PE或PDB文件。最大的代码块是用于解析GUID的-实际的下载是微不足道的。

测试定义使用已知良好的GUID,年龄,名称和符号服务器。注释掉定义使用此定义从_NT_SYMBOL_PATH中指定的符号服务器下载任意符号。如果遇到任何困难,请在调试器中运行该程序– dbghelp会将诊断信息打印到调试器输出窗口。一个问题是dbghelp.dll和symsrv.dll必须与您的工具位于同一目录中–将它们放在路径中并不可靠。

如前所述,UIforETW中随附了最新版本的RetrieveSymbols(上面列出了其源代码的工具)-源位于此处,最新二进制文件位于此处或最新的UIforETW版本中。 symchk工具(Windows调试器工具包中的产品)在传递PE文件时也会下载PDB文件-使用/ v选项可获取有关PDB文件下载位置的信息以及其他信息。如果您有需要符号的.dmp文件或PE文件,则symchk更加方便,而如果您具有GUID,年龄和PDB文件名,则RetrieveSymbols更为方便。

如果您有一个小型转储,但未加载其符号,则建议将小型转储加载到windbg中并使用其诊断程序:

lmv m MyModule –从故障转储的模块列表中打印一条记录,包括其名称,时间戳,图像大小以及找到的PDB的位置

!lmi MyModule –打印模块的标题信息–仅在PE文件已加载的情况下才有效,这是加载符号的先决条件

dumpbin FX.exe /标题| 查找“日期戳” –查找PE文件的链接时间戳 dumpbin FX.exe /标题| 查找“图像大小” –查找PE文件的图像大小 dumpbin FractalX.exe /标题| 查找“格式:” –查找PE文件的PDB文件的GUID,年龄和文件名 此条目发布在Symbols,Visual Studio中,并标记为minidumps,pe文件,pdb文件,symbol服务器,symsrv.dll,windbg。 为永久链接添加书签。