我倾向于使用发布版本进行调试,而不是使用调试版本
我发现自己在工作中和工作之外不时支持的一件事是使用程序的发布版本(无论如何,对于Windows应用程序)进行调试的价值。乍一看,这可能与某些人的想法相矛盾,因为人们往往认为调试构建实际上更适合调试(毕竟它被命名为“调试构建”)。
然而,我倾向于不同意这种观点,理由有如下几个:
- 仅在调试版本上调试是不现实的情况。现实生活中出现的大多数“有趣的”问题往往是在客户站点或生产环境上构建的版本。很多时候,我们都没有足够的能力将调试构建发布给客户或生产环境。
毫无疑问,使用调试构建进行调试会更容易,但我认为,无法有效地调试发布构建是不利的。始终使用发布版本进行调试可以确保在您没有选择的情况下,或者在使用调试版本尝试重新生成问题不可行的情况下,可以执行此操作。 - 调试生成有时会干扰调试。最初,这是一个非常反直觉的概念,许多人似乎对此感到惊讶。要了解我的意思,请考虑出现随机内存损坏错误的情况。
这类问题通常很难找到,而且耗时,所以我们希望使用所有可用的工具来帮助这个过程。任何有能力的Windows调试器工具包中最有用的工具应该是page heap,它是RTL heap的一种特殊模式(它实现了HeapAlloc等api公开的Win32 heap)。
页堆在每个分配的末尾(或之前,取决于其配置)放置一个保护页。此保护页被标记为不可访问,因此任何试图写入超过已分配内存区域边界的分配的尝试都将立即因访问冲突而出错,而不是留下损坏以在以后导致随机失败。实际上,page heap允许在许多堆损坏场景中“赤手空拳”地捕获guility party。
不幸的是,调试构建大大降低了页面堆的操作能力。这是因为当使用C运行时的调试版本时,经过CRT的任何内存分配(例如new、malloc和soforth)在分配之前和之后都有特殊的检查和填充模式。这些填充模式旨在帮助检测内存损坏问题。当使用诸如free之类的API返回内存块时,CRT首先检查填充模式以确保它们完好无损。如果发现差异,CRT将闯入调试器并通知用户内存已损坏。
如果到目前为止一直在跟踪,那么不难看出这与页面堆有何冲突。问题在于,从堆的角度来看,每个分配元数据的调试CRT(包括检查和填充模式)是用户分配的一部分,因此,在填充模式之后(或之前,如果启用了欠运行保护)放置特殊保护页。这意味着某些内存损坏错误类将覆盖调试CRT元数据,但不会触发页面堆,这意味着内存损坏的唯一指示将是释放分配时,而不是实际发生损坏时。 - 在发布版本中,局部变量和源代码行步进是不可靠的。同样,和第一点一样,进入依赖这些便利性的模式是危险的,因为在优化器使用程序之后,它们在发布版本中根本无法正常工作(或以预期的方式)。如果您习惯于总是依赖于本地变量和源代码行支持,当与调试生成一起使用时,那么当您必须调试发布生成时,您将处于一个粗鲁的觉醒状态。在工作中,我不止一次被拉进来帮助一些人,因为他们在调试某个东西时走错了路,因为局部变量显示在发布版本中显示了错误的变量内容。
这个故事的寓意是不要依赖调试器提供的信息,因为它只对调试构建可靠。即使如此,局部变量显示也将无法正常工作,除非您正在单步执行源代码行模式,如在源代码行中(在单步执行汇编模式时),局部变量可能无法按照调试器期望的方式进行初始化(给定调试信息)。
现在,为了清楚起见,我并不是说任何人都应该完全放弃调试构建。调试版本添加了许多有价值的检查(断言、VS CRT中增强的迭代器验证和堆栈变量损坏检查,仅举几个例子)。但是,能够调试发布版本的问题是很重要的,而且在我看来,总是依赖调试版本对能够做到这一点是有害的。(显然,这可能会有所不同,但这只是根据我的个人经验。)
当我调试某个东西时,我通常只使用汇编模式和行号信息(如果可用)(用于手动将指令与源代码匹配)。当然,在许多情况下(如果有的话),源代码仍然是一个有用的时间节省器,但是我不希望依赖于调试器来“正确地处理”这些事情,因为在过去,源代码被烧掉了太多次,在非调试生成中返回了不正确的结果。
只要稍加练习,您就可以获得与通过对反汇编文本进行一些基本读取并检查堆栈和寄存器内容而获得的本地变量显示等信息相同的信息。另外,如果您可以在调试版本中执行此操作,那么根据定义,您也应该能够在发布版本中执行此操作,即使由于调试信息格式的限制,调试器无法正确跟踪本地变量。
当我调试某个东西时,我通常只使用汇编模式和行号信息(如果可用)(用于手动将指令与源代码匹配)。当然,在许多情况下(如果有的话),源代码仍然是一个有用的时间节省器,但是我不希望依赖于调试器来“正确地处理”这些事情,因为在过去,源代码被烧掉了太多次,在非调试生成中返回了不正确的结果。
只要稍加练习,您就可以获得与通过对反汇编文本进行一些基本读取并检查堆栈和寄存器内容而获得的本地变量显示等信息相同的信息。另外,如果您可以在调试版本中执行此操作,那么根据定义,您也应该能够在发布版本中执行此操作,即使由于调试信息格式的限制,调试器无法正确跟踪本地变量。
为虫子生,为虫子死,为虫子奋斗一辈子