每个开发人员必须知道PDB文件知识
大多数开发人员都意识到PDB文件有助于您进行调试,但仅此而已。如果你不知道PDB文件是怎么回事,不要觉得很糟糕,因为虽然有文档在那里,但它分散在周围,而且大部分是为编译器和调试器编写器准备的。虽然编写编译器和调试器非常酷和有趣,但这可能不是你的工作。
我想做的是把每个在微软操作系统上进行开发的人都必须知道的PDB文件放在一个地方。这些信息也适用于本机开发人员和托管开发人员,不过我将提到一个特定于托管开发人员的技巧。我将从讨论PDB文件存储和内容开始。由于调试器使用PDB文件,我将详细讨论调试器如何为二进制文件找到正确的PDB文件。最后,我将讨论调试器在调试时如何查找源文件,并向您展示一个与调试器如何查找源代码相关的常用技巧。
在我们开始之前,我需要定义两个重要的术语。在开发计算机上执行的生成是私有生成。在生成计算机上完成的生成是公共生成。这是一个重要的区别,因为调试在本地生成的二进制文件很容易,总是公共生成导致问题。
所有开发人员需要知道的最重要的事情是:PDB文件和源代码一样重要!很多公司没有人能找到在生产服务器上运行的构建的PDB文件。如果没有匹配的PDB文件,您的调试挑战几乎是不可能的。通过大量的努力,可以在没有正确的PDB文件的情况下找到问题,但是如果您首先拥有正确的PDB文件,它将为您节省大量的资金。
正如Visual Studio的开发经理约翰坎宁安(John Cunningham)在2008年的PDC会议上所说的,“热爱、保持和保护您的pdb”。至少,每个开发商店都必须设置一个符号服务器。您还可以在Windows调试工具帮助文件中阅读Symbol Server文档本身。请查看这些资源以了解有关详细信息的更多信息。简而言之,符号服务器存储所有公共构建的pdb和二进制文件。这样,无论哪个构建有人报告崩溃或问题,您都有与调试器可以访问的公共构建完全匹配的PDB文件。Visual Studio和WinDBG都知道如何访问符号服务器,如果二进制文件来自公共构建,调试器将自动获取匹配的PDB文件。
大多数人在将PDB文件放入Symbol服务器之前还需要做一个准备步骤。这一步是在您的公共PDB文件上运行源服务器工具,这被称为源索引。索引嵌入了版本控制命令,以拉动在特定公共构建中使用的确切源文件。因此,在调试公共生成时,您永远不必担心找到该生成的源文件。如果您是一个一人或两人的团队,则有时可以不使用源服务器步骤。
我听到过一些团队对设置符号服务器的抱怨,他们的软件太大太复杂。我不得不承认,当我听到别人说“我的团队功能失调”时,你的软件绝不可能比微软做的任何事情都更大、更复杂。它们将所有产品的每个版本的索引和存储到一个符号服务器中。这意味着从Windows到Office,再到SQL,再到游戏,所有东西都存储在一个中心位置。我的猜测是,在雷德蒙德的34号楼只不过是存储所有这些文件的SAN驱动器,而大楼里的每个人都支持这些SAN。能够在微软内部调试任何东西都是非常令人惊奇的,而且您不必担心符号或源代码(前提是您对源代码树拥有适当的权限)。
在关键基础设施讨论结束后,让我来看看PDB中的内容以及调试器如何找到它们。PDB文件的实际文件格式是一个严格保密的秘密,但是微软提供了api来为调试器返回数据。本机C++ PDB文件包含相当多的信息:
- 公共、私有和静态函数地址
- 全局变量名称和地址
- 参数和局部变量的名称和偏移在堆栈上的位置
- 由类、结构和数据定义组成的类型数据
- 帧指针省略(FPO)数据,这是在x86上进行本机堆栈遍历的关键
- 源文件名及其行
一个.NET PDB只包含两部分信息:源文件名及其行和本地变量名。所有其他信息都已在.NET元数据中,因此无需在PDB文件中重复相同的信息。
将模块加载到进程地址空间时,调试器使用两条信息来查找匹配的PDB文件。第一个显然是文件名。如果加载ZZZ.DLL,调试器将查找ZZZ.PDB。最重要的部分是调试器如何知道这是与此二进制文件完全匹配的PDB文件。这是通过嵌入在PDB文件和二进制文件中的GUID完成的。如果GUID不匹配,您肯定不会在源代码级别调试模块。
.NET编译器和本机的链接器将此GUID放入二进制和PDB中。既然编译的行为创建了这个GUID,那么请停下来考虑一下。如果您有昨天的版本,但没有保存PDB文件,您是否可以再次调试二进制文件?不!这就是为什么保存每次生成的PDB文件如此重要的原因。因为我知道你在想,所以我会继续回答你脑海中已经形成的问题:不,没有办法更改GUID。
但是,可以查看二进制文件中的GUID值。使用Visual Studio附带的命令行工具DUMPBIN,可以列出可移植可执行文件(PE)的所有部分。要运行DUMPBIN,请从程序菜单中打开Visual Studio 2008命令提示符,因为需要设置PATH环境变量才能找到DUMPBIN EXE。顺便说一下,如果你对DUMPBIN向你展示的信息感兴趣的话,我强烈推荐Matt Pietrek在2002年2月和2002年3月的MSDN杂志上关于PE文件的权威文章。
DUMPBIN有很多命令行选项,但是显示构建GUID的选项是/HEADERS。Pietrek文章将解释输出,但对我们来说重要的一点是调试目录输出:
Debug Directories
Time Type Size RVA Pointer
——– —— ——– ——– ——–
4A03CA66 cv 4A 000025C4 7C4 Format: RSDS,
{4B46C704-B6DE-44B2-B8F5-A200A7E541B0}, 1,
C:junkstuffHelloWorldobjDebugHelloWorld.pdb
根据调试器如何确定正确匹配的PDB文件的知识,我想讨论调试器在哪里查找PDB文件。调试时,通过查看Visual Studio模块窗口的符号文件列,可以看到所有这些命令都是自己加载的。首先搜索的是加载二进制文件的目录。如果PDB文件不存在,调试器将查找嵌入PE文件中的调试目录中的硬编码生成目录。如果查看上面的输出,就会看到完整的路径C:JUNKSTUFFHELLOWORLDOBJDEBUGHELLOWORD.PDB。(用于生成.NET应用程序的MSBUILD任务实际上生成到OBJ<CONFIG>目录,并仅在成功生成时将输出复制到调试或发布目录。)如果PDB文件不在前两个位置,并且在计算机上为设置了符号服务器,则调试器将在符号服务器缓存目录中查找。最后,如果调试器在Symbol Server缓存目录中找不到PDB文件,它将在Symbol Server本身中查找。此搜索顺序就是为什么本地生成和公共生成部件从不冲突的原因。
调试器搜索PDB文件的方式对几乎所有要开发的应用程序都很好。PDB文件加载更有趣的地方是那些需要您将程序集放入全局程序集缓存(GAC)中的.NET应用程序。我特别关注的是你的SharePoint和你对web部件的残忍,但是还有其他的。对于本地计算机上的私有构建,使用起来很容易,因为调试器将在构建目录中找到PDB文件,如我上面所述。当您需要在另一台计算机上调试或测试私有构建时,就会出现问题。
在另一台机器上,我看到许多开发人员在使用GACUTIL将程序集放入GAC后所做的工作是打开一个命令窗口,在C:windowssembly中搜索程序集在磁盘上的物理位置。尽管将来可能会发生更改,但为任何CPU编译的程序集实际上都位于如下目录中:C:WindowsassemblyGAC_MSILExample1.0.0.0__682bc775ff82796a
示例是程序集的名称,1.0.0.0是版本号,682bc775ff82796a是公钥标记值。推导出实际目录后,可以将PDB文件复制到该目录,调试器将加载该目录。
如果你现在对像这样挖掘GAC感到有点不安,你应该,因为它是不受支持和脆弱的。有一个更好的方法,似乎几乎没人知道DEVPATH。其思想是,您可以在.NET中设置一些设置,它会将您指定的目录添加到GAC中,因此您只需将程序集和它的PDB文件放入该目录,这样调试就容易得多。仅在开发计算机上设置DEVPATH,因为存储在指定目录中的任何文件都不会像在真正的GAC中那样进行版本检查。
顺便说一下,如果你在任何一个互联网搜索引擎中搜索DEVPATH,其中一个最热门的条目是Suzanne Cook的一篇过时的博客文章,她说微软正在摆脱DEVPATH。这不再是事实。和其他博客一样,看看苏珊博客上的日期:2003年。这相当于互联网时代的1670年。
要使用DEVPATH,首先要创建一个目录,该目录对所有帐户都具有读访问权限,至少对开发帐户具有写访问权限。此目录可以位于计算机上的任何位置。第二步是设置一个系统范围的环境变量DEVPATH,其值是您创建的目录。有关DEVPATH的文档并没有说明这一点,但是在执行下一步之前,请先设置DEVPATH环境变量。
要告诉.NET运行时已设置DEVPATH,需要根据应用程序的需要将以下内容添加到APP.CONFIG、WEB.CONFIG或MACHINE.CONFIG中:
<configuration>
<runtime>
<developmentMode developerInstallation=”true”/>
</runtime>
</configuration>
一旦打开开发模式,您就会知道在进程中缺少DEVPATH环境变量或者您设置的路径不存在,如果应用程序在启动时死机,错误消息表示完全非直观的:“注册表的无效值”。如果确实要在MACHINE.CONFIG中使用DEVPATH,请格外小心,因为计算机上的每个进程都会受到影响。在一台机器上导致所有.NET应用程序失败不会赢得办公室里的许多朋友。
每个开发人员需要知道的关于PDB文件的最后一项是源文件信息如何存储在PDB文件中。对于已在其上运行源索引工具的公共构建,存储是将源文件获取到所设置的源缓存中的版本控制命令。对于私有构建,存储的是编译器用来生成二进制文件的源文件的完整路径。换句话说,如果在C:FOO中使用源文件MYCODE.CPP,那么PDB文件中嵌入的是C:FOOMYCODE.CPP。这可能是你已经怀疑过的,但我只是想说清楚。
理想情况下,所有的公共构建都会立即自动被源代码索引并存储在符号服务器中,因此如果您甚至不必再考虑源代码在哪里的话。然而,一些团队在PDB文件中不做源索引,直到他们做了冒烟测试或其他祝福,看看该构建是否足够好让其他人使用。这是一个非常合理的方法,但是如果必须在生成的源代码被索引之前调试生成,最好将该源代码拉到与生成计算机使用的驱动器和目录结构完全相同的位置,否则在源代码级别调试时可能会遇到一些问题。虽然Visual Studio调试器和WinDBG都有设置源搜索目录的选项,但我发现很难找到正确的方法。
对于较小的项目,这没有问题,因为您的源代码总是有足够的空间。生活更困难的地方是在更大的项目上。如果你有30MB的源代码,而你的C:驱动器上只剩下20MB的磁盘空间,你该怎么办?有办法控制PDB文件中存储的路径不是很好吗?
虽然我们不能编辑PDB文件,但是有一个简单的技巧可以控制PDB文件中的路径:SUBST.EXE。subt所做的是将路径与驱动器号相关联。如果您将源代码下拉到C:DEV并执行“SUBST R:C:DEV”,那么如果您键入“DIR C:DEV”,R:drive现在将在其顶层显示相同的文件和目录。您还将在资源管理器中看到R:drive作为新驱动器。也可以通过将驱动器映射到资源管理器中的共享目录来实现驱动器到路径的影响我个人更喜欢subt方法,因为它不需要机器上的任何共享。虽然有些人认为可以通过<DRIVE>$共享,但有些组织禁用了该功能。
在生成计算机上要做的是设置一个启动项,该启动项执行特定的SUBST命令。当生成系统帐户登录时,它将提供新的驱动器号,这是您进行生成的位置。通过对PDB文件中嵌入的驱动器和根目录的完全控制,在测试机上设置源代码所需做的全部工作就是在任何需要的地方将其拉下来,并使用与生成机使用的驱动器号相同的驱动器号执行子命令。现在调试器中不再考虑源匹配了。
每个开发人员必须知道PDB文件知识的更多相关文章
- 写给Android App开发人员看的Android底层知识(1)
这个系列的文章一共8篇,我酝酿了很多年,参考了很多资源,查看了很多源码,直到今天把它写出来,也是战战兢兢,生怕什么地方写错了,贻笑大方. (一)引言 早在我还是Android菜鸟的时候,有很多技术我都 ...
- 写给Android App开发人员看的Android底层知识(2)
(五)AMS 如果站在四大组件的角度来看,AMS就是Binder中的Server. AMS全称是ActivityManagerService,看字面意思是管理Activity的,但其实四大组件都归它管 ...
- 写给Android App开发人员看的Android底层知识(3)
(七)App启动流程第2篇 书接上文,App启动一共有七个阶段,上篇文章篇幅所限,我们只看了第一阶段,接下来讲剩余的六个阶段,仍然是拿斗鱼App举例子. 简单回顾一下第一阶段的流程,就是Launche ...
- 写给Android App开发人员看的Android底层知识(5)
(十)Service Service有两套流程,一套是启动流程,另一套是绑定流程.我们做App开发的同学都应该知道. 1)在新进程启动Service 我们先看Service启动过程,假设要启动的Ser ...
- 写给Android App开发人员看的Android底层知识(6)
(十一)BroadcastReceiver BroadcastReceiver,也就是广播,简称Receiver. 很多App开发人员表示,从来没用过Receiver.其实吧,对于音乐播放类App,用 ...
- 写给Android App开发人员看的Android底层知识(7)
(十二)ContentProvider (1)ContentProvider是什么? ContentProvider,简称CP. 做App开发的同学,尤其是电商类App,对CP并不熟悉,对这个概念的最 ...
- 写给Android App开发人员看的Android底层知识(8)
(十)PMS及App安装过程 PMS,全称PackageManagerService,是用来获取Apk包的信息的. 在前面分析四大组件与AMS通信的时候,我们介绍过,AMS总是会使用PMS加载包的信息 ...
- .NET开发人员的完美.gitignore文件
# Build and Object Folders bin/ obj/ # Nuget packages directory packages/ ## Ignore Visual Studio te ...
- web开发人员须知的web缓存知识–将数据缓存到浏览器端Net实现
现实中,服务器在向浏览器发送的数据中,一部分数据是不经常更新的,如果能将这部分数据缓存到浏览器端,将会大大降低传输的数据,提高应用的性能.通过Expires策略,可以使用HTTP 协议定义的缓存机制将 ...
随机推荐
- Spring Security的RBAC数据模型嵌入
1.简介 基于角色的权限访问控制(Role-Based Access Control)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注.在RBAC中,权限与角色相关联,用户通过成 ...
- Scala 系列(四)—— 数组 Array
一.定长数组 在 Scala 中,如果你需要一个长度不变的数组,可以使用 Array.但需要注意以下两点: 在 Scala 中使用 (index) 而不是 [index] 来访问数组中的元素,因为访问 ...
- 论文笔记:DeepCF
Abstract 推荐系统可以看作用户和物品的匹配问题,不过user以及item两者的语义空间差异太大,直接匹配不太符合实际.主流的改进CF的方法有两类:基于表示学习的CF方法以及基于函数学习的表示方 ...
- 学习笔记之大数据(Big Data)
300 秒带你吃透大数据! https://mp.weixin.qq.com/s/VWaqRig6_JBNYC1NX7NQ-Q 手把手教你入门Hadoop(附代码&资源) https://mp ...
- leetcode 学习心得 (1) (24~300)
源代码地址:https://github.com/hopebo/hopelee 语言:C++ 24.Swap Nodes in Pairs Given a linked list, swap ever ...
- JS基础 浏览器弹出的三种提示框(提示信息框、确认框、输入文本框)
浏览器的三种提示框 alert() //提示信息框 confirm() //提示确认框 prompt() //提示输入文本框 1.alert( ) 提示信息框 <script> alert ...
- robotframework Run keyword if ELSE 用法
1.Run keyword if 后面必须跟 关键字 ,可以是你自己的, 也可以是 框架自带的 2.ELSE 必须是大写,
- 设置Linux 程序lib搜索目录
设置Linux 程序lib搜索目录:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:lib路径,例如: export LD_LIBRARY_PATH=$LD_LIBRA ...
- 总结一下NDK crash排查步骤
总结一下NDK crash排查步骤: 先在PC上跑通算法 用Visual Studio写算法的testbed,确保算法能跑通 抓log adb logcat -c; adb logcat > 1 ...
- 【Flask】 python学习第一章 - 6.0 WTF表单 数据库 蓝图
WTF表单 wtf.py pip install flask-wtf # 安装 from flask_wtf import FlaskForm from wtform import StringF ...