调试发行版程序 (二)

客户端程序发布后在用户的计算机上运行,不可能时时刻刻的处于 WinDbg 的监控之下,恐怕没有一个用户会喜欢这样的程序的,所以我们要想办法把程序崩溃时的状态存储到一个文件中,然后通过该文件使用 WinDbg 来查看程序崩溃时的状态,来确定程序为什么发生了异常;

我们要实现的功能是:用户运行发行版的程序,该程序如果一旦发生异常,立刻收集异常时程序的内部信息,然后分门别类的输出到几个文件,此后出现一个界面询问用户是否发送错误报告给软件发行商,用户可以选择发送哪些文件或者不发送错误报告,如果用户点击“发送错误报告”,那么将用户愿意发送的错误输出文件打包到一个 ZIP 文件中,并通过 SMTP 发送到软件发行商的报修邮箱中;

首先我们要考虑的是,程序崩溃时已经不可能实现太多的功能,因为此时的程序已经处于异常状态了,所以在崩溃的程序内部仅仅实现输出错误信息,例如程序的状态、程序加载的模块(哪些动态库),这些动态库的版本和编译时间等,然后启动一个指定的程序,该程序实现后续的功能:提示用户发送错误报告、将错误输出文件打包为 ZIP 文件,然后发送到软件发行商的用户报修邮箱中;

从 Windows 2000 开始系统就提供了 DbgHelp.dll ,但是直到 WinXP 该动态库才算是真正提供了程序异常时完整的信息收集机制,所以如果你的程序需要在 WinXP 之前的计算机上运行,那么最好附带自己的 DbgHelp.dll 文件,该文件在 WinDebug 的安装目录中也有,可以直接复制使用;

另外 WinDebug 还提供了 DbgEng.dll ,该动态库就是 CDB.exe 和 WinDbg.exe 的调试引擎,内部提供仿 COM 接口的完整调试接口,可以自己实现特定的半自动化调试工具,此处不详细解说,有兴趣的可以自己参考相关的文章,另外 ACE 中的 Stack Trace 功能就是基于 DBGENG 实现的;

有了 DbgHelp.dll 文件,即可使用里面的函数来输出程序的状态,这里我们要使用的就是 MiniDumpWriteDump 函数,该函数可以导出程序内部的内存、堆栈、句柄、线程、模块等程序运行相关的信息,该函数的原型如下(具体细节参考 DbgHelp.h ):

MiniDumpWriteDump

因为程序运行时内存占用一般不会太小,尤其是大型程序动态申请的内存都有数百兆甚至更大,比如GIS程序一般都会占用将近 1GB 的内存,如果将内存全部记录下来,即使用户的磁盘空间够用,也不好从用户处发送回程序员处进行错误定义和分析,因此一般都是导出模块、线程和堆栈信息,这些信息通过详细的分析基本上可以定位绝大部分程序崩溃的原因,进而指引对程序的改进,所以 DumpType 参数指定为 MiniDumpNormal,这也是微软错误输出文件的设定值;

下面我们就要实现对程序崩溃的拦截,程序崩溃时Windows系统调用会发出异常通知,我们捕获该异常通知,然后使用上面的函数输出错误信息到指定的文件中,因为该套错误输出程序已经在公司的产品中运行了将近三年,考虑到软件的版权问题,下面我仅仅贴出最初实现的精简版的异常捕获代码: 

CrashOutput.h

 

CrashOutput.cpp

编译程序时将该CPP文件添加到主程序中即可,然后就是实现一个 WinCrash.exe 程序,该程序根据输入的参数找到对应程序的错误输出然后根据用户的选择将错误输出打包并发送到指定的邮箱中;

如果程序比较复杂,有较多的可执行模块构成,比如近百的动态库和较多的辅助工具,并且源代码是一个较大的团队在开发维护,那么就需要很多人来对自己相关的错误进行分析定位和改进程序代码,这样就带来了一个问题,如果搭建一个符号服务器给所有人使用而不是只能在特定的计算机上使用?
欲知如何处理请参阅《调试发行版程序 (三)

posted @ 2009-03-02 11:06  王志科  阅读(1160)  评论(0编辑  收藏  举报