什么是崩溃?
崩溃是一个汉语词语,读音为 bēng kuì,多指人因过度的刺激或悲伤,超过了本人的心理承受极限而彻底的情绪 失控,绝望,无法自制。出自汉 应劭 《风俗通・正失・孝文帝》。
崩溃-百度百科
事实上,程序员应该说程序Crash了,而不是崩溃了,Crash的含义,参照百度百科说明如下
在正常计算机系统运行过程中,因某种原因宕机,或主机、程序停止工作等情况。
Crash-百度百科
在Windows中,系统Crash的现象表现为,系统蓝屏了,需要重启才能恢复运行,如下所示:
而程序Crash则显示一个“丑陋”的消息框,提示程序运行过程中出现了什么异常,点击确定后,程序退出运行,如下所示:
那么,Crash的原因是什么呢?在Windows中,调用 CreateProcess
函数启动主线程或 CreateThread
启动线程时,线程函数会在如下代码中运行(下面的代码引自《Windows核心编程》第25章未处理异常和C++异常):
// 主线程启动函数
// CreateProcess启动线程函数
VOID BaseProcessStart(PPROCESS_START_ROUTINE pfnSatrtAddr){
__try{
ExitThread((pfnSatrtAddr)());
}
__except(UnHandledExceptionFilter(GetExceptionInformation())){
ExitProcess(GetExceptionCode());
}
}
// CreateThread启动线程函数
VOID BaseThreadStart(PTHREAD_START_ROUTINE pfnSatrtAddr, PVOID pvParam){
__try{
ExitThread((pfnSatrtAddr)());
}
__except(UnHandledExceptionFilter(GetExceptionInformation())){
ExitProcess(GetExceptionCode());
}
}
从上面的代码可以看出,当线程运行中出现未捕捉异常时,会调用 UnHandledExceptionFilter(GetExceptionInformation())
来过滤异常信息,然后调用 ExitProcess(GetExceptionCode());
退出进程的运行。
也就是说,当线程中出现未捕获异常时,系统/程序就会Crash,蓝屏信息和异常信息对话框的显示都是在UnHandledExceptionFilter(GetExceptionInformation())
这句代码中出现的。
那么,如何消除程序崩溃时出现的异常信息对话框呢?我们有三种办法来处理。
在每个线程中调用 SetErrorMode(SEM_NOGPFAULTERRORBOX)
来让 UnHandledExceptionFilter(GetExceptionInformation())
调用时直接返回 EXCEPTION_EXECUTE_HANDLER
,不显示异常信息框,直接退出程序。
在每个线程中使用 __try{...}__except{...}
块,捕获未捕获异常,自行处理未捕获异常。
使用自定义的 UnHandledExceptionFilter
函数替换Windows默认的 UnHandledExceptionFilter
函数,自行处理未捕获异常。
这三种方法中,最优的方法是第三种,下面我们就来讨论如何使用第三种方式捕获崩溃(Crash)时的异常,生成dump文件来帮助寻找异常。
什么dump文件?百度百科的解释如下
(1) 在特定时刻,将整个储存装置或储存装置之某部分的内容记录在另一储存装置中。存储的目的通常是为了防止发生错误。
(2) 将具备可读格式的数据从主要或辅助储存体复制至外部媒体,如磁带、磁盘或打印机等媒体。
(3) 为收集错误信息而复制整个虚拟储存体或虚拟储存体之某部分的内容。
dump-百度百科
我们主要使用dump文件的是第三种方式。
(3) 为收集错误信息而复制整个虚拟储存体或虚拟储存体之某部分的内容。
一般而言,Windows程序都是运行在客户的电脑上的,当程序出现Crash时,如果没有使程序生成dump文件的话,就只能远程控制客户的电脑生成,或者当程序出现Crash的时候,让客户不操作电脑,使用windbg之类的工具调试程序来寻找崩溃点。但是,这样也太麻烦了,而且影响客户对软件的使用。所以,我们让程序在Crash时自动生成dump文件,然后在客户处将dump文件获取到手,最后分析dump文件来寻找Crash原因就好了。
那么,如何让程序在Crash时自动生成dump文件呢?
这就用到了我们上一节讨论到的使用自定义的 UnHandledExceptionFilter
函数替换Windows默认的 UnHandledExceptionFilter
函数,自行处理未捕获异常了。例子代码如下所示:
LONG WINAPI UnhandledExceptionFunction(_EXCEPTION_POINTERS* pExceptionInfo)
{
SYSTEMTIME st;
GetLocalTime(&st);
CString time_now = _T("");
time_now.Format(_T("%04d_%02d_%02d_%02d_%02d_%03d.dmp"), st.wYear, st.wMonth, st.wDay,
st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
CString dump_file_path = GetRootPath(); //获取程序所在的文件夹的路径
dump_file_path += _T("dump/");
CreateDirectory(dump_file_path, NULL);
dump_file_path += time_now;
HANDLE hDumpFile = CreateFile(dump_file_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
dumpInfo.ExceptionPointers = pExceptionInfo;
dumpInfo.ThreadId = GetCurrentThreadId();
dumpInfo.ClientPointers = TRUE;
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);
CloseHandle(hDumpFile);
return EXCEPTION_EXECUTE_HANDLER;
}
然后在 main
函数中运行下面代码即可:
SetUnhandledExceptionFilter(UnhandledExceptionFunction);
上述代码的作用就是使用自定义的 UnHandledExceptionFilter
函数来替换Windows默认的 UnHandledExceptionFilter
函数,来达到在程序出现Crash时自动生成dump文件的结果。
但是,在出现未处理CRT异常时,CRT代码中会调用
SetUnhandledExceptionFilter(UNLL);
来还原我们的 UnHandledExceptionFilter
函数,所以在出现未捕获的CRT异常时还是不能生成dump文件,那么要怎么处理呢?
我们可以通过改写kernel32.dll中的 UnHandledExceptionFilter
函数的地址来使未捕获的CRT异常还是调用到我们自定义的 UnHandledExceptionFilter
函数这样来处理。
步骤如下
通过上述步骤我们就能处理出现未处理CRT异常的情况了。
也可以通过调用 _set_invalid_parameter_handler
和 _set_purecall_handler
函数来改写CRT的默认处理方式。代码我就不贴了,我也不会,贴个博客吧 https://blog.csdn.net/sqcfj/article/details/7246391。
至于dump文件的分析可以查看这两篇博客 https://blog.csdn.net/hustd10/article/details/52075265 、https://blog.csdn.net/hustd10/article/details/52075324,在这里就不再赘述了。
写本博客的原因是为了纠正之前本人所写的 关于 “程序闪退,没有生成 DUMP 文件” 的一点总结这篇博客中的错误。当时自己还没有怎么看《Windows核心编程》这本书,对Windows编程也远没有现在这么熟悉,现在看回去真是一堆错误了,在此对由于那篇博客受到影响的读者们致歉。
以上就是本博客的全文,本人限于能力,上文中难免有错误的地方,若读者发现上文的错误,请于评论区中指出,本人看到之后会立即修改的,谢谢。