调试技术--Windows平台调试技术(一)

Windows平台调试技术(一)

一、简介

本文简要介绍了Windows程序的调试技术。范围限于用户模式,包括了基本调试工具的使用:WinDbg,procdump。

二、背景

当使用Windows程序时,我们时常由于未知的原因停止了工作。其现象通常是一个对话框,像下面这样:

当我们看到这种情况时,我们通常选择“关闭程序”然后重新启动程序。如果还是一样的结果,此程序又是第三方提供的,那么我们就会报告这个问题然后等待回复。

现在,我们转换角色,站在开发此软件的团队来看待这个问题、尽快给出一个解决方案。我们进入细节,一步一步分析此应用程序停止的原因,以及该如何解决。

三、定义

应用程序崩溃是一种停止程序运行的异常。看看下面的代码例子:
int main()
{
	int *p = NULL;
	cout<<"This is Start";
	*p = 10;
	cout<<"This is End";
	return 0;
}

当我们运行此代码编译出来的exe文件时,就会看到像上面的崩溃窗口。出现此情况的原因是“*p=42”,给未分配内存的指针赋值,或者说给空指针赋值。我们能找出此问题的原因是因为此源码不多,容易分析。假定此情况出现在成千上万行代码中,修复此问题将会十分困难。因此我们需要一些技术来精确的获取出现此问题的源代码(或者相关原因),而不是分析整个代码。

四、调试技术

有很多不同的技术可以找出程序崩溃的原因,但是不同的技术中很多都是类似的。

第一步:找出出错模块

可以使用事件查看器来找出出错模块。以我们这个例子:AppCrash.exe,当它崩溃的时候,它会在事件查看器中产生一个事件。在运行”命令窗口中输入“eventvwr”:


看看“常规”标签下的内容,有两点值得注意:
1、 错误应用程序名称:指明出错的程序,本例是AppCrash.exe。
2、 错误模块名:指出此程序中的出错的模块。本例值AppCrash.exe。

很明显可以看出是AppCrash.exe除了问题。假如错误模块是“AppCrashLib.dll”,那么我们将要调试此模块。

另一个重要的点是异常代码解释了到底这个错误是什么意思。在当前情况下,异常代码为0 xc0000005这意味着访问违例,这意味着应用程序试图访问无效的内存位置。所有异常代码的列表,请参阅下面的链接:

http://technet.microsoft.com/en-in/sysinternals/dd996900.aspx

第二步:获取崩溃转储(dump)文件

崩溃转储文件基本上包含了异常终止程序的当前工作状态。崩溃转储文件也能提供给我们当前内存的完整状态(例如RAM),可以用来分析出错的问题。最简单的获取转储文件是使用”procdump“(下载链接: Procdump)。”procdump“需要在程序崩溃之前进行设置: procdump -ma -x c:\dumps "E:\Study\Windows Internals\Training\Sample Code\AppCrash\x64\Release\AppCrash.exe".。这只是使用procdump的基本方法,还有更多的功能选项。目前的选项可以启动进程、获取程序崩溃时整个内存的dump文件,并存在C:\dumps目录下。

第三步:分析dump文件

我们已经获取了dump文件,现在来对它进行分析。分许dump最好的方法是使用“Windbg”工具。分析之前,我们需要拿到崩溃的exe对应的文件:pdb。pdb文件是程序的数据库,包含了需要调试程序的所有信息。唯一的需求就是pdb文件需要和exe文件一起编译的时候所生成,否则程序的数据符号不匹配,也就不能分析此dump文件了。

接下来我们启动Windbg,配置pdb文件(ctrl+s):


在Windbg窗口中,File->Open 打开dump文件:



加载dump文件之后便会出现以下结果:


在下面的命令中输入:!analyze -v:



现在我们集中注意力在各种输出信息来找出问题。我们看看STACK_TEXT这一段,它说应用程序崩溃,错误在AppCrash!main+0x13这里,但是并没有给出引起崩溃的源代码的位置。

再往下看,会发现有这么一段:
FAULTING_SOURCE_CODE:  
     5: {
     6: 	int *p = NULL;
     7: 	printf("This is start!\n");
     8: 	*p = 42;
>    9: 	printf("This is end");
    10: 	return 0;
    11: }
这就给出了源码崩溃的具体位置。

在上面的分析中,崩溃其实发生在第8行,但windbg指向第9行。这是编译过程启动优化的结果。

第四步:修复问题、发布

找出了问题所在,自然就知道如何对它修复了。

五、编译器优化

由以上知道由于编译器优化了代码,我们没能让windbg准确指出崩溃的地方。现在来看看编译器优化方面的知识。


我们可以设定编译器优化的等级。当选择"Full Optimization“优化选项时意味着产生的二进制文件(exe)的大小会减小,pdb文件的调试信息也会减少。反之,选择”Disable Optimization“时生成的程序会更大,pdb文件也会更大。Debug模式生成也是一样的情况。

总的来说,有四个选项进行配置。通常情况下,大多数项目的选项选择“Maximize Speed”,这是足够的对于调试崩溃的情况。在上面的例子中,如果我们关掉优化编译,我们会得到下面的结果:



在这里,我们看到了指向了正确的错误位置:*p=42.。出现这种情况的原因是有足够的调试信息来分析出错的根源。因此有一条规律就是:当我们发布应用程序时,我们应该保留一并生成的pdb文件,当出了问题的时候可以拿来进行分析。

六、pdb文件

对于任何非托管代码建,pdb文件与EXE文件一并生成。pdb文件包含了需要的调试信息。换句话来说,此文件也称为符号文件。符号文件包含了调试时候的不同符号。有局部变量、全局变量、函数名、源代码函数等等。每个信息都以符号而存在。有两种可用的符号:
私有符号:包括函数,局部变量,全局变量,用户定义的数据结构,源代码行号
公有符号:函数,全局变量。

与私有符号相比,公有符号包含的信息比较少。公有符号只包含能够在不同文件中共享的信息。因此调用局部变量将不能成为公有符号的一部分。甚至在公共符号将大部分的功能有装饰的名称。

在私有符号中的调试会给出行号,而公有符号中则不会。大多数的公司做维护两个符号服务器:私有符号内部使用,公有符号对外发布。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值