一种dump自动生成工具的设计思路

作为开发人员,遇到最多的可能就是程序中的各种bug,然后根据bug的描述去复现、调试、修改。当所遇到的bug能够被稳定复现的时候,那么修改起来相对来说会容易许多,至少你知道是哪里出了问题,这个bug是怎么样的。

但是有时候我们的程序会偶然的出现一些异常情况,比如突然卡死无响应了、运行时出错了、闪退了等等。那么这个时候,我们就很难去复现当时的场景,就需要借助一些日志或者错误报告等手段来进行分析。

通过日志我们或许可以对程序出现异常的原因进行一个大概的判断,但是由于没有针对性,很难准确地定位到一些未知异常的症结所在。

所以这个时候,我们需要借助一些方法,在程序出现异常的时候,将其当时的进程信息转储为dump文件,通过对这个dump文件的分析,我们可以得知程序在“死”(出现异常)的时候,它当时的一些内存信息,堆栈的详情,各个线程的相关信息等。

当然,本文主要的目的不是介绍dump分析的,而是如何在程序“死”的时候转储dump文件,如何进行一些其他的处理,从而给程序一个体面的“葬礼”。

这里也会介绍一个为了满足这个需求所开发的用于在程序异常时转储dump的小工具。

Dump文件的生成

我们在开发的时候,在一些代码中也会有意识的处理可能出现的异常,从而保证程序的稳定性。比如C++中的try……catch,Delphi中的try……except等。

但是总有一些异常我们是预料不到的,而这些异常往往会导致我们的程序“死”掉。那么该如何在这些异常出现的时候,为我们将要“死”掉的程序料理一下后事呢?这个后事的料理是程序在“死”之前自己安排好,还是由其他程序来为它料理呢?

第一个问题 ↓

首先,程序在遇到未知的异常的时候,可以作一个统一的处理,转储dump文件,然后通知用户程序遇到了未知的异常,是否发送错误报告等,让程序死的体面一些。

第二个问题 ↓

程序自己来料理后事还是由其他程序来进行料理。这里,应该是都可以的,只不过这两种方式各有自己的利弊吧。所以接下来就来说说这两种方式。当然本文的侧重点是后一种方式。

METHOD 1

程序在“死”之前自己安排好后事

首先,对于一个Windows下的程序,如何对其进程进行转储生成dump文件呢?

在Windows下,MS为我们提供了一个dbghelp.dll的动态库,通过其提供的MiniDumpWriteDump方法,我们可以进行dump的转储。

既然我们已经知道了转储dump的方法,那么下一个需要解决的问题是什么时候转储dump。

这里来介绍一下SetUnhandledExceptionFilter这个函数,当异常没有处理的时候,系统就会调用SetUnhandledExceptionFilter所设置异常处理函数,通过传入的回调函数来进行一些处理。那么这个时候,我们就可以在回调函数中对当前程序的进程信息转储dump了。

这种程序在出现未知异常的时候,通过回调函数来进行一些异常的处理,可以帮我们捕获到大多数异常时的dump,比如地址的非法访问,线程的死锁等等。

但是这种方法也有一些无能为力的时候,如果程序自身出现了无响应,整个进程卡死了,或者是程序的主线程中出现了死循环,以及程序在运行时闪退了,这些时候,程序自身是很难处理所出现的异常的,或者是根本没来得及处理。

当程序在“死”的时候没能力或者没有时间来安排它的后事的时候,或许就需要另外一个程序来帮它进行处理了。接下来我们就来着重介绍一下如何使用另外一个程序,为我们“死”去的程序举办一个体面的”葬礼”。

METHOD  2

 程序在“死”的时候,其他程序来为其料理后事

这里我们将使用自己写的一个小工具来对指定的程序进行监测,在其出现异常的时候为其进行一些后事的料理。不过既然是使用额外的工具来监测的话,则需要起一个新的进程,而且一旦这个进程不小心被结束了的话,可能就会导致监测失效。

所以这也是这个方法的弊端所在,但是这种方法也有其好处存在:由于是额外的进程,所以当被监测进程卡死的时候,也不会影响监测程序的运行。同时我们可以让所写的小工具支持命令行参数的调用,根据传入的参数来进行监测。

那么对于大多数的Windows下的程序都可以调用,使其具有通用性,不用再在程序中额外的添加代码实现未知异常的处理,只需启动时调一下所写的工具即可。

如果使用一个另外的程序来监测一个指定的程序,在其出现异常的时候,做一些后续的处理,那么首先面临的两个问题是:
● 问题一:用于监测的程序如何知道被监测的程序出现异常了;
● 问题二:当被监测的程序出现异常之后该如何处理。

Win32有两个API函数可以帮助我们解决问题一,它们分别是IsHungThread和IsHungAppWindow,具体的使用可以参考msdn上的详细介绍。这里只是大概介绍一下,它们可以根据所传入的进程句柄,来判断当前的进程是否处于正常响应的状态。

当被监测的程序出现了非正常响应,且满足一定的条件之后,我们可以认为它是出现了异常了,或者说不是一个理想的状态下了。

在解决了第一个问题之后,这时我们已经可以有方法监测到被监测的程序出现异常了,那么接下来就是对出现异常的程序进行处理了。处理的目标同样是提示用户程序出现了异常,同时生成dump文件。根据前文中所提到的dbghelp.dll的介绍,我们可以使用其中的函数来生成dump,但是可能会需要些一部分代码来实现。

这里来介绍MS为我们提供的另外一个工具:procdump,这个工具可以通过命令行来调用,比如对指定的进程进行dump转储,可以使用以下命令:

-ma 是转储参数,表示是以什么模式来进行转储。 

ma 是全量转储,即将所有进程相关的信息都进行转储。 

mp 是只转储有用的信息。具体介绍可以参考msdn介绍文档。 

8978 是对应进程的进程id。 

Test.dmp 是转储后dump文件的名称。

所以当我们知道被监测的程序出现异常之后,可以通过在程序中调用这个工具来对其进行dump的转储。

对于所面临的两个问题,现在都有了解决方法了之后。我们就可以通过编程实现我们的需求了:编写一个用于监测指定程序,并且在其出现异常时转储dump以及一些后续的处理。

根据这个需求,我使用Delphi简单的实现了这个小工具的开发。这里大概介绍一下思路、实现以及使用方法。

计思路

监测指定程序异常时,自动生成dump小工具(以下简称工具)的思路是:在程序(需要被监测的)启动的时候,随之启动该工具,工具会在后台运行。工具中维护有一个定时器,定时的监测程序的响应状况。当程序出现无响应且满足一定条件的时候,则会弹出提示框,提示程序无响应,让用户选择相应的操作,比如是否生成dump并发送等。

考虑到使用定时器来监测程序异常时,中间会有一定的时间间隔,如果程序闪退了,就很难捕获到当时的异常。所以还需要有另外一个方法来对这种闪退的情况进行处理。这里也还要用到上文中提到的procdump工具。

它自带的有个命令:procdump  -ma  -e  pid  dump.dmp
-ma 是转储模式
-e 是命令参数,exception的缩写
Pid 是进程id
Dump.dmp 是dump的名称

当使用这个命令来启动procdump这个工具时,它会一直在后台运行并监测所指定的程序,在其闪退或者其他异常时抓取dump。

所以结合着这两种方法,我们就可以明确开发的思路了:使用定时器定时监测指定的进程是否处于无响应状态,主要针对的是程序出现无响应,死循环等异常;使用procdump的命令行来监测指定程序的闪退或者运行出错等。

现方法

这里主要介绍一下实现的主要思路,根据这个有兴趣的同学也可以使用自己拿手的开发语言进行编码实现。所以就不贴源码了。

1.维护一个定时器

定时器消息处理函数中,定时对传入的进程信息进行监测,若出现无响应,且满足一定条件(无响应时间达到5秒钟以上),则对该进程达到了可以转储dump的标准,弹出对应的界面让用户进行选择操作。

2.CheckProcessResponse函数

定时器消息处理函数中,定时调用该函数来对程序的响应状况进行监测。通过使用win32的API函数:IsHungThread或者IsHungAppWindow,传入对应的进程句柄,可获得该进程的响应状态,进而返回对应的值进行处理。

同时,该函数中会对传入的参数进行检查,如果没问题的话,则直接使用传入的参数作为监控的对象。如果检查未通过的话,则会根据传入的程序名,窗口标题等重新获取进程ID,句柄等信息,然后进行监控。如果依旧是获取不到正确的信息(比如程序进程不存在等),将会退出运行。

在程序第一次运行时,会创建一个线程来调用MonitorExceptionByPid函数,用来启动procdump对指定程序的监测,主要目的是用来捕获程序闪退时的异常并转储dump。

3.CreatDump函数

当监测到出现无响应的进程且满足生成dump文件的条件时,则会调用该函数来生成dump文件。

程序中通过命令行调用procdump.exe(32位)或者procdump64.exe(64位),根据所传入的参数生成dump文件,下图是调试时所展示的命令行详情:
-mp表示以尽可能全的方式生成dump

8656是被监测进程id
dump\20160912195751是dump文件的存储路径及名称
注:如果电脑上装的有windbg,使用32位的procdump进行dump转储的时候,可能会失败。所以保险起见优先使用64位的转储,32位的系统才会用32位的去转储。

dump文件在生成之后,接下来会调用7Z压缩工具对其进行压缩,并删除原dump文件。下图是7Z压缩的命令详情:

a表示添加被压缩文件至压缩文件中 dump\20160912195751.zip是压缩文件的路径及名称
dump\20160912195751.dmp是被压缩文件的路径及名称
压缩的时候会耗费一些时间,所以程序在执行命令行的时候,使用了createProcess来执行的,通过等待其执行结束之后,再去对原文件进行删除操作,进而保证文件的正常压缩和被删除。

4.MonitorExceptionByPid函数

当工具被调用之后,会创建一个线程调用该函数。该函数中会调用procdump.exe程序来根据进程ID对指定的进程进行监控。当被监控的进程出现异常时(崩溃,闪退等),会捕获到该异常然后转储dump。

不过该函数由于是调用procdump的命令来完成监控的功能,所以会在任务管理器中打开一个procdump进程。

当该函数捕获到异常然后执行结束时,在定时器处理函数中会定时获取其线程退出码。如果是异常退出则执行catchException的相关处理,若是被监测程序是正常退出,则不去处理。

5.SendEmail函数
根据设定好的发件人,收件人,将生成的dump压缩文件发送到指定的错误反馈邮箱中,以方便开发人员进行处理。如果是闪退情况,发送完邮件程序将自动退出。

6.ClearOutTimeFile
每次工具在启动时,会自动清理生成日期超过一周的dump以及其压缩文件,以减少存储空间的占用。

7.其他的一些方法函数
以上只是列了一些主要函数的实现思路,其他一些用于辅助实现上述功能的函数方法,可以根据需要自己来进行添加。

使用方法

1. 命令行调用本工具的方法
被监测的程序在启动时,通过命令行启动该工具程序,调用时需要传入以下参数:
● 程序名称:例如pbrc.exe
● 进程ID:被监测程序的进程ID(如果缺省或者有误的话,工具在处理时则会根据程序名重新获取)
● 窗体标题:例如“机构经纪投资管理系统机构客户端(RC_20160729)”
● 进程句柄:被监测程序的进程句柄(如果不是多开的话,此处可缺省)
● 邮件发送人地址:例如 [email protected]
● 邮件发送人姓名:例如 liudq
【举个例子】

可以使用以下命令行调用本工具:
ProjectDumpTest.exe  pbrc.exe  9999  机构经纪投资管理系统机构客户端(RC_20160729)  345678  [email protected]  liudq
ProjectDumpTest.exe:为本工具程序名
Pbrc.exe:为被监测程序名
9999:为被监测程序的进程ID
机构经纪投资管理系统机构客户端(RC_20160729):为被监测程序的窗体标题
345678:为被监测程序的窗口句柄转为字符串后的值,程序中再转为HWND
[email protected]:为发送异常报告时的收件人地址
liudq:为发送异常报告时的发件人名称

2.存放路径及其他必须文件
程序需要和procdump.exe、procdump64.exe这两个程序存放在同一路径下,同时如果需要进行压缩的话,也需要放入7z程序。
工具在监测到被监测的程序出现无响应且满足一定条件之后,会调用procdump.exe(32位系统)或者procdump64.exe(64位系统)来生成对应的dump文件。

3.Dump文件的路径及生成后处理 
生成的dump文件将存放在程序所在目录下的dump目录(需新建)中
由于转储后的dump文件过大,工具在生成dump之后,会执行压缩操作。压缩结束后将被压缩的文件删除,保留压缩文件。

通过dump文件名可以看出程序出现无响应的时间是2016年9月12日13点30分38秒,根据这个dump以及改时间段的日志文件,可以更方便的找到程序出现无响应的原因。

同时,由于dump文件压缩后依旧较大,若一直存在磁盘中,会比较耗费资原,所以工具在每次被启动的时候,都会检查dump目录下的dump文件(压缩文件)的创建时间,如果是已超出设定日期的(假定超过一周的),则清理删除,以腾出存储空间。程序在压缩完毕之后会将压缩文件发送到指定的邮箱当中。

4.对于多开情况的处理(待单开情况处理成熟后加入多开处理)
当用户打开多个被监测的程序时,进程中会有多个同名的exe进程,但是本工具的进程只有一个。这时会对这几个进程都进程定时监测,如果发现某一个被监测的进程出现了无响应,则会单独对该进程的内存镜像进行转储,生成dump,待到该进程恢复正常或者被重新启动(相当于新的进程)时,继续对其进行监控。

5.工具的运行和退出
工具可以通过直接运行exe进行启动,此时则会使用程序中所设定的缺省值作为监测对象来进行工作。一般情况下则通过被监测程序使用命令行进行启动,所需的命令行命令及参数参照1中内容。

工具在运行时,若被监测的程序未出现无响应等状态,则会一直在后台静默运行,用户看不到界面。当监测到无响应等状态时,则会弹出对应的提升界面,让用户进行选择操作。

如上图所示(待美化),用户可以通过所提供的选项来进行处理。
当所有被监测的进程都不存在时,工具将自动结束运行。

发布了1 篇原创文章 · 获赞 0 · 访问量 2711

猜你喜欢

转载自blog.csdn.net/yjj350418592/article/details/105331972