上篇给出的方案一还要补充几句。通过“crash地址 + MAP文件”来定位出错代码位置虽然需要经过比较复杂的地址计算,但却是最简单实现的方式。如果仅仅想通过崩溃地址定位出错的函数,就更加方便了。我在网上找到一个解析MAP文件的小工具,可以非常清晰的列出每个函数的地址,并且可以将分析表格导出为Excel文件。工具下载地址:http://e.ys168.com/?tinyfun,工具目录下VCMapper.exe。
另外上篇主要参考两篇文章:
http://www.vckbase.com/document/viewdoc/?id=908
http://www.vckbase.com/document/viewdoc/?id=1473
方案二:崩溃地址 + MAP文件 + COD文件
由于VC8以后的版本都不再支持MAP文件中产生代码行信息,因此我们寻找另一种定位方式:COD文件。
1、COD文件
COD文件是一个包含了汇编码、二进制机器码和源代码对应信息的文件,每一个cpp都对应一个COD文件。通过这个文件,我们可以非常方便地进行定位。
在VC6中生成COD文件的设置方式为:Project Settings -> C/C++,在 Category 中选 Listing Files,在 Listing file type 组合框中选 Assembly,Machine code,and source。在VC8中生成COD文件的设置方式为:Project Properties -> C/C++ -> Output Files -> Assembler Output 项,选择 Assembly,Machine code,and Source(/Facs)。
2、定位崩溃行
下面通过举例进行说明。现在我有一个基于对话框的MFC应用程序CrashTest,在CCrashTestDlg::OnInitDialog函数中写入导致crash的代码语句(第99行),源文件如下:
![调试Release发布版程序的Crash错误(二) 调试Release发布版程序的Crash错误(二)](https://i-blog.csdnimg.cn/blog_migrate/965d9aebb4092088724d7ccdb1ccd59f.jpeg)
根据崩溃地址(0x004012A3)以及MAP文件(定位片段图片如下),定位crash函数为OnInitDialog;并且我们可以很容易地计算出崩溃地址相对于崩溃函数的偏移量为 0x004012A3 - 0x004011E0 = 0xC3。
![调试Release发布版程序的Crash错误(二) 调试Release发布版程序的Crash错误(二)](https://i-blog.csdnimg.cn/blog_migrate/a26982c9f6037ff9ada7576a13bc955f.jpeg)
再来看看CrashTestDlg.cod文件,我们根据文件中源码信息找到OnInitDialog函数信息片段:
![调试Release发布版程序的Crash错误(二) 调试Release发布版程序的Crash错误(二)](https://i-blog.csdnimg.cn/blog_migrate/e7f501365ae47994f1c18b8a483919cb.jpeg)
可以看到图片中第一行为OnInitDialog函数汇编代码的起始行;找到“int * p = NULL;”这一句源码,其前面的98表示这行代码在源文件中的行号,下面的000c1表示相对于函数开始位置的偏移量,后面的“33 c0”为机器码,“xor eax,eax”为汇编码。那么我们根据前面算出来的偏移量0xC3,找到对应出错的语句为99行:“*p = 5;”。
总结一下定位步骤:
1) 根据公式 崩溃语句在函数中偏移地址 = 崩溃地址 - 崩溃函数地址 计算出偏移量X;
2) 根据公式 崩溃语句在COD文件中地址 = 崩溃函数在COD文件中地址 + X 计算出地址Y。其中崩溃函数在COD文件中地址为COD文件中函数起始括号“{”后面表明的地址,一般情况下为0x0000;
3) 根据Y在COD文件中找到对应代码行。
ok,方案二介绍完了。这种方法最大的好处是没有VC开发环境版本限制,而且COD文件里面包含的信息更加丰富,不但可以帮助我们定位crash,还能帮我们分析很多东西。当然,这也导致编译生成了很多信息文件。