解决EXE与PDB不匹配的问题
从事Windows下的VC相关开发,很经常遇到EXE与PDB不匹配,导致无法加载符号文件进行调试的情况。有的时候是编译出EXE以后忘记了保存PDB文件,然后丢失了PDB文件。有的时候只是由于保存了PDB之后又不小心多编译了一次,EXE是更新的,PDB却是旧的,虽然内容没有一点问题,但VS和WinDBG就是加载不起符号来了。
以下我会假设你在Windows程序的调试方面已经不是新手,只是遇到了一个麻烦,想要寻求解决办法。所以我不会作太多面向非专业人员的解释和说明。

如何避免

养成用symstore保存符号文件的好习惯,必要的时候把它做到编译流程里面。
symstore的参数稍微有一点复杂,做成批处理脚本用起来会比较方便。一个可供参考的批处理脚本如下:
@echo off
if {%1}=={} goto help
cd %~dp0
symstore add /r /f ".\Release" /s ".\Symbols" /t "My Project" /v %1 /compress
goto :eof

:help
echo SaveSymbols.bat Version
不开compress的话用起来更方便,但PDB文件的压缩率挺高的,用到的时候又少,不压缩有点可惜。
如果手动保存的话,还是会有遗忘的时候,这就只能加强流程管理了。
可是,如果PDB忘记了保存或者丢失的情况已经发生了,没有对应PDB的EXE已经发布,并已经产生了CrashDump需要分析,这个时候怎么办呢?

强行加载

如果你在使用WinDBG进行调试,那么可以采用一些办法来让WinDBG强行加载与EXE不匹配的PDB文件。
最简单的,可以输入:
.symopt + 0x40
这会让WinDBG全局开启SYMOPT_LOAD_ANYTHING。
如果只希望对于某一个EXE或DLL采用强行加载方式,其它的希望不要受影响,也可以通过/i参数进行手动reload:
.reload /i @"MyModule.dll"
如果你确定PDB的内容是对的,或者你现在手上就有与EXE对应一致的源代码,重新编译一次就可以得到一个不匹配但肯定正确的PDB,那么你应该可以采用这种方式来解决你所遇到的问题。

修改PDB

强行加载不匹配的PDB的办法,虽然可以解决问题,但是难登大雅之堂。如果你需要对外提供PDB文件,你肯定不希望对别人说「这个PDB你需要强行加载」。EXE已经发布,CrashDump可能已经产生,无法更改。那么彻底解决问题的方案,就是把PDB改成与EXE匹配。
另外,对于只能使用Visual Studio进行调试的人员而言,目前还没有能够强行加载PDB的办法,只能考虑修改PDB以匹配EXE。
PDB文件的格式是未公开的,尽管有一些研究性的文件对PDB格式进行了一些探讨,但是由于格式并未公之于众,微软私底下完全可以不加通知而改来改去,导致这些研究成果不一定能派上多少用场。

ChkMatch

StackOverflow上的一个问答可以把我们引向ChkMatch,这是一个据说可以修改PDB使之与EXE相匹配的工具。这个网站所附的article页面是值得一看的,它也对PDB格式进行了一番研究,并对我们这次涉及到的专题作了一些专业性的阐述。
可惜的是,有几点不足,使得这条路变得比较艰难:
  1. 发表的时间太早——2004年,对于VC6和VC7.x,我想它们是没有问题的,其余就得看运气了。从StackOverflow上的那个问答看起来,VS2015的时候它也许还能派上用场。
  1. 它只能解决signature不匹配的问题,无法解决age不匹配的问题。原文是这样说的:
  • At the time being, only signature mismatch can be handled for PDB files; age mismatch cannot be handled yet – this is a subject for future research.
  • 这个项目大概后面被放弃了,所以future research也就没有下文了。
  1. 如上所述,这个项目被放弃得如此彻底,以至于下载链接已经404了。当然,应该还可以从别的渠道搞得到。
我的建议是:山穷水尽的时候,不妨一试。因为我们还有别的办法。

手动修改

听上去很难,但是跟ChkMatch做的事情其实差不多。StackOverflow上有一个讨论串里面有一些干货,很技术性,也许可以一试。我在VS2013上没有成功,毕竟这个讨论串是2010年以前的内容了,可能最多能在VS2008上管用吧。
如果你对HEX编辑器比较熟,是老派的程序员,也可以自己试试看。毕竟你肯定备份了PDB,不怕改坏。

重新编译

Visual Studio据说有一个特性,我们可以据此掌控编译生成的PDB的age。
简单地说,如果手头上有旧的PDB,那么你已经成功了一半。在增量编译的时候,EXE/PDB文件中的signature不变,只有age会往上累加。所以你只要把旧的PDB复制回来,放在Debug或Release目录下,然后再编译一次或几次,然后用下面的方法检查它们是不是匹配上了。
如果完全没有旧的PDB,也可以试试看用上面提到的ChkMatch来做出一个signature相同的PDB,然后再套用这个办法。也可以试试看手动修改signature,因为从ChkMatch的说法看来,修改方案应该会比较容易。

检查结果

可以用WinDBG来查看PDB的signature和age:
!chksym H:\MyDLL.pdb

MyDLL.pdb