65.9K
CodeProject 正在变化。 阅读更多。
Home

如何调试异常

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (10投票s)

2009年10月16日

CPOL

5分钟阅读

viewsIcon

145315

帮助我们找到源代码中异常的位置

引言

无论是计算机程序员还是普通用户,我想你都见过那种提示你的应用程序崩溃了,并让你联系应用程序供应商或报告给微软的对话框。

用户只需要重启设备并将此问题报告给服务部门即可。但对于程序员来说,这是可怕的开端,但别担心,这篇文章将帮助你定位导致崩溃的位置。

有很多文章解释这个问题,但我发现它们已经过时了,例如 Visual Studio 无法支持 MAP 文件中的行号,所以依赖行号来定位位置的旧方法不再可行。

我将以 VS2005 为例,介绍两种新的方法来定位异常的位置

  1. 使用 MAP 和 cod 文件根据异常地址
  2. 使用符号和转储文件(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

  1. 打开WinDbg,在打开崩溃转储文件之前,你需要设置“符号文件路径…”,“源文件路径…”和“映像文件路径…”

  2. 通过“打开崩溃转储…”打开转储文件,然后你将立即看到结果,如下所示,它会突出显示源代码中的异常位置。

    看,这与我们使用 MAP 和 COD 文件分析的结果相同。

    这种方法比前一种更方便。我们无需计算,它就能自动帮助我们定位。

    注意

    1. 你必须使用匹配的 PDB 和 EXE 文件,否则可能会得到错误的结果。
    2. 如果无法定位代码,我认为是你设置了我在步骤 1 中提到的错误路径。

B. 使用 Visual Studio

使用 Visual Studio,我们也可以快速获得结果,设置方式类似。

  1. 打开转储文件,我的转储文件是ce010309.kdmp
  2. 在“调试”中设置“源路径映射…”,依我之见,最好将转储文件、PDB 文件和源文件放在同一个路径下,至少在同一个磁盘分区下,这样你就不需要设置这个路径,VS 会自己设置。这可以帮助你避免很多麻烦。
  3. 选择“目标”->“附加设备”,然后 VS 将运行,你可以看到“调用堆栈”、“反汇编”、“线程”,你可以在“调试->窗口->…”中看到其他内容。

这里我解释一下“反汇编”,你可以看到“0x00011064 str r3, [r2]”,我想你还记得0x00011064是异常地址,VS 立即定位到了它,右键点击这一行并选择“转到源”,然后你将看到源代码,它也用黄色箭头指示。这与WinDbg的结果相同。

摘要

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

© . All rights reserved.