如何调试异常






4.81/5 (10投票s)
帮助我们找到源代码中异常的位置
引言
无论是计算机程序员还是普通用户,我想你都见过那种提示你的应用程序崩溃了,并让你联系应用程序供应商或报告给微软的对话框。
用户只需要重启设备并将此问题报告给服务部门即可。但对于程序员来说,这是可怕的开端,但别担心,这篇文章将帮助你定位导致崩溃的位置。
有很多文章解释这个问题,但我发现它们已经过时了,例如 Visual Studio 无法支持 MAP 文件中的行号,所以依赖行号来定位位置的旧方法不再可行。
我将以 VS2005 为例,介绍两种新的方法来定位异常的位置
- 使用 MAP 和 cod 文件根据异常地址
- 使用符号和转储文件(PDB、dump)
1. 使用 MAP 和 COD 文件
首先,你需要一个 MAP 文件。如果你没有,使用崩溃地址来查找应用程序崩溃的位置几乎是不可能的。所以,首先,我将向你展示如何创建一个 MAP 文件。

你只需要将“生成映射文件”设置为“是”。
现在我编写一个简单的代码来引起下面的异常
int *ip = NULL;
*ip = 1234;
显然,这会引起访问冲突异常。
与其它文章的描述一样,一个错误框弹出并告诉你异常地址。
在我的程序中,它是“0x00011064
”。
我的 map 文件如下(我只引用必要的部分)
exception
Timestamp is 4ad6ba58 (Thu Oct 15 13:59:52 2009)
Preferred load address is 00010000
Start Length Name Class
0001:00000000 00000d54H .text CODE
…
Address Publics by Value Rva+Base Lib:Object
0000:00000000 ___safe_se_handler_count 00000000 <absolute>
0000:00000000 ___safe_se_handler_table 00000000 <absolute>
0001:00000000 WinMain 00011000 f exception.obj
0001:000000d4 ?MyRegisterClass@@YAGPAUHINSTANCE__@@PA_W@Z 000110d4 f exception.obj
0001:00000184 ?InitInstance@@YAHPAUHINSTANCE__@@H@Z 00011184 f exception.obj
0001:000002f0 ?WndProc@@YAJPAUHWND__@@IIJ@Z 000112f0 f exception.obj
0001:00000564 ?About@@YAHPAUHWND__@@IIJ@Z 00011564 f exception.obj
…
在这里,我们仍然使用旧方法首先定位异常函数,异常地址是0x00011064
,它介于“0x00011000—WinMain
”和“0x000110d4—MyRegisterClass
”之间,所以异常在“WinMain
”。
由于从 Visual Studio 2003 开始,链接器不再支持 map 文件中的“行号”,依赖“行号”和“代码段”起始地址的旧方法无法工作,我们不再使用“首选加载地址”,这里是0x00010000
。
但我们仍然需要一个偏移量,WinMain 的入口地址是0x00011000
,异常地址是0x00011064
,所以偏移量是
OFFSET = EXCEPTION ADDRESS – FUNCTION ENTRY ADDRESS = 0x00011064–0x00011000= 0x64
现在即使我们知道了偏移量,但我们仍然无法使用它来在源代码中定位,因为这个偏移量是在二进制文件中计算的。
我们必须依赖其他信息,在我这里,是 COD 文件。为了输出 COD 文件,你还需要进行一些设置

然后你会找到一些 COD 文件,例如 stdafx.cod。
这里我也以这个文件的一部分为例,并添加一些黑色注释来解释。
; 44 : //”; 44” indicates the line number in the source code, your *.cpp file
; 45 : int *ip = NULL; //here you also can see the your C codes, it is in the line 45.
00050 e3a03000 mov r3, #0
00054 e58d3020 str r3, [sp, #0x20]
; 46 : *ip = 1234; //line number in the C codes,
the following part between “;46” and “;47” they
//are the asm codes to implement the “*ip = 1234;”
00058 e59d2020 ldr r2, [sp, #0x20]
0005c e3a03b01 mov r3, #1, 22
00060 e38330d2 orr r3, r3, #0xD2
00064 e5823000 str r3, [r2]
00068 |$LN3@WinMain| //here you can see the 0x68, it is the offset
; 47 : // Main message loop:
; 48 : while (GetMessage(&msg, NULL, 0, 0))
00068 e3a03000 mov r3, #0
现在从这个 COD 文件中,根据我的注释,我认为你可以定位源代码的行号。我再重复一遍
我们计算的偏移量是0x68
,所以我们需要检查这一行,它是“00068 |$LN3@WinMain|
”,然后我们向上追溯它的源编号,它是“;46
”。
直到现在,我们已经成功地在源代码中定位了异常位置。
下面是我的源代码,它是正确的。

这确实是一个伟大而精彩的方法。但请记住备份这些文件:MAP 和所有 COD 文件。
2. 使用符号和转储文件
还有另一种更简单的方法,但需要两个文件:一个是你的可执行程序的 PDB 文件,另一个是转储文件。
PDB 文件在构建后创建,并且应该与可执行文件配对。
转储文件由操作系统创建,以 WinCE 为例,如果你启用了 WER(Windows 错误报告),当发生未处理异常时,会创建 kdmp 文件。
要分析这个问题,你可以使用 WinDbg 或 Visual Studio 工具,它们可以帮助我们轻松地定位异常。
A. 使用 WinDbg
- 打开
WinDbg
,在打开崩溃转储文件之前,你需要设置“符号文件路径…”,“源文件路径…”和“映像文件路径…” - 通过“打开崩溃转储…”打开转储文件,然后你将立即看到结果,如下所示,它会突出显示源代码中的异常位置。
看,这与我们使用 MAP 和 COD 文件分析的结果相同。
这种方法比前一种更方便。我们无需计算,它就能自动帮助我们定位。
注意
- 你必须使用匹配的 PDB 和 EXE 文件,否则可能会得到错误的结果。
- 如果无法定位代码,我认为是你设置了我在步骤 1 中提到的错误路径。
B. 使用 Visual Studio
使用 Visual Studio,我们也可以快速获得结果,设置方式类似。
- 打开转储文件,我的转储文件是ce010309.kdmp。
- 在“调试”中设置“源路径映射…”,依我之见,最好将转储文件、PDB 文件和源文件放在同一个路径下,至少在同一个磁盘分区下,这样你就不需要设置这个路径,VS 会自己设置。这可以帮助你避免很多麻烦。
- 选择“目标”->“附加设备”,然后 VS 将运行,你可以看到“调用堆栈”、“反汇编”、“线程”,你可以在“调试->窗口->…”中看到其他内容。
这里我解释一下“反汇编”,你可以看到“0x00011064 str r3, [r2]
”,我想你还记得0x00011064
是异常地址,VS 立即定位到了它,右键点击这一行并选择“转到源”,然后你将看到源代码,它也用黄色箭头指示。这与WinDbg
的结果相同。

摘要
我希望你已经意识到崩溃并不可怕,不是吗?
在每次发布后保留必要的 PDB、MAP 和 COD 文件可以使异常更容易处理。但我还是希望你的程序不会崩溃。设计得更好,麻烦更少。